/** * Return a Comparator that will order the specified units in preferred unload order. If needed it * may also inspect the transport holding the units. */ public static Comparator<Unit> getUnloadableUnitsComparator( final List<Unit> units, final Route route, final PlayerID player) { // compare transports final Comparator<Unit> unloadableTransportsComparator = getUnloadableTransportsComparator(units, route, player, false); // if noTies is set, sort by hashcode so that result is deterministic final Comparator<Unit> movableUnitsComparator = getMovableUnitsComparator(units, route); return (u1, u2) -> { final Unit t1 = TripleAUnit.get(u1).getTransportedBy(); final Unit t2 = TripleAUnit.get(u2).getTransportedBy(); // check if unloadable units are in a transport if (t1 != null && t2 == null) { return -1; } if (t1 == null && t2 != null) { return 1; } if (t1 != null && t2 != null) { final int compareTransports = unloadableTransportsComparator.compare(t1, t2); if (compareTransports != 0) { return compareTransports; } } // we are sorting air units, or no difference found yet // if noTies is set, sort by hashcode so that result is deterministic return movableUnitsComparator.compare(u1, u2); }; }
// In some versions, a transport can never unload into // multiple territories in a given turn. // In WW2V1 a transport can unload to multiple territories in // non-combat phase, provided they are both adjacent to the sea zone. public static boolean isTransportUnloadRestrictedToAnotherTerritory( final Unit transport, final Territory territory) { final Collection<Unit> unloaded = ((TripleAUnit) transport).getUnloaded(); if (unloaded.isEmpty()) { return false; } // See if transport has unloaded anywhere yet final GameData data = transport.getData(); for (final Unit u : unloaded) { final TripleAUnit taUnit = (TripleAUnit) u; if (isWW2V2(data) || isTransportUnloadRestricted(data)) { // cannot unload to two different territories if (!taUnit.getUnloadedTo().equals(territory)) { return true; } } else { // cannot unload to two different territories in combat phase if (!GameStepPropertiesHelper.isNonCombatMove(transport.getData(), true) && !taUnit.getUnloadedTo().equals(territory)) { return true; } } } return false; }
// If a transport has been in combat, it cannot load AND unload in non-combat public static boolean isTransportUnloadRestrictedInNonCombat(final Unit transport) { final TripleAUnit taUnit = (TripleAUnit) transport; if (GameStepPropertiesHelper.isNonCombatMove(transport.getData(), true) && taUnit.getWasInCombat() && taUnit.getWasLoadedAfterCombat()) { return true; } return false; }
public static boolean hasTransportUnloadedInPreviousPhase(final Unit transport) { final Collection<Unit> unloaded = ((TripleAUnit) transport).getUnloaded(); // See if transport has unloaded anywhere yet for (final Unit u : unloaded) { final TripleAUnit taUnit = (TripleAUnit) u; // cannot unload in two different phases if (GameStepPropertiesHelper.isNonCombatMove(transport.getData(), true) && taUnit.getWasUnloadedInCombatPhase()) { return true; } } return false; }
public static Comparator<Unit> getHighestToLowestMovementComparator() { return (u1, u2) -> { final int left1 = TripleAUnit.get(u1).getMovementLeft(); final int left2 = TripleAUnit.get(u2).getMovementLeft(); if (left1 == left2) { return 0; } if (left1 < left2) { return 1; } return -1; }; }
/** Return a Comparator that will order the specified transports in preferred unload order. */ public static Comparator<Unit> getUnloadableTransportsComparator( final List<Unit> transports, final Route route, final PlayerID player, final boolean noTies) { final Comparator<Unit> decreasingCapacityComparator = getDecreasingCapacityComparator(transports); final Match<Unit> incapableTransportMatch = Matches.transportCannotUnload(route.getEnd()); return (t1, t2) -> { // Check if transport is incapable due to game state final boolean isIncapable1 = incapableTransportMatch.match(t1); final boolean isIncapable2 = incapableTransportMatch.match(t2); if (!isIncapable1 && isIncapable2) { return -1; } if (isIncapable1 && !isIncapable2) { return 1; } // Prioritize allied transports final boolean isAlliedTrn1 = !t1.getOwner().equals(player); final boolean isAlliedTrn2 = !t2.getOwner().equals(player); if (isAlliedTrn1 && !isAlliedTrn2) { return -1; } if (!isAlliedTrn1 && isAlliedTrn2) { return 1; } // Sort by decreasing transport capacity final int compareCapacity = decreasingCapacityComparator.compare(t1, t2); if (compareCapacity != 0) { return compareCapacity; } // Sort by increasing movement final int left1 = TripleAUnit.get(t1).getMovementLeft(); final int left2 = TripleAUnit.get(t2).getMovementLeft(); if (left1 != left2) { return left1 - left2; } // If noTies is set, sort by hashcode so that result is deterministic if (noTies) { return Integer.compare(t1.hashCode(), t2.hashCode()); } else { return 0; } }; }
/** Return a Comparator that will order the specified units in preferred move order. */ public static Comparator<Unit> getMovableUnitsComparator( final List<Unit> units, final Route route) { final Comparator<Unit> decreasingCapacityComparator = getDecreasingCapacityComparator(units); return (u1, u2) -> { // Ensure units have enough movement final int left1 = TripleAUnit.get(u1).getMovementLeft(); final int left2 = TripleAUnit.get(u2).getMovementLeft(); if (route != null) { if (left1 >= route.getMovementCost(u1) && left2 < route.getMovementCost(u2)) { return -1; } if (left1 < route.getMovementCost(u1) && left2 >= route.getMovementCost(u2)) { return 1; } } // Prefer transports for which dependents are also selected final Collection<Unit> transporting1 = TripleAUnit.get(u1).getTransporting(); final Collection<Unit> transporting2 = TripleAUnit.get(u2).getTransporting(); final int hasDepends1 = units.containsAll(transporting1) ? 1 : 0; final int hasDepends2 = units.containsAll(transporting2) ? 1 : 0; if (hasDepends1 != hasDepends2) { return hasDepends1 - hasDepends2; } // Sort by decreasing transport capacity (only valid for transports) final int compareCapacity = decreasingCapacityComparator.compare(u1, u2); if (compareCapacity != 0) { return compareCapacity; } // Sort by increasing movement normally, but by decreasing movement during loading if (left1 != left2) { if (route != null && route.isLoad()) { return left2 - left1; } else { return left1 - left2; } } return Integer.compare(u1.hashCode(), u2.hashCode()); }; }
public static Change loadTransportChange(final TripleAUnit transport, final Unit unit) { assertTransport(transport); final CompositeChange change = new CompositeChange(); // clear the loaded by change.add(ChangeFactory.unitPropertyChange(unit, transport, TripleAUnit.TRANSPORTED_BY)); final Collection<Unit> newCarrying = new ArrayList<Unit>(transport.getTransporting()); if (newCarrying.contains(unit)) { throw new IllegalStateException("Already carrying, transport:" + transport + " unt:" + unit); } newCarrying.add(unit); change.add(ChangeFactory.unitPropertyChange(unit, Boolean.TRUE, TripleAUnit.LOADED_THIS_TURN)); change.add(ChangeFactory.unitPropertyChange(transport, true, TripleAUnit.LOADED_THIS_TURN)); // If the transport was in combat, flag it as being loaded AFTER combat if (transport.getWasInCombat()) { change.add( ChangeFactory.unitPropertyChange(transport, true, TripleAUnit.LOADED_AFTER_COMBAT)); } return change; }
public static Change unloadAirTransportChange( final TripleAUnit unit, final Territory territory, final PlayerID id, final boolean dependentBattle) { final CompositeChange change = new CompositeChange(); final TripleAUnit transport = (TripleAUnit) transportedBy(unit); if (transport == null) { return change; } assertTransport(transport); if (!transport.getTransporting().contains(unit)) { throw new IllegalStateException( "Not being carried, unit:" + unit + " transport:" + transport); } final ArrayList<Unit> newUnloaded = new ArrayList<Unit>(transport.getUnloaded()); newUnloaded.add(unit); change.add(ChangeFactory.unitPropertyChange(unit, territory, TripleAUnit.UNLOADED_TO)); if (!GameStepPropertiesHelper.isNonCombatMove(unit.getData(), true)) { change.add( ChangeFactory.unitPropertyChange(unit, true, TripleAUnit.UNLOADED_IN_COMBAT_PHASE)); // change.add(ChangeFactory.unitPropertyChange(unit, true, TripleAUnit.UNLOADED_AMPHIBIOUS)); change.add( ChangeFactory.unitPropertyChange(transport, true, TripleAUnit.UNLOADED_IN_COMBAT_PHASE)); // change.add(ChangeFactory.unitPropertyChange(transport, true, // TripleAUnit.UNLOADED_AMPHIBIOUS)); } if (!dependentBattle) { // TODO: this is causing issues with Scrambling. if the units were unloaded, then scrambling // creates a battle, there is no longer any // way to have the units removed if those transports die. change.add(ChangeFactory.unitPropertyChange(unit, null, TripleAUnit.TRANSPORTED_BY)); } // dependencies for battle calc and casualty selection include unloaded. therefore even if we // have unloaded this unit, it will die if // air transport dies IF we have the unloaded flat set. so don't set it. // TODO: fix this bullshit by re-writing entire transportation engine // change.add(ChangeFactory.unitPropertyChange(transport, newUnloaded, TripleAUnit.UNLOADED)); return change; }
/** Return a Comparator that will order the specified transports in preferred load order. */ public static Comparator<Unit> getLoadableTransportsComparator( final List<Unit> transports, final Route route, final PlayerID player) { final Comparator<Unit> decreasingCapacityComparator = getDecreasingCapacityComparator(transports); final Match<Unit> incapableTransportMatch = Matches.transportCannotUnload(route.getEnd()); return (u1, u2) -> { final TripleAUnit t1 = TripleAUnit.get(u1); final TripleAUnit t2 = TripleAUnit.get(u2); // Check if transport is incapable due to game state final boolean isIncapable1 = incapableTransportMatch.match(t1); final boolean isIncapable2 = incapableTransportMatch.match(t2); if (!isIncapable1 && isIncapable2) { return -1; } if (isIncapable1 && !isIncapable2) { return 1; } // Use allied transports as a last resort final boolean isAlliedTrn1 = !t1.getOwner().equals(player); final boolean isAlliedTrn2 = !t2.getOwner().equals(player); if (!isAlliedTrn1 && isAlliedTrn2) { return -1; } if (isAlliedTrn1 && !isAlliedTrn2) { return 1; } // Sort by decreasing transport capacity final int compareCapacity = decreasingCapacityComparator.compare(t1, t2); if (compareCapacity != 0) { return compareCapacity; } // Sort by decreasing movement final int left1 = t1.getMovementLeft(); final int left2 = t1.getMovementLeft(); if (left1 != left2) { return left2 - left1; } return Integer.compare(t1.hashCode(), t2.hashCode()); }; }
public static Collection<Unit> getUnitsLoadedOnAlliedTransportsThisTurn( final Collection<Unit> units) { final Collection<Unit> rVal = new ArrayList<Unit>(); for (final Unit u : units) { // a unit loaded onto an allied transport // cannot be unloaded in the same turn, so // if we check both wasLoadedThisTurn and // the transport that transports us, we can tell if // we were loaded onto an allied transport // if we are no longer being transported, // then we must have been transported on our own transport final TripleAUnit taUnit = (TripleAUnit) u; if (taUnit.getWasLoadedThisTurn() && taUnit.getTransportedBy() != null && // an allied transport if the owner of the transport is not the owner of the unit !taUnit.getTransportedBy().getOwner().equals(taUnit.getOwner())) { rVal.add(u); } } return rVal; }
public static Change unloadTransportChange( final TripleAUnit unit, final Territory territory, final PlayerID id, final boolean dependentBattle) { final CompositeChange change = new CompositeChange(); final TripleAUnit transport = (TripleAUnit) transportedBy(unit); if (transport == null) { return change; } assertTransport(transport); if (!transport.getTransporting().contains(unit)) { throw new IllegalStateException( "Not being carried, unit:" + unit + " transport:" + transport); } final ArrayList<Unit> newUnloaded = new ArrayList<Unit>(transport.getUnloaded()); newUnloaded.add(unit); change.add(ChangeFactory.unitPropertyChange(unit, territory, TripleAUnit.UNLOADED_TO)); if (!GameStepPropertiesHelper.isNonCombatMove(unit.getData(), true)) { change.add( ChangeFactory.unitPropertyChange(unit, true, TripleAUnit.UNLOADED_IN_COMBAT_PHASE)); change.add(ChangeFactory.unitPropertyChange(unit, true, TripleAUnit.UNLOADED_AMPHIBIOUS)); change.add( ChangeFactory.unitPropertyChange(transport, true, TripleAUnit.UNLOADED_IN_COMBAT_PHASE)); change.add( ChangeFactory.unitPropertyChange(transport, true, TripleAUnit.UNLOADED_AMPHIBIOUS)); } if (!dependentBattle) { // TODO: this is causing issues with Scrambling. if the units were unloaded, then scrambling // creates a battle, there is no longer any // way to have the units removed if those transports die. change.add(ChangeFactory.unitPropertyChange(unit, null, TripleAUnit.TRANSPORTED_BY)); } change.add(ChangeFactory.unitPropertyChange(transport, newUnloaded, TripleAUnit.UNLOADED)); return change; }
private static Comparator<Unit> getCapacityComparator( final List<Unit> transports, final boolean increasing) { // this makes it more efficient final IntegerMap<Unit> capacityMap = new IntegerMap<>(transports.size() + 1, 1); for (final Unit transport : transports) { final Collection<Unit> transporting = TripleAUnit.get(transport).getTransporting(); capacityMap.add(transport, TransportUtils.getTransportCost(transporting)); } return (t1, t2) -> { final int cost1 = capacityMap.getInt(t1); final int cost2 = capacityMap.getInt(t2); if (increasing) { return cost1 - cost2; } else { return cost2 - cost1; } }; }
private void fireRocket( final PlayerID player, final Territory attackedTerritory, final IDelegateBridge bridge, final Territory attackFrom) { final GameData data = bridge.getData(); final PlayerID attacked = attackedTerritory.getOwner(); final Resource PUs = data.getResourceList().getResource(Constants.PUS); final boolean DamageFromBombingDoneToUnits = isDamageFromBombingDoneToUnitsInsteadOfTerritories(data); // unit damage vs territory damage final Collection<Unit> enemyUnits = attackedTerritory .getUnits() .getMatches( new CompositeMatchAnd<Unit>( Matches.enemyUnit(player, data), Matches.unitIsBeingTransported().invert())); final Collection<Unit> enemyTargetsTotal = Match.getMatches( enemyUnits, Matches.UnitIsAtMaxDamageOrNotCanBeDamaged(attackedTerritory).invert()); final Collection<Unit> targets = new ArrayList<Unit>(); final Collection<Unit> rockets; // attackFrom could be null if WW2V1 if (attackFrom == null) rockets = null; else rockets = new ArrayList<Unit>( Match.getMatches(attackFrom.getUnits().getUnits(), rocketMatch(player, data))); final int numberOfAttacks = (rockets == null ? 1 : Math.min( TechAbilityAttachment.getRocketNumberPerTerritory(player, data), TechAbilityAttachment.getRocketDiceNumber(rockets, data))); if (numberOfAttacks <= 0) return; final String transcript; if (DamageFromBombingDoneToUnits) { // TODO: rockets needs to be completely redone to allow for multiple rockets to fire at // different targets, etc etc. final HashSet<UnitType> legalTargetsForTheseRockets = new HashSet<UnitType>(); if (rockets == null) legalTargetsForTheseRockets.addAll(data.getUnitTypeList().getAllUnitTypes()); else { // a hack for now, we let the rockets fire at anyone who could be targetted by any rocket for (final Unit r : rockets) { legalTargetsForTheseRockets.addAll( UnitAttachment.get(r.getType()).getBombingTargets(data)); } } final Collection<Unit> enemyTargets = Match.getMatches(enemyTargetsTotal, Matches.unitIsOfTypes(legalTargetsForTheseRockets)); if (enemyTargets.isEmpty()) return; // TODO: this sucks Unit target = null; if (enemyTargets.size() == 1) target = enemyTargets.iterator().next(); else { while (target == null) { final ITripleaPlayer iplayer = (ITripleaPlayer) bridge.getRemotePlayer(player); target = iplayer.whatShouldBomberBomb(attackedTerritory, enemyTargets, rockets); } } if (target == null) throw new IllegalStateException("No Targets in " + attackedTerritory.getName()); targets.add(target); } final boolean doNotUseBombingBonus = !games.strategy.triplea.Properties.getUseBombingMaxDiceSidesAndBonus(data) || rockets == null; int cost = 0; if (!games.strategy.triplea.Properties.getLL_DAMAGE_ONLY(data)) { if (doNotUseBombingBonus || rockets == null) { // no low luck, and no bonus, so just roll based on the map's dice sides final int[] rolls = bridge.getRandom( data.getDiceSides(), numberOfAttacks, player, DiceType.BOMBING, "Rocket fired by " + player.getName() + " at " + attacked.getName()); for (final int r : rolls) { cost += r + 1; // we are zero based } transcript = "Rockets " + (attackFrom == null ? "" : "in " + attackFrom.getName()) + " roll: " + MyFormatter.asDice(rolls); } else { // we must use bombing bonus int highestMaxDice = 0; int highestBonus = 0; final int diceSides = data.getDiceSides(); for (final Unit u : rockets) { final UnitAttachment ua = UnitAttachment.get(u.getType()); int maxDice = ua.getBombingMaxDieSides(); int bonus = ua.getBombingBonus(); // both could be -1, meaning they were not set. if they were not set, then we use default // dice sides for the map, and zero for the bonus. if (maxDice < 0) maxDice = diceSides; if (bonus < 0) bonus = 0; // we only roll once for rockets, so if there are other rockets here we just roll for the // best rocket if ((bonus + ((maxDice + 1) / 2)) > (highestBonus + ((highestMaxDice + 1) / 2))) { highestMaxDice = maxDice; highestBonus = bonus; } } // now we roll, or don't if there is nothing to roll. if (highestMaxDice > 0) { final int[] rolls = bridge.getRandom( highestMaxDice, numberOfAttacks, player, DiceType.BOMBING, "Rocket fired by " + player.getName() + " at " + attacked.getName()); for (int i = 0; i < rolls.length; i++) { final int r = rolls[i] + highestBonus; rolls[i] = r; cost += r + 1; // we are zero based } transcript = "Rockets " + (attackFrom == null ? "" : "in " + attackFrom.getName()) + " roll: " + MyFormatter.asDice(rolls); } else { cost = highestBonus * numberOfAttacks; transcript = "Rockets " + (attackFrom == null ? "" : "in " + attackFrom.getName()) + " do " + highestBonus + " damage for each rocket"; } } } else { if (doNotUseBombingBonus || rockets == null) { // no bonus, so just roll based on the map's dice sides, but modify for LL final int maxDice = (data.getDiceSides() + 1) / 3; final int bonus = (data.getDiceSides() + 1) / 3; final int[] rolls = bridge.getRandom( maxDice, numberOfAttacks, player, DiceType.BOMBING, "Rocket fired by " + player.getName() + " at " + attacked.getName()); for (int i = 0; i < rolls.length; i++) { final int r = rolls[i] + bonus; rolls[i] = r; cost += r + 1; // we are zero based } transcript = "Rockets " + (attackFrom == null ? "" : "in " + attackFrom.getName()) + " roll: " + MyFormatter.asDice(rolls); } else { int highestMaxDice = 0; int highestBonus = 0; final int diceSides = data.getDiceSides(); for (final Unit u : rockets) { final UnitAttachment ua = UnitAttachment.get(u.getType()); int maxDice = ua.getBombingMaxDieSides(); int bonus = ua.getBombingBonus(); // both could be -1, meaning they were not set. if they were not set, then we use default // dice sides for the map, and zero for the bonus. if (maxDice < 0 || doNotUseBombingBonus) maxDice = diceSides; if (bonus < 0 || doNotUseBombingBonus) bonus = 0; // now, regardless of whether they were set or not, we have to apply "low luck" to them, // meaning in this case that we reduce the luck by 2/3. if (maxDice >= 5) { bonus += (maxDice + 1) / 3; maxDice = (maxDice + 1) / 3; } // we only roll once for rockets, so if there are other rockets here we just roll for the // best rocket if ((bonus + ((maxDice + 1) / 2)) > (highestBonus + ((highestMaxDice + 1) / 2))) { highestMaxDice = maxDice; highestBonus = bonus; } } // now we roll, or don't if there is nothing to roll. if (highestMaxDice > 0) { final int[] rolls = bridge.getRandom( highestMaxDice, numberOfAttacks, player, DiceType.BOMBING, "Rocket fired by " + player.getName() + " at " + attacked.getName()); for (int i = 0; i < rolls.length; i++) { final int r = rolls[i] + highestBonus; rolls[i] = r; cost += r + 1; // we are zero based } transcript = "Rockets " + (attackFrom == null ? "" : "in " + attackFrom.getName()) + " roll: " + MyFormatter.asDice(rolls); } else { cost = highestBonus * numberOfAttacks; transcript = "Rockets " + (attackFrom == null ? "" : "in " + attackFrom.getName()) + " do " + highestBonus + " damage for each rocket"; } } } int territoryProduction = TerritoryAttachment.getProduction(attackedTerritory); if (DamageFromBombingDoneToUnits && !targets.isEmpty()) { // we are doing damage to 'target', not to the territory final Unit target = targets.iterator().next(); // UnitAttachment ua = UnitAttachment.get(target.getType()); final TripleAUnit taUnit = (TripleAUnit) target; final int damageLimit = taUnit.getHowMuchMoreDamageCanThisUnitTake(target, attackedTerritory); cost = Math.max(0, Math.min(cost, damageLimit)); final int totalDamage = taUnit.getUnitDamage() + cost; // Record production lost // DelegateFinder.moveDelegate(data).PUsLost(attackedTerritory, cost); // apply the hits to the targets final IntegerMap<Unit> damageMap = new IntegerMap<Unit>(); damageMap.put(target, totalDamage); bridge.addChange(ChangeFactory.bombingUnitDamage(damageMap)); // attackedTerritory.notifyChanged(); } // in WW2V2, limit rocket attack cost to production value of factory. else if (isWW2V2(data) || isLimitRocketDamageToProduction(data)) { // If we are limiting total PUs lost then take that into account if (isPUCap(data) || isLimitRocketDamagePerTurn(data)) { final int alreadyLost = DelegateFinder.moveDelegate(data).PUsAlreadyLost(attackedTerritory); territoryProduction -= alreadyLost; territoryProduction = Math.max(0, territoryProduction); } if (cost > territoryProduction) { cost = territoryProduction; } } // Record the PUs lost DelegateFinder.moveDelegate(data).PUsLost(attackedTerritory, cost); if (DamageFromBombingDoneToUnits && !targets.isEmpty()) { getRemote(bridge) .reportMessage( "Rocket attack in " + attackedTerritory.getName() + " does " + cost + " damage to " + targets.iterator().next(), "Rocket attack in " + attackedTerritory.getName() + " does " + cost + " damage to " + targets.iterator().next()); bridge .getHistoryWriter() .startEvent( "Rocket attack in " + attackedTerritory.getName() + " does " + cost + " damage to " + targets.iterator().next()); } else { cost *= Properties.getPU_Multiplier(data); getRemote(bridge) .reportMessage( "Rocket attack in " + attackedTerritory.getName() + " costs:" + cost, "Rocket attack in " + attackedTerritory.getName() + " costs:" + cost); // Trying to remove more PUs than the victim has is A Bad Thing[tm] final int availForRemoval = attacked.getResources().getQuantity(PUs); if (cost > availForRemoval) cost = availForRemoval; final String transcriptText = attacked.getName() + " lost " + cost + " PUs to rocket attack by " + player.getName(); bridge.getHistoryWriter().startEvent(transcriptText); final Change rocketCharge = ChangeFactory.changeResourcesChange(attacked, PUs, -cost); bridge.addChange(rocketCharge); } bridge .getHistoryWriter() .addChildToEvent(transcript, rockets == null ? null : new ArrayList<Unit>(rockets)); // this is null in WW2V1 if (attackFrom != null) { if (rockets != null && !rockets.isEmpty()) { // TODO: only a certain number fired... final Change change = ChangeFactory.markNoMovementChange(Collections.singleton(rockets.iterator().next())); bridge.addChange(change); } else { throw new IllegalStateException("No rockets?" + attackFrom.getUnits().getUnits()); } } // kill any units that can die if they have reached max damage (veqryn) if (Match.someMatch(targets, Matches.UnitCanDieFromReachingMaxDamage)) { final List<Unit> unitsCanDie = Match.getMatches(targets, Matches.UnitCanDieFromReachingMaxDamage); unitsCanDie.retainAll( Match.getMatches( unitsCanDie, Matches.UnitIsAtMaxDamageOrNotCanBeDamaged(attackedTerritory))); if (!unitsCanDie.isEmpty()) { // targets.removeAll(unitsCanDie); final Change removeDead = ChangeFactory.removeUnits(attackedTerritory, unitsCanDie); final String transcriptText = MyFormatter.unitsToText(unitsCanDie) + " lost in " + attackedTerritory.getName(); bridge.getHistoryWriter().addChildToEvent(transcriptText, unitsCanDie); bridge.addChange(removeDead); } } // play a sound if (cost > 0) bridge .getSoundChannelBroadcaster() .playSoundForAll(SoundPath.CLIP_BOMBING_ROCKET, player.getName()); }