Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions megamek/resources/megamek/client/messages.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2450,6 +2450,8 @@ DeploymentDisplay.deployHullDown=Hull Down
DeploymentDisplay.deployHullDownOff=Cancel Hull Down
DeploymentDisplay.deployHullDown.tooltip=Toggle whether this vehicle starts the game hull down. Hull down requires deploying onto a fortified hex; turn it off here if you have no fortified hex to deploy into.
DeploymentDisplay.deployTurn=Turn
DeploymentDisplay.deployTurn.tooltip=Place the unit, then click a hex in the direction it should face. You can also hold Shift and drag. Buildings rotate their entire footprint around the origin hex.
DeploymentDisplay.buildingCannotRotate=The building cannot fit at that facing. Its entire footprint must fit on valid hexes within the deployment zone. Choose another direction or move the building.
DeploymentDisplay.deployFacing=Facing
DeploymentDisplay.deployLoad=Load
DeploymentDisplay.deployUnload=Unload
Expand Down Expand Up @@ -3954,6 +3956,11 @@ MovementDisplay.moveShakeOff=Shake Off
MovementDisplay.MoveTakeOff=Lift Off
MovementDisplay.moveTakeCover=Take Cover
MovementDisplay.moveTow=Tow
MovementDisplay.moveModuleLink=Link Modules
MovementDisplay.moveModuleUnlink=Unlink Modules
MovementDisplay.ModulePartner=Choose the adjacent module whose linkages to engage or detach.
CommonMenuBar.moveModuleLink=Link Modules
CommonMenuBar.moveModuleUnlink=Unlink Modules
MovementDisplay.moveTurn=Turn
MovementDisplay.MoveTurnLeft=Turn Left
MovementDisplay.MoveTurnRight=Turn Right
Expand Down Expand Up @@ -7386,3 +7393,10 @@ ForceGeneratorDialog.formationDropped=A {0} formation could not be built here.\n

# SearchableComboBox
SearchableComboBox.tooltip=Type to search the list, Enter picks the highlighted or first match, Escape clears the search
BuildingCriticalWeaponDialog.title=Building Critical Hit
BuildingCriticalWeaponDialog.message=The critical-hit roll gives you the choice. Select the affected weapon.
MovementDisplay.MobileAvoidance=Collision avoided: plot your full walking or cruising move now, then press Done. The Mobile Structure will continue afterward.
MovementDisplay.MobileAvoidanceWalk=Collision avoidance permits walking or cruising movement only.
MovementDisplay.MobilePivot=Choose the central pivot hex
MovementDisplay.MobileSpeedTitle=Water Mobile Structure speed
MovementDisplay.MobileSpeed=Choose this turn's speed (MP). Unplotted movement continues in the last travel direction.
1 change: 1 addition & 0 deletions megamek/resources/megamek/common/messages.properties
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ BuildingType.MEDIUM.text=Medium
BuildingType.HEAVY.text=Heavy
BuildingType.HARDENED.text=Hardened
BuildingType.WALL.text=Wall
BuildingType.RAIL.text=Rail
##### Unsorted Resources
#Aerospace Critical Damage Strings
Aero.avionicsDamageString=Avionics (%d)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,10 @@ GameOptionsInfo.option.assault_drop.description=If checked, Meks, ProtoMeks and
GameOptionsInfo.option.paratroopers.displayableName=(Legacy) Paratroopers
GameOptionsInfo.option.paratroopers.description=If checked, infantry can deploy using assault drop rules (assault drop must also be enabled).
GameOptionsInfo.option.bridge_building_engineers.displayableName=TO Bridge-Building Engineers (Infantry)
GameOptionsInfo.option.building_expanded_cf.displayableName=TO Optional Building Damage Tracking (Expanded CF)
GameOptionsInfo.option.building_expanded_collapse.displayableName=TO Expanded Building Collapse
GameOptionsInfo.option.building_expanded_collapse.description=Collapsing structures damage adjacent parts of the same building and scatter debris into nearby hexes. This is independent of per-floor CF tracking (TO:AR pp. 121-123).
GameOptionsInfo.option.building_expanded_cf.description=Track CF and armor separately on each floor of buildings and Mobile Structures. When disabled, damage is tracked per hex. All players must agree to this optional rule before play begins (TO:AR pp. 119-121; TO:AUE p. 40, Optional Damage Tracking).
GameOptionsInfo.option.bridge_building_engineers.description=If checked, infantry with the Bridge-Building Engineers specialization can spend 6 full turns adjacent to a target hex to raise a single-hex Light Bridge (CF 15, two per scenario) or Medium Bridge (CF 40, one per scenario). CF is doubled when the bridge is built over water. Taking casualties during construction extends the work by 1 turn. TO:AUE.
GameOptionsInfo.option.unofficial_bridge_repair_engineers.displayableName=(Unofficial) Bridge-Building Engineers May Repair Destroyed Sections
GameOptionsInfo.option.unofficial_bridge_repair_engineers.description=Requires the TO Bridge-Building Engineers option. If checked, a Bridge-Building Engineer platoon may also rebuild a single destroyed section (a missing hex) of an existing bridge by working from an adjacent surviving span or bank, using the normal build rules. The player chooses a Light (CF 15) or Medium (CF 40) section, doubled over water, costing bridge-building points by type (Light 1, Medium both); the new section's deck matches the surviving span so the bridge reconnects. Rubble left under the collapsed section remains. This is a house rule, not part of any published BattleTech ruleset.
Expand Down
7 changes: 7 additions & 0 deletions megamek/resources/megamek/common/report-messages.properties
Original file line number Diff line number Diff line change
Expand Up @@ -919,6 +919,11 @@
5297=Industrial elevator at <data> moves <data> from level <data> to level <data>.
5298=<data> fell down elevator shaft!
5299=<data> rides elevator <data> to level <data>.
9891=<data> commits <data> of <data> MP toward its next maneuver; its footprint has not moved yet.
9895=<data> cannot complete this move: collision resolution between Mobile Structures is not yet supported. The colliding step was not applied.
9892=<data> checks environmental integrity at <data>: roll <data>, modifier <data>; <msg:9893,9894>.
9893=breached
9894=seal holds
5300=<data> (<data>) digs in and takes cover.
5301=<data> (<data>) hits the deck.
5305=<data> (<data>) builds fortifications in hex <data>
Expand Down Expand Up @@ -1687,3 +1692,5 @@ report.unknownOwner=Unknown owner
7722=\ no ignition.
7723=<data> (<data>) needs <data>+ for its exhaust to set the hexes behind hex <data> alight as it lands, rolled <data> :<msg:7721,7722>
7724=\ The flammable atmosphere spreads this attack across the platoon as an area-effect attack: <data> damage.
9896=<data> suffers <data> armor and <data> CF damage; <data> CF remains on this segment.
9897=<data> is lost to <data> and begins sinking next turn at <data> depths per turn. Units on its deck and in its bays remain aboard.
32 changes: 31 additions & 1 deletion megamek/src/megamek/client/Client.java
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,10 @@ public void sendBuildingEdit(BuildingEditSpec spec) {
send(new Packet(PacketCommand.BUILDING_EDIT, spec));
}

public void sendBuildingDoor(int buildingId, int doorIndex, boolean open) {
send(new Packet(PacketCommand.BUILDING_DOOR, buildingId, doorIndex, open));
}

/**
* Sends a packet containing multiple entity updates. Should only be used in the lobby phase.
*/
Expand Down Expand Up @@ -864,7 +868,13 @@ protected void receiveBuildingRemove(Packet packet) throws InvalidPacketDataExce

protected void receiveBuildingCollapse(Packet packet) throws InvalidPacketDataException {
int boardId = packet.getIntValue(1);
game.getBoard(boardId).collapseBuilding(packet.getCoordsVector(0));
if (packet.getObject(2) instanceof Integer entityId && game.getEntity(entityId) instanceof IBuilding building) {
for (Coords coords : packet.getCoordsVector(0)) {
game.getBoard(boardId).collapseBuilding(building, coords);
}
} else {
game.getBoard(boardId).collapseBuilding(packet.getCoordsVector(0));
}
}

/**
Expand Down Expand Up @@ -1437,6 +1447,9 @@ protected boolean handleGameSpecificPacket(Packet packet) {
if (cfrType != null) {
GameCFREvent cfrEvt = new GameCFREvent(this, cfrType);
switch (cfrType) {
case CFR_MOBILE_AVOIDANCE:
cfrEvt.setEntityId(packet.getIntValue(1));
break;
case CFR_DOMINO_EFFECT:
cfrEvt.setEntityId(packet.getIntValue(1));
cfrEvt.setDirection(packet.getIntValue(2));
Expand All @@ -1463,6 +1476,10 @@ protected boolean handleGameSpecificPacket(Packet packet) {
cfrEvt.setTAGTargets(packet.getIntList(1));
cfrEvt.setTAGTargetTypes(packet.getIntList(2));
break;
case CFR_BUILDING_WEAPON:
cfrEvt.setEntityId(packet.getIntValue(1));
cfrEvt.setBuildingWeaponIds(packet.getIntList(2));
break;
default:
break;
}
Expand Down Expand Up @@ -1560,6 +1577,15 @@ public void sendTAGTargetCFRResponse(int index) {
send(new Packet(PacketCommand.CLIENT_FEEDBACK_REQUEST, PacketCommand.CFR_TAG_TARGET, index));
}

public void sendMobileAvoidanceCFRResponse(int entityId, MovePath path) {
send(new Packet(PacketCommand.CLIENT_FEEDBACK_REQUEST, PacketCommand.CFR_MOBILE_AVOIDANCE, entityId, path));
}

public void sendBuildingWeaponCFRResponse(int buildingId, int equipmentId) {
send(new Packet(PacketCommand.CLIENT_FEEDBACK_REQUEST, PacketCommand.CFR_BUILDING_WEAPON,
buildingId, equipmentId));
}

public Set<BoardDimensions> getAvailableMapSizes() {
return availableSizes;
}
Expand Down Expand Up @@ -1670,6 +1696,10 @@ public void sendUnloadStranded(int... entityIds) {
send(new Packet(PacketCommand.UNLOAD_STRANDED, entityIds));
}

public void sendUnloadStranded(int[] entityIds, Map<Integer, Coords> exits) {
send(new Packet(PacketCommand.UNLOAD_STRANDED, entityIds, exits));
}

/**
* Change whose turn it is.
*/
Expand Down
22 changes: 22 additions & 0 deletions megamek/src/megamek/client/bot/BotClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,28 @@ public void gameClientFeedbackRequest(GameCFREvent evt) {
case CFR_TAG_TARGET:
sendTAGTargetCFRResponse(pickTagTarget(evt));
break;
case CFR_BUILDING_WEAPON:
sendBuildingWeaponCFRResponse(evt.getEntityId(), evt.getBuildingWeaponIds().getFirst());
break;
case CFR_MOBILE_AVOIDANCE:
Entity avoiding = getGame().getEntity(evt.getEntityId());
MovePath escape = new MovePath(getGame(), avoiding);
for (int direction = 0; direction < 6; direction++) {
MovePath candidate = new MovePath(getGame(), avoiding);
candidate.findSimplePathTo(avoiding.getPosition().translated(direction,
Math.max(1, avoiding.getWalkMP())), megamek.common.enums.MoveStepType.FORWARDS,
direction, avoiding.getFacing());
while (candidate.length() > 0 && (candidate.getMpUsed() > avoiding.getWalkMP()
|| !candidate.getLastStep().isLegal(candidate))) {
candidate.removeLastStep();
}
if (candidate.getFinalCoords().distance(avoiding.getPosition())
> escape.getFinalCoords().distance(avoiding.getPosition())) {
escape = candidate;
}
}
sendMobileAvoidanceCFRResponse(avoiding.getId(), escape);
break;
default:
break;
}
Expand Down
7 changes: 3 additions & 4 deletions megamek/src/megamek/client/bot/princess/FireControl.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@
import megamek.common.Hex;
import megamek.common.HexTarget;
import megamek.common.LosEffects;
import megamek.common.PartialCover;
import megamek.common.Messages;
import megamek.common.PartialCover;
import megamek.common.Player;
import megamek.common.RangeType;
import megamek.common.TargetRollModifier;
Expand Down Expand Up @@ -74,7 +74,6 @@
import megamek.common.planetaryConditions.IlluminationLevel;
import megamek.common.planetaryConditions.PlanetaryConditions;
import megamek.common.rolls.TargetRoll;
import megamek.common.rules.core.CoreRulesManager;
import megamek.common.rules.totalwarfare.TWRulesManager;
import megamek.common.units.*;
import megamek.common.weapons.Weapon;
Expand Down Expand Up @@ -1707,7 +1706,7 @@ protected boolean isSubCommander(final Entity entity) {
// building, or infantry/BA that won't be killed off in one shot.
}
if ((damageFraction < 0.5)
|| (target.getTargetType() == Targetable.TYPE_BUILDING)
|| (Targetable.isBuildingType(target.getTargetType()))
|| (target.getTargetType() == Targetable.TYPE_HEX_CLEAR)
) {
return 0;
Expand Down Expand Up @@ -3328,7 +3327,7 @@ public static double getMaxDamageAtRange(final Entity shooter,
weaponDamage = weaponType.getRackSize();
} else if (weaponDamage == WeaponType.DAMAGE_VARIABLE && shooter.isConventionalInfantry()) {
ConvInfantry infantryShooter = (ConvInfantry) shooter;

weaponDamage = (int) Math.round(infantryShooter.getDamagePerTrooper() * infantryShooter.getShootingStrength());
}

Expand Down
15 changes: 13 additions & 2 deletions megamek/src/megamek/client/ui/SharedUtility.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@

package megamek.client.ui;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
Expand Down Expand Up @@ -63,6 +64,14 @@
public class SharedUtility {
private final static MMLogger LOGGER = MMLogger.create(SharedUtility.class);

/** Displays Mobile Structure quarter-points as MP, retaining integer text for other units. */
public static String formatMovementPoints(@Nullable Entity entity, int points) {
if (entity instanceof MobileStructure) {
return BigDecimal.valueOf(points).divide(BigDecimal.valueOf(4)).stripTrailingZeros().toPlainString();
}
return Integer.toString(points);
}

public static String doPSRCheck(MovePath md) {
return (String) doPSRCheck(md, true);
}
Expand Down Expand Up @@ -515,7 +524,9 @@ private static Object doPSRCheck(MovePath md, boolean stringResult) {
bldg = board.getBuildingAt(curPos);
}

if (bldg != null) {
if (bldg != null && !(bldg instanceof AbstractBuildingEntity buildingEntity
&& buildingEntity.getBuildingRuntimeState().openPassage(buildingEntity, entity,
lastPos, curPos, step.getElevation()))) {
rollTarget = entity.rollMovementInBuilding(bldg, distance, reason, overallMoveType);
SharedUtility.checkNag(rollTarget, nagReport, psrList);
}
Expand Down Expand Up @@ -595,7 +606,7 @@ private static Object doPSRCheck(MovePath md, boolean stringResult) {

firstStep = false;
}

// running with destroyed hip or gyro needs a check
rollTarget = entity.checkRunningWithDamage(overallMoveType, md.getHexesMoved());
checkNag(rollTarget, nagReport, psrList);
Expand Down
24 changes: 22 additions & 2 deletions megamek/src/megamek/client/ui/clientGUI/ClientGUI.java
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,9 @@
import megamek.client.commands.*;
import megamek.client.event.BoardViewEvent;
import megamek.client.event.BoardViewListener;
import megamek.client.ratgenerator.GenerationContext;
import megamek.client.event.MekDisplayEvent;
import megamek.client.event.MekDisplayListener;
import megamek.client.ratgenerator.GenerationContext;
import megamek.client.ui.Messages;
import megamek.client.ui.clientGUI.audio.AudioService;
import megamek.client.ui.clientGUI.audio.SoundManager;
Expand Down Expand Up @@ -3494,7 +3494,7 @@ public void gameClientFeedbackRequest(GameCFREvent gameCFREvent) {
Object[] options;
MovePath[] paths;
int optionType;
if (Game.rulesManager.getRulesMovement().isDominoMoveLegal(direction, entity, stepForward, true)
if (Game.rulesManager.getRulesMovement().isDominoMoveLegal(direction, entity, stepForward, true)
&& Game.rulesManager.getRulesMovement().isDominoMoveLegal(direction, entity, stepBackward,
false)) {
options = new Object[3];
Expand Down Expand Up @@ -3695,6 +3695,26 @@ public void gameClientFeedbackRequest(GameCFREvent gameCFREvent) {
client.sendHiddenPBSCFRResponse(null);
}
break;
case CFR_MOBILE_AVOIDANCE:
if (!(client instanceof BotClient) && entity != null
&& getCurrentPanel() instanceof MovementDisplay movement) {
movement.beginMobileCollisionAvoidance(entity.getId());
} else if (!(client instanceof BotClient)) {
client.sendMobileAvoidanceCFRResponse(gameCFREvent.getEntityId(), null);
}
break;
case CFR_BUILDING_WEAPON:
Entity criticalBuilding = client.getGame().getEntity(gameCFREvent.getEntityId());
List<Integer> criticalWeapons = gameCFREvent.getBuildingWeaponIds();
List<String> criticalChoices = criticalWeapons.stream()
.map(id -> id + ": " + criticalBuilding.getEquipment(id).getDesc()).toList();
input = (String) JOptionPane.showInputDialog(frame,
Messages.getString("BuildingCriticalWeaponDialog.message"),
Messages.getString("BuildingCriticalWeaponDialog.title"),
JOptionPane.QUESTION_MESSAGE, null, criticalChoices.toArray(), criticalChoices.getFirst());
client.sendBuildingWeaponCFRResponse(criticalBuilding.getId(),
criticalWeapons.get(Math.max(0, criticalChoices.indexOf(input))));
break;
case CFR_TELEGUIDED_TARGET:
logger.debug("CFR_TELEGUIDED_TARGET: processing teleguided missile target selection");
List<Integer> targetIds = gameCFREvent.getTelemissileTargetIds();
Expand Down
Loading