diff --git a/game-core/src/main/java/games/strategy/triplea/delegate/battle/AbstractBattle.java b/game-core/src/main/java/games/strategy/triplea/delegate/battle/AbstractBattle.java index aab461a0831..a0c824e2259 100644 --- a/game-core/src/main/java/games/strategy/triplea/delegate/battle/AbstractBattle.java +++ b/game-core/src/main/java/games/strategy/triplea/delegate/battle/AbstractBattle.java @@ -12,7 +12,6 @@ import games.strategy.engine.delegate.IDelegateBridge; import games.strategy.engine.player.Player; import games.strategy.triplea.ai.weak.WeakAi; -import games.strategy.triplea.attachments.UnitAttachment; import games.strategy.triplea.delegate.Matches; import games.strategy.triplea.delegate.TerritoryEffectHelper; import games.strategy.triplea.delegate.TransportTracker; @@ -345,19 +344,6 @@ static GamePlayer findPlayerWithMostUnits(final Collection units) { return player; } - /** - * The maximum number of hits that this collection of units can sustain, taking into account units - * with two hits, and accounting for existing damage. - */ - static int getMaxHits(final Collection units) { - int count = 0; - for (final Unit unit : units) { - count += UnitAttachment.get(unit.getType()).getHitPoints(); - count -= unit.getHits(); - } - return count; - } - void markDamaged(final Collection damaged, final IDelegateBridge bridge) { BattleDelegate.markDamaged(damaged, bridge, battleSite); } diff --git a/game-core/src/main/java/games/strategy/triplea/delegate/battle/BattleActions.java b/game-core/src/main/java/games/strategy/triplea/delegate/battle/BattleActions.java index 367e8aaf79b..4e49ef0acdd 100644 --- a/game-core/src/main/java/games/strategy/triplea/delegate/battle/BattleActions.java +++ b/game-core/src/main/java/games/strategy/triplea/delegate/battle/BattleActions.java @@ -8,17 +8,10 @@ import games.strategy.triplea.ai.weak.WeakAi; import games.strategy.triplea.delegate.battle.MustFightBattle.ReturnFire; import java.util.Collection; -import java.util.function.Predicate; /** Actions that can occur in a battle that require interaction with {@link IDelegateBridge} */ public interface BattleActions { - void fireOffensiveAaGuns(); - - void fireDefensiveAaGuns(); - - void fireNavalBombardment(IDelegateBridge bridge); - void removeNonCombatants(IDelegateBridge bridge); void clearWaitingToDieAndDamagedChangesInto(IDelegateBridge bridge); @@ -28,17 +21,6 @@ void removeCasualties( void endBattle(IBattle.WhoWon whoWon, IDelegateBridge bridge); - void findTargetGroupsAndFire( - ReturnFire returnFire, - String stepName, - boolean defending, - GamePlayer firingPlayer, - Predicate firingUnitPredicate, - Collection firingUnits, - Collection firingUnitsWaitingToDie, - Collection enemyUnits, - Collection enemyUnitsWaitingToDie); - void remove( Collection killedUnits, IDelegateBridge bridge, diff --git a/game-core/src/main/java/games/strategy/triplea/delegate/battle/BattleState.java b/game-core/src/main/java/games/strategy/triplea/delegate/battle/BattleState.java index 35f669918d7..fee2ea4b49d 100644 --- a/game-core/src/main/java/games/strategy/triplea/delegate/battle/BattleState.java +++ b/game-core/src/main/java/games/strategy/triplea/delegate/battle/BattleState.java @@ -92,8 +92,6 @@ public boolean isFirstRound() { void retreatUnits(Side side, Collection units); - Collection getAa(Side... sides); - Collection getBombardingUnits(); GamePlayer getPlayer(Side side); diff --git a/game-core/src/main/java/games/strategy/triplea/delegate/battle/BattleStepStrings.java b/game-core/src/main/java/games/strategy/triplea/delegate/battle/BattleStepStrings.java index e65116b6ba6..7cff130c946 100644 --- a/game-core/src/main/java/games/strategy/triplea/delegate/battle/BattleStepStrings.java +++ b/game-core/src/main/java/games/strategy/triplea/delegate/battle/BattleStepStrings.java @@ -5,8 +5,9 @@ public interface BattleStepStrings { String AA_GUNS_FIRE_SUFFIX = " fire"; String FIRE_SUFFIX = " fire"; + String FIRST_STRIKE_UNITS_FIRE_SUFFIX = " first strike units fire"; String SELECT_PREFIX = " select "; - String REMOVE_PREFIX = " remove "; + String NOTIFY_PREFIX = " notify "; String CASUALTIES_SUFFIX = " casualties"; String CASUALTIES_WITHOUT_SPACE_SUFFIX = "casualties"; String LAND_PARATROOPS = "Land Paratroopers"; @@ -21,8 +22,6 @@ public interface BattleStepStrings { String UNITS = "units"; String FIRST_STRIKE_UNITS = "first strike units"; String REMOVE_SNEAK_ATTACK_CASUALTIES = "Remove sneak attack casualties"; - String FIRE = " fire"; - String FIRST_STRIKE_UNITS_FIRE = " first strike units fire"; String SELECT_FIRST_STRIKE_CASUALTIES = " select first strike casualties"; String SELECT_CASUALTIES = " select casualties"; String REMOVE_CASUALTIES = "Remove casualties"; diff --git a/game-core/src/main/java/games/strategy/triplea/delegate/battle/Fire.java b/game-core/src/main/java/games/strategy/triplea/delegate/battle/Fire.java index bb929b76b4c..488c311509a 100644 --- a/game-core/src/main/java/games/strategy/triplea/delegate/battle/Fire.java +++ b/game-core/src/main/java/games/strategy/triplea/delegate/battle/Fire.java @@ -1,30 +1,38 @@ package games.strategy.triplea.delegate.battle; +import static games.strategy.triplea.delegate.battle.BattleState.Side.DEFENSE; +import static games.strategy.triplea.delegate.battle.BattleState.Side.OFFENSE; +import static games.strategy.triplea.delegate.battle.BattleStepStrings.NAVAL_BOMBARD; +import static games.strategy.triplea.delegate.battle.BattleStepStrings.UNITS; + import games.strategy.engine.data.GamePlayer; import games.strategy.engine.data.Territory; import games.strategy.engine.data.TerritoryEffect; import games.strategy.engine.data.Unit; import games.strategy.engine.delegate.IDelegateBridge; -import games.strategy.triplea.Properties; -import games.strategy.triplea.delegate.BaseEditDelegate; import games.strategy.triplea.delegate.DiceRoll; import games.strategy.triplea.delegate.ExecutionStack; import games.strategy.triplea.delegate.IExecutable; import games.strategy.triplea.delegate.Matches; -import games.strategy.triplea.delegate.battle.casualty.CasualtySelector; +import games.strategy.triplea.delegate.battle.steps.fire.FireRoundState; +import games.strategy.triplea.delegate.battle.steps.fire.FiringGroup; +import games.strategy.triplea.delegate.battle.steps.fire.MainDiceRoller; +import games.strategy.triplea.delegate.battle.steps.fire.MarkCasualties; +import games.strategy.triplea.delegate.battle.steps.fire.RollDiceStep; +import games.strategy.triplea.delegate.battle.steps.fire.SelectCasualties; +import games.strategy.triplea.delegate.battle.steps.fire.SelectMainBattleCasualties; import games.strategy.triplea.delegate.data.CasualtyDetails; -import games.strategy.triplea.delegate.power.calculator.CombatValue; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.UUID; -import java.util.function.Predicate; -import org.triplea.java.Interruptibles; import org.triplea.java.RemoveOnNextMajorRelease; -import org.triplea.java.collections.CollectionUtils; /** Maintains the state of a group of units firing during a {@link MustFightBattle}. */ +@RemoveOnNextMajorRelease +@Deprecated +@SuppressWarnings("unused") public class Fire implements IExecutable { private static final long serialVersionUID = -3687054738070722403L; @@ -47,13 +55,7 @@ public class Fire implements IExecutable { private final Collection allFriendlyUnitsAliveOrWaitingToDie; private final Collection allFriendlyUnitsNotIncludingWaitingToDie; private final Collection allEnemyUnitsNotIncludingWaitingToDie; - - @RemoveOnNextMajorRelease("amphibiousLandAttackers is no longer used") - @SuppressWarnings("unused") private final boolean isAmphibious = false; - - @RemoveOnNextMajorRelease("amphibiousLandAttackers is no longer used") - @SuppressWarnings("unused") private final Collection amphibiousLandAttackers = List.of(); // These variables change state during execution @@ -111,7 +113,19 @@ public void execute(final ExecutionStack stack, final IDelegateBridge bridge) { @Override public void execute(final ExecutionStack stack, final IDelegateBridge bridge) { - rollDice(bridge); + final FireRoundState fireRoundState = new FireRoundState(); + new RollDiceStep( + battle, + defending ? DEFENSE : OFFENSE, + new FiringGroup( + text.equals("Bombard") ? NAVAL_BOMBARD : UNITS, + firingUnits, + attackableUnits, + firingUnits.stream().anyMatch(Matches.unitIsSuicideOnHit())), + fireRoundState, + new MainDiceRoller()) + .execute(stack, bridge); + dice = fireRoundState.getDice(); } }; final IExecutable selectCasualties = @@ -120,7 +134,22 @@ public void execute(final ExecutionStack stack, final IDelegateBridge bridge) { @Override public void execute(final ExecutionStack stack, final IDelegateBridge bridge) { - selectCasualties(bridge); + final FireRoundState fireRoundState = new FireRoundState(); + fireRoundState.setDice(dice); + new SelectCasualties( + battle, + defending ? DEFENSE : OFFENSE, + new FiringGroup( + text.equals("Bombard") ? NAVAL_BOMBARD : UNITS, + firingUnits, + attackableUnits, + firingUnits.stream().anyMatch(Matches.unitIsSuicideOnHit())), + fireRoundState, + new SelectMainBattleCasualties()) + .execute(stack, bridge); + confirmOwnCasualties = fireRoundState.getCasualties().getAutoCalculated(); + killed = fireRoundState.getCasualties().getKilled(); + damaged = fireRoundState.getCasualties().getKilled(); } }; final IExecutable notifyCasualties = @@ -129,187 +158,27 @@ public void execute(final ExecutionStack stack, final IDelegateBridge bridge) { @Override public void execute(final ExecutionStack stack, final IDelegateBridge bridge) { - notifyCasualties(bridge); - if (damaged != null) { - battle.markDamaged(damaged, bridge); - } - battle.removeCasualties(killed, canReturnFire, !defending, bridge); - battle.removeSuicideOnHitCasualties(firingUnits, dice.getHits(), defending, bridge); + final FireRoundState fireRoundState = new FireRoundState(); + fireRoundState.setDice(dice); + fireRoundState.setCasualties( + new CasualtyDetails( + new ArrayList<>(killed), new ArrayList<>(damaged), confirmOwnCasualties)); + new MarkCasualties( + battle, + battle, + defending ? DEFENSE : OFFENSE, + new FiringGroup( + text.equals("Bombard") ? NAVAL_BOMBARD : UNITS, + firingUnits, + attackableUnits, + firingUnits.stream().anyMatch(Matches.unitIsSuicideOnHit())), + fireRoundState, + canReturnFire) + .execute(stack, bridge); } }; stack.push(notifyCasualties); stack.push(selectCasualties); stack.push(rollDice); } - - private void rollDice(final IDelegateBridge bridge) { - if (dice != null) { - throw new IllegalStateException("Already rolled"); - } - final List units = new ArrayList<>(firingUnits); - final String annotation; - if (headless) { - annotation = ""; - } else { - annotation = - DiceRoll.getAnnotation( - units, firingPlayer, battle.getTerritory(), battle.getBattleRound()); - } - - dice = - DiceRoll.rollDice( - units, - firingPlayer, - bridge, - annotation, - CombatValue.buildMainCombatValue( - allEnemyUnitsAliveOrWaitingToDie, - allFriendlyUnitsAliveOrWaitingToDie, - defending, - battle.getGameData(), - battle.getTerritory(), - territoryEffects)); - } - - private void selectCasualties(final IDelegateBridge bridge) { - final int hitCount = dice.getHits(); - bridge.getDisplayChannelBroadcaster().notifyDice(dice, stepName); - // Remove any attackable units that previously died - attackableUnits.retainAll(allEnemyUnitsNotIncludingWaitingToDie); - final int countTransports = - CollectionUtils.countMatches( - attackableUnits, Matches.unitIsTransport().and(Matches.unitIsSea())); - - if (BaseEditDelegate.getEditMode(bridge.getData())) { - final CasualtyDetails message = selectCasualties(bridge, attackableUnits, 0); - killed = message.getKilled(); - damaged = message.getDamaged(); - confirmOwnCasualties = true; - } else if (countTransports > 0 - && Properties.getTransportCasualtiesRestricted(bridge.getData())) { - final Collection nonTransports = - CollectionUtils.getMatches( - attackableUnits, - Matches.unitIsNotTransportButCouldBeCombatTransport().or(Matches.unitIsNotSea())); - final Collection transportsOnly = - CollectionUtils.getMatches( - attackableUnits, - Matches.unitIsTransportButNotCombatTransport().and(Matches.unitIsSea())); - final int numPossibleHits = AbstractBattle.getMaxHits(nonTransports); - // more hits than combat units - if (hitCount > numPossibleHits) { - int extraHits = hitCount - numPossibleHits; - final Collection alliedHitPlayer = new ArrayList<>(); - // find the players who have transports in the attackable pile - for (final Unit unit : transportsOnly) { - if (!alliedHitPlayer.contains(unit.getOwner())) { - alliedHitPlayer.add(unit.getOwner()); - } - } - // Leave enough transports for each defender for overflows so they can select who loses - // them. - for (final GamePlayer player : alliedHitPlayer) { - final Predicate match = - Matches.unitIsTransportButNotCombatTransport().and(Matches.unitIsOwnedBy(player)); - final Collection playerTransports = - CollectionUtils.getMatches(transportsOnly, match); - final int transportsToRemove = Math.max(0, playerTransports.size() - extraHits); - transportsOnly.removeAll( - CollectionUtils.getNMatches( - playerTransports, - transportsToRemove, - Matches.unitIsTransportButNotCombatTransport())); - } - killed = nonTransports; - damaged = List.of(); - if (extraHits > transportsOnly.size()) { - extraHits = transportsOnly.size(); - } - final CasualtyDetails message = selectCasualties(bridge, transportsOnly, extraHits); - killed.addAll(message.getKilled()); - confirmOwnCasualties = true; - } else if (hitCount == numPossibleHits) { // exact number of combat units - killed = nonTransports; - damaged = List.of(); - confirmOwnCasualties = true; - } else { // less than possible number - final CasualtyDetails message = selectCasualties(bridge, nonTransports, dice.getHits()); - killed = message.getKilled(); - damaged = message.getDamaged(); - confirmOwnCasualties = message.getAutoCalculated(); - } - } else { // not isTransportCasualtiesRestricted - // they all die - if (hitCount >= AbstractBattle.getMaxHits(attackableUnits)) { - killed = attackableUnits; - damaged = List.of(); - // everything died, so we need to confirm - confirmOwnCasualties = true; - } else { // Choose casualties - final CasualtyDetails message = selectCasualties(bridge, attackableUnits, dice.getHits()); - killed = message.getKilled(); - damaged = message.getDamaged(); - confirmOwnCasualties = message.getAutoCalculated(); - } - } - } - - private CasualtyDetails selectCasualties( - final IDelegateBridge bridge, final Collection targetsToPickFrom, final int extraHits) { - return CasualtySelector.selectCasualties( - hitPlayer, - targetsToPickFrom, - CombatValue.buildMainCombatValue( - allFriendlyUnitsNotIncludingWaitingToDie, - allEnemyUnitsNotIncludingWaitingToDie, - !defending, - bridge.getData(), - battleSite, - territoryEffects), - battleSite, - bridge, - text, - dice, - battleId, - headless, - extraHits, - true); - } - - private void notifyCasualties(final IDelegateBridge bridge) { - if (headless) { - return; - } - bridge - .getDisplayChannelBroadcaster() - .casualtyNotification( - battleId, - stepName, - dice, - hitPlayer, - new ArrayList<>(killed), - new ArrayList<>(damaged), - dependentUnits); - // execute in a separate thread to allow either player to click continue first. - final Thread t = - new Thread( - () -> { - try { - AbstractBattle.getRemote(firingPlayer, bridge) - .confirmEnemyCasualties(battleId, "Press space to continue", hitPlayer); - } catch (final Exception e) { - // someone else will deal with this, ignore - } - }, - "Click to continue waiter"); - t.start(); - // Always confirm casualties for AI to give them a chance to pause. - if (confirmOwnCasualties || hitPlayer.isAi()) { - AbstractBattle.getRemote(hitPlayer, bridge) - .confirmOwnCasualties(battleId, "Press space to continue"); - } - bridge.leaveDelegateExecution(); - Interruptibles.join(t); - bridge.enterDelegateExecution(); - } } diff --git a/game-core/src/main/java/games/strategy/triplea/delegate/battle/FireAa.java b/game-core/src/main/java/games/strategy/triplea/delegate/battle/FireAa.java index cb9146acaf2..d339db0890c 100644 --- a/game-core/src/main/java/games/strategy/triplea/delegate/battle/FireAa.java +++ b/game-core/src/main/java/games/strategy/triplea/delegate/battle/FireAa.java @@ -1,5 +1,8 @@ package games.strategy.triplea.delegate.battle; +import static games.strategy.triplea.delegate.battle.BattleState.Side.DEFENSE; +import static games.strategy.triplea.delegate.battle.BattleState.Side.OFFENSE; + import games.strategy.engine.data.GamePlayer; import games.strategy.engine.data.Territory; import games.strategy.engine.data.TerritoryEffect; @@ -13,25 +16,31 @@ import games.strategy.triplea.delegate.IExecutable; import games.strategy.triplea.delegate.Matches; import games.strategy.triplea.delegate.battle.MustFightBattle.ReturnFire; -import games.strategy.triplea.delegate.battle.casualty.AaCasualtySelector; +import games.strategy.triplea.delegate.battle.steps.fire.FireRoundState; +import games.strategy.triplea.delegate.battle.steps.fire.FiringGroup; +import games.strategy.triplea.delegate.battle.steps.fire.MarkCasualties; +import games.strategy.triplea.delegate.battle.steps.fire.RollDiceStep; +import games.strategy.triplea.delegate.battle.steps.fire.SelectCasualties; +import games.strategy.triplea.delegate.battle.steps.fire.aa.AaFireAndCasualtyStep; import games.strategy.triplea.delegate.data.CasualtyDetails; -import games.strategy.triplea.delegate.power.calculator.CombatValue; import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; -import org.triplea.java.Interruptibles; import org.triplea.java.RemoveOnNextMajorRelease; import org.triplea.java.collections.CollectionUtils; -import org.triplea.sound.SoundUtils; /** * Maintains the state of a group of AA units firing during a {@link * games.strategy.triplea.delegate.battle.MustFightBattle}. */ +@RemoveOnNextMajorRelease +@Deprecated +@SuppressWarnings("unused") public class FireAa implements IExecutable { private static final long serialVersionUID = -6406659798754841382L; @@ -48,13 +57,7 @@ public class FireAa implements IExecutable { private final Collection territoryEffects; private final List allFriendlyUnitsAliveOrWaitingToDie; private final List allEnemyUnitsAliveOrWaitingToDie; - - @RemoveOnNextMajorRelease("amphibiousLandAttackers is no longer used") - @SuppressWarnings("unused") private final boolean isAmphibious = false; - - @RemoveOnNextMajorRelease("amphibiousLandAttackers is no longer used") - @SuppressWarnings("unused") private final Collection amphibiousLandAttackers = List.of(); private final List aaTypes; @@ -102,7 +105,7 @@ public void execute(final ExecutionStack stack, final IDelegateBridge bridge) { for (final String aaType : aaTypes) { final Collection aaTypeUnits = CollectionUtils.getMatches(firingUnits, Matches.unitIsAaOfTypeAa(aaType)); - final List> firingGroups = MustFightBattle.newFiringUnitGroups(aaTypeUnits); + final List> firingGroups = newFiringUnitGroups(aaTypeUnits); for (final Collection firingGroup : firingGroups) { final Set validTargetTypes = UnitAttachment.get(firingGroup.iterator().next().getType()) @@ -125,23 +128,19 @@ public void execute(final ExecutionStack stack, final IDelegateBridge bridge) { @Override public void execute(final ExecutionStack stack, final IDelegateBridge bridge) { - validTargets.removeAll(casualtiesSoFar); - if (!validTargets.isEmpty()) { - dice = - DiceRoll.rollAa( - validTargets, - firingGroup, - bridge, - battleSite, - CombatValue.buildAaCombatValue( - allEnemyUnitsAliveOrWaitingToDie, - allFriendlyUnitsAliveOrWaitingToDie, - defending, - bridge.getData())); - if (!headless) { - SoundUtils.playFireBattleAa(firingPlayer, aaType, dice.getHits() > 0, bridge); - } - } + final FireRoundState fireRoundState = new FireRoundState(); + new RollDiceStep( + battle, + defending ? DEFENSE : OFFENSE, + new FiringGroup( + aaType, + firingUnits, + attackableUnits, + firingUnits.stream().anyMatch(Matches.unitIsSuicideOnHit())), + fireRoundState, + new AaFireAndCasualtyStep.AaDiceRoller()) + .execute(stack, bridge); + dice = fireRoundState.getDice(); } }; final IExecutable selectCasualties = @@ -150,13 +149,20 @@ public void execute(final ExecutionStack stack, final IDelegateBridge bridge) { @Override public void execute(final ExecutionStack stack, final IDelegateBridge bridge) { - if (!validTargets.isEmpty()) { - final CasualtyDetails details = - selectCasualties(validTargets, firingGroup, bridge, aaType); - battle.markDamaged(details.getDamaged(), bridge); - casualties = details; - casualtiesSoFar.addAll(details.getKilled()); - } + final FireRoundState fireRoundState = new FireRoundState(); + fireRoundState.setDice(dice); + new SelectCasualties( + battle, + defending ? DEFENSE : OFFENSE, + new FiringGroup( + aaType, + firingUnits, + attackableUnits, + firingUnits.stream().anyMatch(Matches.unitIsSuicideOnHit())), + fireRoundState, + new AaFireAndCasualtyStep.SelectAaCasualties()) + .execute(stack, bridge); + casualties = fireRoundState.getCasualties(); } }; final IExecutable notifyCasualties = @@ -165,13 +171,21 @@ public void execute(final ExecutionStack stack, final IDelegateBridge bridge) { @Override public void execute(final ExecutionStack stack, final IDelegateBridge bridge) { - if (!validTargets.isEmpty()) { - notifyCasualtiesAa(bridge, aaType); - battle.removeCasualties( - casualties.getKilled(), ReturnFire.ALL, !defending, bridge); - battle.removeSuicideOnHitCasualties( - firingGroup, dice.getHits(), defending, bridge); - } + final FireRoundState fireRoundState = new FireRoundState(); + fireRoundState.setDice(dice); + fireRoundState.setCasualties(casualties); + new MarkCasualties( + battle, + battle, + defending ? DEFENSE : OFFENSE, + new FiringGroup( + aaType, + firingUnits, + attackableUnits, + firingUnits.stream().anyMatch(Matches.unitIsSuicideOnHit())), + fireRoundState, + ReturnFire.ALL) + .execute(stack, bridge); } }; // push in reverse order of execution @@ -182,77 +196,33 @@ public void execute(final ExecutionStack stack, final IDelegateBridge bridge) { } } - private CasualtyDetails selectCasualties( - final Collection validAttackingUnitsForThisRoll, - final Collection defendingAa, - final IDelegateBridge bridge, - final String currentTypeAa) { - // send defender the dice roll so he can see what the dice are while he waits for attacker to - // select casualties - bridge - .getDisplayChannelBroadcaster() - .notifyDice( - dice, - hitPlayer.getName() - + BattleStepStrings.SELECT_PREFIX - + currentTypeAa - + BattleStepStrings.CASUALTIES_SUFFIX); - return AaCasualtySelector.getAaCasualties( - validAttackingUnitsForThisRoll, - defendingAa, - CombatValue.buildMainCombatValue( - allFriendlyUnitsAliveOrWaitingToDie, - allEnemyUnitsAliveOrWaitingToDie, - !defending, - bridge.getData(), - battleSite, - territoryEffects), - CombatValue.buildAaCombatValue( - allEnemyUnitsAliveOrWaitingToDie, - allFriendlyUnitsAliveOrWaitingToDie, - defending, - bridge.getData()), - "Hits from " + currentTypeAa + ", ", - dice, - bridge, - hitPlayer, - battleId, - battleSite); - } + /** + * Breaks list of units into groups of non suicide on hit units and each type of suicide on hit + * units since each type of suicide on hit units need to roll separately to know which ones get + * hits. + */ + static List> newFiringUnitGroups(final Collection units) { - private void notifyCasualtiesAa(final IDelegateBridge bridge, final String currentTypeAa) { - if (headless) { - return; + // Sort suicide on hit units by type + final Map> map = new HashMap<>(); + for (final Unit unit : CollectionUtils.getMatches(units, Matches.unitIsSuicideOnHit())) { + final UnitType type = unit.getType(); + if (map.containsKey(type)) { + map.get(type).add(unit); + } else { + final Collection unitList = new ArrayList<>(); + unitList.add(unit); + map.put(type, unitList); + } } - bridge - .getDisplayChannelBroadcaster() - .casualtyNotification( - battleId, - hitPlayer.getName() - + BattleStepStrings.REMOVE_PREFIX - + currentTypeAa - + BattleStepStrings.CASUALTIES_SUFFIX, - dice, - hitPlayer, - new ArrayList<>(casualties.getKilled()), - new ArrayList<>(casualties.getDamaged()), - dependentUnits); - AbstractBattle.getRemote(hitPlayer, bridge) - .confirmOwnCasualties(battleId, "Press space to continue"); - final Thread t = - new Thread( - () -> { - try { - AbstractBattle.getRemote(firingPlayer, bridge) - .confirmEnemyCasualties(battleId, "Press space to continue", hitPlayer); - } catch (final Exception e) { - // ignore - } - }, - "click to continue waiter"); - t.start(); - bridge.leaveDelegateExecution(); - Interruptibles.join(t); - bridge.enterDelegateExecution(); + + // Add all suicide on hit groups and the remaining units + final List> result = new ArrayList<>(map.values()); + final Collection remainingUnits = + CollectionUtils.getMatches(units, Matches.unitIsSuicideOnHit().negate()); + if (!remainingUnits.isEmpty()) { + result.add(remainingUnits); + } + return result; } } diff --git a/game-core/src/main/java/games/strategy/triplea/delegate/battle/MustFightBattle.java b/game-core/src/main/java/games/strategy/triplea/delegate/battle/MustFightBattle.java index 19b1996f4e4..11b665edcf6 100644 --- a/game-core/src/main/java/games/strategy/triplea/delegate/battle/MustFightBattle.java +++ b/game-core/src/main/java/games/strategy/triplea/delegate/battle/MustFightBattle.java @@ -26,7 +26,6 @@ import games.strategy.engine.player.Player; import games.strategy.triplea.Properties; import games.strategy.triplea.UnitUtils; -import games.strategy.triplea.attachments.TechAbilityAttachment; import games.strategy.triplea.attachments.UnitAttachment; import games.strategy.triplea.delegate.ExecutionStack; import games.strategy.triplea.delegate.IExecutable; @@ -51,7 +50,6 @@ import games.strategy.triplea.delegate.battle.steps.fire.firststrike.OffensiveFirstStrike; import games.strategy.triplea.delegate.battle.steps.fire.general.DefensiveGeneral; import games.strategy.triplea.delegate.battle.steps.fire.general.OffensiveGeneral; -import games.strategy.triplea.delegate.battle.steps.fire.general.TargetGroup; import games.strategy.triplea.delegate.battle.steps.retreat.DefensiveSubsRetreat; import games.strategy.triplea.delegate.battle.steps.retreat.OffensiveGeneralRetreat; import games.strategy.triplea.delegate.battle.steps.retreat.OffensiveSubsRetreat; @@ -123,10 +121,22 @@ public enum RetreatType { @Getter(onMethod = @__({@Override})) private List stepStrings; + @RemoveOnNextMajorRelease + @SuppressWarnings("unused") private List defendingAa; + + @RemoveOnNextMajorRelease + @SuppressWarnings("unused") private List offensiveAa; + + @RemoveOnNextMajorRelease + @SuppressWarnings("unused") private List defendingAaTypes; + + @RemoveOnNextMajorRelease + @SuppressWarnings("unused") private List offensiveAaTypes; + private final List attackingUnitsRetreated = new ArrayList<>(); private final List defendingUnitsRetreated = new ArrayList<>(); // -1 would mean forever until one side is eliminated (the default is infinite) @@ -461,24 +471,6 @@ public void removeDependentUnits(final Collection unitsWithDependents) { } } - @Override - public Collection getAa(final Side... sides) { - final Collection units = new ArrayList<>(); - for (final Side side : sides) { - switch (side) { - case OFFENSE: - units.addAll(offensiveAa); - break; - case DEFENSE: - units.addAll(defendingAa); - break; - default: - break; - } - } - return units; - } - /** * Used for setting stuff when we make a scrambling battle when there was no previous battle * there, and we need retreat spaces. @@ -641,21 +633,6 @@ public void damagedChangeInto( } } - void removeSuicideOnHitCasualties( - final Collection firingUnits, - final int hits, - final boolean defender, - final IDelegateBridge bridge) { - if (firingUnits.stream().anyMatch(Matches.unitIsSuicideOnHit()) && hits > 0) { - final List units = firingUnits.stream().limit(hits).collect(Collectors.toList()); - bridge - .getDisplayChannelBroadcaster() - .deadUnitNotification( - battleId, defender ? this.defender : attacker, units, dependentUnits); - remove(units, bridge, battleSite, defender); - } - } - @Override public void removeCasualties( final Collection killed, @@ -810,8 +787,6 @@ public void fight(final IDelegateBridge bridge) { } addDependentUnits(TransportTracker.transporting(defendingUnits)); addDependentUnits(TransportTracker.transporting(attackingUnits)); - updateOffensiveAaUnits(); - updateDefendingAaUnits(); stepStrings = determineStepStrings(); final IDisplay display = bridge.getDisplayChannelBroadcaster(); display.showBattle( @@ -932,57 +907,8 @@ private void addPlayerCombatHistoryText( } } - private void updateOffensiveAaUnits() { - final Collection canFire = new ArrayList<>(attackingUnits); - canFire.addAll(attackingWaitingToDie); - // no airborne targets for offensive aa - offensiveAa = - CollectionUtils.getMatches( - canFire, - Matches.unitIsAaThatCanFire( - defendingUnits, - new HashMap<>(), - defender, - Matches.unitIsAaForCombatOnly(), - round, - false, - gameData)); - // comes ordered alphabetically - offensiveAaTypes = UnitAttachment.getAllOfTypeAas(offensiveAa); - // stacks are backwards - Collections.reverse(offensiveAaTypes); - } - - private void updateDefendingAaUnits() { - final Collection canFire = new ArrayList<>(defendingUnits); - canFire.addAll(defendingWaitingToDie); - final Map> airborneTechTargetsAllowed = - TechAbilityAttachment.getAirborneTargettedByAa(attacker, gameData); - defendingAa = - CollectionUtils.getMatches( - canFire, - Matches.unitIsAaThatCanFire( - attackingUnits, - airborneTechTargetsAllowed, - attacker, - Matches.unitIsAaForCombatOnly(), - round, - true, - gameData)); - // comes ordered alphabetically - defendingAaTypes = UnitAttachment.getAllOfTypeAas(defendingAa); - // stacks are backwards - Collections.reverse(defendingAaTypes); - } - @VisibleForTesting public List determineStepStrings() { - if (offensiveAa == null) { - updateOffensiveAaUnits(); - } - if (defendingAa == null) { - updateDefendingAaUnits(); - } return BattleSteps.builder().battleState(this).battleActions(this).build().get(); } @@ -1076,12 +1002,6 @@ private void pushFightLoopOnStack() { */ @VisibleForTesting public List getBattleExecutables() { - if (offensiveAa == null) { - updateOffensiveAaUnits(); - } - if (defendingAa == null) { - updateDefendingAaUnits(); - } final List steps = BattleStep.getAll(this, this).stream() .sorted(Comparator.comparing(BattleStep::getOrder)) @@ -1419,164 +1339,6 @@ public void execute(final ExecutionStack stack, final IDelegateBridge bridge) { }; } - @Override - public void fireOffensiveAaGuns() { - final List allFriendlyUnitsAliveOrWaitingToDie = new ArrayList<>(attackingUnits); - allFriendlyUnitsAliveOrWaitingToDie.addAll(attackingWaitingToDie); - final List allEnemyUnitsAliveOrWaitingToDie = new ArrayList<>(defendingUnits); - allEnemyUnitsAliveOrWaitingToDie.addAll(defendingWaitingToDie); - stack.push( - new FireAa( - defendingUnits, - attacker, - defender, - offensiveAa, - this, - false, - dependentUnits, - headless, - battleSite, - territoryEffects, - allFriendlyUnitsAliveOrWaitingToDie, - allEnemyUnitsAliveOrWaitingToDie, - offensiveAaTypes)); - } - - @Override - public void fireDefensiveAaGuns() { - final List allFriendlyUnitsAliveOrWaitingToDie = new ArrayList<>(defendingUnits); - allFriendlyUnitsAliveOrWaitingToDie.addAll(defendingWaitingToDie); - final List allEnemyUnitsAliveOrWaitingToDie = new ArrayList<>(attackingUnits); - allEnemyUnitsAliveOrWaitingToDie.addAll(attackingWaitingToDie); - stack.push( - new FireAa( - attackingUnits, - defender, - attacker, - defendingAa, - this, - true, - dependentUnits, - headless, - battleSite, - territoryEffects, - allFriendlyUnitsAliveOrWaitingToDie, - allEnemyUnitsAliveOrWaitingToDie, - defendingAaTypes)); - } - - @Override - public void fireNavalBombardment(final IDelegateBridge bridge) { - final Collection bombard = getBombardingUnits(); - final Collection attacked = - CollectionUtils.getMatches( - defendingUnits, - Matches.unitIsNotInfrastructureAndNotCapturedOnEntering( - attacker, battleSite, gameData)); - - if (!headless && !bombard.isEmpty()) { - // bombarding units can't move after bombarding even if there are no units to bombard - final Change change = ChangeFactory.markNoMovementChange(bombard); - bridge.addChange(change); - } - if (!bombard.isEmpty() && !attacked.isEmpty()) { - if (!headless) { - bridge - .getSoundChannelBroadcaster() - .playSoundForAll(SoundPath.CLIP_BATTLE_BOMBARD, attacker); - } - final List allEnemyUnitsAliveOrWaitingToDie = new ArrayList<>(defendingUnits); - allEnemyUnitsAliveOrWaitingToDie.addAll(defendingWaitingToDie); - final boolean canReturnFire = Properties.getNavalBombardCasualtiesReturnFire(gameData); - fire( - SELECT_NAVAL_BOMBARDMENT_CASUALTIES, - bombard, - attacked, - allEnemyUnitsAliveOrWaitingToDie, - bombard, - false, - canReturnFire ? ReturnFire.ALL : ReturnFire.NONE, - "Bombard"); - } - } - - @VisibleForTesting - protected void fire( - final String stepName, - final Collection firingUnits, - final Collection attackableUnits, - final Collection allEnemyUnitsAliveOrWaitingToDie, - final Collection allFriendlyUnitsAliveOrWaitingToDie, - final boolean defender, - final ReturnFire returnFire, - final String text) { - - final Collection targetUnits = - CollectionUtils.getMatches( - attackableUnits, - PredicateBuilder.of(Matches.unitIsNotInfrastructure()) - .andIf(defender, Matches.unitIsSuicideOnAttack().negate()) - .andIf(!defender, Matches.unitIsSuicideOnDefense().negate()) - .build()); - if (firingUnits.isEmpty() || targetUnits.isEmpty()) { - return; - } - final GamePlayer firingPlayer = defender ? this.defender : attacker; - final GamePlayer hitPlayer = !defender ? this.defender : attacker; - - // Fire each type of suicide on hit unit separately and then remaining units - final List> firingGroups = newFiringUnitGroups(firingUnits); - for (final Collection units : firingGroups) { - stack.push( - new Fire( - targetUnits, - returnFire, - firingPlayer, - hitPlayer, - units, - stepName, - text, - this, - defender, - dependentUnits, - headless, - battleSite, - territoryEffects, - allEnemyUnitsAliveOrWaitingToDie, - allFriendlyUnitsAliveOrWaitingToDie)); - } - } - - /** - * Breaks list of units into groups of non suicide on hit units and each type of suicide on hit - * units since each type of suicide on hit units need to roll separately to know which ones get - * hits. - */ - static List> newFiringUnitGroups(final Collection units) { - - // Sort suicide on hit units by type - final Map> map = new HashMap<>(); - for (final Unit unit : CollectionUtils.getMatches(units, Matches.unitIsSuicideOnHit())) { - final UnitType type = unit.getType(); - if (map.containsKey(type)) { - map.get(type).add(unit); - } else { - final Collection unitList = new ArrayList<>(); - unitList.add(unit); - map.put(type, unitList); - } - } - - // Add all suicide on hit groups and the remaining units - final List> result = new ArrayList<>(map.values()); - final Collection remainingUnits = - CollectionUtils.getMatches(units, Matches.unitIsSuicideOnHit().negate()); - if (!remainingUnits.isEmpty()) { - result.add(remainingUnits); - } - return result; - } - @Override public void removeNonCombatants(final IDelegateBridge bridge) { final List notRemovedDefending = @@ -1647,42 +1409,6 @@ private List removeNonCombatants( return unitList; } - @Override - public void findTargetGroupsAndFire( - final ReturnFire returnFire, - final String stepName, - final boolean defending, - final GamePlayer firingPlayer, - final Predicate firingUnitPredicate, - final Collection firingUnits, - final Collection firingUnitsWaitingToDie, - final Collection enemyUnits, - final Collection enemyUnitsWaitingToDie) { - - Collection firing = new ArrayList<>(firingUnits); - firing.addAll(firingUnitsWaitingToDie); - firing = CollectionUtils.getMatches(firing, firingUnitPredicate); - // See if allied air can participate in combat - if (!defending && !Properties.getAlliedAirIndependent(gameData)) { - firing = CollectionUtils.getMatches(firing, Matches.unitIsOwnedBy(attacker)); - } - final List allEnemyUnitsAliveOrWaitingToDie = new ArrayList<>(enemyUnits); - allEnemyUnitsAliveOrWaitingToDie.addAll(enemyUnitsWaitingToDie); - final List allFriendlyUnitsAliveOrWaitingToDie = new ArrayList<>(firingUnits); - allFriendlyUnitsAliveOrWaitingToDie.addAll(firingUnitsWaitingToDie); - for (final TargetGroup firingGroup : TargetGroup.newTargetGroups(firing, enemyUnits)) { - fire( - stepName, - firingGroup.getFiringUnits(firing), - firingGroup.getTargetUnits(enemyUnits), - allEnemyUnitsAliveOrWaitingToDie, - allFriendlyUnitsAliveOrWaitingToDie, - defending, - returnFire, - firingPlayer.getName() + " fire, "); - } - } - private void addRoundResetStep(final List steps) { final IExecutable loop = new IExecutable() { @@ -1721,9 +1447,6 @@ public void execute(final ExecutionStack stack, final IDelegateBridge bridge) { .map(UnitType::getName) .collect(Collectors.joining(","))); } - // determine any AA - updateOffensiveAaUnits(); - updateDefendingAaUnits(); stepStrings = determineStepStrings(); final IDisplay display = bridge.getDisplayChannelBroadcaster(); display.listBattleSteps(battleId, stepStrings); diff --git a/game-core/src/main/java/games/strategy/triplea/delegate/battle/StrategicBombingRaidBattle.java b/game-core/src/main/java/games/strategy/triplea/delegate/battle/StrategicBombingRaidBattle.java index a0756ffb66a..d323c3250e0 100644 --- a/game-core/src/main/java/games/strategy/triplea/delegate/battle/StrategicBombingRaidBattle.java +++ b/game-core/src/main/java/games/strategy/triplea/delegate/battle/StrategicBombingRaidBattle.java @@ -223,7 +223,7 @@ public void fight(final IDelegateBridge bridge) { for (final String typeAa : UnitAttachment.getAllOfTypeAas(defendingAa)) { steps.add(typeAa + AA_GUNS_FIRE_SUFFIX); steps.add(SELECT_PREFIX + typeAa + CASUALTIES_SUFFIX); - steps.add(REMOVE_PREFIX + typeAa + CASUALTIES_SUFFIX); + steps.add(NOTIFY_PREFIX + typeAa + CASUALTIES_SUFFIX); } } steps.add(RAID); @@ -642,7 +642,7 @@ private void notifyAaHits( .getDisplayChannelBroadcaster() .casualtyNotification( battleId, - REMOVE_PREFIX + currentTypeAa + CASUALTIES_SUFFIX, + NOTIFY_PREFIX + currentTypeAa + CASUALTIES_SUFFIX, dice, attacker, new ArrayList<>(casualties.getKilled()), diff --git a/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/BattleStep.java b/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/BattleStep.java index baba2ca8339..5b66c7ba7c9 100644 --- a/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/BattleStep.java +++ b/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/BattleStep.java @@ -17,8 +17,6 @@ import games.strategy.triplea.delegate.battle.steps.fire.NavalBombardment; import games.strategy.triplea.delegate.battle.steps.fire.aa.DefensiveAaFire; import games.strategy.triplea.delegate.battle.steps.fire.aa.OffensiveAaFire; -import games.strategy.triplea.delegate.battle.steps.fire.air.AirAttackVsNonSubsStep; -import games.strategy.triplea.delegate.battle.steps.fire.air.AirDefendVsNonSubsStep; import games.strategy.triplea.delegate.battle.steps.fire.firststrike.ClearFirstStrikeCasualties; import games.strategy.triplea.delegate.battle.steps.fire.firststrike.DefensiveFirstStrike; import games.strategy.triplea.delegate.battle.steps.fire.firststrike.OffensiveFirstStrike; @@ -68,7 +66,6 @@ enum Order { GENERAL_DEFENSIVE, GENERAL_REMOVE_CASUALTIES, SUICIDE_REMOVE_CASUALTIES, - REMOVE_UNPROTECTED_UNITS_GENERAL, GENERAL_BATTLE_END_CHECK, SUB_OFFENSIVE_RETREAT_AFTER_BATTLE, @@ -93,8 +90,6 @@ static List getAll(final BattleState battleState, final BattleAction new DefensiveAaFire(battleState, battleActions), new SubmergeSubsVsOnlyAirStep(battleState, battleActions), new RemoveUnprotectedUnits(battleState, battleActions), - new AirAttackVsNonSubsStep(battleState), - new AirDefendVsNonSubsStep(battleState), new NavalBombardment(battleState, battleActions), new LandParatroopers(battleState, battleActions), new OffensiveSubsRetreat(battleState, battleActions), diff --git a/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/change/ClearAaCasualties.java b/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/change/ClearAaCasualties.java index a563b0afe4b..7834174f393 100644 --- a/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/change/ClearAaCasualties.java +++ b/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/change/ClearAaCasualties.java @@ -1,8 +1,5 @@ package games.strategy.triplea.delegate.battle.steps.change; -import static games.strategy.triplea.delegate.battle.BattleState.Side.DEFENSE; -import static games.strategy.triplea.delegate.battle.BattleState.Side.OFFENSE; - import games.strategy.engine.delegate.IDelegateBridge; import games.strategy.triplea.delegate.ExecutionStack; import games.strategy.triplea.delegate.battle.BattleActions; @@ -31,8 +28,6 @@ public Order getOrder() { @Override public void execute(final ExecutionStack stack, final IDelegateBridge bridge) { - if (!battleState.getAa(OFFENSE).isEmpty() || !battleState.getAa(DEFENSE).isEmpty()) { - battleActions.clearWaitingToDieAndDamagedChangesInto(bridge); - } + battleActions.clearWaitingToDieAndDamagedChangesInto(bridge); } } diff --git a/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/FiringGroup.java b/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/FiringGroup.java index 82cb4d8aae8..f0695d690f2 100644 --- a/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/FiringGroup.java +++ b/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/FiringGroup.java @@ -10,6 +10,7 @@ import java.util.List; import java.util.stream.Collectors; import lombok.Value; +import org.triplea.java.RemoveOnNextMajorRelease; import org.triplea.java.collections.CollectionUtils; /** @@ -60,6 +61,21 @@ private FiringGroup( this.suicideOnHit = this.firingUnits.stream().allMatch(Matches.unitIsSuicideOnHit()); } + // This converts firing groups from old saves + @RemoveOnNextMajorRelease + @Deprecated + public FiringGroup( + final String displayName, + final Collection firingUnits, + final Collection targetUnits, + final boolean suicideOnHit) { + this.displayName = displayName; + this.groupName = displayName; + this.firingUnits = firingUnits; + this.targetUnits = targetUnits; + this.suicideOnHit = suicideOnHit; + } + public Collection getTargetUnits() { return targetUnits; } diff --git a/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/FiringGroupSplitterBombard.java b/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/FiringGroupSplitterBombard.java index a0d7d5629bc..d8aa3612c88 100644 --- a/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/FiringGroupSplitterBombard.java +++ b/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/FiringGroupSplitterBombard.java @@ -25,7 +25,7 @@ *

See {@link FiringGroup} for why isSuicideOnHit needs to be separated by unit type. */ @Value(staticConstructor = "of") -public class FiringGroupSplitterBombard implements Function> { +public class FiringGroupSplitterBombard implements Function> { @Override public List apply(final BattleState battleState) { diff --git a/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/MarkCasualties.java b/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/MarkCasualties.java index ef18f3b677a..f2f8e37285e 100644 --- a/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/MarkCasualties.java +++ b/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/MarkCasualties.java @@ -3,7 +3,7 @@ import static games.strategy.triplea.delegate.battle.BattleState.Side.DEFENSE; import static games.strategy.triplea.delegate.battle.BattleStepStrings.CASUALTIES_SUFFIX; import static games.strategy.triplea.delegate.battle.BattleStepStrings.CASUALTIES_WITHOUT_SPACE_SUFFIX; -import static games.strategy.triplea.delegate.battle.BattleStepStrings.REMOVE_PREFIX; +import static games.strategy.triplea.delegate.battle.BattleStepStrings.NOTIFY_PREFIX; import static games.strategy.triplea.delegate.battle.BattleStepStrings.SELECT_CASUALTIES; import static games.strategy.triplea.delegate.battle.BattleStepStrings.SELECT_FIRST_STRIKE_CASUALTIES; import static games.strategy.triplea.delegate.battle.BattleStepStrings.SELECT_NAVAL_BOMBARDMENT_CASUALTIES; @@ -57,7 +57,7 @@ public List getNames() { private String getName() { return battleState.getPlayer(side.getOpposite()).getName() - + REMOVE_PREFIX + + NOTIFY_PREFIX // displaying UNITS makes the text feel redundant so hide it if that is the group name + (firingGroup.getDisplayName().equals(UNITS) ? CASUALTIES_WITHOUT_SPACE_SUFFIX diff --git a/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/NavalBombardment.java b/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/NavalBombardment.java index f27074fef72..8f19d543b88 100644 --- a/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/NavalBombardment.java +++ b/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/NavalBombardment.java @@ -1,35 +1,42 @@ package games.strategy.triplea.delegate.battle.steps.fire; -import static games.strategy.triplea.delegate.battle.BattleStepStrings.NAVAL_BOMBARDMENT; -import static games.strategy.triplea.delegate.battle.BattleStepStrings.SELECT_NAVAL_BOMBARDMENT_CASUALTIES; +import static games.strategy.triplea.delegate.battle.BattleState.Side.OFFENSE; +import games.strategy.engine.data.Change; +import games.strategy.engine.data.Unit; +import games.strategy.engine.data.changefactory.ChangeFactory; import games.strategy.engine.delegate.IDelegateBridge; +import games.strategy.triplea.Properties; import games.strategy.triplea.delegate.ExecutionStack; import games.strategy.triplea.delegate.battle.BattleActions; import games.strategy.triplea.delegate.battle.BattleState; +import games.strategy.triplea.delegate.battle.MustFightBattle; import games.strategy.triplea.delegate.battle.steps.BattleStep; -import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; import lombok.AllArgsConstructor; +import org.triplea.sound.SoundPath; @AllArgsConstructor public class NavalBombardment implements BattleStep { private static final long serialVersionUID = 3338296388191048761L; + private static final BattleState.Side side = OFFENSE; + protected final BattleState battleState; protected final BattleActions battleActions; @Override public List getNames() { - final List steps = new ArrayList<>(); - if (!valid()) { - return steps; - } - steps.add(NAVAL_BOMBARDMENT); - steps.add(SELECT_NAVAL_BOMBARDMENT_CASUALTIES); - return steps; + return !valid() + ? List.of() + : getSteps().stream() + .flatMap(step -> step.getNames().stream()) + .collect(Collectors.toList()); } @Override @@ -39,9 +46,44 @@ public Order getOrder() { @Override public void execute(final ExecutionStack stack, final IDelegateBridge bridge) { - if (valid()) { - battleActions.fireNavalBombardment(bridge); + if (!valid()) { + return; + } + final Collection bombardingUnits = battleState.getBombardingUnits(); + + if (!bombardingUnits.isEmpty()) { + // bombarding units can't move after bombarding even if there are no units to bombard + final Change change = ChangeFactory.markNoMovementChange(bombardingUnits); + bridge.addChange(change); } + + final List steps = getSteps(); + + if (!steps.isEmpty()) { + bridge + .getSoundChannelBroadcaster() + .playSoundForAll(SoundPath.CLIP_BATTLE_BOMBARD, battleState.getPlayer(side)); + + // steps go in reverse order on the stack + Collections.reverse(steps); + steps.forEach(stack::push); + } + } + + private List getSteps() { + return FireRoundStepsFactory.builder() + .battleState(battleState) + .battleActions(battleActions) + .firingGroupSplitter(FiringGroupSplitterBombard.of()) + .side(side) + .returnFire( + Properties.getNavalBombardCasualtiesReturnFire(battleState.getGameData()) + ? MustFightBattle.ReturnFire.ALL + : MustFightBattle.ReturnFire.NONE) + .diceRoller(new MainDiceRoller()) + .casualtySelector(new SelectMainBattleCasualties()) + .build() + .createSteps(); } private boolean valid() { diff --git a/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/aa/AaFireAndCasualtyStep.java b/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/aa/AaFireAndCasualtyStep.java index 7ece7e48944..1dbfeafcb5e 100644 --- a/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/aa/AaFireAndCasualtyStep.java +++ b/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/aa/AaFireAndCasualtyStep.java @@ -3,28 +3,24 @@ import static games.strategy.triplea.delegate.battle.BattleState.Side.DEFENSE; import static games.strategy.triplea.delegate.battle.BattleState.Side.OFFENSE; import static games.strategy.triplea.delegate.battle.BattleState.UnitBattleFilter.ALIVE; -import static games.strategy.triplea.delegate.battle.BattleStepStrings.AA_GUNS_FIRE_SUFFIX; -import static games.strategy.triplea.delegate.battle.BattleStepStrings.CASUALTIES_SUFFIX; -import static games.strategy.triplea.delegate.battle.BattleStepStrings.REMOVE_PREFIX; -import static games.strategy.triplea.delegate.battle.BattleStepStrings.SELECT_PREFIX; -import games.strategy.engine.data.GamePlayer; -import games.strategy.engine.data.Unit; import games.strategy.engine.delegate.IDelegateBridge; -import games.strategy.triplea.attachments.UnitAttachment; import games.strategy.triplea.delegate.DiceRoll; +import games.strategy.triplea.delegate.ExecutionStack; import games.strategy.triplea.delegate.battle.BattleActions; import games.strategy.triplea.delegate.battle.BattleState; +import games.strategy.triplea.delegate.battle.MustFightBattle; import games.strategy.triplea.delegate.battle.casualty.AaCasualtySelector; import games.strategy.triplea.delegate.battle.steps.BattleStep; +import games.strategy.triplea.delegate.battle.steps.fire.FireRoundStepsFactory; import games.strategy.triplea.delegate.battle.steps.fire.RollDiceStep; import games.strategy.triplea.delegate.battle.steps.fire.SelectCasualties; import games.strategy.triplea.delegate.data.CasualtyDetails; import games.strategy.triplea.delegate.power.calculator.CombatValue; -import java.util.ArrayList; -import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.function.BiFunction; +import java.util.stream.Collectors; import lombok.AllArgsConstructor; import org.triplea.sound.SoundUtils; @@ -38,27 +34,34 @@ public abstract class AaFireAndCasualtyStep implements BattleStep { @Override public List getNames() { - final List steps = new ArrayList<>(); - if (!valid()) { - return steps; - } - for (final String typeAa : UnitAttachment.getAllOfTypeAas(aaGuns())) { - steps.add(firingPlayer().getName() + " " + typeAa + AA_GUNS_FIRE_SUFFIX); - steps.add(firedAtPlayer().getName() + SELECT_PREFIX + typeAa + CASUALTIES_SUFFIX); - steps.add(firedAtPlayer().getName() + REMOVE_PREFIX + typeAa + CASUALTIES_SUFFIX); - } - return steps; + return getSteps().stream() + .flatMap(step -> step.getNames().stream()) + .collect(Collectors.toList()); } - protected boolean valid() { - return !aaGuns().isEmpty(); - } + @Override + public void execute(final ExecutionStack stack, final IDelegateBridge bridge) { + final List steps = getSteps(); - abstract GamePlayer firingPlayer(); + // steps go in reverse order on the stack + Collections.reverse(steps); + steps.forEach(stack::push); + } - abstract GamePlayer firedAtPlayer(); + private List getSteps() { + return FireRoundStepsFactory.builder() + .battleState(battleState) + .battleActions(battleActions) + .firingGroupSplitter(FiringGroupSplitterAa.of(getSide())) + .side(getSide()) + .returnFire(MustFightBattle.ReturnFire.ALL) + .diceRoller(new AaDiceRoller()) + .casualtySelector(new SelectAaCasualties()) + .build() + .createSteps(); + } - abstract Collection aaGuns(); + abstract BattleState.Side getSide(); public static class SelectAaCasualties implements BiFunction { diff --git a/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/aa/DefensiveAaFire.java b/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/aa/DefensiveAaFire.java index a5fcf4b53af..bf377aa5c21 100644 --- a/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/aa/DefensiveAaFire.java +++ b/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/aa/DefensiveAaFire.java @@ -1,20 +1,16 @@ package games.strategy.triplea.delegate.battle.steps.fire.aa; import static games.strategy.triplea.delegate.battle.BattleState.Side.DEFENSE; -import static games.strategy.triplea.delegate.battle.BattleState.Side.OFFENSE; -import games.strategy.engine.data.GamePlayer; -import games.strategy.engine.data.Unit; -import games.strategy.engine.delegate.IDelegateBridge; -import games.strategy.triplea.delegate.ExecutionStack; import games.strategy.triplea.delegate.battle.BattleActions; import games.strategy.triplea.delegate.battle.BattleState; -import java.util.Collection; /** Offensive Aa units can fire and the player can select their casualties */ public class DefensiveAaFire extends AaFireAndCasualtyStep { private static final long serialVersionUID = 3220057715007657960L; + private static final BattleState.Side side = DEFENSE; + public DefensiveAaFire(final BattleState battleState, final BattleActions battleActions) { super(battleState, battleActions); } @@ -25,24 +21,7 @@ public Order getOrder() { } @Override - public void execute(final ExecutionStack stack, final IDelegateBridge bridge) { - if (valid()) { - battleActions.fireDefensiveAaGuns(); - } - } - - @Override - GamePlayer firingPlayer() { - return battleState.getPlayer(DEFENSE); - } - - @Override - GamePlayer firedAtPlayer() { - return battleState.getPlayer(OFFENSE); - } - - @Override - Collection aaGuns() { - return battleState.getAa(DEFENSE); + BattleState.Side getSide() { + return side; } } diff --git a/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/aa/FiringGroupSplitterAa.java b/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/aa/FiringGroupSplitterAa.java index de897ea682c..482148fc77b 100644 --- a/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/aa/FiringGroupSplitterAa.java +++ b/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/aa/FiringGroupSplitterAa.java @@ -32,7 +32,7 @@ *

See {@link FiringGroup} for why isSuicideOnHit needs to be separated by unit type. */ @Value(staticConstructor = "of") -public class FiringGroupSplitterAa implements Function> { +public class FiringGroupSplitterAa implements Function> { BattleState.Side side; diff --git a/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/aa/OffensiveAaFire.java b/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/aa/OffensiveAaFire.java index bbe2634c45a..540983dd985 100644 --- a/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/aa/OffensiveAaFire.java +++ b/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/aa/OffensiveAaFire.java @@ -1,20 +1,16 @@ package games.strategy.triplea.delegate.battle.steps.fire.aa; -import static games.strategy.triplea.delegate.battle.BattleState.Side.DEFENSE; import static games.strategy.triplea.delegate.battle.BattleState.Side.OFFENSE; -import games.strategy.engine.data.GamePlayer; -import games.strategy.engine.data.Unit; -import games.strategy.engine.delegate.IDelegateBridge; -import games.strategy.triplea.delegate.ExecutionStack; import games.strategy.triplea.delegate.battle.BattleActions; import games.strategy.triplea.delegate.battle.BattleState; -import java.util.Collection; /** Offensive Aa units can fire and the player can select their casualties */ public class OffensiveAaFire extends AaFireAndCasualtyStep { private static final long serialVersionUID = 5843852442617511691L; + private static final BattleState.Side side = OFFENSE; + public OffensiveAaFire(final BattleState battleState, final BattleActions battleActions) { super(battleState, battleActions); } @@ -25,24 +21,7 @@ public Order getOrder() { } @Override - public void execute(final ExecutionStack stack, final IDelegateBridge bridge) { - if (valid()) { - battleActions.fireOffensiveAaGuns(); - } - } - - @Override - GamePlayer firingPlayer() { - return battleState.getPlayer(OFFENSE); - } - - @Override - GamePlayer firedAtPlayer() { - return battleState.getPlayer(DEFENSE); - } - - @Override - Collection aaGuns() { - return battleState.getAa(OFFENSE); + BattleState.Side getSide() { + return side; } } diff --git a/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/air/AirAttackVsNonSubsStep.java b/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/air/AirAttackVsNonSubsStep.java index e749f3a075c..affd585b9a3 100644 --- a/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/air/AirAttackVsNonSubsStep.java +++ b/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/air/AirAttackVsNonSubsStep.java @@ -7,8 +7,11 @@ import games.strategy.triplea.delegate.battle.BattleState; import java.util.List; +import org.triplea.java.RemoveOnNextMajorRelease; /** Air can not attack subs unless a destroyer is present */ +@RemoveOnNextMajorRelease +@Deprecated public class AirAttackVsNonSubsStep extends AirVsNonSubsStep { private static final long serialVersionUID = 4273449622231941896L; diff --git a/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/air/AirDefendVsNonSubsStep.java b/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/air/AirDefendVsNonSubsStep.java index d4f9624c89b..d208003dadc 100644 --- a/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/air/AirDefendVsNonSubsStep.java +++ b/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/air/AirDefendVsNonSubsStep.java @@ -7,8 +7,11 @@ import games.strategy.triplea.delegate.battle.BattleState; import java.util.List; +import org.triplea.java.RemoveOnNextMajorRelease; /** Air can not attack subs unless a destroyer is present */ +@RemoveOnNextMajorRelease +@Deprecated public class AirDefendVsNonSubsStep extends AirVsNonSubsStep { private static final long serialVersionUID = -7965786276905309057L; diff --git a/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/air/AirVsNonSubsStep.java b/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/air/AirVsNonSubsStep.java index 2a317e8894a..9faf7800345 100644 --- a/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/air/AirVsNonSubsStep.java +++ b/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/air/AirVsNonSubsStep.java @@ -8,8 +8,11 @@ import games.strategy.triplea.delegate.battle.steps.BattleStep; import java.util.Collection; import lombok.AllArgsConstructor; +import org.triplea.java.RemoveOnNextMajorRelease; /** Air can not attack subs unless a destroyer is present */ +@RemoveOnNextMajorRelease +@Deprecated @AllArgsConstructor public abstract class AirVsNonSubsStep implements BattleStep { private static final long serialVersionUID = 4641526323094044712L; diff --git a/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/firststrike/DefensiveFirstStrike.java b/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/firststrike/DefensiveFirstStrike.java index 762edad453a..ae088687717 100644 --- a/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/firststrike/DefensiveFirstStrike.java +++ b/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/firststrike/DefensiveFirstStrike.java @@ -1,11 +1,8 @@ package games.strategy.triplea.delegate.battle.steps.fire.firststrike; import static games.strategy.triplea.delegate.battle.BattleState.Side.DEFENSE; -import static games.strategy.triplea.delegate.battle.BattleState.Side.OFFENSE; import static games.strategy.triplea.delegate.battle.BattleState.UnitBattleFilter.ALIVE; -import static games.strategy.triplea.delegate.battle.BattleState.UnitBattleFilter.CASUALTY; -import static games.strategy.triplea.delegate.battle.BattleStepStrings.FIRST_STRIKE_UNITS_FIRE; -import static games.strategy.triplea.delegate.battle.BattleStepStrings.SELECT_FIRST_STRIKE_CASUALTIES; +import static games.strategy.triplea.delegate.battle.BattleStepStrings.FIRST_STRIKE_UNITS; import games.strategy.engine.delegate.IDelegateBridge; import games.strategy.triplea.Properties; @@ -15,9 +12,15 @@ import games.strategy.triplea.delegate.battle.BattleState; import games.strategy.triplea.delegate.battle.MustFightBattle.ReturnFire; import games.strategy.triplea.delegate.battle.steps.BattleStep; -import java.util.ArrayList; +import games.strategy.triplea.delegate.battle.steps.fire.FireRoundStepsFactory; +import games.strategy.triplea.delegate.battle.steps.fire.MainDiceRoller; +import games.strategy.triplea.delegate.battle.steps.fire.SelectMainBattleCasualties; +import games.strategy.triplea.delegate.battle.steps.fire.general.FiringGroupSplitterGeneral; +import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; +/** Generates fire steps for the first strike battle phase for the defensive player */ public class DefensiveFirstStrike implements BattleStep { private enum State { @@ -28,6 +31,8 @@ private enum State { private static final long serialVersionUID = 3646211932844911163L; + private static final BattleState.Side side = DEFENSE; + protected final BattleState battleState; protected final BattleActions battleActions; @@ -54,7 +59,7 @@ public DefensiveFirstStrike( } private State calculateState() { - if (battleState.filterUnits(ALIVE, DEFENSE).stream() + if (battleState.filterUnits(ALIVE, side).stream() .noneMatch(Matches.unitIsFirstStrikeOnDefense(battleState.getGameData()))) { return State.NOT_APPLICABLE; } @@ -65,7 +70,8 @@ private State calculateState() { } final boolean canSneakAttack = - battleState.filterUnits(ALIVE, OFFENSE).stream().noneMatch(Matches.unitIsDestroyer()) + battleState.filterUnits(ALIVE, side.getOpposite()).stream() + .noneMatch(Matches.unitIsDestroyer()) && Properties.getDefendingSubsSneakAttack(battleState.getGameData()); if (canSneakAttack) { return State.FIRST_STRIKE; @@ -75,15 +81,11 @@ private State calculateState() { @Override public List getNames() { - final List steps = new ArrayList<>(); - if (this.state == State.NOT_APPLICABLE) { - return steps; - } - - steps.add(battleState.getPlayer(DEFENSE).getName() + FIRST_STRIKE_UNITS_FIRE); - steps.add(battleState.getPlayer(OFFENSE).getName() + SELECT_FIRST_STRIKE_CASUALTIES); - - return steps; + return this.state == State.NOT_APPLICABLE + ? List.of() + : getSteps().stream() + .flatMap(step -> step.getNames().stream()) + .collect(Collectors.toList()); } @Override @@ -99,15 +101,25 @@ public void execute(final ExecutionStack stack, final IDelegateBridge bridge) { if (this.state == State.NOT_APPLICABLE) { return; } - battleActions.findTargetGroupsAndFire( - returnFire, - battleState.getPlayer(OFFENSE).getName() + SELECT_FIRST_STRIKE_CASUALTIES, - true, - battleState.getPlayer(DEFENSE), - Matches.unitIsFirstStrikeOnDefense(battleState.getGameData()), - battleState.filterUnits(ALIVE, DEFENSE), - battleState.filterUnits(CASUALTY, DEFENSE), - battleState.filterUnits(ALIVE, OFFENSE), - battleState.filterUnits(CASUALTY, OFFENSE)); + final List steps = getSteps(); + + // steps go in reverse order on the stack + Collections.reverse(steps); + steps.forEach(stack::push); + } + + private List getSteps() { + return FireRoundStepsFactory.builder() + .battleState(battleState) + .battleActions(battleActions) + .firingGroupSplitter( + FiringGroupSplitterGeneral.of( + side, FiringGroupSplitterGeneral.Type.FIRST_STRIKE, FIRST_STRIKE_UNITS)) + .side(side) + .returnFire(returnFire) + .diceRoller(new MainDiceRoller()) + .casualtySelector(new SelectMainBattleCasualties()) + .build() + .createSteps(); } } diff --git a/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/firststrike/OffensiveFirstStrike.java b/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/firststrike/OffensiveFirstStrike.java index f996a583f2f..cbb0a1b4e12 100644 --- a/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/firststrike/OffensiveFirstStrike.java +++ b/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/firststrike/OffensiveFirstStrike.java @@ -1,11 +1,8 @@ package games.strategy.triplea.delegate.battle.steps.fire.firststrike; -import static games.strategy.triplea.delegate.battle.BattleState.Side.DEFENSE; import static games.strategy.triplea.delegate.battle.BattleState.Side.OFFENSE; import static games.strategy.triplea.delegate.battle.BattleState.UnitBattleFilter.ALIVE; -import static games.strategy.triplea.delegate.battle.BattleState.UnitBattleFilter.CASUALTY; -import static games.strategy.triplea.delegate.battle.BattleStepStrings.FIRST_STRIKE_UNITS_FIRE; -import static games.strategy.triplea.delegate.battle.BattleStepStrings.SELECT_FIRST_STRIKE_CASUALTIES; +import static games.strategy.triplea.delegate.battle.BattleStepStrings.FIRST_STRIKE_UNITS; import games.strategy.engine.delegate.IDelegateBridge; import games.strategy.triplea.Properties; @@ -15,9 +12,15 @@ import games.strategy.triplea.delegate.battle.BattleState; import games.strategy.triplea.delegate.battle.MustFightBattle.ReturnFire; import games.strategy.triplea.delegate.battle.steps.BattleStep; -import java.util.ArrayList; +import games.strategy.triplea.delegate.battle.steps.fire.FireRoundStepsFactory; +import games.strategy.triplea.delegate.battle.steps.fire.MainDiceRoller; +import games.strategy.triplea.delegate.battle.steps.fire.SelectMainBattleCasualties; +import games.strategy.triplea.delegate.battle.steps.fire.general.FiringGroupSplitterGeneral; +import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; +/** Generates fire steps for the first strike battle phase for the offensive player */ public class OffensiveFirstStrike implements BattleStep { private enum State { @@ -28,6 +31,8 @@ private enum State { private static final long serialVersionUID = -2154415762808582704L; + private static final BattleState.Side side = OFFENSE; + protected final BattleState battleState; protected final BattleActions battleActions; @@ -54,7 +59,7 @@ public OffensiveFirstStrike( } private State calculateState() { - if (battleState.filterUnits(ALIVE, OFFENSE).stream().noneMatch(Matches.unitIsFirstStrike())) { + if (battleState.filterUnits(ALIVE, side).stream().noneMatch(Matches.unitIsFirstStrike())) { return State.NOT_APPLICABLE; } @@ -64,7 +69,8 @@ private State calculateState() { } final boolean canSneakAttack = - battleState.filterUnits(ALIVE, DEFENSE).stream().noneMatch(Matches.unitIsDestroyer()); + battleState.filterUnits(ALIVE, side.getOpposite()).stream() + .noneMatch(Matches.unitIsDestroyer()); if (canSneakAttack) { return State.FIRST_STRIKE; } @@ -73,15 +79,11 @@ private State calculateState() { @Override public List getNames() { - final List steps = new ArrayList<>(); - if (this.state == State.NOT_APPLICABLE) { - return steps; - } - - steps.add(battleState.getPlayer(OFFENSE).getName() + FIRST_STRIKE_UNITS_FIRE); - steps.add(battleState.getPlayer(DEFENSE).getName() + SELECT_FIRST_STRIKE_CASUALTIES); - - return steps; + return this.state == State.NOT_APPLICABLE + ? List.of() + : getSteps().stream() + .flatMap(step -> step.getNames().stream()) + .collect(Collectors.toList()); } @Override @@ -97,15 +99,25 @@ public void execute(final ExecutionStack stack, final IDelegateBridge bridge) { if (this.state == State.NOT_APPLICABLE) { return; } - battleActions.findTargetGroupsAndFire( - returnFire, - battleState.getPlayer(DEFENSE).getName() + SELECT_FIRST_STRIKE_CASUALTIES, - false, - battleState.getPlayer(OFFENSE), - Matches.unitIsFirstStrike(), - battleState.filterUnits(ALIVE, OFFENSE), - battleState.filterUnits(CASUALTY, OFFENSE), - battleState.filterUnits(ALIVE, DEFENSE), - battleState.filterUnits(CASUALTY, DEFENSE)); + final List steps = getSteps(); + + // steps go in reverse order on the stack + Collections.reverse(steps); + steps.forEach(stack::push); + } + + private List getSteps() { + return FireRoundStepsFactory.builder() + .battleState(battleState) + .battleActions(battleActions) + .firingGroupSplitter( + FiringGroupSplitterGeneral.of( + side, FiringGroupSplitterGeneral.Type.FIRST_STRIKE, FIRST_STRIKE_UNITS)) + .side(side) + .returnFire(returnFire) + .diceRoller(new MainDiceRoller()) + .casualtySelector(new SelectMainBattleCasualties()) + .build() + .createSteps(); } } diff --git a/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/general/DefensiveGeneral.java b/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/general/DefensiveGeneral.java index d9e723b0003..5bceca9a80a 100644 --- a/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/general/DefensiveGeneral.java +++ b/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/general/DefensiveGeneral.java @@ -1,42 +1,43 @@ package games.strategy.triplea.delegate.battle.steps.fire.general; import static games.strategy.triplea.delegate.battle.BattleState.Side.DEFENSE; -import static games.strategy.triplea.delegate.battle.BattleState.Side.OFFENSE; -import static games.strategy.triplea.delegate.battle.BattleState.UnitBattleFilter.ALIVE; -import static games.strategy.triplea.delegate.battle.BattleState.UnitBattleFilter.CASUALTY; -import static games.strategy.triplea.delegate.battle.BattleStepStrings.FIRE; -import static games.strategy.triplea.delegate.battle.BattleStepStrings.SELECT_CASUALTIES; +import static games.strategy.triplea.delegate.battle.BattleStepStrings.UNITS; import games.strategy.engine.delegate.IDelegateBridge; import games.strategy.triplea.delegate.ExecutionStack; -import games.strategy.triplea.delegate.Matches; import games.strategy.triplea.delegate.battle.BattleActions; import games.strategy.triplea.delegate.battle.BattleState; import games.strategy.triplea.delegate.battle.MustFightBattle.ReturnFire; import games.strategy.triplea.delegate.battle.steps.BattleStep; -import java.util.ArrayList; +import games.strategy.triplea.delegate.battle.steps.fire.FireRoundStepsFactory; +import games.strategy.triplea.delegate.battle.steps.fire.MainDiceRoller; +import games.strategy.triplea.delegate.battle.steps.fire.SelectMainBattleCasualties; +import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; import lombok.AllArgsConstructor; +/** + * Generates fire steps for the General battle phase for the defensive player + * + *

The General battle phase is after all special units have had their turn + */ @AllArgsConstructor public class DefensiveGeneral implements BattleStep { private static final long serialVersionUID = -3571056706315021648L; + private static final BattleState.Side side = DEFENSE; + protected final BattleState battleState; protected final BattleActions battleActions; @Override public List getNames() { - final List steps = new ArrayList<>(); - if (battleState.filterUnits(ALIVE, DEFENSE).stream() - .anyMatch(Matches.unitIsFirstStrikeOnDefense(battleState.getGameData()).negate())) { - steps.add(battleState.getPlayer(DEFENSE).getName() + FIRE); - steps.add(battleState.getPlayer(OFFENSE).getName() + SELECT_CASUALTIES); - } - - return steps; + return getSteps().stream() + .flatMap(step -> step.getNames().stream()) + .collect(Collectors.toList()); } @Override @@ -46,15 +47,24 @@ public Order getOrder() { @Override public void execute(final ExecutionStack stack, final IDelegateBridge bridge) { - battleActions.findTargetGroupsAndFire( - ReturnFire.ALL, - battleState.getPlayer(OFFENSE).getName() + SELECT_CASUALTIES, - true, - battleState.getPlayer(DEFENSE), - Matches.unitIsFirstStrikeOnDefense(battleState.getGameData()).negate(), - battleState.filterUnits(ALIVE, DEFENSE), - battleState.filterUnits(CASUALTY, DEFENSE), - battleState.filterUnits(ALIVE, OFFENSE), - battleState.filterUnits(CASUALTY, OFFENSE)); + final List steps = getSteps(); + + // steps go in reverse order on the stack + Collections.reverse(steps); + steps.forEach(stack::push); + } + + private List getSteps() { + return FireRoundStepsFactory.builder() + .battleState(battleState) + .battleActions(battleActions) + .firingGroupSplitter( + FiringGroupSplitterGeneral.of(side, FiringGroupSplitterGeneral.Type.NORMAL, UNITS)) + .side(side) + .returnFire(ReturnFire.ALL) + .diceRoller(new MainDiceRoller()) + .casualtySelector(new SelectMainBattleCasualties()) + .build() + .createSteps(); } } diff --git a/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/general/FiringGroupSplitterGeneral.java b/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/general/FiringGroupSplitterGeneral.java index 1232b699e70..c20a529e077 100644 --- a/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/general/FiringGroupSplitterGeneral.java +++ b/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/general/FiringGroupSplitterGeneral.java @@ -37,7 +37,7 @@ *

See {@link FiringGroup} for why isSuicideOnHit needs to be separated by unit type. */ @Value(staticConstructor = "of") -public class FiringGroupSplitterGeneral implements Function> { +public class FiringGroupSplitterGeneral implements Function> { public enum Type { NORMAL, diff --git a/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/general/OffensiveGeneral.java b/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/general/OffensiveGeneral.java index e22fe0cf112..1a17aabee9e 100644 --- a/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/general/OffensiveGeneral.java +++ b/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/general/OffensiveGeneral.java @@ -1,43 +1,43 @@ package games.strategy.triplea.delegate.battle.steps.fire.general; -import static games.strategy.triplea.delegate.battle.BattleState.Side.DEFENSE; import static games.strategy.triplea.delegate.battle.BattleState.Side.OFFENSE; -import static games.strategy.triplea.delegate.battle.BattleState.UnitBattleFilter.ALIVE; -import static games.strategy.triplea.delegate.battle.BattleState.UnitBattleFilter.CASUALTY; -import static games.strategy.triplea.delegate.battle.BattleStepStrings.FIRE; -import static games.strategy.triplea.delegate.battle.BattleStepStrings.SELECT_CASUALTIES; +import static games.strategy.triplea.delegate.battle.BattleStepStrings.UNITS; import games.strategy.engine.delegate.IDelegateBridge; import games.strategy.triplea.delegate.ExecutionStack; -import games.strategy.triplea.delegate.Matches; import games.strategy.triplea.delegate.battle.BattleActions; import games.strategy.triplea.delegate.battle.BattleState; import games.strategy.triplea.delegate.battle.MustFightBattle.ReturnFire; import games.strategy.triplea.delegate.battle.steps.BattleStep; -import java.util.ArrayList; +import games.strategy.triplea.delegate.battle.steps.fire.FireRoundStepsFactory; +import games.strategy.triplea.delegate.battle.steps.fire.MainDiceRoller; +import games.strategy.triplea.delegate.battle.steps.fire.SelectMainBattleCasualties; +import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; import lombok.AllArgsConstructor; +/** + * Generates fire steps for the General battle phase for the offensive player + * + *

The General battle phase is after all special units have had their turn + */ @AllArgsConstructor public class OffensiveGeneral implements BattleStep { private static final long serialVersionUID = 5770484176987786287L; + private static final BattleState.Side side = OFFENSE; + protected final BattleState battleState; protected final BattleActions battleActions; @Override public List getNames() { - final List steps = new ArrayList<>(); - - if (battleState.filterUnits(ALIVE, OFFENSE).stream() - .anyMatch(Matches.unitIsFirstStrike().negate())) { - steps.add(battleState.getPlayer(OFFENSE).getName() + FIRE); - steps.add(battleState.getPlayer(DEFENSE).getName() + SELECT_CASUALTIES); - } - - return steps; + return getSteps().stream() + .flatMap(step -> step.getNames().stream()) + .collect(Collectors.toList()); } @Override @@ -47,15 +47,24 @@ public Order getOrder() { @Override public void execute(final ExecutionStack stack, final IDelegateBridge bridge) { - battleActions.findTargetGroupsAndFire( - ReturnFire.ALL, - battleState.getPlayer(DEFENSE).getName() + SELECT_CASUALTIES, - false, - battleState.getPlayer(OFFENSE), - Matches.unitIsFirstStrike().negate(), - battleState.filterUnits(ALIVE, OFFENSE), - battleState.filterUnits(CASUALTY, OFFENSE), - battleState.filterUnits(ALIVE, DEFENSE), - battleState.filterUnits(CASUALTY, DEFENSE)); + final List steps = getSteps(); + + // steps go in reverse order on the stack + Collections.reverse(steps); + steps.forEach(stack::push); + } + + private List getSteps() { + return FireRoundStepsFactory.builder() + .battleState(battleState) + .battleActions(battleActions) + .firingGroupSplitter( + FiringGroupSplitterGeneral.of(side, FiringGroupSplitterGeneral.Type.NORMAL, UNITS)) + .side(side) + .returnFire(ReturnFire.ALL) + .diceRoller(new MainDiceRoller()) + .casualtySelector(new SelectMainBattleCasualties()) + .build() + .createSteps(); } } diff --git a/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/general/TargetGroup.java b/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/general/TargetGroup.java index 088e53619be..53d8431e085 100644 --- a/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/general/TargetGroup.java +++ b/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/fire/general/TargetGroup.java @@ -19,12 +19,12 @@ /** Group of firing units and their targets. */ @Getter(AccessLevel.PACKAGE) -public class TargetGroup { +class TargetGroup { private final Set firingUnitTypes; private final Set targetUnitTypes; - public TargetGroup(final UnitType firingUnitType, final Set targetUnitTypes) { + TargetGroup(final UnitType firingUnitType, final Set targetUnitTypes) { firingUnitTypes = Sets.newHashSet(firingUnitType); this.targetUnitTypes = targetUnitTypes; } @@ -50,7 +50,7 @@ public static List newTargetGroups( units.stream().map(unit -> unit.getType()).collect(Collectors.toSet()); final Set enemyUnitTypes = enemyUnits.stream().map(unit -> unit.getType()).collect(Collectors.toSet()); - final List targetGroups = new ArrayList(); + final List targetGroups = new ArrayList<>(); for (final UnitType unitType : unitTypes) { final Set targets = findTargets(unitType, unitTypes, enemyUnitTypes); if (targets.isEmpty()) { diff --git a/game-core/src/test/java/games/strategy/triplea/delegate/battle/FakeBattleState.java b/game-core/src/test/java/games/strategy/triplea/delegate/battle/FakeBattleState.java index 97acba1f621..69404b2223c 100644 --- a/game-core/src/test/java/games/strategy/triplea/delegate/battle/FakeBattleState.java +++ b/game-core/src/test/java/games/strategy/triplea/delegate/battle/FakeBattleState.java @@ -53,10 +53,6 @@ public class FakeBattleState implements BattleState { final @NonNull Collection defendingWaitingToDie; - final @NonNull Collection offensiveAa; - - final @NonNull Collection defendingAa; - final @NonNull Collection killed; final @NonNull Collection retreatUnits; @@ -183,24 +179,6 @@ public void retreatUnits(final Side side, final Collection units) { retreatUnits.addAll(units); } - @Override - public Collection getAa(final Side... sides) { - final Collection units = new ArrayList<>(); - for (final Side side : sides) { - switch (side) { - case OFFENSE: - units.addAll(offensiveAa); - break; - case DEFENSE: - units.addAll(defendingAa); - break; - default: - break; - } - } - return units; - } - @Override public List getStepStrings() { return List.of(); @@ -218,8 +196,6 @@ public static FakeBattleState.FakeBattleStateBuilder givenBattleStateBuilder() { .defendingWaitingToDie(List.of()) .attacker(mock(GamePlayer.class)) .defender(mock(GamePlayer.class)) - .offensiveAa(List.of()) - .defendingAa(List.of()) .bombardingUnits(List.of()) .dependentUnits(List.of()) .killed(List.of()) diff --git a/game-core/src/test/java/games/strategy/triplea/delegate/battle/steps/BattleStepsTest.java b/game-core/src/test/java/games/strategy/triplea/delegate/battle/steps/BattleStepsTest.java index 185476ea5fc..8a032efa249 100644 --- a/game-core/src/test/java/games/strategy/triplea/delegate/battle/steps/BattleStepsTest.java +++ b/game-core/src/test/java/games/strategy/triplea/delegate/battle/steps/BattleStepsTest.java @@ -2,23 +2,17 @@ import static games.strategy.triplea.Constants.UNIT_ATTACHMENT_NAME; import static games.strategy.triplea.delegate.battle.BattleStepStrings.AA_GUNS_FIRE_SUFFIX; -import static games.strategy.triplea.delegate.battle.BattleStepStrings.AIR_ATTACK_NON_SUBS; -import static games.strategy.triplea.delegate.battle.BattleStepStrings.AIR_DEFEND_NON_SUBS; import static games.strategy.triplea.delegate.battle.BattleStepStrings.ATTACKER_WITHDRAW; import static games.strategy.triplea.delegate.battle.BattleStepStrings.CASUALTIES_SUFFIX; import static games.strategy.triplea.delegate.battle.BattleStepStrings.CASUALTIES_WITHOUT_SPACE_SUFFIX; -import static games.strategy.triplea.delegate.battle.BattleStepStrings.FIRE; import static games.strategy.triplea.delegate.battle.BattleStepStrings.FIRE_SUFFIX; -import static games.strategy.triplea.delegate.battle.BattleStepStrings.FIRST_STRIKE_UNITS_FIRE; +import static games.strategy.triplea.delegate.battle.BattleStepStrings.FIRST_STRIKE_UNITS; import static games.strategy.triplea.delegate.battle.BattleStepStrings.LAND_PARATROOPS; -import static games.strategy.triplea.delegate.battle.BattleStepStrings.NAVAL_BOMBARDMENT; +import static games.strategy.triplea.delegate.battle.BattleStepStrings.NAVAL_BOMBARD; +import static games.strategy.triplea.delegate.battle.BattleStepStrings.NOTIFY_PREFIX; import static games.strategy.triplea.delegate.battle.BattleStepStrings.REMOVE_CASUALTIES; -import static games.strategy.triplea.delegate.battle.BattleStepStrings.REMOVE_PREFIX; import static games.strategy.triplea.delegate.battle.BattleStepStrings.REMOVE_SNEAK_ATTACK_CASUALTIES; import static games.strategy.triplea.delegate.battle.BattleStepStrings.REMOVE_UNESCORTED_TRANSPORTS; -import static games.strategy.triplea.delegate.battle.BattleStepStrings.SELECT_CASUALTIES; -import static games.strategy.triplea.delegate.battle.BattleStepStrings.SELECT_FIRST_STRIKE_CASUALTIES; -import static games.strategy.triplea.delegate.battle.BattleStepStrings.SELECT_NAVAL_BOMBARDMENT_CASUALTIES; import static games.strategy.triplea.delegate.battle.BattleStepStrings.SELECT_PREFIX; import static games.strategy.triplea.delegate.battle.BattleStepStrings.SUBMERGE_SUBS_VS_AIR_ONLY; import static games.strategy.triplea.delegate.battle.BattleStepStrings.SUBS_SUBMERGE; @@ -160,12 +154,6 @@ public static Unit givenUnitAirTransport() { return unitAndAttachment.unit; } - public static Unit givenUnitWithTypeAa() { - final UnitAndAttachment unitAndAttachment = newUnitAndAttachment(); - when(unitAndAttachment.unitAttachment.getTypeAa()).thenReturn("AntiAirGun"); - return unitAndAttachment.unit; - } - public static Unit givenUnitIsCombatAa() { final UnitAndAttachment unitAndAttachment = newUnitAndAttachment(); when(unitAndAttachment.unitAttachment.getTypeAa()).thenReturn("AntiAirGun"); @@ -235,33 +223,34 @@ private List basicFightStepStrings() { public static List generalFightStepStrings( final GamePlayer firingPlayer, final GamePlayer hitPlayer) { - return List.of(firingPlayer.getName() + FIRE, hitPlayer.getName() + SELECT_CASUALTIES); + return List.of( + firingPlayer.getName() + FIRE_SUFFIX, + hitPlayer.getName() + SELECT_PREFIX + CASUALTIES_WITHOUT_SPACE_SUFFIX, + hitPlayer.getName() + NOTIFY_PREFIX + CASUALTIES_WITHOUT_SPACE_SUFFIX); } public static List generalFightStepStrings( final GamePlayer firingPlayer, final GamePlayer hitPlayer, final String groupName) { - if (groupName.equals("")) { - return List.of( - firingPlayer.getName() + FIRE_SUFFIX, - hitPlayer.getName() + SELECT_PREFIX + CASUALTIES_WITHOUT_SPACE_SUFFIX, - hitPlayer.getName() + REMOVE_PREFIX + CASUALTIES_WITHOUT_SPACE_SUFFIX); - } else { - return List.of( - firingPlayer.getName() + " " + groupName + FIRE_SUFFIX, - hitPlayer.getName() + SELECT_PREFIX + groupName + CASUALTIES_SUFFIX, - hitPlayer.getName() + REMOVE_PREFIX + groupName + CASUALTIES_SUFFIX); - } + return List.of( + firingPlayer.getName() + " " + groupName + FIRE_SUFFIX, + hitPlayer.getName() + SELECT_PREFIX + groupName + CASUALTIES_SUFFIX, + hitPlayer.getName() + NOTIFY_PREFIX + groupName + CASUALTIES_SUFFIX); } public static List firstStrikeFightStepStrings( final GamePlayer firingPlayer, final GamePlayer hitPlayer) { return List.of( - firingPlayer.getName() + FIRST_STRIKE_UNITS_FIRE, - hitPlayer.getName() + SELECT_FIRST_STRIKE_CASUALTIES); + firingPlayer.getName() + " " + FIRST_STRIKE_UNITS + FIRE_SUFFIX, + hitPlayer.getName() + SELECT_PREFIX + FIRST_STRIKE_UNITS + CASUALTIES_SUFFIX, + hitPlayer.getName() + NOTIFY_PREFIX + FIRST_STRIKE_UNITS + CASUALTIES_SUFFIX); } - private List navalBombardmentFightStepStrings() { - return List.of(NAVAL_BOMBARDMENT, SELECT_NAVAL_BOMBARDMENT_CASUALTIES); + private List navalBombardmentFightStepStrings( + final GamePlayer firingPlayer, final GamePlayer hitPlayer) { + return List.of( + firingPlayer.getName() + " " + NAVAL_BOMBARD + FIRE_SUFFIX, + hitPlayer.getName() + SELECT_PREFIX + NAVAL_BOMBARD + CASUALTIES_SUFFIX, + hitPlayer.getName() + NOTIFY_PREFIX + NAVAL_BOMBARD + CASUALTIES_SUFFIX); } private List aaFightStepStrings( @@ -269,7 +258,7 @@ private List aaFightStepStrings( return List.of( firingPlayer.getName() + " " + name + AA_GUNS_FIRE_SUFFIX, hitPlayer.getName() + SELECT_PREFIX + name + CASUALTIES_SUFFIX, - hitPlayer.getName() + REMOVE_PREFIX + name + CASUALTIES_SUFFIX); + hitPlayer.getName() + NOTIFY_PREFIX + name + CASUALTIES_SUFFIX); } private List givenBattleSteps(final BattleState battleState) { @@ -308,6 +297,7 @@ void basicLandBattle() { givenGameData() .withDefendingSuicideAndMunitionUnitsDoNotFire(false) .withSubRetreatBeforeBattle(false) + .withAlliedAirIndependent(true) .build()) .attacker(attacker) .defender(defender) @@ -328,7 +318,14 @@ void bombardOnFirstRun() { final List steps = givenBattleSteps( givenBattleStateBuilder() - .gameData(givenGameData().build()) + .gameData( + givenGameData() + .withDefendingSuicideAndMunitionUnitsDoNotFire(false) + .withSubRetreatBeforeBattle(false) + .withNavalBombardCasualtiesReturnFire(false) + .withCaptureUnitsOnEnteringTerritory(false) + .withAlliedAirIndependent(true) + .build()) .attacker(attacker) .defender(defender) .attackingUnits(List.of(unit1)) @@ -338,7 +335,11 @@ void bombardOnFirstRun() { .battleRound(1) .build()); - assertThat(steps, is(mergeSteps(navalBombardmentFightStepStrings(), basicFightStepStrings()))); + assertThat( + steps, + is( + mergeSteps( + navalBombardmentFightStepStrings(attacker, defender), basicFightStepStrings()))); verify(battleSite, never()).getUnits(); } @@ -354,6 +355,7 @@ void bombardOnSubsequentRun() { givenGameData() .withDefendingSuicideAndMunitionUnitsDoNotFire(false) .withSubRetreatBeforeBattle(false) + .withAlliedAirIndependent(true) .build()) .attacker(attacker) .defender(defender) @@ -380,6 +382,7 @@ void impossibleSeaBattleWithBombarding() { .withDefendingSuicideAndMunitionUnitsDoNotFire(false) .withSubRetreatBeforeBattle(false) .withTransportCasualtiesRestricted(false) + .withAlliedAirIndependent(true) .build()) .battleRound(1) .attacker(attacker) @@ -440,6 +443,7 @@ void noAirTransportTech() { givenGameData() .withDefendingSuicideAndMunitionUnitsDoNotFire(false) .withSubRetreatBeforeBattle(false) + .withAlliedAirIndependent(true) .build()) .battleRound(1) .attacker(attacker) @@ -465,6 +469,7 @@ void paratroopersSubsequentRun() { givenGameData() .withDefendingSuicideAndMunitionUnitsDoNotFire(false) .withSubRetreatBeforeBattle(false) + .withAlliedAirIndependent(true) .build()) .attacker(attacker) .defender(defender) @@ -522,6 +527,7 @@ void impossibleSeaBattleWithParatroopers() { .withDefendingSuicideAndMunitionUnitsDoNotFire(false) .withSubRetreatBeforeBattle(false) .withTransportCasualtiesRestricted(false) + .withAlliedAirIndependent(true) .build()) .battleRound(1) .attacker(attacker) @@ -540,15 +546,21 @@ void impossibleSeaBattleWithParatroopers() { @DisplayName("Verify basic land battle with offensive Aa") void offensiveAaFire() { final Unit unit2 = givenAnyUnit(); - final Unit unit1 = givenUnitWithTypeAa(); + when(unit2.getOwner()).thenReturn(defender); + final Unit unit1 = + givenUnitIsCombatAa(Set.of(unit2.getType()), attacker, BattleState.Side.OFFENSE); + when(unit1.getOwner()).thenReturn(attacker); final List steps = givenBattleSteps( givenBattleStateBuilder() - .gameData(givenGameData().build()) + .gameData( + givenGameData() + .withWarRelationship(attacker, defender, true) + .withWarRelationship(defender, attacker, true) + .build()) .attacker(attacker) .defender(defender) - .offensiveAa(List.of(unit1)) .attackingUnits(List.of(unit1)) .defendingUnits(List.of(unit2)) .battleSite(battleSite) @@ -567,17 +579,23 @@ void offensiveAaFire() { @DisplayName("Verify basic land battle with defensive Aa") void defensiveAaFire() { final Unit unit1 = givenAnyUnit(); - final Unit unit2 = givenUnitWithTypeAa(); + when(unit1.getOwner()).thenReturn(attacker); + final Unit unit2 = + givenUnitIsCombatAa(Set.of(unit1.getType()), defender, BattleState.Side.DEFENSE); + when(unit2.getOwner()).thenReturn(defender); final List steps = givenBattleSteps( givenBattleStateBuilder() - .gameData(givenGameData().build()) + .gameData( + givenGameData() + .withWarRelationship(attacker, defender, true) + .withWarRelationship(defender, attacker, true) + .build()) .attacker(attacker) .defender(defender) .attackingUnits(List.of(unit1)) .defendingUnits(List.of(unit2)) - .defendingAa(List.of(unit2)) .battleSite(battleSite) .build()); @@ -593,19 +611,30 @@ void defensiveAaFire() { @Test @DisplayName("Verify basic land battle with offensive and defensive Aa") void offensiveAndDefensiveAaFire() { - final Unit unit1 = givenUnitWithTypeAa(); - final Unit unit2 = givenUnitWithTypeAa(); + final Unit target1 = givenAnyUnit(); + final Unit target2 = givenAnyUnit(); + when(target1.getOwner()).thenReturn(attacker); + when(target2.getOwner()).thenReturn(defender); + + final Unit unit1 = + givenUnitIsCombatAa(Set.of(target2.getType()), attacker, BattleState.Side.OFFENSE); + final Unit unit2 = + givenUnitIsCombatAa(Set.of(target1.getType()), defender, BattleState.Side.DEFENSE); + when(unit1.getOwner()).thenReturn(attacker); + when(unit2.getOwner()).thenReturn(defender); final List steps = givenBattleSteps( givenBattleStateBuilder() - .gameData(givenGameData().build()) + .gameData( + givenGameData() + .withWarRelationship(attacker, defender, true) + .withWarRelationship(defender, attacker, true) + .build()) .attacker(attacker) .defender(defender) - .attackingUnits(List.of(unit1)) - .offensiveAa(List.of(unit1)) - .defendingUnits(List.of(unit2)) - .defendingAa(List.of(unit2)) + .attackingUnits(List.of(unit1, target1)) + .defendingUnits(List.of(unit2, target2)) .battleSite(battleSite) .build()); @@ -635,6 +664,7 @@ void defendingSubsRetreatIfNoDestroyersAndCanRetreatBeforeBattle() { .withDefendingSuicideAndMunitionUnitsDoNotFire(false) .withSubRetreatBeforeBattle(true) .withSubmersibleSubs(true) + .withAlliedAirIndependent(true) .build()) .attacker(attacker) .defender(defender) @@ -663,6 +693,7 @@ void defendingSubsNotRetreatIfDestroyersAndCanRetreatBeforeBattle() { .withDefendingSuicideAndMunitionUnitsDoNotFire(false) .withSubRetreatBeforeBattle(true) .withSubmersibleSubs(true) + .withAlliedAirIndependent(true) .build()) .attacker(attacker) .defender(defender) @@ -688,6 +719,7 @@ void defendingSubsRetreatIfCanNotRetreatBeforeBattle() { givenGameData() .withDefendingSuicideAndMunitionUnitsDoNotFire(false) .withSubRetreatBeforeBattle(false) + .withAlliedAirIndependent(true) .build()) .attacker(attacker) .defender(defender) @@ -717,6 +749,7 @@ void defendingFirstStrikeSubmergeBeforeBattleIfSubmersibleSubsAndRetreatBeforeBa .withDefendingSuicideAndMunitionUnitsDoNotFire(false) .withWW2V2(false) .withDefendingSubsSneakAttack(true) + .withAlliedAirIndependent(true) .build()) .attacker(attacker) .defender(defender) @@ -750,6 +783,7 @@ void unescortedAttackingTransportsAreRemovedWhenCasualtiesAreRestricted() { .withDefendingSuicideAndMunitionUnitsDoNotFire(false) .withSubRetreatBeforeBattle(false) .withTransportCasualtiesRestricted(true) + .withAlliedAirIndependent(true) .build()) .attacker(attacker) .defender(defender) @@ -777,6 +811,7 @@ void unescortedAttackingTransportsAreNotRemovedWhenCasualtiesAreNotRestricted() .withDefendingSuicideAndMunitionUnitsDoNotFire(false) .withSubRetreatBeforeBattle(false) .withTransportCasualtiesRestricted(false) + .withAlliedAirIndependent(true) .build()) .attacker(attacker) .defender(defender) @@ -803,6 +838,7 @@ void unescortedDefendingTransportsAreRemovedWhenCasualtiesAreRestricted() { .withDefendingSuicideAndMunitionUnitsDoNotFire(false) .withSubRetreatBeforeBattle(false) .withTransportCasualtiesRestricted(true) + .withAlliedAirIndependent(true) .build()) .attacker(attacker) .defender(defender) @@ -831,6 +867,7 @@ void unescortedDefendingTransportsAreNotRemovedWhenCasualtiesAreNotRestricted() .withDefendingSuicideAndMunitionUnitsDoNotFire(false) .withSubRetreatBeforeBattle(false) .withTransportCasualtiesRestricted(false) + .withAlliedAirIndependent(true) .build()) .attacker(attacker) .defender(defender) @@ -859,6 +896,7 @@ void attackingFirstStrikeBasic() { .withWW2V2(false) .withDefendingSuicideAndMunitionUnitsDoNotFire(false) .withSubRetreatBeforeBattle(false) + .withAlliedAirIndependent(true) .build()) .attacker(attacker) .defender(defender) @@ -891,6 +929,7 @@ void attackingFirstStrikeWithDestroyers() { .withWW2V2(false) .withDefendingSuicideAndMunitionUnitsDoNotFire(false) .withSubRetreatBeforeBattle(false) + .withAlliedAirIndependent(true) .build()) .attacker(attacker) .defender(defender) @@ -925,6 +964,7 @@ void defendingFirstStrikeBasic() { .withWW2V2(false) .withDefendingSubsSneakAttack(false) .withSubRetreatBeforeBattle(false) + .withAlliedAirIndependent(true) .build()) .attacker(attacker) .defender(defender) @@ -955,6 +995,7 @@ void defendingFirstStrikeWithSneakAttackAllowed() { givenGameData() .withSubRetreatBeforeBattle(false) .withDefendingSuicideAndMunitionUnitsDoNotFire(false) + .withAlliedAirIndependent(true) .withTransportCasualtiesRestricted(false) .withWW2V2(false) .withDefendingSubsSneakAttack(true) @@ -989,6 +1030,7 @@ void defendingFirstStrikeWithWW2v2() { givenGameData() .withSubRetreatBeforeBattle(false) .withDefendingSuicideAndMunitionUnitsDoNotFire(false) + .withAlliedAirIndependent(true) .withTransportCasualtiesRestricted(false) .withWW2V2(true) .build()) @@ -1022,6 +1064,7 @@ void defendingFirstStrikeWithWW2v2AndDestroyers() { givenGameData() .withSubRetreatBeforeBattle(false) .withDefendingSuicideAndMunitionUnitsDoNotFire(false) + .withAlliedAirIndependent(true) .withTransportCasualtiesRestricted(false) .withWW2V2(true) .build()) @@ -1058,6 +1101,7 @@ void attackingDefendingFirstStrikeBasic() { .withDefendingSuicideAndMunitionUnitsDoNotFire(false) .withDefendingSubsSneakAttack(false) .withSubRetreatBeforeBattle(false) + .withAlliedAirIndependent(true) .build()) .attacker(attacker) .defender(defender) @@ -1092,6 +1136,7 @@ void attackingDefendingFirstStrikeWithSneakAttackAllowed() { .withTransportCasualtiesRestricted(false) .withWW2V2(false) .withDefendingSubsSneakAttack(true) + .withAlliedAirIndependent(true) .build()) .attacker(attacker) .defender(defender) @@ -1124,6 +1169,7 @@ void attackingDefendingFirstStrikeWithWW2v2() { .withSubRetreatBeforeBattle(false) .withTransportCasualtiesRestricted(false) .withWW2V2(true) + .withAlliedAirIndependent(true) .build()) .attacker(attacker) .defender(defender) @@ -1159,6 +1205,7 @@ void attackingDefendingFirstStrikeWithWW2v2AndDestroyers() { .withSubRetreatBeforeBattle(false) .withTransportCasualtiesRestricted(false) .withWW2V2(true) + .withAlliedAirIndependent(true) .build()) .attacker(attacker) .defender(defender) @@ -1197,6 +1244,7 @@ void attackingDefendingFirstStrikeWithSneakAttackAllowedAndDefendingDestroyers() .withTransportCasualtiesRestricted(false) .withWW2V2(false) .withDefendingSubsSneakAttack(true) + .withAlliedAirIndependent(true) .build()) .attacker(attacker) .defender(defender) @@ -1232,6 +1280,7 @@ void attackingDefendingFirstStrikeWithWW2v2AndDefendingDestroyers() { .withSubRetreatBeforeBattle(false) .withTransportCasualtiesRestricted(false) .withWW2V2(true) + .withAlliedAirIndependent(true) .build()) .attacker(attacker) .defender(defender) @@ -1267,6 +1316,7 @@ void attackingDefendingFirstStrikeWithWW2v2AndAttackingDestroyers() { .withSubRetreatBeforeBattle(false) .withTransportCasualtiesRestricted(false) .withWW2V2(true) + .withAlliedAirIndependent(true) .build()) .attacker(attacker) .defender(defender) @@ -1301,6 +1351,7 @@ void attackingFirstStrikeVsAir() { .withSubRetreatBeforeBattle(false) .withTransportCasualtiesRestricted(false) .withWW2V2(true) + .withAlliedAirIndependent(true) .build()) .attacker(attacker) .defender(defender) @@ -1315,7 +1366,44 @@ void attackingFirstStrikeVsAir() { mergeSteps( List.of(SUBMERGE_SUBS_VS_AIR_ONLY), firstStrikeFightStepStrings(attacker, defender), - List.of(REMOVE_SNEAK_ATTACK_CASUALTIES, AIR_DEFEND_NON_SUBS), + List.of(REMOVE_SNEAK_ATTACK_CASUALTIES), + List.of(REMOVE_CASUALTIES)))); + } + + @Test + @DisplayName("Verify attacking firstStrikes against air with other units on both sides") + void attackingFirstStrikeVsAirWithOtherUnits() { + final Unit unit2 = givenUnitIsAir(); + final Unit unit1 = givenUnitFirstStrikeAndEvadeAndCanNotBeTargetedBy(unit2.getType()); + final Unit unit3 = givenAnyUnit(); + final Unit unit4 = givenAnyUnit(); + + final List steps = + givenBattleSteps( + givenBattleStateBuilder() + .gameData( + givenGameData() + .withDefendingSuicideAndMunitionUnitsDoNotFire(false) + .withSubRetreatBeforeBattle(false) + .withTransportCasualtiesRestricted(false) + .withWW2V2(true) + .withAlliedAirIndependent(true) + .build()) + .attacker(attacker) + .defender(defender) + .attackingUnits(List.of(unit1, unit4)) + .defendingUnits(List.of(unit2, unit3)) + .battleSite(givenSeaBattleSite()) + .build()); + + assertThat( + steps, + is( + mergeSteps( + firstStrikeFightStepStrings(attacker, defender), + List.of(REMOVE_SNEAK_ATTACK_CASUALTIES), + generalFightStepStrings(attacker, defender), + generalFightStepStrings(defender, attacker, "air vs non subs"), generalFightStepStrings(defender, attacker), List.of(REMOVE_CASUALTIES)))); } @@ -1336,6 +1424,7 @@ void attackingFirstStrikeVsAirAndDestroyer() { .withSubRetreatBeforeBattle(false) .withTransportCasualtiesRestricted(false) .withWW2V2(true) + .withAlliedAirIndependent(true) .build()) .attacker(attacker) .defender(defender) @@ -1357,7 +1446,7 @@ void attackingFirstStrikeVsAirAndDestroyer() { @DisplayName("Verify defending firstStrikes against air") void defendingFirstStrikeVsAir() { final Unit unit1 = givenUnitIsAir(); - final Unit unit2 = givenUnitFirstStrikeAndEvadeAndCanNotBeTargetedBy(mock(UnitType.class)); + final Unit unit2 = givenUnitFirstStrikeAndEvadeAndCanNotBeTargetedBy(unit1.getType()); final List steps = givenBattleSteps( @@ -1368,6 +1457,7 @@ void defendingFirstStrikeVsAir() { .withSubRetreatBeforeBattle(false) .withTransportCasualtiesRestricted(false) .withWW2V2(true) + .withAlliedAirIndependent(true) .build()) .attacker(attacker) .defender(defender) @@ -1382,8 +1472,7 @@ void defendingFirstStrikeVsAir() { mergeSteps( List.of(SUBMERGE_SUBS_VS_AIR_ONLY), firstStrikeFightStepStrings(defender, attacker), - List.of(REMOVE_SNEAK_ATTACK_CASUALTIES, AIR_ATTACK_NON_SUBS), - generalFightStepStrings(attacker, defender), + List.of(REMOVE_SNEAK_ATTACK_CASUALTIES), List.of(REMOVE_CASUALTIES, attacker.getName() + ATTACKER_WITHDRAW)))); } @@ -1403,6 +1492,7 @@ void defendingFirstStrikeVsAirAndDestroyer() { .withSubRetreatBeforeBattle(false) .withTransportCasualtiesRestricted(false) .withWW2V2(true) + .withAlliedAirIndependent(true) .build()) .attacker(attacker) .defender(defender) @@ -1420,6 +1510,44 @@ void defendingFirstStrikeVsAirAndDestroyer() { List.of(REMOVE_CASUALTIES, attacker.getName() + ATTACKER_WITHDRAW)))); } + @Test + @DisplayName("Verify defending firstStrikes against air with other units on both sides") + void defendingFirstStrikeVsAirWithOtherUnits() { + final Unit unit2 = givenUnitIsAir(); + final Unit unit1 = givenUnitFirstStrikeAndEvadeAndCanNotBeTargetedBy(unit2.getType()); + final Unit unit3 = givenAnyUnit(); + final Unit unit4 = givenAnyUnit(); + + final List steps = + givenBattleSteps( + givenBattleStateBuilder() + .gameData( + givenGameData() + .withDefendingSuicideAndMunitionUnitsDoNotFire(false) + .withSubRetreatBeforeBattle(false) + .withTransportCasualtiesRestricted(false) + .withWW2V2(true) + .withAlliedAirIndependent(true) + .build()) + .attacker(attacker) + .defender(defender) + .attackingUnits(List.of(unit2, unit3)) + .defendingUnits(List.of(unit1, unit4)) + .battleSite(givenSeaBattleSite()) + .build()); + + assertThat( + steps, + is( + mergeSteps( + firstStrikeFightStepStrings(defender, attacker), + List.of(REMOVE_SNEAK_ATTACK_CASUALTIES), + generalFightStepStrings(attacker, defender, "air vs non subs"), + generalFightStepStrings(attacker, defender), + generalFightStepStrings(defender, attacker), + List.of(REMOVE_CASUALTIES, attacker.getName() + ATTACKER_WITHDRAW)))); + } + @Test @DisplayName("Verify attacking firstStrike can submerge if SUBMERSIBLE_SUBS is true") void attackingFirstStrikeCanSubmergeIfSubmersibleSubs() { @@ -1433,6 +1561,7 @@ void attackingFirstStrikeCanSubmergeIfSubmersibleSubs() { givenGameData() .withSubRetreatBeforeBattle(false) .withDefendingSuicideAndMunitionUnitsDoNotFire(false) + .withAlliedAirIndependent(true) .withTransportCasualtiesRestricted(false) .withWW2V2(false) .withSubmersibleSubs(true) @@ -1467,6 +1596,7 @@ void defendingFirstStrikeCanSubmergeIfSubmersibleSubs() { givenGameData() .withSubRetreatBeforeBattle(false) .withDefendingSuicideAndMunitionUnitsDoNotFire(false) + .withAlliedAirIndependent(true) .withTransportCasualtiesRestricted(false) .withWW2V2(false) .withDefendingSubsSneakAttack(true) @@ -1503,6 +1633,7 @@ void defendingFirstStrikeCanSubmergeIfSubmersibleSubsAndDestroyers() { givenGameData() .withSubRetreatBeforeBattle(false) .withDefendingSuicideAndMunitionUnitsDoNotFire(false) + .withAlliedAirIndependent(true) .withTransportCasualtiesRestricted(false) .withWW2V2(false) .withSubmersibleSubs(true) @@ -1537,6 +1668,7 @@ void attackingFirstStrikeWithdrawIfAble() { .withWW2V2(false) .withDefendingSuicideAndMunitionUnitsDoNotFire(false) .withSubRetreatBeforeBattle(false) + .withAlliedAirIndependent(true) .build()) .attacker(attacker) .defender(defender) @@ -1575,6 +1707,7 @@ void attackingFirstStrikeNoWithdrawIfEmptyTerritories() { .withWW2V2(false) .withDefendingSuicideAndMunitionUnitsDoNotFire(false) .withSubRetreatBeforeBattle(false) + .withAlliedAirIndependent(true) .build()) .attacker(attacker) .defender(defender) @@ -1609,6 +1742,7 @@ void attackingFirstStrikeNoWithdrawIfDestroyers() { .withWW2V2(false) .withDefendingSuicideAndMunitionUnitsDoNotFire(false) .withSubRetreatBeforeBattle(false) + .withAlliedAirIndependent(true) .build()) .attacker(attacker) .defender(defender) @@ -1645,6 +1779,7 @@ void attackingFirstStrikeWithdrawIfNonRestrictedDefenselessTransports() { .withDefendingSuicideAndMunitionUnitsDoNotFire(false) .withSubRetreatBeforeBattle(false) .withTransportCasualtiesRestricted(false) + .withAlliedAirIndependent(true) .build()) .attacker(attacker) .defender(defender) @@ -1688,6 +1823,7 @@ void defendingFirstStrikeWithdrawIfAble() { .withWW2V2(false) .withDefendingSubsSneakAttack(false) .withSubRetreatBeforeBattle(false) + .withAlliedAirIndependent(true) .build()) .attacker(attacker) .defender(defender) @@ -1722,6 +1858,7 @@ void defendingFirstStrikeNoWithdrawIfEmptyTerritories() { .withSubRetreatBeforeBattle(false) .withWW2V2(false) .withDefendingSubsSneakAttack(false) + .withAlliedAirIndependent(true) .build()) .attacker(attacker) .defender(defender) @@ -1755,6 +1892,7 @@ void defendingFirstStrikeNoWithdrawIfDestroyers() { .withDefendingSuicideAndMunitionUnitsDoNotFire(false) .withWW2V2(false) .withSubRetreatBeforeBattle(false) + .withAlliedAirIndependent(true) .build()) .attacker(attacker) .defender(defender) @@ -1786,6 +1924,7 @@ void attackingAirUnitsAtSeaCanWithdraw() { .withDefendingSuicideAndMunitionUnitsDoNotFire(false) .withSubRetreatBeforeBattle(false) .withTransportCasualtiesRestricted(false) + .withAlliedAirIndependent(true) .build()) .attacker(attacker) .defender(defender) @@ -1815,6 +1954,7 @@ void partialAmphibiousAttackCanWithdrawIfHasNonAmphibious() { givenGameData() .withSubRetreatBeforeBattle(false) .withDefendingSuicideAndMunitionUnitsDoNotFire(false) + .withAlliedAirIndependent(true) .withPartialAmphibiousRetreat(true) .build()) .attacker(attacker) @@ -1846,6 +1986,7 @@ void partialAmphibiousAttackCanNotWithdrawIfHasAllAmphibious() { givenGameData() .withSubRetreatBeforeBattle(false) .withWW2V2(false) + .withAlliedAirIndependent(true) .withAttackerRetreatPlanes(false) .withDefendingSuicideAndMunitionUnitsDoNotFire(false) .withPartialAmphibiousRetreat(true) @@ -1876,6 +2017,7 @@ void partialAmphibiousAttackCanNotWithdrawIfNotAllowed() { givenGameData() .withDefendingSuicideAndMunitionUnitsDoNotFire(false) .withSubRetreatBeforeBattle(false) + .withAlliedAirIndependent(true) .build()) .attacker(attacker) .defender(defender) @@ -1903,6 +2045,7 @@ void attackingPlanesCanWithdrawWW2v2AndAmphibious() { givenGameData() .withDefendingSuicideAndMunitionUnitsDoNotFire(false) .withSubRetreatBeforeBattle(false) + .withAlliedAirIndependent(true) .withPartialAmphibiousRetreat(false) .withWW2V2(true) .build()) @@ -1938,6 +2081,7 @@ void attackingPlanesCanWithdrawPartialAmphibiousAndAmphibious() { .withWW2V2(false) .withAttackerRetreatPlanes(false) .withDefendingSuicideAndMunitionUnitsDoNotFire(false) + .withAlliedAirIndependent(true) .withPartialAmphibiousRetreat(true) .build()) .attacker(attacker) @@ -1967,6 +2111,7 @@ void attackingPlanesCanWithdrawPlanesRetreatAndAmphibious() { .withSubRetreatBeforeBattle(false) .withWW2V2(false) .withDefendingSuicideAndMunitionUnitsDoNotFire(false) + .withAlliedAirIndependent(true) .withPartialAmphibiousRetreat(false) .withAttackerRetreatPlanes(true) .build()) @@ -1996,6 +2141,7 @@ void attackingPlanesCanNotWithdrawWW2v2AndNotAmphibious() { givenGameData() .withDefendingSuicideAndMunitionUnitsDoNotFire(false) .withSubRetreatBeforeBattle(false) + .withAlliedAirIndependent(true) .withTransportCasualtiesRestricted(true) .build()) .attacker(attacker) diff --git a/game-core/src/test/java/games/strategy/triplea/delegate/battle/steps/change/ClearAaCasualtiesTest.java b/game-core/src/test/java/games/strategy/triplea/delegate/battle/steps/change/ClearAaCasualtiesTest.java index 83d4ada468c..83949c1a44e 100644 --- a/game-core/src/test/java/games/strategy/triplea/delegate/battle/steps/change/ClearAaCasualtiesTest.java +++ b/game-core/src/test/java/games/strategy/triplea/delegate/battle/steps/change/ClearAaCasualtiesTest.java @@ -2,14 +2,11 @@ import static games.strategy.triplea.delegate.battle.FakeBattleState.givenBattleStateBuilder; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -import games.strategy.engine.data.Unit; import games.strategy.engine.delegate.IDelegateBridge; import games.strategy.triplea.delegate.ExecutionStack; import games.strategy.triplea.delegate.battle.BattleActions; -import java.util.List; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; @@ -25,21 +22,7 @@ class ClearAaCasualtiesTest { @Test void clearCasualtiesIfOffensiveAaExists() { final ClearAaCasualties clearAaCasualties = - new ClearAaCasualties( - givenBattleStateBuilder().offensiveAa(List.of(mock(Unit.class))).build(), - battleActions); - - clearAaCasualties.execute(executionStack, delegateBridge); - - verify(battleActions).clearWaitingToDieAndDamagedChangesInto(eq(delegateBridge)); - } - - @Test - void clearCasualtiesIfDefendingAaExists() { - final ClearAaCasualties clearAaCasualties = - new ClearAaCasualties( - givenBattleStateBuilder().defendingAa(List.of(mock(Unit.class))).build(), - battleActions); + new ClearAaCasualties(givenBattleStateBuilder().build(), battleActions); clearAaCasualties.execute(executionStack, delegateBridge); diff --git a/game-core/src/test/java/games/strategy/triplea/delegate/battle/steps/fire/FireRoundStepsFactoryTest.java b/game-core/src/test/java/games/strategy/triplea/delegate/battle/steps/fire/FireRoundStepsFactoryTest.java index 86cf92fce77..36d659c8e56 100644 --- a/game-core/src/test/java/games/strategy/triplea/delegate/battle/steps/fire/FireRoundStepsFactoryTest.java +++ b/game-core/src/test/java/games/strategy/triplea/delegate/battle/steps/fire/FireRoundStepsFactoryTest.java @@ -130,8 +130,6 @@ void getNamesWithDefaultUnitName() { .createSteps()); assertThat( - "units should not be in the name", - names, - is(generalFightStepStrings(attacker, defender, ""))); + "units should not be in the name", names, is(generalFightStepStrings(attacker, defender))); } } diff --git a/game-core/src/test/java/games/strategy/triplea/delegate/battle/steps/fire/NavalBombardmentTest.java b/game-core/src/test/java/games/strategy/triplea/delegate/battle/steps/fire/NavalBombardmentTest.java index 3f1865c9b39..983d46dc402 100644 --- a/game-core/src/test/java/games/strategy/triplea/delegate/battle/steps/fire/NavalBombardmentTest.java +++ b/game-core/src/test/java/games/strategy/triplea/delegate/battle/steps/fire/NavalBombardmentTest.java @@ -1,17 +1,27 @@ package games.strategy.triplea.delegate.battle.steps.fire; +import static games.strategy.triplea.Constants.UNIT_ATTACHMENT_NAME; import static games.strategy.triplea.delegate.battle.FakeBattleState.givenBattleStateBuilder; +import static games.strategy.triplea.delegate.battle.steps.BattleStepsTest.givenAnyUnit; import static games.strategy.triplea.delegate.battle.steps.BattleStepsTest.givenSeaBattleSite; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.collection.IsEmptyCollection.empty; import static org.hamcrest.core.Is.is; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import games.strategy.engine.data.GameData; +import games.strategy.engine.data.GamePlayer; import games.strategy.engine.data.Unit; +import games.strategy.engine.data.UnitType; import games.strategy.engine.delegate.IDelegateBridge; +import games.strategy.triplea.attachments.UnitAttachment; import games.strategy.triplea.delegate.ExecutionStack; import games.strategy.triplea.delegate.battle.BattleActions; import games.strategy.triplea.delegate.battle.BattleState; @@ -21,6 +31,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.triplea.sound.ISound; @ExtendWith(MockitoExtension.class) class NavalBombardmentTest { @@ -32,32 +43,52 @@ class NavalBombardmentTest { @Test @DisplayName("Has bombardment units and first round") void bombardmentHappensIfHasBombardmentUnitsAndIsFirstRound() { + final UnitType unitType = spy(new UnitType("type", mock(GameData.class))); + when(unitType.getAttachment(UNIT_ATTACHMENT_NAME)).thenReturn(mock(UnitAttachment.class)); + final Unit bombarder = spy(unitType.create(1, mock(GamePlayer.class), true).get(0)); + + when(delegateBridge.getSoundChannelBroadcaster()).thenReturn(mock(ISound.class)); + final BattleState battleState = - givenBattleStateBuilder().bombardingUnits(List.of(mock(Unit.class))).battleRound(1).build(); + givenBattleStateBuilder() + .bombardingUnits(List.of(bombarder)) + .defendingUnits(List.of(givenAnyUnit())) + .battleRound(1) + .build(); + final NavalBombardment navalBombardment = new NavalBombardment(battleState, battleActions); - assertThat(navalBombardment.getNames(), hasSize(2)); + assertThat(navalBombardment.getNames(), hasSize(3)); + navalBombardment.execute(executionStack, delegateBridge); - verify(battleActions).fireNavalBombardment(delegateBridge); + verify(executionStack, times(3)).push(any()); } @Test void bombardmentDoesNotHappenIfNotFirstRound() { final BattleState battleState = - givenBattleStateBuilder().bombardingUnits(List.of(mock(Unit.class))).battleRound(2).build(); + givenBattleStateBuilder() + .bombardingUnits(List.of(mock(Unit.class))) + .defendingUnits(List.of(givenAnyUnit())) + .battleRound(2) + .build(); final NavalBombardment navalBombardment = new NavalBombardment(battleState, battleActions); assertThat(navalBombardment.getNames(), is(empty())); navalBombardment.execute(executionStack, delegateBridge); - verify(battleActions, never()).fireNavalBombardment(delegateBridge); + verify(executionStack, never()).push(any()); } @Test void bombardmentDoesNotHappenIfNoBombardmentUnitsAndFirstRound() { final BattleState battleState = - givenBattleStateBuilder().bombardingUnits(List.of()).battleRound(1).build(); + givenBattleStateBuilder() + .bombardingUnits(List.of()) + .defendingUnits(List.of(givenAnyUnit())) + .battleRound(1) + .build(); final NavalBombardment navalBombardment = new NavalBombardment(battleState, battleActions); assertThat(navalBombardment.getNames(), is(empty())); navalBombardment.execute(executionStack, delegateBridge); - verify(battleActions, never()).fireNavalBombardment(delegateBridge); + verify(executionStack, never()).push(any()); } @Test @@ -65,12 +96,13 @@ void bombardmentDoesNotHappenIfSeaBattle() { final BattleState battleState = givenBattleStateBuilder() .bombardingUnits(List.of(mock(Unit.class))) + .defendingUnits(List.of(givenAnyUnit())) .battleRound(1) .battleSite(givenSeaBattleSite()) .build(); final NavalBombardment navalBombardment = new NavalBombardment(battleState, battleActions); assertThat(navalBombardment.getNames(), is(empty())); navalBombardment.execute(executionStack, delegateBridge); - verify(battleActions, never()).fireNavalBombardment(delegateBridge); + verify(executionStack, never()).push(any()); } } diff --git a/game-core/src/test/java/games/strategy/triplea/delegate/battle/steps/fire/aa/DefensiveAaFireTest.java b/game-core/src/test/java/games/strategy/triplea/delegate/battle/steps/fire/aa/DefensiveAaFireTest.java index 31777dcca45..637fa214436 100644 --- a/game-core/src/test/java/games/strategy/triplea/delegate/battle/steps/fire/aa/DefensiveAaFireTest.java +++ b/game-core/src/test/java/games/strategy/triplea/delegate/battle/steps/fire/aa/DefensiveAaFireTest.java @@ -1,21 +1,28 @@ package games.strategy.triplea.delegate.battle.steps.fire.aa; +import static games.strategy.triplea.delegate.battle.BattleState.Side.DEFENSE; import static games.strategy.triplea.delegate.battle.FakeBattleState.givenBattleStateBuilder; -import static games.strategy.triplea.delegate.battle.steps.BattleStepsTest.givenUnitWithTypeAa; +import static games.strategy.triplea.delegate.battle.steps.BattleStepsTest.givenAnyUnit; +import static games.strategy.triplea.delegate.battle.steps.BattleStepsTest.givenUnitIsCombatAa; +import static games.strategy.triplea.delegate.battle.steps.MockGameData.givenGameData; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.collection.IsEmptyCollection.empty; import static org.hamcrest.core.Is.is; -import static org.mockito.Mockito.mock; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import games.strategy.engine.data.GamePlayer; import games.strategy.engine.data.Unit; import games.strategy.engine.delegate.IDelegateBridge; import games.strategy.triplea.delegate.ExecutionStack; import games.strategy.triplea.delegate.battle.BattleActions; import games.strategy.triplea.delegate.battle.BattleState; import java.util.List; +import java.util.Set; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -28,20 +35,31 @@ class DefensiveAaFireTest { @Mock ExecutionStack executionStack; @Mock IDelegateBridge delegateBridge; @Mock BattleActions battleActions; + @Mock GamePlayer attacker; + @Mock GamePlayer defender; @Nested class GetNames { @Test void hasNamesIfAaIsAvailable() { + final Unit targetUnit = givenAnyUnit(); + final Unit aaUnit = givenUnitIsCombatAa(Set.of(targetUnit.getType()), defender, DEFENSE); + when(aaUnit.getOwner()).thenReturn(defender); final BattleState battleState = - givenBattleStateBuilder().defendingAa(List.of(givenUnitWithTypeAa())).build(); + givenBattleStateBuilder() + .gameData(givenGameData().withWarRelationship(attacker, defender, true).build()) + .attacker(attacker) + .defender(defender) + .attackingUnits(List.of(targetUnit)) + .defendingUnits(List.of(aaUnit)) + .build(); final DefensiveAaFire defensiveAaFire = new DefensiveAaFire(battleState, battleActions); assertThat(defensiveAaFire.getNames(), hasSize(3)); } @Test void hasNoNamesIfNoAaIsAvailable() { - final BattleState battleState = givenBattleStateBuilder().defendingAa(List.of()).build(); + final BattleState battleState = givenBattleStateBuilder().defendingUnits(List.of()).build(); final DefensiveAaFire defensiveAaFire = new DefensiveAaFire(battleState, battleActions); assertThat(defensiveAaFire.getNames(), is(empty())); } @@ -51,25 +69,34 @@ void hasNoNamesIfNoAaIsAvailable() { class FireAa { @Test void firedIfAaAreAvailable() { + final Unit targetUnit = givenAnyUnit(); + final Unit aaUnit = givenUnitIsCombatAa(Set.of(targetUnit.getType()), defender, DEFENSE); + when(aaUnit.getOwner()).thenReturn(defender); final DefensiveAaFire defensiveAaFire = new DefensiveAaFire( - givenBattleStateBuilder().defendingAa(List.of(mock(Unit.class))).build(), + givenBattleStateBuilder() + .gameData(givenGameData().withWarRelationship(attacker, defender, true).build()) + .attacker(attacker) + .defender(defender) + .attackingUnits(List.of(targetUnit)) + .defendingUnits(List.of(aaUnit)) + .build(), battleActions); defensiveAaFire.execute(executionStack, delegateBridge); - verify(battleActions).fireDefensiveAaGuns(); + verify(executionStack, times(3)).push(any()); } @Test void notFiredIfNoAaAreAvailable() { final DefensiveAaFire defensiveAaFire = new DefensiveAaFire( - givenBattleStateBuilder().defendingAa(List.of()).build(), battleActions); + givenBattleStateBuilder().defendingUnits(List.of()).build(), battleActions); defensiveAaFire.execute(executionStack, delegateBridge); - verify(battleActions, never()).fireDefensiveAaGuns(); + verify(executionStack, never()).push(any()); } } } diff --git a/game-core/src/test/java/games/strategy/triplea/delegate/battle/steps/fire/aa/OffensiveAaFireTest.java b/game-core/src/test/java/games/strategy/triplea/delegate/battle/steps/fire/aa/OffensiveAaFireTest.java index 01fc0a800d1..9d028e3cde9 100644 --- a/game-core/src/test/java/games/strategy/triplea/delegate/battle/steps/fire/aa/OffensiveAaFireTest.java +++ b/game-core/src/test/java/games/strategy/triplea/delegate/battle/steps/fire/aa/OffensiveAaFireTest.java @@ -1,21 +1,28 @@ package games.strategy.triplea.delegate.battle.steps.fire.aa; +import static games.strategy.triplea.delegate.battle.BattleState.Side.OFFENSE; import static games.strategy.triplea.delegate.battle.FakeBattleState.givenBattleStateBuilder; -import static games.strategy.triplea.delegate.battle.steps.BattleStepsTest.givenUnitWithTypeAa; +import static games.strategy.triplea.delegate.battle.steps.BattleStepsTest.givenAnyUnit; +import static games.strategy.triplea.delegate.battle.steps.BattleStepsTest.givenUnitIsCombatAa; +import static games.strategy.triplea.delegate.battle.steps.MockGameData.givenGameData; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.collection.IsEmptyCollection.empty; import static org.hamcrest.core.Is.is; -import static org.mockito.Mockito.mock; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import games.strategy.engine.data.GamePlayer; import games.strategy.engine.data.Unit; import games.strategy.engine.delegate.IDelegateBridge; import games.strategy.triplea.delegate.ExecutionStack; import games.strategy.triplea.delegate.battle.BattleActions; import games.strategy.triplea.delegate.battle.BattleState; import java.util.List; +import java.util.Set; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -28,20 +35,31 @@ class OffensiveAaFireTest { @Mock ExecutionStack executionStack; @Mock IDelegateBridge delegateBridge; @Mock BattleActions battleActions; + @Mock GamePlayer attacker; + @Mock GamePlayer defender; @Nested class GetNames { @Test void hasNamesIfAaIsAvailable() { + final Unit targetUnit = givenAnyUnit(); + final Unit aaUnit = givenUnitIsCombatAa(Set.of(targetUnit.getType()), attacker, OFFENSE); + when(aaUnit.getOwner()).thenReturn(attacker); final BattleState battleState = - givenBattleStateBuilder().offensiveAa(List.of(givenUnitWithTypeAa())).build(); + givenBattleStateBuilder() + .gameData(givenGameData().withWarRelationship(defender, attacker, true).build()) + .attacker(attacker) + .defender(defender) + .attackingUnits(List.of(aaUnit)) + .defendingUnits(List.of(targetUnit)) + .build(); final OffensiveAaFire offensiveAaFire = new OffensiveAaFire(battleState, battleActions); assertThat(offensiveAaFire.getNames(), hasSize(3)); } @Test void hasNoNamesIfNoAaIsAvailable() { - final BattleState battleState = givenBattleStateBuilder().offensiveAa(List.of()).build(); + final BattleState battleState = givenBattleStateBuilder().attackingUnits(List.of()).build(); final OffensiveAaFire offensiveAaFire = new OffensiveAaFire(battleState, battleActions); assertThat(offensiveAaFire.getNames(), is(empty())); } @@ -51,25 +69,34 @@ void hasNoNamesIfNoAaIsAvailable() { class FireAa { @Test void firedIfAaAreAvailable() { + final Unit targetUnit = givenAnyUnit(); + final Unit aaUnit = givenUnitIsCombatAa(Set.of(targetUnit.getType()), attacker, OFFENSE); + when(aaUnit.getOwner()).thenReturn(attacker); final OffensiveAaFire offensiveAaFire = new OffensiveAaFire( - givenBattleStateBuilder().offensiveAa(List.of(mock(Unit.class))).build(), + givenBattleStateBuilder() + .gameData(givenGameData().withWarRelationship(defender, attacker, true).build()) + .attacker(attacker) + .defender(defender) + .attackingUnits(List.of(aaUnit)) + .defendingUnits(List.of(targetUnit)) + .build(), battleActions); offensiveAaFire.execute(executionStack, delegateBridge); - verify(battleActions).fireOffensiveAaGuns(); + verify(executionStack, times(3)).push(any()); } @Test void notFiredIfNoAaAreAvailable() { final OffensiveAaFire offensiveAaFire = new OffensiveAaFire( - givenBattleStateBuilder().offensiveAa(List.of()).build(), battleActions); + givenBattleStateBuilder().attackingUnits(List.of()).build(), battleActions); offensiveAaFire.execute(executionStack, delegateBridge); - verify(battleActions, never()).fireOffensiveAaGuns(); + verify(executionStack, never()).push(any()); } } } diff --git a/game-core/src/test/java/games/strategy/triplea/delegate/battle/steps/fire/firststrike/DefensiveFirstStrikeTest.java b/game-core/src/test/java/games/strategy/triplea/delegate/battle/steps/fire/firststrike/DefensiveFirstStrikeTest.java index 561735c7f8e..d844cc56cb0 100644 --- a/game-core/src/test/java/games/strategy/triplea/delegate/battle/steps/fire/firststrike/DefensiveFirstStrikeTest.java +++ b/game-core/src/test/java/games/strategy/triplea/delegate/battle/steps/fire/firststrike/DefensiveFirstStrikeTest.java @@ -6,10 +6,8 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.collection.IsEmptyCollection.empty; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyCollection; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import games.strategy.engine.delegate.IDelegateBridge; @@ -45,17 +43,7 @@ void willNotExecuteIfNoDefensiveFirstStrikeAvailable() { assertThat(defensiveFirstStrike.getNames(), is(empty())); defensiveFirstStrike.execute(executionStack, delegateBridge); - verify(battleActions, never()) - .findTargetGroupsAndFire( - any(), - anyString(), - anyBoolean(), - any(), - any(), - anyCollection(), - anyCollection(), - anyCollection(), - anyCollection()); + verify(executionStack, never()).push(any()); } @ParameterizedTest @@ -66,21 +54,11 @@ void getStep(final List parameters, final BattleStep.Order final DefensiveFirstStrike defensiveFirstStrike = new DefensiveFirstStrike(battleState, battleActions); - assertThat(defensiveFirstStrike.getNames(), hasSize(2)); + assertThat(defensiveFirstStrike.getNames(), hasSize(3)); assertThat(defensiveFirstStrike.getOrder(), is(stepOrder)); defensiveFirstStrike.execute(executionStack, delegateBridge); - verify(battleActions) - .findTargetGroupsAndFire( - any(), - anyString(), - anyBoolean(), - any(), - any(), - anyCollection(), - anyCollection(), - anyCollection(), - anyCollection()); + verify(executionStack, times(3)).push(any()); } static List getStep() { diff --git a/game-core/src/test/java/games/strategy/triplea/delegate/battle/steps/fire/firststrike/OffensiveFirstStrikeTest.java b/game-core/src/test/java/games/strategy/triplea/delegate/battle/steps/fire/firststrike/OffensiveFirstStrikeTest.java index 379a4cbd882..1462f33480b 100644 --- a/game-core/src/test/java/games/strategy/triplea/delegate/battle/steps/fire/firststrike/OffensiveFirstStrikeTest.java +++ b/game-core/src/test/java/games/strategy/triplea/delegate/battle/steps/fire/firststrike/OffensiveFirstStrikeTest.java @@ -1,16 +1,16 @@ package games.strategy.triplea.delegate.battle.steps.fire.firststrike; +import static games.strategy.triplea.Constants.ALLIED_AIR_INDEPENDENT; import static games.strategy.triplea.delegate.battle.steps.fire.firststrike.BattleStateBuilder.givenBattleState; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.collection.IsEmptyCollection.empty; import static org.hamcrest.core.Is.is; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyCollection; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import games.strategy.engine.delegate.IDelegateBridge; import games.strategy.triplea.delegate.ExecutionStack; @@ -44,17 +44,7 @@ void willNotExecuteIfNoOffensiveFirstStrikeAvailable() { assertThat(offensiveFirstStrike.getNames(), is(empty())); offensiveFirstStrike.execute(executionStack, delegateBridge); - verify(battleActions, never()) - .findTargetGroupsAndFire( - any(), - anyString(), - anyBoolean(), - any(), - any(), - anyCollection(), - anyCollection(), - anyCollection(), - anyCollection()); + verify(executionStack, never()).push(any()); } @ParameterizedTest @@ -62,24 +52,16 @@ void willNotExecuteIfNoOffensiveFirstStrikeAvailable() { void getStep(final List parameters, final Order stepOrder) { final BattleState battleState = givenBattleState(parameters); + when(battleState.getGameData().getProperties().get(ALLIED_AIR_INDEPENDENT, false)) + .thenReturn(true); final OffensiveFirstStrike offensiveFirstStrike = new OffensiveFirstStrike(battleState, battleActions); - assertThat(offensiveFirstStrike.getNames(), hasSize(2)); + assertThat(offensiveFirstStrike.getNames(), hasSize(3)); assertThat(offensiveFirstStrike.getOrder(), is(stepOrder)); offensiveFirstStrike.execute(executionStack, delegateBridge); - verify(battleActions) - .findTargetGroupsAndFire( - any(), - anyString(), - anyBoolean(), - any(), - any(), - anyCollection(), - anyCollection(), - anyCollection(), - anyCollection()); + verify(executionStack, times(3)).push(any()); } static List getStep() { diff --git a/game-core/src/test/java/games/strategy/triplea/delegate/battle/steps/fire/general/DefensiveGeneralTest.java b/game-core/src/test/java/games/strategy/triplea/delegate/battle/steps/fire/general/DefensiveGeneralTest.java index a21ba7e3133..bbb69bb509b 100644 --- a/game-core/src/test/java/games/strategy/triplea/delegate/battle/steps/fire/general/DefensiveGeneralTest.java +++ b/game-core/src/test/java/games/strategy/triplea/delegate/battle/steps/fire/general/DefensiveGeneralTest.java @@ -37,10 +37,11 @@ void hasNamesIfStandardUnitAvailable() { final BattleState battleState = givenBattleStateBuilder() .defendingUnits(List.of(givenAnyUnit())) + .attackingUnits(List.of(givenAnyUnit())) .gameData(gameData) .build(); final DefensiveGeneral defensiveGeneral = new DefensiveGeneral(battleState, battleActions); - assertThat(defensiveGeneral.getNames(), hasSize(2)); + assertThat(defensiveGeneral.getNames(), hasSize(3)); } @Test @@ -50,6 +51,7 @@ void hasNoNamesIfStandardUnitIsNotAvailable() { final BattleState battleState = givenBattleStateBuilder() .defendingUnits(List.of(givenUnitFirstStrike())) + .attackingUnits(List.of(givenAnyUnit())) .gameData(gameData) .build(); final DefensiveGeneral defensiveGeneral = new DefensiveGeneral(battleState, battleActions); diff --git a/game-core/src/test/java/games/strategy/triplea/delegate/battle/steps/fire/general/OffensiveGeneralTest.java b/game-core/src/test/java/games/strategy/triplea/delegate/battle/steps/fire/general/OffensiveGeneralTest.java index 66f3abd5954..1a8395546e2 100644 --- a/game-core/src/test/java/games/strategy/triplea/delegate/battle/steps/fire/general/OffensiveGeneralTest.java +++ b/game-core/src/test/java/games/strategy/triplea/delegate/battle/steps/fire/general/OffensiveGeneralTest.java @@ -3,13 +3,12 @@ import static games.strategy.triplea.delegate.battle.FakeBattleState.givenBattleStateBuilder; import static games.strategy.triplea.delegate.battle.steps.BattleStepsTest.givenAnyUnit; import static games.strategy.triplea.delegate.battle.steps.BattleStepsTest.givenUnitFirstStrike; +import static games.strategy.triplea.delegate.battle.steps.MockGameData.givenGameData; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; -import games.strategy.engine.delegate.IDelegateBridge; -import games.strategy.triplea.delegate.ExecutionStack; import games.strategy.triplea.delegate.battle.BattleActions; import games.strategy.triplea.delegate.battle.BattleState; import java.util.List; @@ -22,8 +21,6 @@ @ExtendWith(MockitoExtension.class) class OffensiveGeneralTest { - @Mock ExecutionStack executionStack; - @Mock IDelegateBridge delegateBridge; @Mock BattleActions battleActions; @Nested @@ -31,15 +28,23 @@ class GetNames { @Test void hasNamesIfStandardUnitAvailable() { final BattleState battleState = - givenBattleStateBuilder().attackingUnits(List.of(givenAnyUnit())).build(); + givenBattleStateBuilder() + .gameData(givenGameData().withAlliedAirIndependent(true).build()) + .attackingUnits(List.of(givenAnyUnit())) + .defendingUnits(List.of(givenAnyUnit())) + .build(); final OffensiveGeneral offensiveGeneral = new OffensiveGeneral(battleState, battleActions); - assertThat(offensiveGeneral.getNames(), hasSize(2)); + assertThat(offensiveGeneral.getNames(), hasSize(3)); } @Test void hasNoNamesIfStandardUnitIsNotAvailable() { final BattleState battleState = - givenBattleStateBuilder().attackingUnits(List.of(givenUnitFirstStrike())).build(); + givenBattleStateBuilder() + .gameData(givenGameData().withAlliedAirIndependent(true).build()) + .attackingUnits(List.of(givenUnitFirstStrike())) + .defendingUnits(List.of(givenAnyUnit())) + .build(); final OffensiveGeneral offensiveGeneral = new OffensiveGeneral(battleState, battleActions); assertThat(offensiveGeneral.getNames(), is(empty())); }