private static void getNeutralOutOfWarWithAllies( final PoliticalActionAttachment paa, final PlayerID player, final IDelegateBridge aBridge) { final GameData data = aBridge.getData(); if (!games.strategy.triplea.Properties.getAlliancesCanChainTogether(data)) { return; } final Collection<PlayerID> players = data.getPlayerList().getPlayers(); final Collection<PlayerID> p1AlliedWith = Match.getMatches(players, Matches.isAlliedAndAlliancesCanChainTogether(player, data)); final CompositeChange change = new CompositeChange(); for (final String relationshipChangeString : paa.getRelationshipChange()) { final String[] relationshipChange = relationshipChangeString.split(":"); final PlayerID p1 = data.getPlayerList().getPlayerID(relationshipChange[0]); final PlayerID p2 = data.getPlayerList().getPlayerID(relationshipChange[1]); if (!(p1.equals(player) || p2.equals(player))) { continue; } final PlayerID pOther = (p1.equals(player) ? p2 : p1); final RelationshipType currentType = data.getRelationshipTracker().getRelationshipType(p1, p2); final RelationshipType newType = data.getRelationshipTypeList().getRelationshipType(relationshipChange[2]); if (Matches.RelationshipTypeIsAtWar.match(currentType) && Matches.RelationshipTypeIsAtWar.invert().match(newType)) { final Collection<PlayerID> pOtherAlliedWith = Match.getMatches(players, Matches.isAlliedAndAlliancesCanChainTogether(pOther, data)); if (!pOtherAlliedWith.contains(pOther)) { pOtherAlliedWith.add(pOther); } if (!p1AlliedWith.contains(player)) { p1AlliedWith.add(player); } for (final PlayerID p3 : p1AlliedWith) { for (final PlayerID p4 : pOtherAlliedWith) { final RelationshipType currentOther = data.getRelationshipTracker().getRelationshipType(p3, p4); if (!currentOther.equals(newType) && Matches.RelationshipTypeIsAtWar.match(currentOther)) { change.add(ChangeFactory.relationshipChange(p3, p4, currentOther, newType)); aBridge .getHistoryWriter() .addChildToEvent( p3.getName() + " and " + p4.getName() + " sign a " + newType.getName() + " treaty"); MoveDelegate.getBattleTracker(data) .addRelationshipChangesThisTurn(p3, p4, currentOther, newType); } } } } } if (!change.isEmpty()) { aBridge.addChange(change); } }
/** 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 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()); }; }
/** * @param player * @return gets the valid actions for this player. */ public static Collection<PoliticalActionAttachment> getValidActions( final PlayerID player, final HashMap<ICondition, Boolean> testedConditions, final GameData data) { if (!games.strategy.triplea.Properties.getUsePolitics(data) || !player.amNotDeadYet(data)) return new ArrayList<PoliticalActionAttachment>(); return Match.getMatches( getPoliticalActionAttachments(player), new CompositeMatchAnd<PoliticalActionAttachment>( Matches.AbstractUserActionAttachmentCanBeAttempted(testedConditions), Matches.politicalActionAffectsAtLeastOneAlivePlayer(player, data))); }
@Override public void end() { super.end(); resetAttempts(); if (games.strategy.triplea.Properties.getTriggers(getData())) { // First set up a match for what we want to have fire as a default in this delegate. List out // as a composite match // OR. // use 'null, null' because this is the Default firing location for any trigger that does NOT // have 'when' set. final Match<TriggerAttachment> politicsDelegateTriggerMatch = new CompositeMatchAnd<>( TriggerAttachment.availableUses, TriggerAttachment.whenOrDefaultMatch(null, null), new CompositeMatchOr<TriggerAttachment>(TriggerAttachment.relationshipChangeMatch())); // get all possible triggers based on this match. final HashSet<TriggerAttachment> toFirePossible = TriggerAttachment.collectForAllTriggersMatching( new HashSet<>(Collections.singleton(m_player)), politicsDelegateTriggerMatch, m_bridge); if (!toFirePossible.isEmpty()) { // get all conditions possibly needed by these triggers, and then test them. final HashMap<ICondition, Boolean> testedConditions = TriggerAttachment.collectTestsForAllTriggers(toFirePossible, m_bridge); // get all triggers that are satisfied based on the tested conditions. final Set<TriggerAttachment> toFireTestedAndSatisfied = new HashSet<>( Match.getMatches( toFirePossible, TriggerAttachment.isSatisfiedMatch(testedConditions))); // now list out individual types to fire, once for each of the matches above. TriggerAttachment.triggerRelationshipChange( toFireTestedAndSatisfied, m_bridge, null, null, true, true, true, true); } } chainAlliancesTogether(m_bridge); givesBackOriginalTerritories(m_bridge); // m_needToInitialize = true; }
public static void chainAlliancesTogether(final IDelegateBridge aBridge) { final GameData data = aBridge.getData(); if (!games.strategy.triplea.Properties.getAlliancesCanChainTogether(data)) { return; } final Collection<RelationshipType> allTypes = data.getRelationshipTypeList().getAllRelationshipTypes(); RelationshipType alliedType = null; RelationshipType warType = null; for (final RelationshipType type : allTypes) { if (type.getRelationshipTypeAttachment().getIsDefaultWarPosition()) { warType = type; } else if (type.getRelationshipTypeAttachment().getAlliancesCanChainTogether()) { alliedType = type; } } if (alliedType == null) { return; } // first do alliances. then, do war (since we don't want to declare war on a potential ally). final Collection<PlayerID> players = data.getPlayerList().getPlayers(); for (final PlayerID p1 : players) { final HashSet<PlayerID> p1NewAllies = new HashSet<>(); final Collection<PlayerID> p1AlliedWith = Match.getMatches(players, Matches.isAlliedAndAlliancesCanChainTogether(p1, data)); for (final PlayerID p2 : p1AlliedWith) { p1NewAllies.addAll( Match.getMatches(players, Matches.isAlliedAndAlliancesCanChainTogether(p2, data))); } p1NewAllies.removeAll(p1AlliedWith); p1NewAllies.remove(p1); for (final PlayerID p3 : p1NewAllies) { if (!data.getRelationshipTracker().getRelationshipType(p1, p3).equals(alliedType)) { final RelationshipType current = data.getRelationshipTracker().getRelationshipType(p1, p3); aBridge.addChange(ChangeFactory.relationshipChange(p1, p3, current, alliedType)); aBridge .getHistoryWriter() .addChildToEvent( p1.getName() + " and " + p3.getName() + " are joined together in an " + alliedType.getName() + " treaty"); MoveDelegate.getBattleTracker(data) .addRelationshipChangesThisTurn(p1, p3, current, alliedType); } } } // now war if (warType == null) { return; } for (final PlayerID p1 : players) { final HashSet<PlayerID> p1NewWar = new HashSet<>(); final Collection<PlayerID> p1WarWith = Match.getMatches(players, Matches.isAtWar(p1, data)); final Collection<PlayerID> p1AlliedWith = Match.getMatches(players, Matches.isAlliedAndAlliancesCanChainTogether(p1, data)); for (final PlayerID p2 : p1AlliedWith) { p1NewWar.addAll(Match.getMatches(players, Matches.isAtWar(p2, data))); } p1NewWar.removeAll(p1WarWith); p1NewWar.remove(p1); for (final PlayerID p3 : p1NewWar) { if (!data.getRelationshipTracker().getRelationshipType(p1, p3).equals(warType)) { final RelationshipType current = data.getRelationshipTracker().getRelationshipType(p1, p3); aBridge.addChange(ChangeFactory.relationshipChange(p1, p3, current, warType)); aBridge .getHistoryWriter() .addChildToEvent( p1.getName() + " and " + p3.getName() + " declare " + warType.getName() + " on each other"); MoveDelegate.getBattleTracker(data) .addRelationshipChangesThisTurn(p1, p3, current, warType); } } } }
/** * Get a list of players that should accept this action and then ask each player if it accepts * this action. * * @param paa the politicalActionAttachment that should be accepted */ private boolean actionIsAccepted(final PoliticalActionAttachment paa) { final GameData data = getData(); final CompositeMatchOr<PoliticalActionAttachment> intoAlliedChainOrIntoOrOutOfWar = new CompositeMatchOr<>( Matches.politicalActionIsRelationshipChangeOf( null, Matches.RelationshipTypeIsAlliedAndAlliancesCanChainTogether.invert(), Matches.RelationshipTypeIsAlliedAndAlliancesCanChainTogether, data), Matches.politicalActionIsRelationshipChangeOf( null, Matches.RelationshipTypeIsAtWar.invert(), Matches.RelationshipTypeIsAtWar, data), Matches.politicalActionIsRelationshipChangeOf( null, Matches.RelationshipTypeIsAtWar, Matches.RelationshipTypeIsAtWar.invert(), data)); if (!games.strategy.triplea.Properties.getAlliancesCanChainTogether(data) || !intoAlliedChainOrIntoOrOutOfWar.match(paa)) { for (final PlayerID player : paa.getActionAccept()) { if (!(getRemotePlayer(player)) .acceptAction( m_player, PoliticsText.getInstance().getAcceptanceQuestion(paa.getText()), true)) { return false; } } } else { // if alliances chain together, then our allies must have a say in anyone becoming a new // ally/enemy final LinkedHashSet<PlayerID> playersWhoNeedToAccept = new LinkedHashSet<>(); playersWhoNeedToAccept.addAll(paa.getActionAccept()); playersWhoNeedToAccept.addAll( Match.getMatches( data.getPlayerList().getPlayers(), Matches.isAlliedAndAlliancesCanChainTogether(m_player, data))); for (final PlayerID player : paa.getActionAccept()) { playersWhoNeedToAccept.addAll( Match.getMatches( data.getPlayerList().getPlayers(), Matches.isAlliedAndAlliancesCanChainTogether(player, data))); } final HashSet<PlayerID> alliesWhoMustAccept = playersWhoNeedToAccept; alliesWhoMustAccept.removeAll(paa.getActionAccept()); for (final PlayerID player : playersWhoNeedToAccept) { String actionText = PoliticsText.getInstance().getAcceptanceQuestion(paa.getText()); if (actionText.equals("NONE")) { actionText = m_player.getName() + " wants to take the following action: " + MyFormatter.attachmentNameToText(paa.getName()) + " \r\n Do you approve?"; } else { actionText = m_player.getName() + " wants to take the following action: " + MyFormatter.attachmentNameToText(paa.getName()) + ". Do you approve? \r\n\r\n " + m_player.getName() + " will ask " + MyFormatter.defaultNamedToTextList(paa.getActionAccept()) + ", the following question: \r\n " + actionText; } if (!(getRemotePlayer(player)).acceptAction(m_player, actionText, true)) { return false; } } for (final PlayerID player : paa.getActionAccept()) { if (!(getRemotePlayer(player)) .acceptAction( m_player, PoliticsText.getInstance().getAcceptanceQuestion(paa.getText()), true)) { return false; } } } return true; }
public static Map<Territory, Double> findTerritoryValues( final PlayerID player, final List<Territory> territoriesThatCantBeHeld, final List<Territory> territoriesToAttack) { final GameData data = ProData.getData(); final List<Territory> allTerritories = data.getMap().getTerritories(); // Get all enemy factories and capitals (check if most territories have factories and if so // remove them) final Set<Territory> enemyCapitalsAndFactories = new HashSet<Territory>(); enemyCapitalsAndFactories.addAll( Match.getMatches( allTerritories, ProMatches.territoryHasInfraFactoryAndIsOwnedByPlayersOrCantBeHeld( player, data, ProUtils.getPotentialEnemyPlayers(player), territoriesThatCantBeHeld))); final int numPotentialEnemyTerritories = Match.countMatches( allTerritories, Matches.isTerritoryOwnedBy(ProUtils.getPotentialEnemyPlayers(player))); if (enemyCapitalsAndFactories.size() * 2 >= numPotentialEnemyTerritories) { enemyCapitalsAndFactories.clear(); } enemyCapitalsAndFactories.addAll(ProUtils.getLiveEnemyCapitals(data, player)); enemyCapitalsAndFactories.removeAll(territoriesToAttack); // Find max land mass size int maxLandMassSize = 1; for (final Territory t : allTerritories) { if (!t.isWater()) { final int landMassSize = 1 + data.getMap() .getNeighbors( t, 6, ProMatches.territoryCanPotentiallyMoveLandUnits(player, data, true)) .size(); if (landMassSize > maxLandMassSize) { maxLandMassSize = landMassSize; } } } // Loop through factories/capitals and find value final Map<Territory, Double> enemyCapitalsAndFactoriesMap = new HashMap<Territory, Double>(); for (final Territory t : enemyCapitalsAndFactories) { // Get factory production if factory int factoryProduction = 0; if (ProMatches.territoryHasInfraFactoryAndIsLand(player).match(t)) { factoryProduction = TerritoryAttachment.getProduction(t); } // Get player production if capital double playerProduction = 0; final TerritoryAttachment ta = TerritoryAttachment.get(t); if (ta != null && ta.isCapital()) { playerProduction = ProUtils.getPlayerProduction(t.getOwner(), data); } // Check if neutral final int isNeutral = t.getOwner().isNull() ? 1 : 0; // Calculate value final int landMassSize = 1 + data.getMap() .getNeighbors( t, 6, ProMatches.territoryCanPotentiallyMoveLandUnits(player, data, true)) .size(); final double value = Math.sqrt(factoryProduction + Math.sqrt(playerProduction)) * 32 / (1 + 3 * isNeutral) * landMassSize / maxLandMassSize; enemyCapitalsAndFactoriesMap.put(t, value); } // Determine value for land territories final Map<Territory, Double> territoryValueMap = new HashMap<Territory, Double>(); for (final Territory t : allTerritories) { if (!t.isWater() && !territoriesThatCantBeHeld.contains(t)) { // Determine value based on enemy factory land distance final List<Double> values = new ArrayList<Double>(); for (final Territory enemyCapitalOrFactory : enemyCapitalsAndFactoriesMap.keySet()) { final int distance = data.getMap() .getDistance( t, enemyCapitalOrFactory, ProMatches.territoryCanPotentiallyMoveLandUnits(player, data, true)); if (distance > 0) { values.add( enemyCapitalsAndFactoriesMap.get(enemyCapitalOrFactory) / Math.pow(2, distance)); } } Collections.sort(values, Collections.reverseOrder()); double capitalOrFactoryValue = 0; for (int i = 0; i < values.size(); i++) { capitalOrFactoryValue += values.get(i) / Math.pow(2, i); // Decrease each additional factory value by half } // Determine value based on nearby territory production double nearbyEnemyValue = 0; final Set<Territory> nearbyTerritories = data.getMap() .getNeighbors( t, 2, ProMatches.territoryCanPotentiallyMoveLandUnits(player, data, true)); final List<Territory> nearbyEnemyTerritories = Match.getMatches( nearbyTerritories, ProMatches.territoryIsEnemyOrCantBeHeld(player, data, territoriesThatCantBeHeld)); nearbyEnemyTerritories.removeAll(territoriesToAttack); for (final Territory nearbyEnemyTerritory : nearbyEnemyTerritories) { final int distance = data.getMap() .getDistance( t, nearbyEnemyTerritory, ProMatches.territoryCanPotentiallyMoveLandUnits(player, data, true)); if (distance > 0) { double value = TerritoryAttachment.getProduction(nearbyEnemyTerritory); if (nearbyEnemyTerritory.getOwner().isNull()) { value = findTerritoryAttackValue(player, nearbyEnemyTerritory) / 3; // find neutral value } else if (ProMatches.territoryIsAlliedLandAndHasNoEnemyNeighbors(player, data) .match(nearbyEnemyTerritory)) { value *= 0.1; // reduce value for can't hold amphib allied territories } if (value > 0) { nearbyEnemyValue += (value / Math.pow(2, distance)); } } } final int landMassSize = 1 + data.getMap() .getNeighbors( t, 6, ProMatches.territoryCanPotentiallyMoveLandUnits(player, data, true)) .size(); double value = nearbyEnemyValue * landMassSize / maxLandMassSize + capitalOrFactoryValue; if (ProMatches.territoryHasInfraFactoryAndIsLand(player).match(t)) { value *= 1.1; // prefer territories with factories } territoryValueMap.put(t, value); } else if (!t.isWater()) { territoryValueMap.put(t, 0.0); } } // Determine value for water territories for (final Territory t : allTerritories) { if (!territoriesThatCantBeHeld.contains(t) && t.isWater() && !data.getMap().getNeighbors(t, Matches.TerritoryIsWater).isEmpty()) { // Determine value based on enemy factory distance final List<Double> values = new ArrayList<Double>(); for (final Territory enemyCapitalOrFactory : enemyCapitalsAndFactoriesMap.keySet()) { final Route route = data.getMap() .getRoute_IgnoreEnd( t, enemyCapitalOrFactory, ProMatches.territoryCanMoveSeaUnits(player, data, true)); if (route == null || MoveValidator.validateCanal(route, null, player, data) != null) { continue; } final int distance = route.numberOfSteps(); if (distance > 0) { values.add( enemyCapitalsAndFactoriesMap.get(enemyCapitalOrFactory) / Math.pow(2, distance)); } } Collections.sort(values, Collections.reverseOrder()); double capitalOrFactoryValue = 0; for (int i = 0; i < values.size(); i++) { capitalOrFactoryValue += values.get(i) / Math.pow(2, i); // Decrease each additional factory value by half } // Determine value based on nearby territory production double nearbyLandValue = 0; final Set<Territory> nearbyTerritories = data.getMap().getNeighbors(t, 3); final List<Territory> nearbyLandTerritories = Match.getMatches( nearbyTerritories, ProMatches.territoryCanPotentiallyMoveLandUnits(player, data, false)); nearbyLandTerritories.removeAll(territoriesToAttack); for (final Territory nearbyLandTerritory : nearbyLandTerritories) { final Route route = data.getMap() .getRoute_IgnoreEnd( t, nearbyLandTerritory, ProMatches.territoryCanMoveSeaUnits(player, data, true)); if (route == null || MoveValidator.validateCanal(route, null, player, data) != null) { continue; } final int distance = route.numberOfSteps(); if (distance > 0 && distance <= 3) { if (ProMatches.territoryIsEnemyOrCantBeHeld(player, data, territoriesThatCantBeHeld) .match(nearbyLandTerritory)) { double value = TerritoryAttachment.getProduction(nearbyLandTerritory); if (nearbyLandTerritory.getOwner().isNull()) { value = findTerritoryAttackValue(player, nearbyLandTerritory); } nearbyLandValue += value; } nearbyLandValue += territoryValueMap.get(nearbyLandTerritory); } } final double value = capitalOrFactoryValue / 100 + nearbyLandValue / 10; territoryValueMap.put(t, value); } else if (t.isWater()) { territoryValueMap.put(t, 0.0); } } return territoryValueMap; }
public static Map<Territory, Double> findSeaTerritoryValues( final PlayerID player, final List<Territory> territoriesThatCantBeHeld) { final GameData data = ProData.getData(); final List<Territory> allTerritories = data.getMap().getTerritories(); // Determine value for water territories final Map<Territory, Double> territoryValueMap = new HashMap<Territory, Double>(); for (final Territory t : allTerritories) { if (!territoriesThatCantBeHeld.contains(t) && t.isWater() && !data.getMap().getNeighbors(t, Matches.TerritoryIsWater).isEmpty()) { // Determine sea value based on nearby convoy production double nearbySeaProductionValue = 0; final Set<Territory> nearbySeaTerritories = data.getMap() .getNeighbors(t, 4, ProMatches.territoryCanMoveSeaUnits(player, data, true)); final List<Territory> nearbyEnemySeaTerritories = Match.getMatches( nearbySeaTerritories, ProMatches.territoryIsEnemyOrCantBeHeld(player, data, territoriesThatCantBeHeld)); for (final Territory nearbyEnemySeaTerritory : nearbyEnemySeaTerritories) { final Route route = data.getMap() .getRoute_IgnoreEnd( t, nearbyEnemySeaTerritory, ProMatches.territoryCanMoveSeaUnits(player, data, true)); if (route == null || MoveValidator.validateCanal(route, null, player, data) != null) { continue; } final int distance = route.numberOfSteps(); if (distance > 0) { nearbySeaProductionValue += TerritoryAttachment.getProduction(nearbyEnemySeaTerritory) / Math.pow(2, distance); } } // Determine sea value based on nearby enemy sea units double nearbyEnemySeaUnitValue = 0; final List<Territory> nearbyEnemySeaUnitTerritories = Match.getMatches(nearbySeaTerritories, Matches.territoryHasEnemyUnits(player, data)); for (final Territory nearbyEnemySeaTerritory : nearbyEnemySeaUnitTerritories) { final Route route = data.getMap() .getRoute_IgnoreEnd( t, nearbyEnemySeaTerritory, ProMatches.territoryCanMoveSeaUnits(player, data, true)); if (route == null || MoveValidator.validateCanal(route, null, player, data) != null) { continue; } final int distance = route.numberOfSteps(); if (distance > 0) { nearbyEnemySeaUnitValue += nearbyEnemySeaTerritory.getUnits().countMatches(Matches.unitIsEnemyOf(data, player)) / Math.pow(2, distance); } } // Set final values final double value = 100 * nearbySeaProductionValue + nearbyEnemySeaUnitValue; territoryValueMap.put(t, value); } else if (t.isWater()) { territoryValueMap.put(t, 0.0); } } return territoryValueMap; }
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()); }