Skip to content

Buildings - #8946

Open
exeea wants to merge 17 commits into
MegaMek:mainfrom
exeea:buildings
Open

Buildings#8946
exeea wants to merge 17 commits into
MegaMek:mainfrom
exeea:buildings

Conversation

@exeea

@exeea exeea commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator

This PR adds mobile buildings and the parts needed for buildings to be constructed in MegaMekLab

@exeea
exeea requested a review from a team as a code owner September 11, 2026 13:38
Copilot AI lite review requested due to automatic review settings September 11, 2026 13:38

@github-advanced-security github-advanced-security AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CodeQL found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

One or more issues must be addressed before approval.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Adds mobile buildings and MegaMekLab construction support, including movement, cargo, environmental effects, walls, and validation.

Changes:

  • Adds mobile-structure movement, linkage, naval, collision, and cargo rules.
  • Extends building construction, environmental damage, collapse, flight decks, and walls.
  • Adds broad regression-test coverage.
File summaries
File Description
megamek/unittests/megamek/server/totalWarfare/EngineExplosionRatingTest.java Updated as part of this pull request.
megamek/unittests/megamek/server/totalWarfare/BuildingWeaponUnjamTest.java Updated as part of this pull request.
megamek/unittests/megamek/server/totalWarfare/BuildingEntryMovementTest.java Updated as part of this pull request.
megamek/unittests/megamek/common/verifier/TestSupportVehicleTest.java Updated as part of this pull request.
megamek/unittests/megamek/common/verifier/TestInfantrySquadCountTest.java Updated as part of this pull request.
megamek/unittests/megamek/common/verifier/TestAdvancedAerospaceTest.java Updated as part of this pull request.
megamek/unittests/megamek/common/units/BuildingGmStateEditTest.java Updated as part of this pull request.
megamek/unittests/megamek/common/options/GameOptionsTest.java Updated as part of this pull request.
megamek/unittests/megamek/common/loaders/BLKStructureFileTest.java Updated as part of this pull request.
megamek/unittests/megamek/common/cost/CostCalculatorReportTest.java Updated as part of this pull request.
megamek/unittests/megamek/common/ComputeTest.java Updated as part of this pull request.
megamek/unittests/megamek/common/ComputeECMTest.java Updated as part of this pull request.
megamek/unittests/megamek/common/bays/InfantryBayPersonnelTest.java Updated as part of this pull request.
megamek/unittests/megamek/common/actions/compute/ComputeToHitTest.java Updated as part of this pull request.
megamek/unittests/megamek/client/ui/tileset/MekTilesetBuildingTest.java Updated as part of this pull request.
megamek/unittests/megamek/client/ui/panels/GameOptionsPaneTest.java Updated as part of this pull request.
megamek/testresources/megamek/common/units/MekBayMobileStructure.blk Updated as part of this pull request.
megamek/testresources/megamek/common/units/MekBayMobilePortal.blk Updated as part of this pull request.
megamek/src/megamek/server/totalWarfare/WeaponMalfunctionRepairHandler.java Updated as part of this pull request.
megamek/src/megamek/server/totalWarfare/TWPhasePreparationManager.java Updated as part of this pull request.
megamek/src/megamek/server/totalWarfare/TWPhaseEndManager.java Updated as part of this pull request.
megamek/src/megamek/server/totalWarfare/TWDamageManager.java Updated as part of this pull request.
megamek/src/megamek/server/totalWarfare/DeploymentProcessor.java Updated as part of this pull request.
megamek/src/megamek/server/totalWarfare/BuildingEditHandler.java Updated as part of this pull request.
megamek/src/megamek/server/IndustrialElevatorProcessor.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/infantry/InfantryWeaponHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/infantry/InfantryInfernoSRMHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/infantry/InfantryDisposableWeaponHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/handlers/VariableSpeedPulseLaserWeaponHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/handlers/UltraWeaponHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/handlers/ThunderBoltWeaponHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/handlers/StreakHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/handlers/srm/SRMTandemChargeHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/handlers/srm/SRMInfernoHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/handlers/srm/SRMHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/handlers/srm/SRMFragHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/handlers/srm/SRMDeadFireHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/handlers/srm/SRMAntiTSMHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/handlers/RifleWeaponHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/handlers/RapidFireACWeaponHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/handlers/PulseLaserWeaponHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/handlers/prototype/PrototypeLBXHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/handlers/PPCHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/handlers/PopUpMineLauncherHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/handlers/plasma/PlasmaRifleHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/handlers/plasma/PlasmaCannonHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/handlers/NarcExplosiveHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/handlers/MPodHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/handlers/MissileWeaponHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/handlers/MissileMineClearanceHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/handlers/MissileBayWeaponHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/handlers/MGHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/handlers/MGAWeaponHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/handlers/mekMortar/MekMortarHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/handlers/mekMortar/MekMortarAntiPersonnelHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/handlers/mekMortar/MekMortarAirburstHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/handlers/lrm/StreakLRMHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/handlers/lrm/LRMSwarmHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/handlers/lrm/LRMHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/handlers/lrm/LRMFragHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/handlers/lrm/LRMAntiTSMHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/handlers/LBXHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/handlers/HyperLaserHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/handlers/HAGWeaponHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/handlers/GRHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/handlers/FlamerHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/handlers/EnergyWeaponHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/handlers/CLLBXPrototypeHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/handlers/CLIATMHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/handlers/ChemicalLaserHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/handlers/capitalMissile/CapitalMissileHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/handlers/capitalMissile/CapitalMissileBearingsOnlyHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/handlers/capitalMissile/CapitalMissileBayHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/handlers/CapitalLaserBayOrbitalBombardmentHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/handlers/BPodHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/handlers/BayWeaponHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/handlers/AttackHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/handlers/ATMHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/handlers/artillery/ArtilleryWeaponDistantHomingHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/handlers/artillery/ArtilleryWeaponDistantFireHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/handlers/artillery/ArtilleryCannonWeaponHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/handlers/artillery/ArtilleryBayWeaponDistantHomingHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/handlers/artillery/ArtilleryBayWeaponDistantFireHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/handlers/AR10Handler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/handlers/ac/ACWeaponHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/handlers/ac/ACAPHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/battleArmor/BAMGHandler.java Updated as part of this pull request.
megamek/src/megamek/common/weapons/battleArmor/BALBXHandler.java Updated as part of this pull request.
megamek/src/megamek/common/verifier/TestEntity.java Updated as part of this pull request.
megamek/src/megamek/common/verifier/TestBattleArmor.java Updated as part of this pull request.
megamek/src/megamek/common/verifier/TestAdvancedAerospace.java Updated as part of this pull request.
megamek/src/megamek/common/verifier/BayData.java Updated as part of this pull request.
megamek/src/megamek/common/util/SerializationHelper.java Updated as part of this pull request.
megamek/src/megamek/common/units/Targetable.java Updated as part of this pull request.
megamek/src/megamek/common/units/MobileStructureNavalRules.java Updated as part of this pull request.
megamek/src/megamek/common/units/MobileStructureBayLaunch.java Updated as part of this pull request.
megamek/src/megamek/common/units/Mek.java Updated as part of this pull request.
megamek/src/megamek/common/units/IAero.java Updated as part of this pull request.
megamek/src/megamek/common/units/Dropship.java Updated as part of this pull request.
megamek/src/megamek/common/units/DamageEditSpec.java Updated as part of this pull request.
megamek/src/megamek/common/units/ConvInfantry.java Updated as part of this pull request.
megamek/src/megamek/common/units/ConstructionUtil.java Updated as part of this pull request.
megamek/src/megamek/common/units/BuildingTarget.java Updated as part of this pull request.
megamek/src/megamek/common/units/BuildingFoundationRules.java Updated as part of this pull request.
megamek/src/megamek/common/ToHitData.java Updated as part of this pull request.
megamek/src/megamek/common/planetaryConditions/PlanetaryConditions.java Updated as part of this pull request.
megamek/src/megamek/common/options/OptionsConstants.java Updated as part of this pull request.
megamek/src/megamek/common/options/GameOptions.java Updated as part of this pull request.
megamek/src/megamek/common/net/enums/PacketCommand.java Updated as part of this pull request.
megamek/src/megamek/common/net/connections/DataStreamConnection.java Updated as part of this pull request.
megamek/src/megamek/common/net/connections/AbstractConnection.java Updated as part of this pull request.
megamek/src/megamek/common/moves/WallStep.java Updated as part of this pull request.
megamek/src/megamek/common/moves/TurnStep.java Updated as part of this pull request.
megamek/src/megamek/common/moves/PhasePassSelector.java Updated as part of this pull request.
megamek/src/megamek/common/moves/MovePath.java Updated as part of this pull request.
megamek/src/megamek/common/moves/MobileStructureSupport.java Updated as part of this pull request.
megamek/src/megamek/common/loaders/MekFileParser.java Updated as part of this pull request.
megamek/src/megamek/common/HitData.java Updated as part of this pull request.
megamek/src/megamek/common/event/GameCFREvent.java Updated as part of this pull request.
megamek/src/megamek/common/equipment/Sensor.java Updated as part of this pull request.
megamek/src/megamek/common/equipment/PowerGeneratorType.java Updated as part of this pull request.
megamek/src/megamek/common/equipment/EquipmentType.java Updated as part of this pull request.
megamek/src/megamek/common/equipment/enums/StructureEngine.java Updated as part of this pull request.
megamek/src/megamek/common/equipment/Engine.java Updated as part of this pull request.
megamek/src/megamek/common/enums/MoveStepType.java Updated as part of this pull request.
megamek/src/megamek/common/enums/BuildingType.java Updated as part of this pull request.
megamek/src/megamek/common/cost/CostCalculator.java Updated as part of this pull request.
megamek/src/megamek/common/compute/ComputeECM.java Updated as part of this pull request.
megamek/src/megamek/common/board/CubeCoords.java Updated as part of this pull request.
megamek/src/megamek/common/board/AllowedDeploymentHelper.java Updated as part of this pull request.
megamek/src/megamek/common/bays/SuperHeavyVehicleBay.java Updated as part of this pull request.
megamek/src/megamek/common/bays/SteerageQuartersCargoBay.java Updated as part of this pull request.
megamek/src/megamek/common/bays/StandardSeatCargoBay.java Updated as part of this pull request.
megamek/src/megamek/common/bays/SmallCraftBay.java Updated as part of this pull request.
megamek/src/megamek/common/bays/SecondClassQuartersCargoBay.java Updated as part of this pull request.
megamek/src/megamek/common/bays/RefrigeratedCargoBay.java Updated as part of this pull request.
megamek/src/megamek/common/bays/ProtoMekBay.java Updated as part of this pull request.
megamek/src/megamek/common/bays/MekBay.java Updated as part of this pull request.
megamek/src/megamek/common/bays/LivestockCargoBay.java Updated as part of this pull request.
megamek/src/megamek/common/bays/LiquidCargoBay.java Updated as part of this pull request.
megamek/src/megamek/common/bays/LightVehicleBay.java Updated as part of this pull request.
megamek/src/megamek/common/bays/InsulatedCargoBay.java Updated as part of this pull request.
megamek/src/megamek/common/bays/InfantryBay.java Updated as part of this pull request.
megamek/src/megamek/common/bays/HeavyVehicleBay.java Updated as part of this pull request.
megamek/src/megamek/common/bays/FirstClassQuartersCargoBay.java Updated as part of this pull request.
megamek/src/megamek/common/bays/CrewQuartersCargoBay.java Updated as part of this pull request.
megamek/src/megamek/common/bays/CargoBay.java Updated as part of this pull request.
megamek/src/megamek/common/bays/Bay.java Updated as part of this pull request.
megamek/src/megamek/common/bays/BattleArmorBay.java Updated as part of this pull request.
megamek/src/megamek/common/bays/ASFBay.java Updated as part of this pull request.
megamek/src/megamek/common/battleValue/AbstractBuildingEntityBVCalculator.java Updated as part of this pull request.
megamek/src/megamek/common/actions/UnloadStrandedAction.java Updated as part of this pull request.
megamek/src/megamek/common/actions/ThrashAttackAction.java Updated as part of this pull request.
megamek/src/megamek/common/actions/PushAttackAction.java Updated as part of this pull request.
megamek/src/megamek/common/actions/PunchAttackAction.java Updated as part of this pull request.
megamek/src/megamek/common/actions/PhysicalAttackAction.java Updated as part of this pull request.
megamek/src/megamek/common/actions/LayExplosivesAttackAction.java Updated as part of this pull request.
megamek/src/megamek/common/actions/KickAttackAction.java Updated as part of this pull request.
megamek/src/megamek/common/actions/JumpJetAttackAction.java Updated as part of this pull request.
megamek/src/megamek/common/actions/DfaAttackAction.java Updated as part of this pull request.
megamek/src/megamek/common/actions/compute/ComputeToHitIsImpossible.java Updated as part of this pull request.
megamek/src/megamek/common/actions/compute/ComputeToHit.java Updated as part of this pull request.
megamek/src/megamek/common/actions/compute/ComputeTerrainMods.java Updated as part of this pull request.
megamek/src/megamek/common/actions/compute/ComputeEnvironmentalToHitMods.java Updated as part of this pull request.
megamek/src/megamek/common/actions/compute/ComputeAbilityMods.java Updated as part of this pull request.
megamek/src/megamek/common/actions/ClubAttackAction.java Updated as part of this pull request.
megamek/src/megamek/common/actions/ChargeAttackAction.java Updated as part of this pull request.
megamek/src/megamek/common/actions/BrushOffAttackAction.java Updated as part of this pull request.
megamek/src/megamek/common/actions/AirMekRamAttackAction.java Updated as part of this pull request.
megamek/src/megamek/common/actions/AbstractAttackAction.java Updated as part of this pull request.
megamek/src/megamek/client/ui/tileset/MekTileset.java Updated as part of this pull request.
megamek/src/megamek/client/ui/SharedUtility.java Updated as part of this pull request.
megamek/src/megamek/client/ui/panels/phaseDisplay/FiringDisplay.java Updated as part of this pull request.
megamek/src/megamek/client/ui/panels/phaseDisplay/DeploymentDisplay.java Updated as part of this pull request.
megamek/src/megamek/client/ui/panels/phaseDisplay/commands/MoveCommand.java Updated as part of this pull request.
megamek/src/megamek/client/ui/panels/phaseDisplay/AimedShotHandler.java Updated as part of this pull request.
megamek/src/megamek/client/ui/panels/GameOptionsPresentation.java Updated as part of this pull request.
megamek/src/megamek/client/ui/dialogs/unitEditor/UnitDamageSpecBuilder.java Updated as part of this pull request.
megamek/src/megamek/client/ui/dialogs/unitEditor/UnitDamagePanelBuilder.java Updated as part of this pull request.
megamek/src/megamek/client/ui/dialogs/unitEditor/UnitDamageControls.java Updated as part of this pull request.
megamek/src/megamek/client/ui/clientGUI/ClientGUI.java Updated as part of this pull request.
megamek/src/megamek/client/ui/clientGUI/boardview/sprite/EntitySprite.java Updated as part of this pull request.
megamek/src/megamek/client/ui/clientGUI/boardview/BoardView.java Updated as part of this pull request.
megamek/src/megamek/client/Client.java Updated as part of this pull request.
megamek/src/megamek/client/bot/princess/FireControl.java Updated as part of this pull request.
megamek/src/megamek/client/bot/BotClient.java Updated as part of this pull request.
megamek/resources/megamek/common/report-messages.properties Updated as part of this pull request.
megamek/resources/megamek/common/options/messages.properties Updated as part of this pull request.
megamek/resources/megamek/common/messages.properties Updated as part of this pull request.
megamek/resources/megamek/client/messages.properties Updated as part of this pull request.
Review details

Suppressed comments (8)

megamek/src/megamek/common/bays/CargoBay.java:75

  • Bay.canLoad(Entity) now passes the computed usable-door count into this overload, but this implementation ignores it and therefore still returns true when every physical bay door is unusable (for example after all authored doors are damaged or blocked). Loading then bypasses the building/mobile-structure door restriction. Preserve the infantry/capacity check and also require an available door for this turn, as the base implementation does.
    megamek/src/megamek/common/cost/BuildingCostCalculator.java:62
  • For a mobile structure with hex_heights, this cost loop multiplies every footprint hex by the overall height. A shorter hex is therefore charged for floors it does not have, even though the verifier explicitly permits per-hex heights; use each hex's getHeight(...) for the structure-cost multiplier.
    megamek/src/megamek/common/loaders/BLKStructureFile.java:130
  • The new per-hex-height format is accepted here, but the loader later iterates every level up to the overall structure height and calls loadEquipment for it. A BLK file can therefore place equipment in levels that do not exist in a shorter footprint hex; the equipment-loading loop must skip levels at or above getHeight(coords) (or reject them).
    megamek/src/megamek/common/units/BuildingConstruction.java:180
  • When a mobile structure uses hex_heights, this branch still gives a shorter hex the full structure-height carrying capacity. The verifier calls this per occupied hex, so equipment in a two-level hex of a four-level structure can be accepted as if it had four floors; use the actual getHeight(hex) here, as the static-building branch already does.
    megamek/src/megamek/common/units/MobileStructureCargoRules.java:141
  • InfantryBay, BattleArmorBay, and CargoBay explicitly allow infantry/battle-armor unloading based on adjacency even when the bay's door count is exhausted, but this projected-pose check applies usableDoors to every passenger before it builds the exits. A mobile structure carrying infantry therefore cannot unload after its physical/legacy door count reaches zero, unlike the transporter contract used elsewhere. Apply the door-count limit only to non-infantry passengers (while still letting authored-door filtering decide whether a physical exit exists).
    megamek/src/megamek/common/units/MobileStructureCargoRules.java:201
  • result is keyed only by the outside coordinate, but Exit also carries elevation, deck elevation, and the authored door. Hangar validation permits multiple doors on one exterior edge, including doors on different floors, so putIfAbsent discards one valid exit and prevents passengers from using that floor's door. Preserve distinct exits by floor/door identity rather than deduplicating solely by Coords.
    megamek/src/megamek/common/units/MobileStructurePortalRules.java:142
  • The tunnel-matching code deliberately chooses the centered lateral footprint hex as the entrance, but this lookup uses closed.getFirst(). The authored coordinate order is not the centered entrance for every valid multi-hex portal, so deployment can miss a matching complex simply because that complex is not behind the first list element. Select the same centered lateral hex used by tunnelEquipmentMatches before translating rearward.
    megamek/src/megamek/common/verifier/TestBuilding.java:412
  • The per-hex heavy-weapon limit also uses the structure-wide height, so a shorter hex_heights footprint hex receives the same weapon allowance as a full-height hex. Since this check is performed per hex, calculate the Fortress/Castles Brian limit from structure.getHeight(hex) instead.
  • Files reviewed: 229/283 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

}

static boolean isBuildingType(int type) {
return type == TYPE_BUILDING || isWallType(type);
Comment on lines +316 to +317
addReport(gameManager.damageBuilding(unit, damage, "demolishing a building", unit.relativeToBoard(entering),
0, null, false));

@IllianiBird IllianiBird left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the most part this looks good. I reviewed it in the same way I would a mhq PR, so some of my suggestions won't match legacy mm code style. No abbreviated or single letter variable names, for example. That's a big one because it reduces code clarity.

I also spotted a lot of hardcoded player-facing Strings. Those need to be nixed.

My two main concerns however revolving around performance and code cleanliness. I noticed a lot of streams being used throughout this PR and I don't believe a stream is a good call in most of them.

Streams are surprisingly more expensive that folks realize and unless you're working with thousands of objects a traditional loop is more performative. Having a stream within a stream is a big no-no. Furthermore, I really don't think we should use streams if we're performing more than one or two actions on the array. Once you start chaining actions the stream can quickly get out of hand.

The other thing that had a really bad code smell was the large number of loops nested within loops nested within loops. Nesting a loop within a loop always makes me second guess the design. When we're hitting three loops deep that tells me we might need to reconsider our approach.

As always, I generally only highlight a handful of instances where I see a particular individual issue. So please review your PR for other instances where a similar problems exist but weren't flagged.

Graphics2D g = (Graphics2D) graphics.create();
double[] x = { .25, .75, 1, .75, .25, 0 };
double[] y = { 0, 0, .5, 1, 1, .5 };
for (var segment : megamek.common.units.WallRules.segments(game, boardId)) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer strong typing. I know var is a more modern style, but we get a lot of inexperienced contributors and strong typing is less likely to result in dumbness


/** Authored walls occupy actual hexsides; an ordinary whole-hex building tile would misrepresent open sides. */
private void drawWallSegments(Graphics2D graphics) {
Graphics2D g = (Graphics2D) graphics.create();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're trying to move away from single letter or abbreviated variable names to improve code clarity.

}

static JMenu buildingDoorsMenu(Game game, Board board, Coords coords, Client client) {
JMenu menu = new JMenu("Building doors");

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hardcoded player facing String

if (!game.getPhase().isEnd()) {
return menu;
}
String[] directions = { "N", "NE", "SE", "S", "SW", "NW" };

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And here, I won't mark any others but we shouldn't be hardcoding player facing strings

Comment on lines +228 to +231
for (IBuilding candidate : board.getBuildingsAt(coords)) {
if (!(candidate instanceof AbstractBuildingEntity building)) { continue; }
var doors = building.getDesign().getDoors();
for (int index = 0; index < doors.size(); index++) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any way to make this n(1)?

Comment on lines +95 to +98




Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And here

}
public double capacity() {
if (!landingDeck()) { return 200; }
return 1000.0 * hexes.size();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this has special meaning please store it as a const, or at least define it in the method so we're not using a magic number

return false;
}
/** Local surface-relative elevation of an authorized underground passage's floor, or null outside it. */
public static Integer supportElevation(MobileStructure unit, Coords coords) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any method that can return null as part of its WAI operations should have the @Nullable tag

Comment on lines +95 to +102
for (IBuilding candidate : game.getBoard(boardId).getBuildingsVector()) {
if (!(candidate instanceof AbstractBuildingEntity building) || !BuildingConstruction.usesHexsides(building)
|| building.isDestroyed()) {
continue;
}
building.getWallSegmentState().initialize(building);
for (CubeCoords hex : building.getInternalBuilding().getOriginalCoordsList()) {
for (int side = 0; side < 6; side++) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another loop within a loop within a loop

Comment on lines +334 to +339
for (Entity entity : List.copyOf(getGame().getEntitiesVector())) {
if (!(entity instanceof MobileStructure mobile)
|| !mobile.getNavalState().checkFootingThisRound(getGame().getRoundCount())) { continue; }
for (Coords coords : List.copyOf(mobile.getCoordsList())) {
int roof = BuildingElevation.roof(mobile, coords);
for (Entity rider : List.copyOf(getGame().getEntitiesVector(coords, mobile.getBoardId()))) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

loop within a loop within a loop

@HammerGS

Copy link
Copy Markdown
Member

Review: MegaMek PR #8946 "Buildings" (exeea/buildings -> main)

Reviewed at head 6629ddc (merge-base = current main b3891f1). 283 files, +24,116 / -1,102. Checked against the project coding style guide and PR checklist.
Every finding below was verified against the code, the CI logs, or a
local build/test run of the branch. Each item: what was checked, what was found, what to do.

Verdict: NOT MERGEABLE as-is. Five blockers (B1-B5), then a long tail of should-fix items. The architecture is
sound in several places (shared client/server rule, XStream record converters, validated CFR paths), so the
work is salvageable; the problems are wire compatibility, red CI, failing own tests, scope, and style volume.


A. Blockers

# Check Finding Recommendation
B1 PacketCommand additions appended at END of enum Three values inserted mid-enum: CFR_BUILDING_WEAPON, CFR_MOBILE_AVOIDANCE after CFR_TAG_TARGET (~line 209) and BUILDING_DOOR after BUILDING_EDIT (line 291). The ordinal is the wire id: NativeSerializationMarshaller:55 writes command().ordinal(), :68 reads PACKET_COMMANDS[command], and the enum's own note at line 231 says so. Every command after the insertion point is misread by any peer on a different build (mixed nightlies, MekHQ against a stock server). Move all three to the end after INFANTRY_ACTION_DECLARATION. Keep the isCFR() change but express it as isCFRBuildingWeapon() / isCFRMobileAvoidance() like its neighbours.
B2 CI green pr_build: compileTestJava fails, DeploymentDisplayTest:603 calls processTurn(Entity, Coords); signature is (Entity, Coords, boolean). pr_java_doc: Game.java:3776 @param location on removeIndustrialElevator(IndustrialElevator) (param is elevator). checkstyle: Spotless violations in 66 files. Reproduced all three locally. Fix the call (, false), fix the Javadoc, run spotlessApply. See S14 before running Spotless.
B3 Tests pass With the compile fix applied locally, 1,326 tests ran, 16 failed, all new in this PR: DeploymentDisplayTest$BuildingRotation x14 (clickingTheOriginDoesNotResetFacing expected 4 got 5 = real defect, see D1; the Turn-button cases NPE at :550 because the test drives a modal BuildingFacingDialog through a mocked ClientGUI); MobileStructureFallTest.failedSinkingDeckFootingFallsOntoAnAdjacentLowerMobileRoof (expected CF 148, got 150); MobileStructureWallBridgeTest.anActualOldJavaBridgeSave... (ConversionException on CubeCoords: the test round-trips with getSaveGameXStream(), but the record converters are registered only in getLoadSaveGameXStream()). Fix D1, investigate the fall damage delta, switch the bridge test to the load factory for fromXML. Delete the Swing-driving tests (S9).
B4 PR too big / stepping stones 24k lines across 283 files, plus unrelated rules changes riding along: Mek automatic Clan CASE rewrite, TestBattleArmor squad loop bounds (< to <=, affects every BA validation), TestAdvancedAerospace officer count formula, InfantryBay personnel capacity fixed to 30/7, TestEntity armour rounding (Math.nextUp), TestSupportVehicle, ConvInfantry.survivalGearStored, Sensor/ComputeECM EMI signature changes. Each has its own test file. Split. Suggested cuts: (1) shared model + BLK + serialization (BuildingDesign, BuildingDesignCodec, BLKStructureFile, TestBuilding, converters); (2) static building rules (walls, floors, expanded collapse, environment); (3) mobile structure movement + linkage; (4) naval + air; (5) bays, doors, cargo; (6) each unrelated rules fix as its own small PR with its test. The MML PR only needs (1).
B5 State changed through the owning API, not in place MobileStructureNavalHandler:237-240: board.getHex(coords).addTerrain(RUBBLE) then sendChangedHex. MobileStructureMovementHandler:396: board.getHex(coords).removeTerrain(INDUSTRIAL_ELEVATOR), hex resent at :444. Server skips Board.initializeHex; clients run it on receipt; the two sides diverge. Board.collapseBuilding does it right (setHex(coords, curHex)). Copy the hex, mutate, board.setHex(coords, hex), then send.

B. Server-side gate

# Check Finding Recommendation
G1 Client and server ask the same question PASS. isDeploymentPositionAndFacingValid is called from DeploymentDisplay (x3), AllowedDeploymentHelper:122 and DeploymentProcessor:120. Door ownership is enforced in BuildingRuntimeState.changeDoor for both the menu and the packet. Keep.
G2 Packet path tested, positive and negative PARTIAL. DeploymentProcessorTest: 6 facings accepted, 5 obstruction cases rejected via receiveDeployment (not handlePacket, acceptable). No test sends BUILDING_DOOR, CFR_BUILDING_WEAPON or CFR_MOBILE_AVOIDANCE through handlePacket. Add a handlePacket(BUILDING_DOOR) test: owner opens, non-owner is refused, plus a malformed door index.
G3 Authorisation guard PASS for doors (changeDoor checks player; BuildingDoorMenuTest asserts both outcomes on the rule). CFR replies are filtered by connectionId == ownerId in requestMobileCollisionAvoidance. Client-supplied MovePath is recompiled and checked by isAvoidancePathLegal before processMovement (MobileStructureCollisionHandler:229-236). Keep.
G4 New state Serializable PASS with gaps. BuildingDesign, BuildingRuntimeState, BuildingFloorState, WallSegmentState, MobileStructureNavalState, BuildingDamageTracker, WallTarget all Serializable; nine Serializable records have XStream converters; round-trip tests exist. Missing serialVersionUID on BuildingDesign, BuildingRuntimeState, BuildingFloorState. Add the UIDs.
G5 Field added to an already-serialized class comes back null FAIL. AbstractBuildingEntity:94 private final BuildingDesign design = new BuildingDesign();. Any BuildingEntity written before this PR (saves since #8927 merged, cached units) deserialises with design == null; getDesign().getSite() then NPEs in hasRequiredSurfacePart, Entity (~2720), Board.getBuildingAt(coords, elevation). The sibling fields wallSegmentState and buildingRuntimeState ARE lazily initialised, so the author knows the trap. Drop final, lazy-init in getDesign() or restore in readObject. Add a round-trip test from a pre-PR .ser (the repo already has LegacyMapBridge.ser as precedent).
G6 Rejections logged with reason FAIL. DeploymentProcessor:118-124 splices the building check into the existing 5-clause `

C. Monolithic-class gate

# Check Finding Recommendation
C1 No new feature logic in TWGameManager FAIL. +736 / -159 (net +577 lines, 33,055 to 33,632), 106 hunks. New methods: damageWall, climbWallSegment, changeBuildingDoor, requestMobileCollisionAvoidance, selectAutomatedBuildingAttack, isAutomatedBuildingAttack, jettisonBattleArmorMissiles, attemptLandingCheck, criticalBuilding, isInsideBuilding. damageBuilding grows to 7 overloads, the widest taking 7 parameters (+128 lines in one). unloadUnit +47, passBuildingWall +36, artilleryDamageHex +35, doEntityFallsInto +33. Move wall damage/climb to WallSegmentHandler (already exists), door change to a BuildingDoorHandler, collision avoidance into MobileStructureCollisionHandler, automated attacks into a handler. Replace the damageBuilding overload family with one method taking a BuildingDamage record. Leave one-line delegators.
C2 No new rules on Entity PARTIAL. Entity +90: occupiedWall field, isAffectedByEMI, rollMovementInBuilding overload with inline modifier logic, interior elevation branches in isElevationValid/canGoDown/canGoUp. The heavy logic is already in BuildingInteriorRules/BuildingElevation/WallRules, which is the right shape. Move the remaining inline blocks (piloting modifier assembly, building-entry MP cost block in getMovementCost) into those utilities.
C3 Don't bloat existing methods FAIL in places. MoveStep +250 with 28 inline FQNs; MovementDisplay +261 including a 7-way instanceof chain (CodeQL 11130 at :481); LosEffects +97. Extract per-feature helpers; the mobile-structure branches in MovementDisplay belong in a MobileStructureMoveCommands helper.

D. Deep dive: deployment facing (requested)

Files: DeploymentDisplay.processTurn / determineDeploymentPosition, AllowedDeploymentHelper.hasFacingDependentFootprint, AbstractBuildingEntity.isDeploymentPositionAndFacingValid, DeploymentProcessor.receiveDeployment.

# Check Finding Recommendation
D1 Same-hex guard placement in processTurn DEFECT. The new if (coords.equals(entity.getPosition())) return; (line ~1114) sits AFTER the hasFacingDependentFootprint branch, so a multi-hex building never reaches it. hexMoused:936 routes a shift-click or Turn-mode click on the building's own origin to processTurn, then turnBuildingToward computes position.direction(position) (atan2 of a zero vector) and rotates the building to a junk facing. The PR's own test proves it: clickingTheOriginDoesNotResetFacing expects 4, gets 5. Hoist the same-hex guard to the first statement of processTurn (or into turnBuildingToward). Keep the test, but as a rule test on turnBuildingToward's inputs rather than a Swing test.
D2 hasFacingDependentFootprint still the right predicate INCONSISTENT. It is footprint.size() > 1 and gates the whole turn UX (processTurn:1106, isFacingClickOnPlacedBuilding:1092, DEPLOY_TURN:1217, labelTurnButtonFor:1489). This PR makes validity facing-dependent for single-hex buildings too: WallRules.hasOverlappingSegment (hexside walls) and MobileStructurePortalRules.validDeployment both run inside isDeploymentPositionAndFacingValid. A single-hex wall therefore takes the generic path: the new toast branch refuses the facing, and no chooser is offered. Meanwhile findAllowedFacings was changed to instanceof AbstractBuildingEntity (all buildings) so two different predicates now describe the same idea. One predicate, used everywhere: AbstractBuildingEntity AND (footprint > 1 OR BuildingConstruction.usesHexsides OR MobileStructure). Put it on AllowedDeploymentHelper and delete the second form.
D3 Facing computed at the same elevation the server checks DEFECT. determineDeploymentPosition:876 computes findAllowedFacings(FACING_ELEVATION = 0), but the new final check at :907 and the server at DeploymentProcessor:120 use finalElevation. isDeploymentPositionAndFacingValid depends on elevation (hasRequiredSurfacePart, hasInvalidBuildingOverlap, stacking). A sub-surface or elevated building is offered facings valid at 0 that the final check refuses; the player sees "cannot deploy here" with no valid choice reachable. Choose elevation first, then findAllowedFacings(finalElevation). Remove the FACING_ELEVATION constant.
D4 Cancel behaviour of the facing prompt BEHAVIOUR CHANGE. promptForFacingIfNeeded now returns -1 on cancel and the caller aborts the deployment (return null) instead of keeping the current facing. Applies to every unit type, not only buildings. Javadoc updated. Acceptable, arguably correct; call it out in the PR body.
D5 isDeploymentPositionAndFacingValid itself GOOD SHAPE, POOR FORM. Non-mutating (preserves the #7858 rework), shared by client and server. But: five && clauses with an inline stream in the return; game.getBoard(boardId) evaluated once per clause and once per footprint hex; no log of which clause failed; relies on getDesign() non-null (G5); hasRequiredSurfacePart flat-maps getBuildingsAt per footprint hex. Guard clauses, each with a [DeployBuilding] debug line; hoist Board board = game.getBoard(boardId) with a null guard; store the footprint once.
D6 Server counterpart PASS with G6 caveat. Rejection leaves the entity and board untouched (asserted by rejectsIllegalRotatedFootprintBeforeChangingTheBuildingOrBoard). Extract boolean isBuildingPoseValid and log the reason.
D7 setFacing side effects AbstractBuildingEntity.setFacing now fires GameEntityChangeEvent; processTurn also calls redrawEntity on every board view plus repaint on every board view. Double redraw per turn click. Keep the event, drop the explicit repaint loop, or vice versa.
D8 Dead code left behind getValidFacingsAt() (public) and isFacingDependentDeployment() (private) have no callers after this PR. Delete the private one; @Deprecated(since = "0.51.x", forRemoval = true) the public one.
D9 Hygiene in the touched region Duplicate import megamek.common.units.Dropship;; trailing whitespace after } at :1113. Fix with Spotless.

E. Style and hygiene (0, 2)

# Check Finding Recommendation
E1 No dirty edits MtfFile.java: two blank lines removed, nothing else. git checkout main -- megamek/src/megamek/common/loaders/MtfFile.java.
E2 No fully-qualified names mid-code 393 added lines use inline FQNs. Worst: MovementDisplay 38, TWGameManager 34, MoveStep 28, MobileStructureMovementHandler 27, LosEffects 22, MobileStructure 21, SerializationHelper 9, Board 8, Bay 8. Also java.util.Comparator, java.io.Serial, java.util.HashMap inline. Duplicate imports in DeploymentDisplay (Dropship) and ComputeToHit (AbstractBuildingEntity). Import everything.
E3 No abbreviated names bldg 85 uses, cf 28, loc 17, dest 16; lambda params m -> 54, c -> 22, p -> 14, h -> 9, e -> 8. Rename. This is the single most repeated review callout on this project.
E4 var only when obvious 1,319 var declarations, most of the form var x = someCall(). Explicit types except new X() right-hand sides.
E5 Braces / one statement per line 217 one-line { stmt; } blocks (e.g. if (...) { elevation = 0; }, for (...) { fallFromLostDeck(...); }). Expand.
E6 Chained conditionals split into named booleans 16 single lines carry 4+ boolean operators, more when wrapped: isDeploymentPositionAndFacingValid, DeploymentProcessor:118-124, MobileStructureCargoRules:314-317, MekBay.canLoad, TestBuilding door check count > bay.getDoors() || requireComplete && (count > 0 || modular && !bay.isQuarters()) && .... One boolean, one concept.
E7 Javadoc on public members; @param on records 242 of 347 public methods in the 40 new source files have no Javadoc. 26 new records, none with @param tags. Document. Records: @param per component.
E8 No Unicode in code 21 added lines with en dashes, ×, in comments and test messages (AbstractBuildingEntity, BuildingDesign, MobileStructure*, tests). ASCII: pp. 86-87, x.
E9 @Nullable where null is returned 17 new files return null from methods; zero @Nullable annotations among them (Bay.buildingCarrier, MobileStructurePortalRules.complexBehind, MobileStructureMovement x7, BuildingFlightDeckRules x3 ...). Annotate and document the null contract.
E10 Hardcoded user-facing strings Report/modifier reasons as literals: "demolishing a building", "mobile structure collision", "building contents, ceilings and superstructure", "Elevator capacity must be a finite, non-negative tonnage". Report id 9895 added to report-messages.properties but never referenced (it also documents an unimplemented path: "collision resolution between Mobile Structures is not yet supported"). Message keys; remove or wire 9895.
E11 Deprecated API not newly called MobileStructureCargoRules:316 calls Bay.getNumberLoadedThisTurn() (forRemoval). BuildingBayDoorRepairTest calls restoreDoor/restoreAllDoors (forRemoval since 0.51.0). Use the replacements.
E12 @Override present Missing on MobileStructure:469 and BuildingEntity:207 hasPower() (CodeQL 11132/11133). Add.

F. Diagnostic logging

# Check Finding Recommendation
F1 Gates, transitions and rejections log their reason FAIL, comprehensively. All 40 new source files contain zero log calls, including every new server handler (MobileStructureCollisionHandler 711 lines, MobileStructureMovementHandler 447, BuildingEnvironmentHandler 346, MobileStructureNavalHandler 367, ExpandedBuildingCollapseHandler, WallSegmentHandler, BuildingFlightDeckHandler, ...). A playtester asking "why did my mobile structure stop / why can't I open the door / why was the facing refused" gets nothing from megamek.log. MMLogger per class; debug on every refusal path with the value that failed; info on one-shot events (collapse, sinking starts, module linked). Feature tags: [MobileStructure], [Building], [Wall].

G. Performance

# Check Finding Recommendation
P1 No hot-path scans Bay.buildingCarrier() iterates every entity in the game and every transporter on each, and is called from getUsableDoors(), canLoad(), canUnloadUnits(), canLoadAt(), damageDoor/restoreDoor (14 call sites; Entity alone calls getUsableDoors() 5 times in load eligibility). Bot loading loops and the lobby call canLoad per unit per transporter: O(entities x bays) per query. Set the owning building on the bay when it is added (AbstractBuildingEntity.addTransporter) and store a transient back-reference, or pass the carrier from the call sites that already have it.
P2 isDeploymentPositionAndFacingValid recomputes footprint and board lookups per clause See D5. Hoist.

H. Tests

# Check Finding Recommendation
T1 No Swing/GUI tests FAIL. DeploymentDisplayTest$BuildingRotation (reflective write to cen, mocked BoardView, presses deployTurn which opens a modal BuildingFacingDialog); BuildingDoorMenuTest builds a JMenu, calls doClick(), asserts menu text contains "Level G". Maintainer position: "we shouldn't be testing gui components". Both also fail (T2/B3). Test the rule the GUI reads: isDeploymentPositionAndFacingValid per facing (already in BuildingRotationTest), BuildingRuntimeState.changeDoor ownership, the door-menu model if one is extracted. Delete the Swing parts.
T2 Tests green 16 failures (B3). Fix or remove.
T3 Integration test stability BuildingNetworkIntegrationTest starts a real Server on a socket with two connecting clients and waits on state predicates. Valuable, but a CI flake risk (ports, timing). Keep, but give it generous timeouts and an ephemeral port; consider tagging it so it can be excluded from the fast suite.
T4 Tests assert behaviour, not strings BuildingDoorMenuTest asserts on label text. MobileStructureFallTest expects 148 vs 150 with unmocked dice elsewhere in the path. Assert on state; control Compute.d6 where the outcome depends on it.

I. Automated review triage

Verified each against the code. Accept the valid ones, decline the rest with the stated reason.

Source Location Verdict Reason
Copilot Targetable.java:80 wall types in getTargetTotalHP VALID (low) Compute.getTargetTotalHP:3305 sums the parent building hex CF + armour for a WallTarget; a wall segment should report its own segment CF + armour. Add a WallTarget branch.
Copilot MobileStructureMovementHandler.java:324 VALID entering is taken from next (post-step layout) but mapped with unit.relativeToBoard(entering) using the pre-step pose; own-structure damage lands in the wrong hex. orElseThrow() at :319 is also unguarded.
Copilot (suppressed) CargoBay.java:75 ignores usableDoors DECLINE Infantry/cargo bays never required doors (TW p.223-225); behaviour is unchanged from main. The unused parameter is still a smell: document why.
Copilot (suppressed) MobileStructurePortalRules.java:142 closed.getFirst() DECLINE The candidate set behind the first hex is then filtered with allMatch over every closed hex requiring complex.isIn(back), so any matching complex must be behind the first hex too.
Copilot (suppressed) MobileStructureCargoRules.java:201 putIfAbsent by outside hex PLAUSIBLE Two doors on different floors sharing an exterior hex collapse to the first. Author to confirm against TO:AUE multi-floor door rules; key by (hex, level) if both are legal exits.
Copilot (suppressed) hex_heights x4 (BuildingCostCalculator:62, BLKStructureFile:130, BuildingConstruction:180, TestBuilding:412) NOT VERIFIED HERE All four say the structure-wide height is used where a per-hex height exists. Author to confirm and add a per-hex-height test.
CodeQL 25x dereferenced-value-may-be-null (Entity 2668-3040, MoveStep 3652-3683, ComputeToHit 124/132, MovementDisplay 5108, homing handlers 326/328, AreaEffectHelper 675) REVIEW EACH Pattern is "guarded once, dereferenced later". Bind once with a pattern variable or early-return.
CodeQL 11x internal-representation-exposure (BuildingDesign getters, BuildingRuntimeState.getDamagedBayDoors, MobileStructureNavalState x2) VALID Return Collections.unmodifiableList / List.copyOf; callers that mutate (getBayDoors().add(DOOR) in the network test) need a proper mutator.
CodeQL 8x unused parameter, 2x missing @Override, 3x uncaught NumberFormatException (BuildingDesignCodec:130, MapMenu:1662-1663) VALID Remove params or use them; add @Override; catch and log in the codec and menu.

J. Spotless warning before you format

spotlessApply strips import java.lang.System; as redundant. BoardView and MoveStep both carry that import
because they wildcard-import megamek.common.units.*, which contains a System enum, and both call
System.currentTimeMillis(). After formatting they will not compile. Change those calls to
java.lang.System.currentTimeMillis() first, then format. (Known trap on this project.)


K. Public API surface

Change External callers Action
Bay.canLoad(Entity) kept, delegates to new canLoad(Entity, int) MekHQ x20, MML OK.
IndustrialElevator.getCapacityTons() int to double; constructor int to double MekHQ AtBDynamicScenarioFactory:973 Source-compatible, binary-incompatible. Note in PR; MekHQ needs a rebuild against this.
BuildingEntity.hasEngine(), getWeaponFiringHeight(WeaponMounted) removed None found in MML/MekHQ OK, but deprecate rather than delete if either was public API in a release.
IBuilding +81 lines of new interface methods BuildingTerrain, Building, all implementors in this PR Any out-of-tree implementor breaks; acceptable for an internal interface.

L. PR body and process

  • Body is one sentence. The repo template (What this changes / Testing / What is not proven yet + checklist) is unused; no issue link.
  • No AI Assisted Development label despite AI-assisted authorship.
  • No statement of what was played in game. Given the size, the body must say which parts were exercised live (deploy a multi-hex building, open a door, move a mobile structure, sink one) and which were unit-tested only.

M. What is done well (keep)

  • Shared client/server deployment rule; non-mutating validity check.
  • XStream converters registered for every Serializable record; Java and XStream round-trip tests; legacy .ser fixture.
  • Lazy init of wallSegmentState / buildingRuntimeState; Game.readObject migration for the elevator map.
  • Bay.canLoad(Entity) preserved for MekHQ.
  • CFR-supplied MovePath recompiled and validated server-side before use.
  • Bot handling placed in BotClient, so Princess and CASPAR both get it.
  • Board.collapseBuilding uses setHex.

@HammerGS

Copy link
Copy Markdown
Member

Follow-up: deployment orientation and duplicated logic

Two items I was asked to look at specifically after the main review.

Deployment orientation (section D of the review, condensed)

  • Clicking a multi-hex building's own origin hex while turning it rotates the building to a junk facing. The new same-hex guard in processTurn sits after the multi-hex branch, so it never protects buildings; turnBuildingToward then computes position.direction(position). Reachable via hexMoused:936 with shift-click or Turn mode. The PR's own test shows it: clickingTheOriginDoesNotResetFacing expects 4 and gets 5.
  • Facings are offered at elevation 0 (determineDeploymentPosition:876, FACING_ELEVATION) but validated at the chosen elevation at :907 and in DeploymentProcessor:120. A sub-surface or raised building can be offered a facing it is then refused, with no valid option reachable.
  • hasFacingDependentFootprint is still footprint > 1, but hexside walls and mobile portals now make single-hex validity facing-dependent inside isDeploymentPositionAndFacingValid. Single-hex walls take the plain-unit path and get no facing chooser.
  • The rotation maths itself agrees between client and server; DeploymentProcessorTest stamps the correct footprint for all six facings.

Duplicated logic

What Where Recommendation
Hex rotation, same formula three times AbstractBuildingEntity.rotateCoordByFacing (main, private), MobileStructureLinkage.rotate (this PR), the lambda in MML BuildingUtil.rotate (#2328) One public CubeCoords.rotate(int turns) on the record; delete the other two.
Two facing predicates AllowedDeploymentHelper.hasFacingDependentFootprint (size > 1) gates the turn UX; findAllowedFacings was switched to instanceof AbstractBuildingEntity; getValidFacingsAt is left unused and duplicates the new six-facing loop One predicate on AllowedDeploymentHelper, used everywhere; delete isFacingDependentDeployment, deprecate getValidFacingsAt.
Client-side deployment validity runs three times plus the server checkNags (on Done), determineDeploymentPosition:907, processTurn:1119; findAllowedFacings already filters Once the elevation mismatch above is fixed, the :907 check is redundant with findAllowedFacings. Keep checkNags as the safety net.
Building elevation in three layers main: IBuilding.getHeight, Building.getBuildingHeight; this PR: BuildingConstruction.baseLevel (11 callers), BuildingElevation.base/roof/floor, MobileStructure.getBaseElevation + getStructureBaseElevation (25 callers), each handling bridges differently Consolidate on BuildingElevation; make baseLevel and the MobileStructure accessors delegate to it.

Checked and not duplicated: the three collapse handlers compose rather than repeat; BLKStructureFile delegates to BuildingDesignCodec; the rollMovementInBuilding overload delegates; MML BuildingUtil calls BuildingConstruction and TestBuilding rather than re-deriving the rules.

Overlap with an in-progress branch

Implement-Building-Editor-Hex-Layout (pushed, not yet a PR) adds a hex-map view to the in-game damage editor. It touches four of the same files as this PR: AbstractBuildingEntity, UnitDamageControls, UnitDamagePanelBuilder, and the client messages.properties. Whichever lands second will conflict there.

It also names building hexes differently. That branch adds BuildingHexNames.hexName driven by AbstractBuildingEntity.getHexIndex(location); #2328 adds BuildingUtil.locationLabel (sheet-grid label plus level) and absoluteHexLabel (q,r). The same hex would carry one name in MegaMek's damage editor and another on the MegaMekLab sheet. That naming belongs in one helper in megamek.common that both apps call. Happy to coordinate on which one wins.

@HammerGS

Copy link
Copy Markdown
Member

Recommendation: resolving the duplication

For each area: the one implementation that survives, what the others become, and the functionality each side needs kept.

1. Hex rotation: one method on the record

  • Keep: a new CubeCoords.rotate(int turns) (one clockwise step is (-r, -s, -q), floorMod on turns). The signed-zero fix in the CubeCoords constructor stays; rotation is what produces -0.0.
  • Remove: AbstractBuildingEntity.rotateCoordByFacing (delegate, then delete), MobileStructureLinkage.rotate; the lambda in MML BuildingUtil.rotate becomes hex -> hex.rotate(1).
  • Test: rotate(6) is identity, and rotate(1) of each unit vector matches the neighbour in the next facing. That pins the "facing 0 is up, clockwise" convention the three copies currently assume silently.

2. Facing predicates: two concepts, one home (AllowedDeploymentHelper)

  • isBuildingWithFacingRules(entity) = any AbstractBuildingEntity. Drives findAllowedFacings, the Turn button label, and processTurn routing to turnBuildingToward / turnBuildingToValidFacing. Those already toast and log, so the single-hex toast branch in processTurn and the buildingCannotRotate message go away.
  • hasFacingDependentFootprint(entity) = multi-hex only. Drives only isFacingClickOnPlacedBuilding: a single-hex building keeps click-to-move on adjacent hexes, a multi-hex one keeps click-to-turn.
  • Delete isFacingDependentDeployment; make getValidFacingsAt delegate to the deployment-valid loop and deprecate it.
  • Gain: single-hex walls and portals get the facing chooser with invalid facings greyed. Symmetric single-hex buildings see all six enabled, which is correct.

3. Deployment validity: one rule that says why

  • Turn isDeploymentPositionAndFacingValid into deploymentRejection(position, facing, elevation, boardId) returning a small enum (NONE, OFF_BOARD, UNEVEN, OVERLAP, OCCUPIED, OUTSIDE_ZONE, NO_SURFACE_PART, PORTAL_UNMATCHED, WALL_OVERLAP). The boolean form becomes == NONE.
  • Three gates, same rule: findAllowedFacings(finalElevation) builds the menu after the elevation is chosen; checkNags is the client's last gate; DeploymentProcessor is the server's. Delete the check at determineDeploymentPosition:907.
  • Gain: the client toast and the server log both name the reason. The elevation mismatch disappears because only one elevation is in play.

4. Elevation: design offset versus runtime absolute

These are two different quantities, so the fix is naming and routing, not merging.

  • BuildingConstruction.baseLevel is an authoring value (lowest floor relative to the building's own ground): rename designBaseLevel. MobileStructure.getStructureBaseElevation is the same kind of value: rename designBaseOffset and keep the two together.
  • BuildingElevation is the only runtime facade: base, roof, floor, contains, at. Give IBuilding a default getDeckLevel(coords) so the two bridge cases (terrain bridge, entity bridge) stop being an instanceof ladder inside base.
  • MobileStructure.getBaseElevation(coords) stays as the mobile implementation but is reached only through the facade. Route the 25 direct callers through BuildingElevation.base so the sinking, tunnel and VTOL cases cannot be bypassed.
  • Test: one BuildingElevationTest covering surface, underground, terrain bridge, entity bridge, tracked mobile on a tunnel, sinking naval, VTOL. Today those cases are split across three implementations with no single test.

5. Hex naming: one identity, two views

The two schemes serve different audiences, so keep both views but give them one identity.

  • Identity: the hex's number in the unit file's declaration order, 1-based. Implement-Building-Editor-Hex-Layout already has getHexIndex(location); promote it to AbstractBuildingEntity with a getHexNumber(CubeCoords). It is stable across rotation because BuildingUtil.rotate preserves list order, and across re-save because the file is that list.
  • Shared helper: move BuildingHexNames from the client package to megamek.common.units as BuildingHexLabels, with hexName(building, index) giving "Hex 3" or "Hex 3 (0304)" once placed, and locationName(building, location) giving "Hex 3, Level 2". Its message keys move to the common bundle so MegaMekLab can read them.
  • Sheet view (Add subcapital cannon ammo weights #2328): keep SheetGrid for drawing the map, but print the hex number inside every drawn hex and use locationName in the inventory location column. The "0104" grid label becomes a drawing coordinate only. Drop absoluteHexLabel from anything a person reads.
  • Gain: "Hex 3, Level 2" in the in-game damage editor is the same hex the printed sheet calls "Hex 3, Level 2".

6. Order of landing

Land Implement-Building-Editor-Hex-Layout first (738 lines, already pushed, owns the identity in item 5). This PR has to be split anyway; its shared-model slice rebases on top and adopts getHexIndex and BuildingHexLabels. Items 1 to 4 belong in that same slice, since every later slice depends on them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants