use of games.strategy.util.IntegerMap in project triplea by triplea-game.
the class RandomStatsDetails method getAllStats.
/**
* Returns a JPanel displaying information about all Statistics.
*/
public JPanel getAllStats() {
final Insets insets = new Insets(2, 2, 2, 2);
final JPanel panel = new JPanel();
panel.setLayout(new GridBagLayout());
panel.setBorder(BorderFactory.createEmptyBorder());
panel.add(getStatsDisplay(m_totalMap, m_totalStats, "Total"), new GridBagConstraints(0, 0, 1, 1, 1, 1, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.NONE, insets, 0, 0));
if (getData().containsKey(null)) {
panel.add(getStatsDisplay(getData().get(null), m_playerStats.get(null), "Null / Other"), new GridBagConstraints(1, 0, 1, 1, 1, 1, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.NONE, insets, 0, 0));
}
final int rows = Math.max(2, getData().size() / 6);
int x = 0;
for (final Entry<PlayerID, IntegerMap<Integer>> entry : getData().entrySet()) {
if (entry.getKey() == null) {
continue;
}
panel.add(getStatsDisplay(entry.getValue(), m_playerStats.get(entry.getKey()), (entry.getKey() == null ? "Null / Other" : entry.getKey().getName() + " Combat")), new GridBagConstraints((x / rows), 1 + (x % rows), 1, 1, 1, 1, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.NONE, insets, 0, 0));
x++;
}
return panel;
}
use of games.strategy.util.IntegerMap in project triplea by triplea-game.
the class BattleCalculator method sortUnitsForCasualtiesWithSupport.
/**
* The purpose of this is to return a list in the PERFECT order of which units should be selected to die first,
* And that means that certain units MUST BE INTERLEAVED.
* This list assumes that you have already taken any extra hit points away from any 2 hitpoint units.
* Example: You have a 1 attack Artillery unit that supports, and a 1 attack infantry unit that can receive support.
* The best selection of units to die is first to take whichever unit has excess, then cut that down til they are both
* the same size,
* then to take 1 artillery followed by 1 infantry, followed by 1 artillery, then 1 inf, etc, until everyone is dead.
* If you just return all infantry followed by all artillery, or the other way around, you will be missing out on some
* important support
* provided.
* (Veqryn)
*/
private static List<Unit> sortUnitsForCasualtiesWithSupport(final Collection<Unit> targetsToPickFrom, final boolean defending, final PlayerID player, final Collection<Unit> enemyUnits, final boolean amphibious, final Collection<Unit> amphibiousLandAttackers, final Territory battlesite, final IntegerMap<UnitType> costs, final Collection<TerritoryEffect> territoryEffects, final GameData data, final boolean bonus) {
// Convert unit lists to unit type lists
final List<UnitType> targetTypes = new ArrayList<>();
for (final Unit u : targetsToPickFrom) {
targetTypes.add(u.getType());
}
final List<UnitType> amphibTypes = new ArrayList<>();
if (amphibiousLandAttackers != null) {
for (final Unit u : amphibiousLandAttackers) {
amphibTypes.add(u.getType());
}
}
// Calculate hashes and cache key
int targetsHashCode = 1;
for (final UnitType ut : targetTypes) {
targetsHashCode += ut.hashCode();
}
targetsHashCode *= 31;
int amphibHashCode = 1;
for (final UnitType ut : amphibTypes) {
amphibHashCode += ut.hashCode();
}
amphibHashCode *= 31;
String key = player.getName() + "|" + battlesite.getName() + "|" + defending + "|" + amphibious + "|" + targetsHashCode + "|" + amphibHashCode;
// Check OOL cache
final List<UnitType> stored = oolCache.get(key);
if (stored != null) {
// System.out.println("Hit with cacheSize=" + oolCache.size() + ", key=" + key);
final List<Unit> result = new ArrayList<>();
final List<Unit> selectFrom = new ArrayList<>(targetsToPickFrom);
for (final UnitType ut : stored) {
for (final Iterator<Unit> it = selectFrom.iterator(); it.hasNext(); ) {
final Unit u = it.next();
if (ut.equals(u.getType())) {
result.add(u);
it.remove();
}
}
}
return result;
}
// System.out.println("Miss with cacheSize=" + oolCache.size() + ", key=" + key);
// Sort enough units to kill off
final List<Unit> sortedUnitsList = new ArrayList<>(targetsToPickFrom);
sortedUnitsList.sort(new UnitBattleComparator(defending, costs, territoryEffects, data, bonus, false));
// Sort units starting with strongest so that support gets added to them first
Collections.reverse(sortedUnitsList);
final UnitBattleComparator unitComparatorWithoutPrimaryPower = new UnitBattleComparator(defending, costs, territoryEffects, data, bonus, true);
final Map<Unit, IntegerMap<Unit>> unitSupportPowerMap = new HashMap<>();
final Map<Unit, IntegerMap<Unit>> unitSupportRollsMap = new HashMap<>();
final Map<Unit, Tuple<Integer, Integer>> unitPowerAndRollsMap = DiceRoll.getUnitPowerAndRollsForNormalBattles(sortedUnitsList, new ArrayList<>(enemyUnits), defending, false, data, battlesite, territoryEffects, amphibious, amphibiousLandAttackers, unitSupportPowerMap, unitSupportRollsMap);
// Sort units starting with weakest for finding the worst units
Collections.reverse(sortedUnitsList);
final List<Unit> sortedWellEnoughUnitsList = new ArrayList<>();
for (int i = 0; i < sortedUnitsList.size(); ++i) {
// Loop through all target units to find the best unit to take as casualty
Unit worstUnit = null;
int minPower = Integer.MAX_VALUE;
final Set<UnitType> unitTypes = new HashSet<>();
for (final Unit u : sortedUnitsList) {
if (unitTypes.contains(u.getType())) {
continue;
}
unitTypes.add(u.getType());
// Find unit power
final Map<Unit, Tuple<Integer, Integer>> currentUnitMap = new HashMap<>();
currentUnitMap.put(u, unitPowerAndRollsMap.get(u));
int power = DiceRoll.getTotalPower(currentUnitMap, data);
// Add any support power that it provides to other units
final IntegerMap<Unit> unitSupportPowerMapForUnit = unitSupportPowerMap.get(u);
if (unitSupportPowerMapForUnit != null) {
for (final Unit supportedUnit : unitSupportPowerMapForUnit.keySet()) {
Tuple<Integer, Integer> strengthAndRolls = unitPowerAndRollsMap.get(supportedUnit);
if (strengthAndRolls == null) {
continue;
}
// Remove any rolls provided by this support so they aren't counted twice
final IntegerMap<Unit> unitSupportRollsMapForUnit = unitSupportRollsMap.get(u);
if (unitSupportRollsMapForUnit != null) {
strengthAndRolls = Tuple.of(strengthAndRolls.getFirst(), strengthAndRolls.getSecond() - unitSupportRollsMapForUnit.getInt(supportedUnit));
}
// If one roll then just add the power
if (strengthAndRolls.getSecond() == 1) {
power += unitSupportPowerMapForUnit.getInt(supportedUnit);
continue;
}
// Find supported unit power with support
final Map<Unit, Tuple<Integer, Integer>> supportedUnitMap = new HashMap<>();
supportedUnitMap.put(supportedUnit, strengthAndRolls);
final int powerWithSupport = DiceRoll.getTotalPower(supportedUnitMap, data);
// Find supported unit power without support
final int strengthWithoutSupport = strengthAndRolls.getFirst() - unitSupportPowerMapForUnit.getInt(supportedUnit);
final Tuple<Integer, Integer> strengthAndRollsWithoutSupport = Tuple.of(strengthWithoutSupport, strengthAndRolls.getSecond());
supportedUnitMap.put(supportedUnit, strengthAndRollsWithoutSupport);
final int powerWithoutSupport = DiceRoll.getTotalPower(supportedUnitMap, data);
// Add the actual power provided by the support
final int addedPower = powerWithSupport - powerWithoutSupport;
power += addedPower;
}
}
// Add any power from support rolls that it provides to other units
final IntegerMap<Unit> unitSupportRollsMapForUnit = unitSupportRollsMap.get(u);
if (unitSupportRollsMapForUnit != null) {
for (final Unit supportedUnit : unitSupportRollsMapForUnit.keySet()) {
final Tuple<Integer, Integer> strengthAndRolls = unitPowerAndRollsMap.get(supportedUnit);
if (strengthAndRolls == null) {
continue;
}
// Find supported unit power with support
final Map<Unit, Tuple<Integer, Integer>> supportedUnitMap = new HashMap<>();
supportedUnitMap.put(supportedUnit, strengthAndRolls);
final int powerWithSupport = DiceRoll.getTotalPower(supportedUnitMap, data);
// Find supported unit power without support
final int rollsWithoutSupport = strengthAndRolls.getSecond() - unitSupportRollsMap.get(u).getInt(supportedUnit);
final Tuple<Integer, Integer> strengthAndRollsWithoutSupport = Tuple.of(strengthAndRolls.getFirst(), rollsWithoutSupport);
supportedUnitMap.put(supportedUnit, strengthAndRollsWithoutSupport);
final int powerWithoutSupport = DiceRoll.getTotalPower(supportedUnitMap, data);
// Add the actual power provided by the support
final int addedPower = powerWithSupport - powerWithoutSupport;
power += addedPower;
}
}
// Check if unit has lower power
if (power < minPower || (power == minPower && unitComparatorWithoutPrimaryPower.compare(u, worstUnit) < 0)) {
worstUnit = u;
minPower = power;
}
}
// Add worst unit to sorted list, update any units it supported, and remove from other collections
final IntegerMap<Unit> unitSupportPowerMapForUnit = unitSupportPowerMap.get(worstUnit);
if (unitSupportPowerMapForUnit != null) {
for (final Unit supportedUnit : unitSupportPowerMapForUnit.keySet()) {
final Tuple<Integer, Integer> strengthAndRolls = unitPowerAndRollsMap.get(supportedUnit);
if (strengthAndRolls == null) {
continue;
}
final int strengthWithoutSupport = strengthAndRolls.getFirst() - unitSupportPowerMapForUnit.getInt(supportedUnit);
final Tuple<Integer, Integer> strengthAndRollsWithoutSupport = Tuple.of(strengthWithoutSupport, strengthAndRolls.getSecond());
unitPowerAndRollsMap.put(supportedUnit, strengthAndRollsWithoutSupport);
sortedUnitsList.remove(supportedUnit);
sortedUnitsList.add(0, supportedUnit);
}
}
final IntegerMap<Unit> unitSupportRollsMapForUnit = unitSupportRollsMap.get(worstUnit);
if (unitSupportRollsMapForUnit != null) {
for (final Unit supportedUnit : unitSupportRollsMapForUnit.keySet()) {
final Tuple<Integer, Integer> strengthAndRolls = unitPowerAndRollsMap.get(supportedUnit);
if (strengthAndRolls == null) {
continue;
}
final int rollsWithoutSupport = strengthAndRolls.getSecond() - unitSupportRollsMapForUnit.getInt(supportedUnit);
final Tuple<Integer, Integer> strengthAndRollsWithoutSupport = Tuple.of(strengthAndRolls.getFirst(), rollsWithoutSupport);
unitPowerAndRollsMap.put(supportedUnit, strengthAndRollsWithoutSupport);
sortedUnitsList.remove(supportedUnit);
sortedUnitsList.add(0, supportedUnit);
}
}
sortedWellEnoughUnitsList.add(worstUnit);
sortedUnitsList.remove(worstUnit);
unitPowerAndRollsMap.remove(worstUnit);
unitSupportPowerMap.remove(worstUnit);
unitSupportRollsMap.remove(worstUnit);
}
sortedWellEnoughUnitsList.addAll(sortedUnitsList);
// Cache result and all subsets of the result
final List<UnitType> unitTypes = new ArrayList<>();
for (final Unit u : sortedWellEnoughUnitsList) {
unitTypes.add(u.getType());
}
for (final Iterator<UnitType> it = unitTypes.iterator(); it.hasNext(); ) {
oolCache.put(key, new ArrayList<>(unitTypes));
final UnitType unitTypeToRemove = it.next();
targetTypes.remove(unitTypeToRemove);
if (Collections.frequency(targetTypes, unitTypeToRemove) < Collections.frequency(amphibTypes, unitTypeToRemove)) {
amphibTypes.remove(unitTypeToRemove);
}
targetsHashCode = 1;
for (final UnitType ut : targetTypes) {
targetsHashCode += ut.hashCode();
}
targetsHashCode *= 31;
amphibHashCode = 1;
for (final UnitType ut : amphibTypes) {
amphibHashCode += ut.hashCode();
}
amphibHashCode *= 31;
key = player.getName() + "|" + battlesite.getName() + "|" + defending + "|" + amphibious + "|" + targetsHashCode + "|" + amphibHashCode;
it.remove();
}
return sortedWellEnoughUnitsList;
}
use of games.strategy.util.IntegerMap in project triplea by triplea-game.
the class BattleDelegate method doKamikazeSuicideAttacks.
/**
* KamikazeSuicideAttacks are attacks that are made during an Opponent's turn, using Resources that you own that have
* been designated.
* The resources are designated in PlayerAttachment, and hold information like the attack power of the resource.
* KamikazeSuicideAttacks are done in any territory that is a kamikazeZone, and the attacks are done by the original
* owner of that
* territory.
* The user has the option not to do any attacks, and they make target any number of units with any number of resource
* tokens.
* The units are then attacked individually by each resource token (meaning that casualties do not get selected
* because the attacks are
* targeted).
* The enemies of current player should decide all their attacks before the attacks are rolled.
*/
private void doKamikazeSuicideAttacks() {
final GameData data = getData();
if (!Properties.getUseKamikazeSuicideAttacks(data)) {
return;
}
// the current player is not the one who is doing these attacks, it is the all the enemies of this player who will
// do attacks
final Collection<PlayerID> enemies = CollectionUtils.getMatches(data.getPlayerList().getPlayers(), Matches.isAtWar(player, data));
if (enemies.isEmpty()) {
return;
}
final Predicate<Unit> canBeAttackedDefault = Matches.unitIsOwnedBy(player).and(Matches.unitIsSea()).and(Matches.unitIsNotTransportButCouldBeCombatTransport()).and(Matches.unitIsNotSub());
final boolean onlyWhereThereAreBattlesOrAmphibious = Properties.getKamikazeSuicideAttacksOnlyWhereBattlesAre(data);
final Collection<Territory> pendingBattles = battleTracker.getPendingBattleSites(false);
// create a list of all kamikaze zones, listed by enemy
final Map<PlayerID, Collection<Territory>> kamikazeZonesByEnemy = new HashMap<>();
for (final Territory t : data.getMap().getTerritories()) {
final TerritoryAttachment ta = TerritoryAttachment.get(t);
if (ta == null || !ta.getKamikazeZone()) {
continue;
}
final PlayerID owner = !Properties.getKamikazeSuicideAttacksDoneByCurrentTerritoryOwner(data) ? ta.getOriginalOwner() : t.getOwner();
if (owner == null) {
continue;
}
if (enemies.contains(owner)) {
if (t.getUnits().getUnits().stream().noneMatch(Matches.unitIsOwnedBy(player))) {
continue;
}
if (onlyWhereThereAreBattlesOrAmphibious) {
// if no battle or amphibious from here, ignore it
if (!pendingBattles.contains(t)) {
if (!Matches.territoryIsWater().test(t)) {
continue;
}
boolean amphib = false;
final Collection<Territory> landNeighbors = data.getMap().getNeighbors(t, Matches.territoryIsLand());
for (final Territory neighbor : landNeighbors) {
final IBattle battle = battleTracker.getPendingBattle(neighbor, false, BattleType.NORMAL);
if (battle == null) {
final Map<Territory, Collection<Unit>> whereFrom = battleTracker.getFinishedBattlesUnitAttackFromMap().get(neighbor);
if (whereFrom != null && whereFrom.containsKey(t)) {
amphib = true;
break;
}
continue;
}
if (battle.isAmphibious() && ((battle instanceof MustFightBattle && ((MustFightBattle) battle).getAmphibiousAttackTerritories().contains(t)) || (battle instanceof NonFightingBattle && ((NonFightingBattle) battle).getAmphibiousAttackTerritories().contains(t)))) {
amphib = true;
break;
}
}
if (!amphib) {
continue;
}
}
}
final Collection<Territory> currentTerrs = kamikazeZonesByEnemy.getOrDefault(owner, new ArrayList<>());
currentTerrs.add(t);
kamikazeZonesByEnemy.put(owner, currentTerrs);
}
}
if (kamikazeZonesByEnemy.isEmpty()) {
return;
}
for (final Entry<PlayerID, Collection<Territory>> entry : kamikazeZonesByEnemy.entrySet()) {
final PlayerID currentEnemy = entry.getKey();
final PlayerAttachment pa = PlayerAttachment.get(currentEnemy);
if (pa == null) {
continue;
}
Predicate<Unit> canBeAttacked = canBeAttackedDefault;
final Set<UnitType> suicideAttackTargets = pa.getSuicideAttackTargets();
if (suicideAttackTargets != null) {
canBeAttacked = Matches.unitIsOwnedBy(player).and(Matches.unitIsOfTypes(suicideAttackTargets));
}
// See if the player has any attack tokens
final IntegerMap<Resource> resourcesAndAttackValues = pa.getSuicideAttackResources();
if (resourcesAndAttackValues.size() <= 0) {
continue;
}
final IntegerMap<Resource> playerResourceCollection = currentEnemy.getResources().getResourcesCopy();
final IntegerMap<Resource> attackTokens = new IntegerMap<>();
for (final Resource possible : resourcesAndAttackValues.keySet()) {
final int amount = playerResourceCollection.getInt(possible);
if (amount > 0) {
attackTokens.put(possible, amount);
}
}
if (attackTokens.size() <= 0) {
continue;
}
// now let the enemy decide if they will do attacks
final Collection<Territory> kamikazeZones = entry.getValue();
final HashMap<Territory, Collection<Unit>> possibleUnitsToAttack = new HashMap<>();
for (final Territory t : kamikazeZones) {
final List<Unit> validTargets = t.getUnits().getMatches(canBeAttacked);
if (!validTargets.isEmpty()) {
possibleUnitsToAttack.put(t, validTargets);
}
}
final Map<Territory, HashMap<Unit, IntegerMap<Resource>>> attacks = getRemotePlayer(currentEnemy).selectKamikazeSuicideAttacks(possibleUnitsToAttack);
if (attacks == null || attacks.isEmpty()) {
continue;
}
// now validate that we have the resources and those units are valid targets
for (final Entry<Territory, HashMap<Unit, IntegerMap<Resource>>> territoryEntry : attacks.entrySet()) {
final Territory t = territoryEntry.getKey();
final Collection<Unit> possibleUnits = possibleUnitsToAttack.get(t);
if (possibleUnits == null || !possibleUnits.containsAll(territoryEntry.getValue().keySet())) {
throw new IllegalStateException("Player has chosen illegal units during Kamikaze Suicide Attacks");
}
for (final IntegerMap<Resource> resourceMap : territoryEntry.getValue().values()) {
attackTokens.subtract(resourceMap);
}
}
if (!attackTokens.isPositive()) {
throw new IllegalStateException("Player has chosen illegal resource during Kamikaze Suicide Attacks");
}
for (final Entry<Territory, HashMap<Unit, IntegerMap<Resource>>> territoryEntry : attacks.entrySet()) {
final Territory location = territoryEntry.getKey();
for (final Entry<Unit, IntegerMap<Resource>> unitEntry : territoryEntry.getValue().entrySet()) {
final Unit unitUnderFire = unitEntry.getKey();
final IntegerMap<Resource> numberOfAttacks = unitEntry.getValue();
if (numberOfAttacks != null && numberOfAttacks.size() > 0 && numberOfAttacks.totalValues() > 0) {
fireKamikazeSuicideAttacks(unitUnderFire, numberOfAttacks, resourcesAndAttackValues, currentEnemy, location);
}
}
}
}
}
use of games.strategy.util.IntegerMap in project triplea by triplea-game.
the class BattleDelegate method markDamaged.
static void markDamaged(final Collection<Unit> damaged, final IDelegateBridge bridge) {
if (damaged.size() == 0) {
return;
}
final IntegerMap<Unit> damagedMap = new IntegerMap<>();
for (final Unit u : damaged) {
damagedMap.add(u, 1);
}
bridge.addChange(createDamageChange(damagedMap, bridge));
}
use of games.strategy.util.IntegerMap in project triplea by triplea-game.
the class BattleTracker method captureOrDestroyUnits.
/**
* Called when a territory is conquered to determine if remaining enemy units should be
* captured, destroyed, or take damage.
*/
public static void captureOrDestroyUnits(final Territory territory, final PlayerID id, final PlayerID newOwner, final IDelegateBridge bridge, final UndoableMove changeTracker) {
final GameData data = bridge.getData();
// destroy any units that should be destroyed on capture
if (Properties.getUnitsCanBeDestroyedInsteadOfCaptured(data)) {
final Predicate<Unit> enemyToBeDestroyed = Matches.enemyUnit(id, data).and(Matches.unitDestroyedWhenCapturedByOrFrom(id));
final Collection<Unit> destroyed = territory.getUnits().getMatches(enemyToBeDestroyed);
if (!destroyed.isEmpty()) {
final Change destroyUnits = ChangeFactory.removeUnits(territory, destroyed);
bridge.getHistoryWriter().addChildToEvent("Some non-combat units are destroyed: ", destroyed);
bridge.addChange(destroyUnits);
if (changeTracker != null) {
changeTracker.addChange(destroyUnits);
}
}
}
// destroy any capture on entering units, IF the property to destroy them instead of capture is turned on
if (Properties.getOnEnteringUnitsDestroyedInsteadOfCaptured(data)) {
final Collection<Unit> destroyed = territory.getUnits().getMatches(Matches.unitCanBeCapturedOnEnteringToInThisTerritory(id, territory, data));
if (!destroyed.isEmpty()) {
final Change destroyUnits = ChangeFactory.removeUnits(territory, destroyed);
bridge.getHistoryWriter().addChildToEvent(id.getName() + " destroys some units instead of capturing them", destroyed);
bridge.addChange(destroyUnits);
if (changeTracker != null) {
changeTracker.addChange(destroyUnits);
}
}
}
// destroy any disabled units owned by the enemy that are NOT infrastructure or factories
final Predicate<Unit> enemyToBeDestroyed = Matches.enemyUnit(id, data).and(Matches.unitIsDisabled()).and(Matches.unitIsInfrastructure().negate());
final Collection<Unit> destroyed = territory.getUnits().getMatches(enemyToBeDestroyed);
if (!destroyed.isEmpty()) {
final Change destroyUnits = ChangeFactory.removeUnits(territory, destroyed);
bridge.getHistoryWriter().addChildToEvent(id.getName() + " destroys some disabled combat units", destroyed);
bridge.addChange(destroyUnits);
if (changeTracker != null) {
changeTracker.addChange(destroyUnits);
}
}
// take over non combatants
final Predicate<Unit> enemyNonCom = Matches.enemyUnit(id, data).and(Matches.unitIsInfrastructure());
final Predicate<Unit> willBeCaptured = enemyNonCom.or(Matches.unitCanBeCapturedOnEnteringToInThisTerritory(id, territory, data));
final Collection<Unit> nonCom = territory.getUnits().getMatches(willBeCaptured);
// change any units that change unit types on capture
if (Properties.getUnitsCanBeChangedOnCapture(data)) {
final Collection<Unit> toReplace = CollectionUtils.getMatches(nonCom, Matches.unitWhenCapturedChangesIntoDifferentUnitType());
for (final Unit u : toReplace) {
final Map<String, Tuple<String, IntegerMap<UnitType>>> map = UnitAttachment.get(u.getType()).getWhenCapturedChangesInto();
final PlayerID currentOwner = u.getOwner();
for (final String value : map.keySet()) {
final String[] s = value.split(":");
if (!(s[0].equals("any") || data.getPlayerList().getPlayerId(s[0]).equals(currentOwner))) {
continue;
}
// we could use "id" or "newOwner" here... not sure which to use
if (!(s[1].equals("any") || data.getPlayerList().getPlayerId(s[1]).equals(id))) {
continue;
}
final CompositeChange changes = new CompositeChange();
final Collection<Unit> toAdd = new ArrayList<>();
final Tuple<String, IntegerMap<UnitType>> toCreate = map.get(value);
final boolean translateAttributes = toCreate.getFirst().equalsIgnoreCase("true");
for (final UnitType ut : toCreate.getSecond().keySet()) {
toAdd.addAll(ut.create(toCreate.getSecond().getInt(ut), newOwner));
}
if (!toAdd.isEmpty()) {
if (translateAttributes) {
final Change translate = TripleAUnit.translateAttributesToOtherUnits(u, toAdd, territory);
if (!translate.isEmpty()) {
changes.add(translate);
}
}
changes.add(ChangeFactory.removeUnits(territory, Collections.singleton(u)));
changes.add(ChangeFactory.addUnits(territory, toAdd));
changes.add(ChangeFactory.markNoMovementChange(toAdd));
bridge.getHistoryWriter().addChildToEvent(id.getName() + " converts " + u.toStringNoOwner() + " into different units", toAdd);
bridge.addChange(changes);
if (changeTracker != null) {
changeTracker.addChange(changes);
}
// don't forget to remove this unit from the list
nonCom.remove(u);
break;
}
}
}
}
if (!nonCom.isEmpty()) {
// FYI: a dummy delegate will not do anything with this change,
// meaning that the battle calculator will think this unit lived,
// even though it died or was captured, etc!
final Change capture = ChangeFactory.changeOwner(nonCom, newOwner, territory);
bridge.addChange(capture);
if (changeTracker != null) {
changeTracker.addChange(capture);
}
final Change noMovementChange = ChangeFactory.markNoMovementChange(nonCom);
bridge.addChange(noMovementChange);
if (changeTracker != null) {
changeTracker.addChange(noMovementChange);
}
final IntegerMap<Unit> damageMap = new IntegerMap<>();
for (final Unit unit : CollectionUtils.getMatches(nonCom, Matches.unitWhenCapturedSustainsDamage())) {
final TripleAUnit taUnit = (TripleAUnit) unit;
final int damageLimit = taUnit.getHowMuchMoreDamageCanThisUnitTake(unit, territory);
final int sustainedDamage = UnitAttachment.get(unit.getType()).getWhenCapturedSustainsDamage();
final int actualDamage = Math.max(0, Math.min(sustainedDamage, damageLimit));
final int totalDamage = taUnit.getUnitDamage() + actualDamage;
damageMap.put(unit, totalDamage);
}
if (!damageMap.isEmpty()) {
final Change damageChange = ChangeFactory.bombingUnitDamage(damageMap);
bridge.addChange(damageChange);
if (changeTracker != null) {
changeTracker.addChange(damageChange);
}
// Kill any units that can die if they have reached max damage
if (damageMap.keySet().stream().anyMatch(Matches.unitCanDieFromReachingMaxDamage())) {
final List<Unit> unitsCanDie = CollectionUtils.getMatches(damageMap.keySet(), Matches.unitCanDieFromReachingMaxDamage());
unitsCanDie.retainAll(CollectionUtils.getMatches(unitsCanDie, Matches.unitIsAtMaxDamageOrNotCanBeDamaged(territory)));
if (!unitsCanDie.isEmpty()) {
final Change removeDead = ChangeFactory.removeUnits(territory, unitsCanDie);
bridge.addChange(removeDead);
if (changeTracker != null) {
changeTracker.addChange(removeDead);
}
}
}
}
}
}
Aggregations