Skip to content

Commit

Permalink
feat: adds SUA damage into the mix
Browse files Browse the repository at this point in the history
  • Loading branch information
Scoppio committed Jan 6, 2025
1 parent e0d2273 commit 9653342
Show file tree
Hide file tree
Showing 8 changed files with 151 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ public boolean isEmpty() {
return specialAbilities.isEmpty();
}

public EnumMap<BattleForceSUA, Object> getInternalRepr() {
return specialAbilities;
}

@Override
public String toString() {
return specialAbilities.keySet().stream()
Expand Down Expand Up @@ -156,4 +160,4 @@ public boolean hasSUA(BattleForceSUA sua) {
public void removeSUA(BattleForceSUA sua) {
specialAbilities.remove(sua);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,7 @@ public List<Formation> getActiveFormations() {
.map(u -> (Formation) u)
.toList();
}

public List<Formation> getActiveDeployedFormations() {
return getActiveFormations().stream()
.filter(Formation::isDeployed)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
import megamek.common.Compute;
import megamek.common.Entity;
import megamek.common.Roll;
import megamek.common.alphaStrike.ASDamage;
import megamek.common.alphaStrike.ASDamageVector;
import megamek.common.alphaStrike.ASRange;
import megamek.common.alphaStrike.AlphaStrikeElement;
import megamek.common.autoresolve.acar.SimulationManager;
import megamek.common.autoresolve.acar.action.AttackToHitData;
Expand All @@ -25,10 +28,13 @@
import megamek.common.autoresolve.component.EngagementControl;
import megamek.common.autoresolve.component.Formation;
import megamek.common.strategicBattleSystems.SBFUnit;
import megamek.common.util.weightedMaps.WeightedDoubleMap;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

public class StandardUnitAttackHandler extends AbstractActionHandler {

Expand Down Expand Up @@ -131,7 +137,7 @@ private void applyDamage(Formation target, SBFUnit targetUnit, int[] damage, SBF

reporter.reportDamageDealt(targetUnit, totalDamageApplied, targetUnit.getCurrentArmor());

if (totalDamageApplied * 2 >= targetUnit.getCurrentArmor()) {
if (targetUnit.getCurrentArmor() * 2 < totalDamageApplied) {
target.setHighStressEpisode();
reporter.reportStressEpisode();
}
Expand Down Expand Up @@ -165,10 +171,60 @@ private int[] calculateDamage(Formation attacker, StandardUnitAttack attack,
bonusDamage += 1;
}

var damage = attackingUnit.getElements().stream().mapToInt(e -> e.getStandardDamage().getDamage(attack.getRange()).damage).toArray();
var damage = attackingUnit.getElements().stream().mapToInt(e -> getDamage(e, attack.getRange())).toArray();
return processDamageByEngagementControl(attacker, target, bonusDamage, damage);
}

private enum ArcSelection {
FRONT, LEFT, RIGHT, REAR
}

private int getDamage(AlphaStrikeElement element, ASRange range) {
var stdDamage = element.getStdDamage();
if (stdDamage.hasDamage()) {
return stdDamage.getDamage(range).damage;
}

var frontArcDamages = element.getFrontArc().getInternalRepr().values().stream().filter(o -> o instanceof ASDamageVector)
.map(o -> (ASDamageVector) o).toList();
var leftArcDamages = element.getLeftArc().getInternalRepr().values().stream().filter(o -> o instanceof ASDamageVector)
.map(o -> (ASDamageVector) o).toList();
var rightArcDamages = element.getRightArc().getInternalRepr().values().stream().filter(o -> o instanceof ASDamageVector)
.map(o -> (ASDamageVector) o).toList();
var rearArcDamages = element.getRearArc().getInternalRepr().values().stream().filter(o -> o instanceof ASDamageVector)
.map(o -> (ASDamageVector) o).toList();

var frontArcDmgTotal = frontArcDamages.stream().mapToInt(d -> d.getDamage(range).damage).sum();
var leftArcDmgTotal = leftArcDamages.stream().mapToInt(d -> d.getDamage(range).damage).sum();
var rightArcDmgTotal = rightArcDamages.stream().mapToInt(d -> d.getDamage(range).damage).sum();
var rearArcDmgTotal = rearArcDamages.stream().mapToInt(d -> d.getDamage(range).damage).sum();

var arcSelectionWeightedDoubleMap = WeightedDoubleMap.of(
ArcSelection.FRONT, frontArcDmgTotal,
ArcSelection.LEFT, leftArcDmgTotal,
ArcSelection.RIGHT, rightArcDmgTotal,
ArcSelection.REAR, rearArcDmgTotal);

if (!arcSelectionWeightedDoubleMap.isEmpty()) {
switch (arcSelectionWeightedDoubleMap.randomItem()) {
case FRONT -> {
return frontArcDmgTotal;
}
case LEFT -> {
return leftArcDmgTotal;
}
case RIGHT -> {
return rightArcDmgTotal;
}
case REAR -> {
return rearArcDmgTotal;
}
}
}

return 0;
}

private void handleCrits(Formation target, SBFUnit targetUnit, int criticalRollResult, SBFUnit attackingUnit) {
switch (criticalRollResult) {
case 2, 3, 4 -> reporter.reportNoCrit();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ public void execute() {
if (unitWithdrawn.get() > 0) {
reporter.reportSuccessfulWithdraw();
}
game().removeFormation(withdrawFormation);
// game().removeFormation(withdrawFormation);
} else {
reporter.reportFailedWithdraw();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ protected void executePhase() {

private void checkUnitDestruction() {
var printLatch = false;
var allFormations = getContext().getActiveFormations();
var allFormations = getContext().getActiveDeployedFormations();
for (var formation : allFormations) {
var destroyedUnits = formation.getUnits().stream()
.filter(u -> u.getCurrentArmor() <= 0)
Expand All @@ -69,7 +69,7 @@ private void checkWithdrawingForces() {
// If the game is over, no need to withdraw
return;
}
var forcedWithdrawingUnits = getSimulationManager().getGame().getActiveFormations().stream()
var forcedWithdrawingUnits = getSimulationManager().getGame().getActiveDeployedFormations().stream()
.filter(f -> f.moraleStatus() == Formation.MoraleStatus.ROUTED || f.isCrippled())
.toList();

Expand All @@ -79,7 +79,7 @@ private void checkWithdrawingForces() {
}

private void checkMorale() {
var formationNeedsMoraleCheck = getSimulationManager().getGame().getActiveFormations().stream()
var formationNeedsMoraleCheck = getSimulationManager().getGame().getActiveDeployedFormations().stream()
.filter(Formation::hadHighStressEpisode)
.toList();

Expand All @@ -89,7 +89,7 @@ private void checkMorale() {
}

private void checkRecoveringNerves() {
var recoveringNerves = getSimulationManager().getGame().getActiveFormations().stream()
var recoveringNerves = getSimulationManager().getGame().getActiveDeployedFormations().stream()
.filter(SBFFormation::isDeployed)
.filter(f -> f.moraleStatus().ordinal() > Formation.MoraleStatus.NORMAL.ordinal())
.toList();
Expand Down
92 changes: 76 additions & 16 deletions megamek/src/megamek/common/autoresolve/acar/phase/FiringPhase.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@

import megamek.common.Compute;
import megamek.common.Entity;
import megamek.common.alphaStrike.ASDamageVector;
import megamek.common.alphaStrike.ASRange;
import megamek.common.alphaStrike.AlphaStrikeElement;
import megamek.common.annotations.Nullable;
import megamek.common.autoresolve.acar.SimulationManager;
import megamek.common.autoresolve.acar.action.Action;
import megamek.common.autoresolve.acar.action.ManeuverToHitData;
Expand Down Expand Up @@ -85,27 +88,13 @@ private void standardUnitAttacks(AttackRecord attackRecord) {
&& !actingFormation.isEngagementControlFailed()) {
range = ASRange.SHORT;
} else {
var rangeMap = WeightedDoubleMap.of(
ASRange.LONG, actingFormation.getStdDamage().L.damage,
ASRange.MEDIUM, actingFormation.getStdDamage().M.damage,
ASRange.SHORT, actingFormation.getStdDamage().S.damage
);
if (!rangeMap.isEmpty()) {
range = rangeMap.randomItem();
}
range = selectBestRange(actingFormation, range);

}
manueverModifier = StandardUnitAttack.ManeuverResult.SUCCESS;
target.setRange(actingFormation.getId(), range);
} else if (actingFormationMos < targetFormationMos) {
var rangeMap = WeightedDoubleMap.of(
ASRange.LONG, target.getStdDamage().L.damage,
ASRange.MEDIUM, target.getStdDamage().M.damage,
ASRange.SHORT, target.getStdDamage().S.damage
);
if (!rangeMap.isEmpty()) {
range = rangeMap.randomItem();
}
range = selectBestRange(target, range);
manueverModifier = StandardUnitAttack.ManeuverResult.FAILURE;
target.setRange(actingFormation.getId(), range);
}
Expand All @@ -131,6 +120,77 @@ private void standardUnitAttacks(AttackRecord attackRecord) {
getSimulationManager().addAttack(attacks, actingFormation);
}

private static ASRange selectBestRange(Formation actingFormation, ASRange range) {
var stdDamage = actingFormation.getStdDamage();
if (stdDamage.hasDamage()) {
var rangeMap = WeightedDoubleMap.of(
ASRange.LONG, actingFormation.getStdDamage().L.damage,
ASRange.MEDIUM, actingFormation.getStdDamage().M.damage,
ASRange.SHORT, actingFormation.getStdDamage().S.damage
);
if (!rangeMap.isEmpty()) {
range = rangeMap.randomItem();
}
return range;
}
for (var unit : actingFormation.getUnits()) {
for (var element : unit.getElements()) {
var elementRange = selectBestRange(element);
if (elementRange != null) {
return elementRange;
}
}
}

return ASRange.LONG;
}

@Nullable
private static ASRange selectBestRange(AlphaStrikeElement element) {
var stdDamage = element.getStdDamage();

if (stdDamage.hasDamage()) {
var rangeMap = WeightedDoubleMap.of(
ASRange.LONG, element.getStdDamage().L.damage,
ASRange.MEDIUM, element.getStdDamage().M.damage,
ASRange.SHORT, element.getStdDamage().S.damage
);
if (!rangeMap.isEmpty()) {
return rangeMap.randomItem();
}
} else {

var damages = element.getFrontArc().getInternalRepr().values().stream().filter(o -> o instanceof ASDamageVector)
.map(o -> (ASDamageVector) o).collect(Collectors.toList());
damages.addAll(element.getLeftArc().getInternalRepr().values().stream().filter(o -> o instanceof ASDamageVector)
.map(o -> (ASDamageVector) o).toList());
damages.addAll(element.getRightArc().getInternalRepr().values().stream().filter(o -> o instanceof ASDamageVector)
.map(o -> (ASDamageVector) o).toList());
damages.addAll(element.getRearArc().getInternalRepr().values().stream().filter(o -> o instanceof ASDamageVector)
.map(o -> (ASDamageVector) o).toList());

damages.add(element.getFrontArc().getStdDamage());
damages.add(element.getLeftArc().getStdDamage());
damages.add(element.getRightArc().getStdDamage());
damages.add(element.getRearArc().getStdDamage());

var longRangeDamage = damages.stream().mapToDouble(d -> d.L.damage).sum();
var mediumRangeDamage = damages.stream().mapToDouble(d -> d.M.damage).sum();
var shortRangeDamage = damages.stream().mapToDouble(d -> d.S.damage).sum();

var ranges = WeightedDoubleMap.of(
ASRange.LONG, longRangeDamage,
ASRange.MEDIUM, mediumRangeDamage,
ASRange.SHORT, shortRangeDamage);

if (!ranges.isEmpty()) {
return ranges.randomItem();
}
}

return null;
}

private record AttackRecord(Formation actingFormation, Formation target, List<Integer> attackingUnits) { }

private List<AttackRecord> attack(Formation actingFormation) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ private void engagementAndControl(EngagementControlRecord engagement) {
private Optional<Formation> selectTarget(Formation actingFormation) {
var game = getSimulationManager().getGame();
var player = game.getPlayer(actingFormation.getOwnerId());
var canBeTargets = getSimulationManager().getGame().getActiveFormations().stream()
var canBeTargets = getSimulationManager().getGame().getActiveDeployedFormations().stream()
.filter(f -> actingFormation.getTargetFormationId() == Entity.NONE || f.getId() == actingFormation.getTargetFormationId())
.filter(SBFFormation::isDeployed)
.filter(f -> f.isGround() == actingFormation.isGround())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
package megamek.common.autoresolve.acar.phase;

import megamek.common.Entity;
import megamek.common.IEntityRemovalConditions;
import megamek.common.autoresolve.acar.SimulationContext;
import megamek.common.autoresolve.acar.SimulationManager;
import megamek.common.autoresolve.acar.report.VictoryPhaseReporter;
Expand Down Expand Up @@ -49,6 +50,10 @@ private static void applyDamageToRemainingUnits(SimulationContext context) {
DamageApplierChooser.damageRemovedEntity(entity, entity.getRemovalCondition());
}
}

for (var entity : context.getRetreatingUnits()) {
DamageApplierChooser.damageRemovedEntity(entity, IEntityRemovalConditions.REMOVE_IN_RETREAT);
}
}

private static void applyDamageToEntitiesFromFormation(SimulationContext context, Formation formation) {
Expand Down

0 comments on commit 9653342

Please sign in to comment.