package games.strategy.triplea; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import games.strategy.engine.data.Change; import games.strategy.engine.data.CompositeChange; import games.strategy.engine.data.GameData; import games.strategy.engine.data.PlayerID; import games.strategy.engine.data.Territory; import games.strategy.engine.data.Unit; import games.strategy.engine.data.UnitType; import games.strategy.engine.data.annotations.GameProperty; import games.strategy.engine.data.changefactory.ChangeFactory; import games.strategy.triplea.attachments.TechAbilityAttachment; import games.strategy.triplea.attachments.TerritoryAttachment; import games.strategy.triplea.attachments.UnitAttachment; import games.strategy.triplea.delegate.Matches; import games.strategy.util.CompositeMatchAnd; import games.strategy.util.IntegerMap; import games.strategy.util.Match; import games.strategy.util.Tuple; /** * Extended unit for triplea games. * * <p> * As with all game data components, changes made to this unit must be made through a Change instance. Calling setters * on this directly will * not serialize the changes across the network. * </p> */ public class TripleAUnit extends Unit { // compatable with 0.9.2 private static final long serialVersionUID = 8811372406957115036L; public static final String TRANSPORTED_BY = "transportedBy"; public static final String UNLOADED = "unloaded"; public static final String LOADED_THIS_TURN = "wasLoadedThisTurn"; public static final String UNLOADED_TO = "unloadedTo"; public static final String UNLOADED_IN_COMBAT_PHASE = "wasUnloadedInCombatPhase"; public static final String ALREADY_MOVED = "alreadyMoved"; public static final String BONUS_MOVEMENT = "bonusMovement"; public static final String MOVEMENT_LEFT = "movementLeft"; public static final String SUBMERGED = "submerged"; public static final String ORIGINAL_OWNER = "originalOwner"; public static final String WAS_IN_COMBAT = "wasInCombat"; public static final String LOADED_AFTER_COMBAT = "wasLoadedAfterCombat"; public static final String UNLOADED_AMPHIBIOUS = "wasAmphibious"; public static final String ORIGINATED_FROM = "originatedFrom"; public static final String WAS_SCRAMBLED = "wasScrambled"; public static final String MAX_SCRAMBLE_COUNT = "maxScrambleCount"; public static final String WAS_IN_AIR_BATTLE = "wasInAirBattle"; public static final String DISABLED = "disabled"; public static final String LAUNCHED = "launched"; public static final String AIRBORNE = "airborne"; // the transport that is currently transporting us private TripleAUnit m_transportedBy = null; // the units we have unloaded this turn private List<Unit> m_unloaded = Collections.emptyList(); // was this unit loaded this turn? private boolean m_wasLoadedThisTurn = false; // the territory this unit was unloaded to this turn private Territory m_unloadedTo = null; // was this unit unloaded in combat phase this turn? private boolean m_wasUnloadedInCombatPhase = false; // movement used this turn private int m_alreadyMoved = 0; // movement used this turn private int m_bonusMovement = 0; // amount of damage unit has sustained private int m_unitDamage = 0; // is this submarine submerged private boolean m_submerged = false; // original owner of this unit private PlayerID m_originalOwner = null; // Was this unit in combat private boolean m_wasInCombat = false; private boolean m_wasLoadedAfterCombat = false; private boolean m_wasAmphibious = false; // the territory this unit started in (for use with scrambling) private Territory m_originatedFrom = null; private boolean m_wasScrambled = false; private int m_maxScrambleCount = -1; private boolean m_wasInAirBattle = false; private boolean m_disabled = false; // the number of airborne units launched by this unit this turn private int m_launched = 0; // was this unit airborne and launched this turn private boolean m_airborne = false; public static TripleAUnit get(final Unit u) { return (TripleAUnit) u; } public TripleAUnit(final UnitType type, final PlayerID owner, final GameData data) { super(type, owner, data); } public Unit getTransportedBy() { return m_transportedBy; } @GameProperty(xmlProperty = false, gameProperty = true, adds = false) public void setTransportedBy(final TripleAUnit transportedBy) { m_transportedBy = transportedBy; } /** * This is a very slow method because it checks all territories on the map. Try not to use this method if possible. */ public List<Unit> getTransporting() { // we don't store the units we are transporting // rather we look at the transported by property of units for (final Territory t : getData().getMap()) { // find the territory this transport is in if (t.getUnits().getUnits().contains(this)) { return t.getUnits().getMatches(new Match<Unit>() { @Override public boolean match(final Unit o) { return TripleAUnit.get(o).getTransportedBy() == TripleAUnit.this; } }); } } return Collections.emptyList(); } public List<Unit> getTransporting(final Collection<Unit> transportedUnitsPossible) { // we don't store the units we are transporting // rather we look at the transported by property of units return Match.getMatches(transportedUnitsPossible, new Match<Unit>() { @Override public boolean match(final Unit o) { return TripleAUnit.get(o).getTransportedBy() == TripleAUnit.this; } }); } public List<Unit> getUnloaded() { return m_unloaded; } @GameProperty(xmlProperty = false, gameProperty = true, adds = false) public void setUnloaded(final List<Unit> unloaded) { if (unloaded == null || unloaded.isEmpty()) { m_unloaded = Collections.emptyList(); } else { m_unloaded = new ArrayList<>(unloaded); } } public boolean getWasLoadedThisTurn() { return m_wasLoadedThisTurn; } @GameProperty(xmlProperty = false, gameProperty = true, adds = false) public void setWasLoadedThisTurn(final boolean value) { m_wasLoadedThisTurn = value; } public Territory getUnloadedTo() { return m_unloadedTo; } @GameProperty(xmlProperty = false, gameProperty = true, adds = false) public void setUnloadedTo(final Territory unloadedTo) { m_unloadedTo = unloadedTo; } public Territory getOriginatedFrom() { return m_originatedFrom; } @GameProperty(xmlProperty = false, gameProperty = true, adds = false) public void setOriginatedFrom(final Territory t) { m_originatedFrom = t; } public boolean getWasUnloadedInCombatPhase() { return m_wasUnloadedInCombatPhase; } @GameProperty(xmlProperty = false, gameProperty = true, adds = false) public void setWasUnloadedInCombatPhase(final boolean value) { m_wasUnloadedInCombatPhase = value; } public int getAlreadyMoved() { return m_alreadyMoved; } @GameProperty(xmlProperty = false, gameProperty = true, adds = false) public void setAlreadyMoved(final int alreadyMoved) { m_alreadyMoved = alreadyMoved; } @GameProperty(xmlProperty = false, gameProperty = true, adds = false) public void setBonusMovement(final int bonusMovement) { m_bonusMovement = bonusMovement; } public int getBonusMovement() { return m_bonusMovement; } /** * Does not account for any movement already made. Generally equal to UnitType movement */ public int getMaxMovementAllowed() { return Math.max(0, m_bonusMovement + UnitAttachment.get(getType()).getMovement(getOwner())); } public int getMovementLeft() { return Math.max(0, UnitAttachment.get(getType()).getMovement(getOwner()) + m_bonusMovement - m_alreadyMoved); } public static Tuple<Integer, Integer> getMinAndMaxMovementLeft(final Collection<Unit> units) { int min = 100000; int max = 0; for (final Unit u : units) { final int left = ((TripleAUnit) u).getMovementLeft(); if (left > max) { max = left; } if (left < min) { min = left; } } if (max < min) { min = max; } return Tuple.of(min, max); } public int getUnitDamage() { return m_unitDamage; } @GameProperty(xmlProperty = false, gameProperty = true, adds = false) public void setUnitDamage(final int unitDamage) { m_unitDamage = unitDamage; } public boolean getSubmerged() { return m_submerged; } @GameProperty(xmlProperty = false, gameProperty = true, adds = false) public void setSubmerged(final boolean submerged) { m_submerged = submerged; } public PlayerID getOriginalOwner() { return m_originalOwner; } @GameProperty(xmlProperty = false, gameProperty = true, adds = false) public void setOriginalOwner(final PlayerID originalOwner) { m_originalOwner = originalOwner; } public boolean getWasInCombat() { return m_wasInCombat; } @GameProperty(xmlProperty = false, gameProperty = true, adds = false) public void setWasInCombat(final boolean value) { m_wasInCombat = value; } public boolean getWasScrambled() { return m_wasScrambled; } @GameProperty(xmlProperty = false, gameProperty = true, adds = false) public void setWasScrambled(final boolean value) { m_wasScrambled = value; } public int getMaxScrambleCount() { return m_maxScrambleCount; } @GameProperty(xmlProperty = false, gameProperty = true, adds = false) public void setMaxScrambleCount(final int value) { m_maxScrambleCount = value; } public int getLaunched() { return m_launched; } @GameProperty(xmlProperty = false, gameProperty = true, adds = false) public void setLaunched(final int value) { m_launched = value; } public boolean getAirborne() { return m_airborne; } @GameProperty(xmlProperty = false, gameProperty = true, adds = false) public void setAirborne(final boolean value) { m_airborne = value; } @GameProperty(xmlProperty = false, gameProperty = true, adds = false) public void setWasInAirBattle(final boolean value) { m_wasInAirBattle = value; } public boolean getWasInAirBattle() { return m_wasInAirBattle; } public boolean getWasLoadedAfterCombat() { return m_wasLoadedAfterCombat; } @GameProperty(xmlProperty = false, gameProperty = true, adds = false) public void setWasLoadedAfterCombat(final boolean value) { m_wasLoadedAfterCombat = value; } public List<Unit> getDependents() { return getTransporting(); } public Unit getDependentOf() { if (m_transportedBy != null) { return m_transportedBy; } // TODO: add support for carriers as well return null; } public boolean getWasAmphibious() { return m_wasAmphibious; } @GameProperty(xmlProperty = false, gameProperty = true, adds = false) public void setWasAmphibious(final boolean value) { m_wasAmphibious = value; } public boolean getDisabled() { return m_disabled; } @GameProperty(xmlProperty = false, gameProperty = true, adds = false) public void setDisabled(final boolean value) { m_disabled = value; } /** * How much more damage can this unit take? * Will return 0 if the unit cannot be damaged, or is at max damage. */ public int getHowMuchMoreDamageCanThisUnitTake(final Unit u, final Territory t) { if (!Matches.UnitCanBeDamaged.match(u)) { return 0; } final TripleAUnit taUnit = (TripleAUnit) u; if (games.strategy.triplea.Properties.getDamageFromBombingDoneToUnitsInsteadOfTerritories(u.getData())) { return Math.max(0, getHowMuchDamageCanThisUnitTakeTotal(u, t) - taUnit.getUnitDamage()); } else { return Integer.MAX_VALUE; } } /** * How much damage is the max this unit can take, accounting for territory, etc. * Will return -1 if the unit is of the type that cannot be damaged */ public int getHowMuchDamageCanThisUnitTakeTotal(final Unit u, final Territory t) { if (!Matches.UnitCanBeDamaged.match(u)) { return -1; } final UnitAttachment ua = UnitAttachment.get(u.getType()); final int territoryUnitProduction = TerritoryAttachment.getUnitProduction(t); if (games.strategy.triplea.Properties.getDamageFromBombingDoneToUnitsInsteadOfTerritories(u.getData())) { if (ua.getMaxDamage() <= 0) { // factories may or may not have max damage set, so we must still determine here // assume that if maxDamage <= 0, then the max damage must be based on the territory value // can use "production" or "unitProduction" return territoryUnitProduction * 2; } else { if (Matches.UnitCanProduceUnits.match(u)) { if (ua.getCanProduceXUnits() < 0) { // can use "production" or "unitProduction" return territoryUnitProduction * ua.getMaxDamage(); } else { return ua.getMaxDamage(); } } else { return ua.getMaxDamage(); } } } else { return Integer.MAX_VALUE; } } public int getHowMuchCanThisUnitBeRepaired(final Unit u, final Territory t) { return Math.max(0, (this.getHowMuchDamageCanThisUnitTakeTotal(u, t) - this.getHowMuchMoreDamageCanThisUnitTake(u, t))); } public int getHowMuchShouldUnitBeRepairedToNotBeDisabled(final Unit u, final Territory t) { final UnitAttachment ua = UnitAttachment.get(u.getType()); final int maxOperationalDamage = ua.getMaxOperationalDamage(); if (maxOperationalDamage < 0) { return 0; } final TripleAUnit taUnit = (TripleAUnit) u; final int currentDamage = taUnit.getUnitDamage(); return Math.max(0, currentDamage - maxOperationalDamage); } public static int getProductionPotentialOfTerritory(final Collection<Unit> unitsAtStartOfStepInTerritory, final Territory producer, final PlayerID player, final GameData data, final boolean accountForDamage, final boolean mathMaxZero) { return getHowMuchCanUnitProduce( getBiggestProducer(unitsAtStartOfStepInTerritory, producer, player, data, accountForDamage), producer, player, data, accountForDamage, mathMaxZero); } public static Unit getBiggestProducer(final Collection<Unit> units, final Territory producer, final PlayerID player, final GameData data, final boolean accountForDamage) { final CompositeMatchAnd<Unit> factoryMatch = new CompositeMatchAnd<>( Matches.UnitIsOwnedAndIsFactoryOrCanProduceUnits(player), Matches.unitIsBeingTransported().invert()); if (producer.isWater()) { factoryMatch.add(Matches.UnitIsLand.invert()); } else { factoryMatch.add(Matches.UnitIsSea.invert()); } final Collection<Unit> factories = Match.getMatches(units, factoryMatch); if (factories.isEmpty()) { return null; } final IntegerMap<Unit> productionPotential = new IntegerMap<>(); Unit highestUnit = factories.iterator().next(); int highestCapacity = Integer.MIN_VALUE; for (final Unit u : factories) { final int capacity = getHowMuchCanUnitProduce(u, producer, player, data, accountForDamage, false); productionPotential.put(u, capacity); if (capacity > highestCapacity) { highestCapacity = capacity; highestUnit = u; } } return highestUnit; } public static int getHowMuchCanUnitProduce(final Unit u, final Territory producer, final PlayerID player, final GameData data, final boolean accountForDamage, final boolean mathMaxZero) { if (u == null) { return 0; } if (!Matches.UnitCanProduceUnits.match(u)) { return 0; } int productionCapacity = 0; final UnitAttachment ua = UnitAttachment.get(u.getType()); final TripleAUnit taUnit = (TripleAUnit) u; final TerritoryAttachment ta = TerritoryAttachment.get(producer); int territoryProduction = 0; int territoryUnitProduction = 0; if (ta != null) { territoryProduction = ta.getProduction(); territoryUnitProduction = ta.getUnitProduction(); } if (accountForDamage) { if (games.strategy.triplea.Properties.getDamageFromBombingDoneToUnitsInsteadOfTerritories(data)) { if (ua.getCanProduceXUnits() < 0) { // we could use territoryUnitProduction OR // territoryProduction if we wanted to, however we should // change damage to be based on whichever we choose. productionCapacity = territoryUnitProduction - taUnit.getUnitDamage(); } else { productionCapacity = ua.getCanProduceXUnits() - taUnit.getUnitDamage(); } } else { productionCapacity = territoryProduction; if (productionCapacity < 1) { productionCapacity = (games.strategy.triplea.Properties.getWW2V2(data) || games.strategy.triplea.Properties.getWW2V3(data)) ? 0 : 1; } } } else { if (ua.getCanProduceXUnits() < 0 && !games.strategy.triplea.Properties.getDamageFromBombingDoneToUnitsInsteadOfTerritories(data)) { productionCapacity = territoryProduction; } else if (ua.getCanProduceXUnits() < 0 && games.strategy.triplea.Properties.getDamageFromBombingDoneToUnitsInsteadOfTerritories(data)) { productionCapacity = territoryUnitProduction; } else { productionCapacity = ua.getCanProduceXUnits(); } if (productionCapacity < 1 && !games.strategy.triplea.Properties.getDamageFromBombingDoneToUnitsInsteadOfTerritories(data)) { productionCapacity = (games.strategy.triplea.Properties.getWW2V2(data) || games.strategy.triplea.Properties.getWW2V3(data)) ? 0 : 1; } } // Increase production if have industrial technology if (territoryProduction >= TechAbilityAttachment.getMinimumTerritoryValueForProductionBonus(player, data)) { productionCapacity += TechAbilityAttachment.getProductionBonus(u.getType(), player, data); } if (mathMaxZero) { return Math.max(0, productionCapacity); } else { return productionCapacity; } } /** * Currently made for translating unit damage from one unit to another unit. Will adjust damage to be within max * damage for the new units. * * @return change for unit's properties */ public static Change translateAttributesToOtherUnits(final Unit unitGivingAttributes, final Collection<Unit> unitsThatWillGetAttributes, final Territory t) { final CompositeChange changes = new CompositeChange(); // must look for m_hits, m_unitDamage, final TripleAUnit taUnit = (TripleAUnit) unitGivingAttributes; final int combatDamage = taUnit.getHits(); final IntegerMap<Unit> hits = new IntegerMap<>(); if (combatDamage > 0) { for (final Unit u : unitsThatWillGetAttributes) { hits.put(u, combatDamage); } } if (hits.size() > 0) { changes.add(ChangeFactory.unitsHit(hits)); } final int unitDamage = taUnit.getUnitDamage(); final IntegerMap<Unit> damageMap = new IntegerMap<>(); if (unitDamage > 0) { for (final Unit u : unitsThatWillGetAttributes) { final TripleAUnit taNew = (TripleAUnit) u; final int maxDamage = taNew.getHowMuchDamageCanThisUnitTakeTotal(u, t); final int transferDamage = Math.max(0, Math.min(unitDamage, maxDamage)); if (transferDamage <= 0) { continue; } damageMap.put(u, transferDamage); } } changes.add(ChangeFactory.bombingUnitDamage(damageMap)); return changes; } }