/**
  * 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);
   };
 }
Exemple #2
0
 // 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;
 }
Exemple #3
0
 // 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;
 }
Exemple #4
0
 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());
    };
  }
Exemple #8
0
 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;
 }
Exemple #9
0
 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());
    };
  }
Exemple #11
0
 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;
 }
Exemple #12
0
 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());
 }