package games.strategy.triplea.attachments;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import games.strategy.engine.data.Attachable;
import games.strategy.engine.data.DefaultAttachment;
import games.strategy.engine.data.GameData;
import games.strategy.engine.data.GameParseException;
import games.strategy.engine.data.PlayerID;
import games.strategy.engine.data.Resource;
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.triplea.Constants;
import games.strategy.triplea.MapSupport;
import games.strategy.triplea.Properties;
import games.strategy.triplea.delegate.Matches;
import games.strategy.triplea.delegate.TechTracker;
import games.strategy.triplea.formatter.MyFormatter;
import games.strategy.util.CompositeMatchAnd;
import games.strategy.util.IntegerMap;
import games.strategy.util.Match;
import games.strategy.util.Tuple;
/**
* Despite the misleading name, this attaches not to individual Units but to UnitTypes.
* Please follow this naming convention:
* if the property is called "m_fooBar"
* then you must have a "setFooBar" and "getFooBar",
* and if the set method adds to a list or map, then you also need a "clearFooBar".
* Do not change the name fooBar to make it plural or any other crap.
*/
@MapSupport
public class UnitAttachment extends DefaultAttachment {
private static final long serialVersionUID = -2946748686268541820L;
/**
* Convenience method.
*/
public static UnitAttachment get(final UnitType type) {
final UnitAttachment rVal = (UnitAttachment) type.getAttachment(Constants.UNIT_ATTACHMENT_NAME);
if (rVal == null) {
throw new IllegalStateException("No unit type attachment for:" + type.getName());
}
return rVal;
}
public static UnitAttachment get(final UnitType type, final String nameOfAttachment) {
final UnitAttachment rVal = (UnitAttachment) type.getAttachment(nameOfAttachment);
if (rVal == null) {
throw new IllegalStateException(
"No unit type attachment for:" + type.getName() + " with name:" + nameOfAttachment);
}
return rVal;
}
private static Collection<UnitType> getUnitTypesFromUnitList(final Collection<Unit> units) {
final Collection<UnitType> types = new ArrayList<>();
for (final Unit u : units) {
if (!types.contains(u.getType())) {
types.add(u.getType());
}
}
return types;
}
public static final String UNITSMAYNOTLANDONCARRIER = "unitsMayNotLandOnCarrier";
public static final String UNITSMAYNOTLEAVEALLIEDCARRIER = "unitsMayNotLeaveAlliedCarrier";
// movement related
private boolean m_isAir = false;
private boolean m_isSea = false;
private int m_movement = 0;
private boolean m_canBlitz = false;
private boolean m_isKamikaze = false;
// a colon delimited list of transports where this unit may invade from, it supports "none"
// and if empty it allows you to invade from all
private String[] m_canInvadeOnlyFrom = null;
private IntegerMap<Resource> m_fuelCost = new IntegerMap<>();
private boolean m_canNotMoveDuringCombatMove = false;
private Tuple<Integer, String> m_movementLimit = null;
// combat related
private int m_attack = 0;
private int m_defense = 0;
private boolean m_isInfrastructure = false;
private boolean m_canBombard = false;
private int m_bombard = -1;
private boolean m_isSub = false;
private boolean m_isDestroyer = false;
private boolean m_artillery = false;
private boolean m_artillerySupportable = false;
private int m_unitSupportCount = -1;
private int m_isMarine = 0;
private boolean m_isSuicide = false;
private Tuple<Integer, String> m_attackingLimit = null;
private int m_attackRolls = 1;
private int m_defenseRolls = 1;
private boolean m_chooseBestRoll = false;
// transportation related
private boolean m_isCombatTransport = false;
// -1 if cant transport
private int m_transportCapacity = -1;
// -1 if cant be transported
private int m_transportCost = -1;
// -1 if cant act as a carrier
private int m_carrierCapacity = -1;
// -1 if cant land on a carrier
private int m_carrierCost = -1;
private boolean m_isAirTransport = false;
private boolean m_isAirTransportable = false;
private boolean m_isInfantry = false;
private boolean m_isLandTransport = false;
// aa related
// "isAA" and "isAAmovement" are also valid setters, used as shortcuts for calling multiple aa related setters. Must
// keep.
private boolean m_isAAforCombatOnly = false;
private boolean m_isAAforBombingThisUnitOnly = false;
private boolean m_isAAforFlyOverOnly = false;
private boolean m_isRocket = false;
private int m_attackAA = 1;
private int m_offensiveAttackAA = 0;
private int m_attackAAmaxDieSides = -1;
private int m_offensiveAttackAAmaxDieSides = -1;
// -1 means infinite
private int m_maxAAattacks = -1;
// -1 means infinite
private int m_maxRoundsAA = 1;
// default value for when it is not set
private String m_typeAA = "AA";
// null means targeting air units only
private HashSet<UnitType> m_targetsAA = null;
// if false, we cannot shoot more times than there are number of planes
private boolean m_mayOverStackAA = false;
// if false, we instantly kill anything our AA shot hits
private boolean m_damageableAA = false;
// if these enemy units are present, the gun does not fire at all
private HashSet<UnitType> m_willNotFireIfPresent = new HashSet<>();
// strategic bombing related
private boolean m_isStrategicBomber = false;
private int m_bombingMaxDieSides = -1;
private int m_bombingBonus = -1;
private boolean m_canIntercept = false;
private boolean m_canEscort = false;
private boolean m_canAirBattle = false;
private int m_airDefense = 0;
private int m_airAttack = 0;
// null means they can target any unit that can be damaged
private HashSet<UnitType> m_bombingTargets = null;
// production related
// this has been split into canProduceUnits, isConstruction, canBeDamaged, and isInfrastructure
// private boolean m_isFactory = false;
private boolean m_canProduceUnits = false;
// -1 means either it can't produce any, or it produces at the value of the territory it is located in
private int m_canProduceXUnits = -1;
private IntegerMap<UnitType> m_createsUnitsList = new IntegerMap<>();
private IntegerMap<Resource> m_createsResourcesList = new IntegerMap<>();
// damage related
private int m_hitPoints = 1;
private boolean m_canBeDamaged = false;
// this is bombing damage, not hitpoints. default of 2 means that factories will take 2x the territory value
// they are in, of damage.
private int m_maxDamage = 2;
// -1 if can't be disabled
private int m_maxOperationalDamage = -1;
private boolean m_canDieFromReachingMaxDamage = false;
// placement related
private boolean m_isConstruction = false;
// can be any String except for "none" if isConstruction is true
private String m_constructionType = "none";
// -1 if not set, is meaningless
private int m_constructionsPerTerrPerTypePerTurn = -1;
// -1 if not set, is meaningless
private int m_maxConstructionsPerTypePerTerr = -1;
// -1 means anywhere
private int m_canOnlyBePlacedInTerritoryValuedAtX = -1;
// multiple colon delimited lists of the unit combos required for
// this unit to be built somewhere. (units must be in same
// territory, owned by player, not be disabled)
private ArrayList<String[]> m_requiresUnits = new ArrayList<>();
private IntegerMap<UnitType> m_consumesUnits = new IntegerMap<>();
// a colon delimited list of territories where this unit may not be placed
// also an allowed setter is "setUnitPlacementOnlyAllowedIn",
// which just creates m_unitPlacementRestrictions with an inverted list of territories
private String[] m_unitPlacementRestrictions = null;
// -1 if infinite (infinite is default)
private int m_maxBuiltPerPlayer = -1;
private Tuple<Integer, String> m_placementLimit = null;
// scrambling related
private boolean m_canScramble = false;
private boolean m_isAirBase = false;
// -1 if can't scramble
private int m_maxScrambleDistance = -1;
// -1 for infinite
private int m_maxScrambleCount = -1;
// special abilities
private int m_blockade = 0;
// a colon delimited list of the units this unit can repair.
// (units must be in same territory, unless this unit is land
// and the repaired unit is sea)
private IntegerMap<UnitType> m_repairsUnits = new IntegerMap<>();
private IntegerMap<UnitType> m_givesMovement = new IntegerMap<>();
private ArrayList<Tuple<String, PlayerID>> m_destroyedWhenCapturedBy = new ArrayList<>();
// also an allowed setter is "setDestroyedWhenCapturedFrom" which will just create m_destroyedWhenCapturedBy with a
// specific list
private LinkedHashMap<String, Tuple<String, IntegerMap<UnitType>>> m_whenCapturedChangesInto =
new LinkedHashMap<>();
private ArrayList<PlayerID> m_canBeCapturedOnEnteringBy = new ArrayList<>();
private ArrayList<PlayerID> m_canBeGivenByTerritoryTo = new ArrayList<>();
// a set of information for dealing with special abilities or
// loss of abilities when a unit takes x-y amount of damage
private ArrayList<Tuple<Tuple<Integer, Integer>, Tuple<String, String>>> m_whenCombatDamaged =
new ArrayList<>();
// a kind of support attachment for giving actual unit
// attachment abilities or other to a unit, when in the
// precense or on the same route with another unit
private ArrayList<String> m_receivesAbilityWhenWith = new ArrayList<>();
// currently used for: placement in original territories only
private HashSet<String> m_special = new HashSet<>();
/** Creates new UnitAttachment. */
public UnitAttachment(final String name, final Attachable attachable, final GameData gameData) {
super(name, attachable, gameData);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setCanIntercept(final String value) {
m_canIntercept = getBool(value);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setCanIntercept(final boolean value) {
m_canIntercept = value;
}
public boolean getCanIntercept() {
return m_canIntercept;
}
public void resetCanIntercept() {
m_canIntercept = false;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setCanEscort(final String value) {
m_canEscort = getBool(value);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setCanEscort(final boolean value) {
m_canEscort = value;
}
public boolean getCanEscort() {
return m_canEscort;
}
public void resetCanEscort() {
m_canEscort = false;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setCanAirBattle(final String value) {
m_canAirBattle = getBool(value);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setCanAirBattle(final Boolean value) {
m_canAirBattle = value;
}
public boolean getCanAirBattle() {
return m_canAirBattle;
}
public void resetCanAirBattle() {
m_canAirBattle = false;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setAirDefense(final String value) {
m_airDefense = getInt(value);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setAirDefense(final Integer value) {
m_airDefense = value;
}
public int getAirDefense(final PlayerID player) {
return (Math.min(getData().getDiceSides(), Math.max(0,
m_airDefense + TechAbilityAttachment.getAirDefenseBonus((UnitType) this.getAttachedTo(), player, getData()))));
}
public void resetAirDefense() {
m_airDefense = 0;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setAirAttack(final String value) {
m_airAttack = getInt(value);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setAirAttack(final Integer value) {
m_airAttack = value;
}
public int getAirAttack(final PlayerID player) {
return (Math.min(getData().getDiceSides(), Math.max(0,
m_airAttack + TechAbilityAttachment.getAirAttackBonus((UnitType) this.getAttachedTo(), player, getData()))));
}
public void resetAirAttack() {
m_airAttack = 0;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setIsAirTransport(final String s) {
m_isAirTransport = getBool(s);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setIsAirTransport(final Boolean s) {
m_isAirTransport = s;
}
public boolean getIsAirTransport() {
return m_isAirTransport;
}
public void resetIsAirTransport() {
m_isAirTransport = false;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setIsAirTransportable(final String s) {
m_isAirTransportable = getBool(s);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setIsAirTransportable(final Boolean s) {
m_isAirTransportable = s;
}
public boolean getIsAirTransportable() {
return m_isAirTransportable;
}
public void resetIsAirTransportable() {
m_isAirTransportable = false;
}
/**
* Adds to, not sets. Anything that adds to instead of setting needs a clear function as well.
*/
@GameProperty(xmlProperty = true, gameProperty = true, adds = true)
public void setCanBeGivenByTerritoryTo(final String value) throws GameParseException {
final String[] temp = value.split(":");
for (final String name : temp) {
final PlayerID tempPlayer = getData().getPlayerList().getPlayerID(name);
if (tempPlayer != null) {
m_canBeGivenByTerritoryTo.add(tempPlayer);
} else if (name.equalsIgnoreCase("true") || name.equalsIgnoreCase("false")) {
m_canBeGivenByTerritoryTo.clear();
} else {
throw new GameParseException("No player named: " + name + thisErrorMsg());
}
}
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setCanBeGivenByTerritoryTo(final ArrayList<PlayerID> value) {
m_canBeGivenByTerritoryTo = value;
}
public ArrayList<PlayerID> getCanBeGivenByTerritoryTo() {
return m_canBeGivenByTerritoryTo;
}
public void clearCanBeGivenByTerritoryTo() {
m_canBeGivenByTerritoryTo.clear();
}
public void resetCanBeGivenByTerritoryTo() {
m_canBeGivenByTerritoryTo = new ArrayList<>();
}
/**
* Adds to, not sets. Anything that adds to instead of setting needs a clear function as well.
*/
@GameProperty(xmlProperty = true, gameProperty = true, adds = true)
public void setCanBeCapturedOnEnteringBy(final String value) throws GameParseException {
final String[] temp = value.split(":");
for (final String name : temp) {
final PlayerID tempPlayer = getData().getPlayerList().getPlayerID(name);
if (tempPlayer != null) {
m_canBeCapturedOnEnteringBy.add(tempPlayer);
} else {
throw new GameParseException("No player named: " + name + thisErrorMsg());
}
}
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setCanBeCapturedOnEnteringBy(final ArrayList<PlayerID> value) {
m_canBeCapturedOnEnteringBy = value;
}
public ArrayList<PlayerID> getCanBeCapturedOnEnteringBy() {
return m_canBeCapturedOnEnteringBy;
}
public void clearCanBeCapturedOnEnteringBy() {
m_canBeCapturedOnEnteringBy.clear();
}
public void resetCanBeCapturedOnEnteringBy() {
m_canBeCapturedOnEnteringBy = new ArrayList<>();
}
/**
* Adds to, not sets. Anything that adds to instead of setting needs a clear function as well.
*/
@GameProperty(xmlProperty = true, gameProperty = true, adds = true)
public void setWhenCapturedChangesInto(final String value) throws GameParseException {
final String[] s = value.split(":");
if (s.length < 5 || (s.length - 1) % 2 != 0) {
throw new GameParseException("whenCapturedChangesInto must have 5 or more values, "
+ "playerFrom:playerTo:keepAttributes:unitType:howMany "
+ "(you may have additional unitType:howMany:unitType:howMany, etc" + thisErrorMsg());
}
final PlayerID pfrom = getData().getPlayerList().getPlayerID(s[0]);
if (pfrom == null && !s[0].equals("any")) {
throw new GameParseException("whenCapturedChangesInto: No player named: " + s[0] + thisErrorMsg());
}
final PlayerID pto = getData().getPlayerList().getPlayerID(s[1]);
if (pto == null && !s[1].equals("any")) {
throw new GameParseException("whenCapturedChangesInto: No player named: " + s[1] + thisErrorMsg());
}
getBool(s[2]);
final IntegerMap<UnitType> unitsToMake = new IntegerMap<>();
for (int i = 3; i < s.length; i++) {
final UnitType ut = getData().getUnitTypeList().getUnitType(s[i]);
if (ut == null) {
throw new GameParseException("whenCapturedChangesInto: No unit named: " + s[3] + thisErrorMsg());
}
i++;
final int howMany = getInt(s[i]);
unitsToMake.put(ut, howMany);
}
m_whenCapturedChangesInto.put(s[0] + ":" + s[1], Tuple.of(s[2], unitsToMake));
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setWhenCapturedChangesInto(final LinkedHashMap<String, Tuple<String, IntegerMap<UnitType>>> value) {
m_whenCapturedChangesInto = value;
}
public LinkedHashMap<String, Tuple<String, IntegerMap<UnitType>>> getWhenCapturedChangesInto() {
return m_whenCapturedChangesInto;
}
public void clearWhenCapturedChangesInto() {
m_whenCapturedChangesInto.clear();
}
public void resetWhenCapturedChangesInto() {
m_whenCapturedChangesInto = new LinkedHashMap<>();
}
/**
* Adds to, not sets. Anything that adds to instead of setting needs a clear function as well.
*/
@GameProperty(xmlProperty = true, gameProperty = true, adds = true)
public void setDestroyedWhenCapturedBy(String value) throws GameParseException {
// We can prefix this value with "BY" or "FROM" to change the setting. If no setting, default to "BY" since this
// this is called by
// destroyedWhenCapturedBy
String byOrFrom = "BY";
if (value.startsWith("BY:") && getData().getPlayerList().getPlayerID("BY") == null) {
byOrFrom = "BY";
value = value.replaceFirst("BY:", "");
} else if (value.startsWith("FROM:") && getData().getPlayerList().getPlayerID("FROM") == null) {
byOrFrom = "FROM";
value = value.replaceFirst("FROM:", "");
}
final String[] temp = value.split(":");
for (final String name : temp) {
final PlayerID tempPlayer = getData().getPlayerList().getPlayerID(name);
if (tempPlayer != null) {
m_destroyedWhenCapturedBy.add(Tuple.of(byOrFrom, tempPlayer));
} else {
throw new GameParseException("No player named: " + name + thisErrorMsg());
}
}
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setDestroyedWhenCapturedBy(final ArrayList<Tuple<String, PlayerID>> value) {
m_destroyedWhenCapturedBy = value;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = true)
public void setDestroyedWhenCapturedFrom(String value) throws GameParseException {
if (!(value.startsWith("BY:") || value.startsWith("FROM:"))) {
value = "FROM:" + value;
}
setDestroyedWhenCapturedBy(value);
}
public ArrayList<Tuple<String, PlayerID>> getDestroyedWhenCapturedBy() {
return m_destroyedWhenCapturedBy;
}
public void clearDestroyedWhenCapturedBy() {
m_destroyedWhenCapturedBy.clear();
}
public void resetDestroyedWhenCapturedBy() {
m_destroyedWhenCapturedBy = new ArrayList<>();
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setCanBlitz(final String s) {
m_canBlitz = getBool(s);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setCanBlitz(final Boolean s) {
m_canBlitz = s;
}
public boolean getCanBlitz(final PlayerID player) {
if (m_canBlitz) {
return true;
}
return TechAbilityAttachment.getUnitAbilitiesGained(TechAbilityAttachment.ABILITY_CAN_BLITZ,
(UnitType) this.getAttachedTo(), player, getData());
}
public void resetCanBlitz() {
m_canBlitz = false;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setIsSub(final String s) {
m_isSub = getBool(s);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setIsSub(final Boolean s) {
m_isSub = s;
}
public boolean getIsSub() {
return m_isSub;
}
public void resetIsSub() {
m_isSub = false;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setIsCombatTransport(final String s) {
m_isCombatTransport = getBool(s);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setIsCombatTransport(final Boolean s) {
m_isCombatTransport = s;
}
public boolean getIsCombatTransport() {
return m_isCombatTransport;
}
public void resetIsCombatTransport() {
m_isCombatTransport = false;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setIsStrategicBomber(final String s) {
m_isStrategicBomber = getBool(s);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setIsStrategicBomber(final Boolean s) {
m_isStrategicBomber = s;
}
public boolean getIsStrategicBomber() {
return m_isStrategicBomber;
}
public void resetIsStrategicBomber() {
m_isStrategicBomber = false;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setIsDestroyer(final String s) {
m_isDestroyer = getBool(s);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setIsDestroyer(final Boolean s) {
m_isDestroyer = s;
}
public boolean getIsDestroyer() {
return m_isDestroyer;
}
public void resetIsDestroyer() {
m_isDestroyer = false;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setCanBombard(final String s) {
m_canBombard = getBool(s);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setCanBombard(final Boolean s) {
m_canBombard = s;
}
public boolean getCanBombard(final PlayerID player) {
if (m_canBombard) {
return true;
}
return TechAbilityAttachment.getUnitAbilitiesGained(TechAbilityAttachment.ABILITY_CAN_BOMBARD,
(UnitType) this.getAttachedTo(), player, getData());
}
public void resetCanBombard() {
m_canBombard = false;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setIsAir(final String s) {
m_isAir = getBool(s);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setIsAir(final Boolean s) {
m_isAir = s;
}
public boolean getIsAir() {
return m_isAir;
}
public void resetIsAir() {
m_isAir = false;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setIsSea(final String s) {
m_isSea = getBool(s);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setIsSea(final Boolean s) {
m_isSea = s;
}
public boolean getIsSea() {
return m_isSea;
}
public void resetIsSea() {
m_isSea = false;
}
// DO NOT REMOVE, this is an important convenience method for xmls
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setIsFactory(final String s) {
setIsFactory(getBool(s));
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setIsFactory(final Boolean s) {
setCanBeDamaged(s);
setIsInfrastructure(s);
setCanProduceUnits(s);
setIsConstruction(s);
if (s) {
setConstructionType(Constants.CONSTRUCTION_TYPE_FACTORY);
setMaxConstructionsPerTypePerTerr("1");
setConstructionsPerTerrPerTypePerTurn("1");
} else {
// return to defaults
setConstructionType("none");
setMaxConstructionsPerTypePerTerr("-1");
setConstructionsPerTerrPerTypePerTurn("-1");
}
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setCanProduceUnits(final String s) {
m_canProduceUnits = getBool(s);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setCanProduceUnits(final Boolean s) {
m_canProduceUnits = s;
}
public boolean getCanProduceUnits() {
return m_canProduceUnits;
}
public void resetCanProduceUnits() {
m_canProduceUnits = false;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setCanProduceXUnits(final String s) {
m_canProduceXUnits = getInt(s);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setCanProduceXUnits(final Integer s) {
m_canProduceXUnits = s;
}
public int getCanProduceXUnits() {
return m_canProduceXUnits;
}
public void resetCanProduceXUnits() {
m_canProduceXUnits = -1;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setCanOnlyBePlacedInTerritoryValuedAtX(final String s) {
m_canOnlyBePlacedInTerritoryValuedAtX = getInt(s);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setCanOnlyBePlacedInTerritoryValuedAtX(final Integer s) {
m_canOnlyBePlacedInTerritoryValuedAtX = s;
}
public int getCanOnlyBePlacedInTerritoryValuedAtX() {
return m_canOnlyBePlacedInTerritoryValuedAtX;
}
public void resetCanOnlyBePlacedInTerritoryValuedAtX() {
m_canOnlyBePlacedInTerritoryValuedAtX = -1;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setUnitPlacementRestrictions(final String value) {
if (value == null) {
m_unitPlacementRestrictions = null;
return;
}
m_unitPlacementRestrictions = value.split(":");
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setUnitPlacementRestrictions(final String[] value) {
m_unitPlacementRestrictions = value;
}
public String[] getUnitPlacementRestrictions() {
return m_unitPlacementRestrictions;
}
public void resetUnitPlacementRestrictions() {
m_unitPlacementRestrictions = null;
}
// no m_ variable for this, since it is the inverse of m_unitPlacementRestrictions
// we might as well just use m_unitPlacementRestrictions
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setUnitPlacementOnlyAllowedIn(final String value) throws GameParseException {
final Collection<Territory> allowedTerritories = getListedTerritories(value.split(":"));
final Collection<Territory> restrictedTerritories = new HashSet<>(getData().getMap().getTerritories());
restrictedTerritories.removeAll(allowedTerritories);
m_unitPlacementRestrictions = restrictedTerritories.stream()
.map(Territory::getName)
.toArray(size -> new String[size]);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = true)
public void setRepairsUnits(final String value) throws GameParseException {
final String[] s = value.split(":");
if (s.length <= 0) {
throw new GameParseException("repairsUnits cannot be empty" + thisErrorMsg());
}
int amount = 1;
int i = 0;
try {
amount = Integer.parseInt(s[0]);
i++;
} catch (final NumberFormatException nfe) {
amount = 1;
}
for (; i < s.length; i++) {
final UnitType ut = getData().getUnitTypeList().getUnitType(s[i]);
if (ut == null) {
throw new GameParseException("No unit called:" + s[i] + thisErrorMsg());
}
m_repairsUnits.put(ut, amount);
}
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setRepairsUnits(final IntegerMap<UnitType> value) {
m_repairsUnits = value;
}
public IntegerMap<UnitType> getRepairsUnits() {
return m_repairsUnits;
}
public void clearRepairsUnits() {
m_repairsUnits.clear();
}
public void resetRepairsUnits() {
m_repairsUnits = new IntegerMap<>();
}
/**
* Adds to, not sets. Anything that adds to instead of setting needs a clear function as well.
*/
@GameProperty(xmlProperty = true, gameProperty = true, adds = true)
public void setSpecial(final String value) throws GameParseException {
final String[] s = value.split(":");
for (final String option : s) {
if (!(option.equals("none") || option.equals("canOnlyPlaceInOriginalTerritories"))) {
throw new GameParseException("special does not allow: " + option + thisErrorMsg());
}
m_special.add(option);
}
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setSpecial(final HashSet<String> value) {
m_special = value;
}
public HashSet<String> getSpecial() {
return m_special;
}
public void clearSpecial() {
m_special.clear();
}
public void resetSpecial() {
m_special = new HashSet<>();
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setCanInvadeOnlyFrom(final String value) {
if (value == null) {
m_canInvadeOnlyFrom = null;
return;
}
final String[] canOnlyInvadeFrom = value.split(":");
if (canOnlyInvadeFrom[0].toLowerCase().equals("none")) {
m_canInvadeOnlyFrom = new String[] {"none"};
return;
}
if (canOnlyInvadeFrom[0].toLowerCase().equals("all")) {
m_canInvadeOnlyFrom = new String[] {"all"};
return;
}
m_canInvadeOnlyFrom = canOnlyInvadeFrom;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setCanInvadeOnlyFrom(final String[] value) {
m_canInvadeOnlyFrom = value;
}
public boolean canInvadeFrom(final String transport) {
final UnitType ut = getData().getUnitTypeList().getUnitType(transport);
if (ut == null) {
throw new IllegalStateException("No unit called:" + transport + thisErrorMsg());
}
// (UnitAttachment) ut.getAttachments().values().iterator().next();
// UnitAttachment ua = UnitAttachment.get(ut);
// Units may be considered transported if they are on a carrier, or if they are paratroopers, or if they are mech
// infantry. The
// "transporter" may not be an actual transport, so we should not check for that here.
if (m_canInvadeOnlyFrom == null || Arrays.asList(m_canInvadeOnlyFrom).isEmpty() || m_canInvadeOnlyFrom[0].equals("")
|| m_canInvadeOnlyFrom[0].equals("all")) {
return true;
}
return Arrays.asList(m_canInvadeOnlyFrom).contains(transport);
}
public void resetCanInvadeOnlyFrom() {
m_canInvadeOnlyFrom = null;
}
/**
* Adds to, not sets. Anything that adds to instead of setting needs a clear function as well.
*/
@GameProperty(xmlProperty = true, gameProperty = true, adds = true)
public void setRequiresUnits(final String value) {
m_requiresUnits.add(value.split(":"));
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setRequiresUnits(final ArrayList<String[]> value) {
m_requiresUnits = value;
}
public ArrayList<String[]> getRequiresUnits() {
return m_requiresUnits;
}
public void clearRequiresUnits() {
m_requiresUnits.clear();
}
public void resetRequiresUnits() {
m_requiresUnits = new ArrayList<>();
}
/**
* Adds to, not sets. Anything that adds to instead of setting needs a clear function as well.
*/
@GameProperty(xmlProperty = true, gameProperty = true, adds = true)
public void setWhenCombatDamaged(final String value) throws GameParseException {
final String[] s = value.split(":");
if (!(s.length == 3 || s.length == 4)) {
throw new GameParseException(
"whenCombatDamaged must have 3 or 4 parts: value=effect:optionalNumber, count=integer:integer"
+ thisErrorMsg());
}
final int from = getInt(s[0]);
final int to = getInt(s[1]);
if (from < 0 || to < 0 || to < from) {
throw new GameParseException("whenCombatDamaged damaged integers must be positive, and the second integer must "
+ "be equal to or greater than the first" + thisErrorMsg());
}
final Tuple<Integer, Integer> fromTo = Tuple.of(from, to);
Tuple<String, String> effectNum;
if (s.length == 3) {
effectNum = Tuple.of(s[2], null);
} else {
effectNum = Tuple.of(s[2], s[3]);
}
m_whenCombatDamaged.add(Tuple.of(fromTo, effectNum));
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setWhenCombatDamaged(final ArrayList<Tuple<Tuple<Integer, Integer>, Tuple<String, String>>> value) {
m_whenCombatDamaged = value;
}
public ArrayList<Tuple<Tuple<Integer, Integer>, Tuple<String, String>>> getWhenCombatDamaged() {
return m_whenCombatDamaged;
}
public void clearWhenCombatDamaged() {
m_whenCombatDamaged.clear();
}
public void resetWhenCombatDamaged() {
m_whenCombatDamaged = new ArrayList<>();
}
/**
* Adds to, not sets. Anything that adds to instead of setting needs a clear function as well.
*/
@GameProperty(xmlProperty = true, gameProperty = true, adds = true)
public void setReceivesAbilityWhenWith(final String value) {
m_receivesAbilityWhenWith.add(value);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setReceivesAbilityWhenWith(final ArrayList<String> value) {
m_receivesAbilityWhenWith = value;
}
public ArrayList<String> getReceivesAbilityWhenWith() {
return m_receivesAbilityWhenWith;
}
public void clearReceivesAbilityWhenWith() {
m_receivesAbilityWhenWith.clear();
}
public void resetReceivesAbilityWhenWith() {
m_receivesAbilityWhenWith = new ArrayList<>();
}
private static IntegerMap<Tuple<String, String>> getReceivesAbilityWhenWithMap(final Collection<Unit> units,
final String filterForAbility, final GameData data) {
final IntegerMap<Tuple<String, String>> map = new IntegerMap<>();
final Collection<UnitType> canReceive =
getUnitTypesFromUnitList(Match.getMatches(units, Matches.UnitCanReceivesAbilityWhenWith()));
for (final UnitType ut : canReceive) {
final Collection<String> receives = UnitAttachment.get(ut).getReceivesAbilityWhenWith();
for (final String receive : receives) {
final String[] s = receive.split(":");
if (filterForAbility != null && !filterForAbility.equals(s[0])) {
continue;
}
map.put(Tuple.of(s[0], s[1]),
Match.countMatches(units, Matches.unitIsOfType(data.getUnitTypeList().getUnitType(s[1]))));
}
}
return map;
}
public static Collection<Unit> getUnitsWhichReceivesAbilityWhenWith(final Collection<Unit> units,
final String filterForAbility, final GameData data) {
if (Match.noneMatch(units, Matches.UnitCanReceivesAbilityWhenWith())) {
return new ArrayList<>();
}
final Collection<Unit> unitsCopy = new ArrayList<>(units);
final HashSet<Unit> whichReceiveNoDuplicates = new HashSet<>();
final IntegerMap<Tuple<String, String>> whichGive =
getReceivesAbilityWhenWithMap(unitsCopy, filterForAbility, data);
for (final Tuple<String, String> abilityUnitType : whichGive.keySet()) {
final Collection<Unit> receives = Match.getNMatches(unitsCopy, whichGive.getInt(abilityUnitType),
Matches.UnitCanReceivesAbilityWhenWith(filterForAbility, abilityUnitType.getSecond()));
whichReceiveNoDuplicates.addAll(receives);
unitsCopy.removeAll(receives);
}
return whichReceiveNoDuplicates;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setIsConstruction(final String s) {
m_isConstruction = getBool(s);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setIsConstruction(final Boolean s) {
m_isConstruction = s;
}
public boolean getIsConstruction() {
return m_isConstruction;
}
public void resetIsConstruction() {
m_isConstruction = false;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setConstructionType(final String s) {
m_constructionType = s;
}
public String getConstructionType() {
return m_constructionType;
}
public void resetConstructionType() {
m_constructionType = "none";
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setConstructionsPerTerrPerTypePerTurn(final String s) {
m_constructionsPerTerrPerTypePerTurn = getInt(s);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setConstructionsPerTerrPerTypePerTurn(final Integer s) {
m_constructionsPerTerrPerTypePerTurn = s;
}
public int getConstructionsPerTerrPerTypePerTurn() {
return m_constructionsPerTerrPerTypePerTurn;
}
public void resetConstructionsPerTerrPerTypePerTurn() {
m_constructionsPerTerrPerTypePerTurn = -1;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setMaxConstructionsPerTypePerTerr(final String s) {
m_maxConstructionsPerTypePerTerr = getInt(s);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setMaxConstructionsPerTypePerTerr(final Integer s) {
m_maxConstructionsPerTypePerTerr = s;
}
public int getMaxConstructionsPerTypePerTerr() {
return m_maxConstructionsPerTypePerTerr;
}
public void resetMaxConstructionsPerTypePerTerr() {
m_maxConstructionsPerTypePerTerr = -1;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setIsMarine(final String s) {
if (s.equalsIgnoreCase(Constants.PROPERTY_TRUE)) {
m_isMarine = 1;
} else if (s.equalsIgnoreCase(Constants.PROPERTY_FALSE)) {
m_isMarine = 0;
} else {
m_isMarine = getInt(s);
}
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setIsMarine(final Integer s) {
m_isMarine = s;
}
public int getIsMarine() {
return m_isMarine;
}
public void resetIsMarine() {
m_isMarine = 0;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setIsInfantry(final String s) {
m_isInfantry = getBool(s);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setIsInfantry(final Boolean s) {
m_isInfantry = s;
}
public boolean getIsInfantry() {
return m_isInfantry;
}
public void resetIsInfantry() {
m_isInfantry = false;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setIsLandTransport(final String s) {
m_isLandTransport = getBool(s);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setIsLandTransport(final Boolean s) {
m_isLandTransport = s;
}
public boolean isLandTransport() {
return m_isLandTransport;
}
public boolean getIsLandTransport() {
return m_isLandTransport;
}
public void resetIsLandTransport() {
m_isLandTransport = false;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setTransportCapacity(final String s) {
m_transportCapacity = getInt(s);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setTransportCapacity(final Integer s) {
m_transportCapacity = s;
}
public int getTransportCapacity() {
return m_transportCapacity;
}
public void resetTransportCapacity() {
m_transportCapacity = -1;
}
/**
* DO NOT REMOVE.
*/
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setIsTwoHit(final String s) {
m_hitPoints = getBool(s) ? 2 : 1;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setHitPoints(final String s) {
m_hitPoints = getInt(s);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setHitPoints(final Integer value) {
m_hitPoints = value;
}
public int getHitPoints() {
return m_hitPoints;
}
public void resetHitPoints() {
m_hitPoints = 1;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setTransportCost(final String s) {
m_transportCost = getInt(s);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setTransportCost(final Integer s) {
m_transportCost = s;
}
public int getTransportCost() {
return m_transportCost;
}
public void resetTransportCost() {
m_transportCost = -1;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setMaxBuiltPerPlayer(final String s) {
m_maxBuiltPerPlayer = getInt(s);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setMaxBuiltPerPlayer(final Integer s) {
m_maxBuiltPerPlayer = s;
}
public int getMaxBuiltPerPlayer() {
return m_maxBuiltPerPlayer;
}
public void resetMaxBuiltPerPlayer() {
m_maxBuiltPerPlayer = -1;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setCarrierCapacity(final String s) {
m_carrierCapacity = getInt(s);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setCarrierCapacity(final Integer s) {
m_carrierCapacity = s;
}
public int getCarrierCapacity() {
return m_carrierCapacity;
}
public void resetCarrierCapacity() {
m_carrierCapacity = -1;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setCarrierCost(final String s) {
m_carrierCost = getInt(s);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setCarrierCost(final Integer s) {
m_carrierCost = s;
}
public int getCarrierCost() {
return m_carrierCost;
}
public void resetCarrierCost() {
m_carrierCost = -1;
}
@GameProperty(xmlProperty = true, gameProperty = false, adds = false)
public void setArtillery(final String s) throws GameParseException {
m_artillery = getBool(s);
if (m_artillery) {
UnitSupportAttachment.addRule((UnitType) getAttachedTo(), getData(), false);
}
}
@GameProperty(xmlProperty = true, gameProperty = false, adds = false)
public void setArtillery(final Boolean s) throws GameParseException {
m_artillery = s;
if (m_artillery) {
UnitSupportAttachment.addRule((UnitType) getAttachedTo(), getData(), false);
}
}
public boolean getArtillery() {
return m_artillery;
}
public void resetArtillery() {
throw new IllegalStateException(
"Resetting Artillery (UnitAttachment) is not allowed, please use Support Attachments instead.");
}
@GameProperty(xmlProperty = true, gameProperty = false, adds = false)
public void setArtillerySupportable(final String s) throws GameParseException {
m_artillerySupportable = getBool(s);
if (m_artillerySupportable) {
UnitSupportAttachment.addTarget((UnitType) getAttachedTo(), getData());
}
}
@GameProperty(xmlProperty = true, gameProperty = false, adds = false)
public void setArtillerySupportable(final Boolean s) throws GameParseException {
m_artillerySupportable = s;
if (m_artillerySupportable) {
UnitSupportAttachment.addTarget((UnitType) getAttachedTo(), getData());
}
}
public boolean getArtillerySupportable() {
return m_artillerySupportable;
}
public void resetArtillerySupportable() {
throw new IllegalStateException(
"Resetting Artillery Supportable (UnitAttachment) is not allowed, please use Support Attachments instead.");
}
@GameProperty(xmlProperty = true, gameProperty = false, adds = false)
public void setUnitSupportCount(final String s) {
m_unitSupportCount = getInt(s);
UnitSupportAttachment.setOldSupportCount((UnitType) getAttachedTo(), getData(), s);
}
@GameProperty(xmlProperty = true, gameProperty = false, adds = false)
public void setUnitSupportCount(final Integer s) {
m_unitSupportCount = s;
UnitSupportAttachment.setOldSupportCount((UnitType) getAttachedTo(), getData(), s.toString());
}
public int getUnitSupportCount() {
return m_unitSupportCount > 0 ? m_unitSupportCount : 1;
}
public void resetUnitSupportCount() {
throw new IllegalStateException(
"Resetting Artillery Support Count (UnitAttachment) is not allowed, please use Support Attachments instead.");
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setBombard(final String s) {
m_bombard = getInt(s);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setBombard(final Integer s) {
m_bombard = s;
}
public int getBombard(final PlayerID player) {
return m_bombard > 0 ? m_bombard : m_attack;
}
public void resetBombard() {
m_bombard = -1;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setMovement(final String s) {
m_movement = getInt(s);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setMovement(final Integer s) {
m_movement = s;
}
public int getMovement(final PlayerID player) {
return Math.max(0,
m_movement + TechAbilityAttachment.getMovementBonus((UnitType) this.getAttachedTo(), player, getData()));
}
public void resetMovement() {
m_movement = 0;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setAttack(final String s) {
m_attack = getInt(s);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setAttack(final int s) {
m_attack = s;
}
public int getAttack(final PlayerID player) {
int attackValue =
m_attack + TechAbilityAttachment.getAttackBonus((UnitType) this.getAttachedTo(), player, getData());
if (attackValue > 0 && player.isAI()) {
attackValue += games.strategy.triplea.Properties.getAIBonusAttack(getData());
}
return Math.min(getData().getDiceSides(), Math.max(0, attackValue));
}
int getRawAttack() {
return m_attack;
}
public void resetAttack() {
m_attack = 0;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setAttackRolls(final String s) {
m_attackRolls = getInt(s);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setAttackRolls(final Integer s) {
m_attackRolls = s;
}
public int getAttackRolls(final PlayerID player) {
return Math.max(0,
m_attackRolls + TechAbilityAttachment.getAttackRollsBonus((UnitType) this.getAttachedTo(), player, getData()));
}
public void resetAttackRolls() {
m_attackRolls = 1;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setDefense(final String s) {
m_defense = getInt(s);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setDefense(final Integer s) {
m_defense = s;
}
public int getDefense(final PlayerID player) {
int defenseValue =
m_defense + TechAbilityAttachment.getDefenseBonus((UnitType) this.getAttachedTo(), player, getData());
if (defenseValue > 0 && m_isSub && TechTracker.hasSuperSubs(player)) {
final int bonus = games.strategy.triplea.Properties.getSuper_Sub_Defense_Bonus(getData());
defenseValue += bonus;
}
if (defenseValue > 0 && player.isAI()) {
defenseValue += games.strategy.triplea.Properties.getAIBonusDefense(getData());
}
return Math.min(getData().getDiceSides(), Math.max(0, defenseValue));
}
int getRawDefense() {
return m_defense;
}
public void resetDefense() {
m_defense = 0;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setDefenseRolls(final String s) {
m_defenseRolls = getInt(s);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setDefenseRolls(final Integer s) {
m_defenseRolls = s;
}
public int getDefenseRolls(final PlayerID player) {
return Math.max(0, m_defenseRolls
+ TechAbilityAttachment.getDefenseRollsBonus((UnitType) this.getAttachedTo(), player, getData()));
}
public void resetDefenseRolls() {
m_defenseRolls = 1;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setChooseBestRoll(final String s) {
m_chooseBestRoll = getBool(s);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setChooseBestRoll(final Boolean s) {
m_chooseBestRoll = s;
}
public boolean getChooseBestRoll() {
return m_chooseBestRoll;
}
public void resetChooseBestRoll() {
m_chooseBestRoll = false;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setCanScramble(final String s) {
m_canScramble = getBool(s);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setCanScramble(final Boolean s) {
m_canScramble = s;
}
public boolean getCanScramble() {
return m_canScramble;
}
public void resetCanScramble() {
m_canScramble = false;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setMaxScrambleCount(final String s) {
m_maxScrambleCount = getInt(s);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setMaxScrambleCount(final Integer s) {
m_maxScrambleCount = s;
}
public int getMaxScrambleCount() {
return m_maxScrambleCount;
}
public void resetMaxScrambleCount() {
m_maxScrambleCount = -1;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setMaxScrambleDistance(final String s) {
m_maxScrambleDistance = getInt(s);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setMaxScrambleDistance(final Integer s) {
m_maxScrambleDistance = s;
}
public int getMaxScrambleDistance() {
return m_maxScrambleDistance;
}
public void resetMaxScrambleDistance() {
m_maxScrambleDistance = -1;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setMaxOperationalDamage(final String s) {
m_maxOperationalDamage = getInt(s);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setMaxOperationalDamage(final Integer s) {
m_maxOperationalDamage = s;
}
public int getMaxOperationalDamage() {
return m_maxOperationalDamage;
}
public void resetMaxOperationalDamage() {
m_maxOperationalDamage = -1;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setMaxDamage(final String s) {
m_maxDamage = getInt(s);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setMaxDamage(final Integer s) {
m_maxDamage = s;
}
public int getMaxDamage() {
return m_maxDamage;
}
public void resetMaxDamage() {
m_maxDamage = 2;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setIsAirBase(final String s) {
m_isAirBase = getBool(s);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setIsAirBase(final Boolean s) {
m_isAirBase = s;
}
public boolean getIsAirBase() {
return m_isAirBase;
}
public void resetIsAirBase() {
m_isAirBase = false;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setIsInfrastructure(final String s) {
m_isInfrastructure = getBool(s);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setIsInfrastructure(final Boolean s) {
m_isInfrastructure = s;
}
public boolean getIsInfrastructure() {
return m_isInfrastructure;
}
public void resetIsInfrastructure() {
m_isInfrastructure = false;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setCanBeDamaged(final String s) {
m_canBeDamaged = getBool(s);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setCanBeDamaged(final Boolean s) {
m_canBeDamaged = s;
}
public boolean getCanBeDamaged() {
return m_canBeDamaged;
}
public void resetCanBeDamaged() {
m_canBeDamaged = false;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setCanDieFromReachingMaxDamage(final String s) {
m_canDieFromReachingMaxDamage = getBool(s);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setCanDieFromReachingMaxDamage(final Boolean s) {
m_canDieFromReachingMaxDamage = s;
}
public boolean getCanDieFromReachingMaxDamage() {
return m_canDieFromReachingMaxDamage;
}
public void resetCanDieFromReachingMaxDamage() {
m_canDieFromReachingMaxDamage = false;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setIsSuicide(final String s) {
m_isSuicide = getBool(s);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setIsSuicide(final boolean s) {
m_isSuicide = s;
}
public boolean getIsSuicide() {
return m_isSuicide;
}
public void resetIsSuicide() {
m_isSuicide = false;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setIsKamikaze(final String s) {
m_isKamikaze = getBool(s);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setIsKamikaze(final boolean s) {
m_isKamikaze = s;
}
public boolean getIsKamikaze() {
return m_isKamikaze;
}
public void resetIsKamikaze() {
m_isKamikaze = false;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setBlockade(final String s) {
m_blockade = getInt(s);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setBlockade(final int s) {
m_blockade = s;
}
public int getBlockade() {
return m_blockade;
}
public void resetBlockade() {
m_blockade = 0;
}
/**
* Adds to, not sets. Anything that adds to instead of setting needs a clear function as well.
*/
@GameProperty(xmlProperty = true, gameProperty = true, adds = true)
public void setGivesMovement(final String value) throws GameParseException {
final String[] s = value.split(":");
if (s.length <= 0 || s.length > 2) {
throw new GameParseException("givesMovement cannot be empty or have more than two fields" + thisErrorMsg());
}
String unitTypeToProduce;
unitTypeToProduce = s[1];
// validate that this unit exists in the xml
final UnitType ut = getData().getUnitTypeList().getUnitType(unitTypeToProduce);
if (ut == null) {
throw new GameParseException("No unit called:" + unitTypeToProduce + thisErrorMsg());
}
// we should allow positive and negative numbers, since you can give bonuses to units or take away a unit's movement
final int n = getInt(s[0]);
m_givesMovement.put(ut, n);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setGivesMovement(final IntegerMap<UnitType> value) {
m_givesMovement = value;
}
public IntegerMap<UnitType> getGivesMovement() {
return m_givesMovement;
}
public void clearGivesMovement() {
m_givesMovement.clear();
}
public void resetGivesMovement() {
m_givesMovement = new IntegerMap<>();
}
/**
* Adds to, not sets. Anything that adds to instead of setting needs a clear function as well.
*/
@GameProperty(xmlProperty = true, gameProperty = true, adds = true)
public void setConsumesUnits(final String value) throws GameParseException {
final String[] s = value.split(":");
if (s.length != 2) {
throw new GameParseException("consumesUnits must have two fields" + thisErrorMsg());
}
String unitTypeToProduce;
unitTypeToProduce = s[1];
// validate that this unit exists in the xml
final UnitType ut = getData().getUnitTypeList().getUnitType(unitTypeToProduce);
if (ut == null) {
throw new GameParseException("No unit called:" + unitTypeToProduce + thisErrorMsg());
}
final int n = getInt(s[0]);
if (n < 1) {
throw new GameParseException("consumesUnits must have positive values" + thisErrorMsg());
}
m_consumesUnits.put(ut, n);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setConsumesUnits(final IntegerMap<UnitType> value) {
m_consumesUnits = value;
}
public IntegerMap<UnitType> getConsumesUnits() {
return m_consumesUnits;
}
public void clearConsumesUnits() {
m_consumesUnits.clear();
}
public void resetConsumesUnits() {
m_consumesUnits = new IntegerMap<>();
}
/**
* Adds to, not sets. Anything that adds to instead of setting needs a clear function as well.
*/
@GameProperty(xmlProperty = true, gameProperty = true, adds = true)
public void setCreatesUnitsList(final String value) throws GameParseException {
final String[] s = value.split(":");
if (s.length <= 0 || s.length > 2) {
throw new GameParseException("createsUnitsList cannot be empty or have more than two fields" + thisErrorMsg());
}
String unitTypeToProduce;
unitTypeToProduce = s[1];
// validate that this unit exists in the xml
final UnitType ut = getData().getUnitTypeList().getUnitType(unitTypeToProduce);
if (ut == null) {
throw new GameParseException("createsUnitsList: No unit called:" + unitTypeToProduce + thisErrorMsg());
}
final int n = getInt(s[0]);
if (n < 1) {
throw new GameParseException("createsUnitsList must have positive values" + thisErrorMsg());
}
m_createsUnitsList.put(ut, n);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setCreatesUnitsList(final IntegerMap<UnitType> value) {
m_createsUnitsList = value;
}
public IntegerMap<UnitType> getCreatesUnitsList() {
return m_createsUnitsList;
}
public void clearCreatesUnitsList() {
m_createsUnitsList.clear();
}
public void resetCreatesUnitsList() {
m_createsUnitsList = new IntegerMap<>();
}
/**
* Adds to, not sets. Anything that adds to instead of setting needs a clear function as well.
*/
@GameProperty(xmlProperty = true, gameProperty = true, adds = true)
public void setCreatesResourcesList(final String value) throws GameParseException {
final String[] s = value.split(":");
if (s.length <= 0 || s.length > 2) {
throw new GameParseException(
"createsResourcesList cannot be empty or have more than two fields" + thisErrorMsg());
}
String resourceToProduce;
resourceToProduce = s[1];
// validate that this resource exists in the xml
final Resource r = getData().getResourceList().getResource(resourceToProduce);
if (r == null) {
throw new GameParseException("createsResourcesList: No resource called:" + resourceToProduce + thisErrorMsg());
}
final int n = getInt(s[0]);
m_createsResourcesList.put(r, n);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setCreatesResourcesList(final IntegerMap<Resource> value) {
m_createsResourcesList = value;
}
public IntegerMap<Resource> getCreatesResourcesList() {
return m_createsResourcesList;
}
public void clearCreatesResourcesList() {
m_createsResourcesList.clear();
}
public void resetCreatesResourcesList() {
m_createsResourcesList = new IntegerMap<>();
}
/**
* Adds to, not sets. Anything that adds to instead of setting needs a clear function as well.
*/
@GameProperty(xmlProperty = true, gameProperty = true, adds = true)
public void setFuelCost(final String value) throws GameParseException {
final String[] s = value.split(":");
if (s.length != 2) {
throw new GameParseException("fuelCost must have two fields" + thisErrorMsg());
}
String resourceToProduce;
resourceToProduce = s[1];
// validate that this resource exists in the xml
final Resource r = getData().getResourceList().getResource(resourceToProduce);
if (r == null) {
throw new GameParseException("fuelCost: No resource called:" + resourceToProduce + thisErrorMsg());
}
final int n = getInt(s[0]);
if (n < 0) {
throw new GameParseException("fuelCost must have positive values" + thisErrorMsg());
}
m_fuelCost.put(r, n);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setFuelCost(final IntegerMap<Resource> value) {
m_fuelCost = value;
}
public IntegerMap<Resource> getFuelCost() {
return m_fuelCost;
}
public void clearFuelCost() {
m_fuelCost.clear();
}
public void resetFuelCost() {
m_fuelCost = new IntegerMap<>();
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setBombingBonus(final String s) {
m_bombingBonus = getInt(s);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setBombingBonus(final int s) {
m_bombingBonus = s;
}
public int getBombingBonus() {
return m_bombingBonus;
}
public void resetBombingBonus() {
m_bombingBonus = -1;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setBombingMaxDieSides(final String s) {
m_bombingMaxDieSides = getInt(s);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setBombingMaxDieSides(final int s) {
m_bombingMaxDieSides = s;
}
public int getBombingMaxDieSides() {
return m_bombingMaxDieSides;
}
public void resetBombingMaxDieSides() {
m_bombingMaxDieSides = -1;
}
/**
* Adds to, not sets. Anything that adds to instead of setting needs a clear function as well.
*/
@GameProperty(xmlProperty = true, gameProperty = true, adds = true)
public void setBombingTargets(final String value) throws GameParseException {
if (value == null) {
m_bombingTargets = null;
return;
}
if (m_bombingTargets == null) {
m_bombingTargets = new HashSet<>();
}
final String[] s = value.split(":");
for (final String u : s) {
final UnitType ut = getData().getUnitTypeList().getUnitType(u);
if (ut == null) {
throw new GameParseException("bombingTargets: no such unit type: " + u + thisErrorMsg());
}
m_bombingTargets.add(ut);
}
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setBombingTargets(final HashSet<UnitType> value) {
m_bombingTargets = value;
}
public HashSet<UnitType> getBombingTargets(final GameData data) {
if (m_bombingTargets != null) {
return m_bombingTargets;
}
return new HashSet<>(data.getUnitTypeList().getAllUnitTypes());
}
public void clearBombingTargets() {
m_bombingTargets.clear();
}
public void resetBombingTargets() {
m_bombingTargets = null;
}
public static Set<UnitType> getAllowedBombingTargetsIntersection(final Collection<Unit> bombersOrRockets,
final GameData data) {
if (bombersOrRockets.isEmpty()) {
return new HashSet<>();
}
Collection<UnitType> allowedTargets = data.getUnitTypeList().getAllUnitTypes();
for (final Unit u : bombersOrRockets) {
final UnitAttachment ua = UnitAttachment.get(u.getType());
final HashSet<UnitType> bombingTargets = ua.getBombingTargets(data);
if (bombingTargets != null) {
allowedTargets = games.strategy.util.Util.intersection(allowedTargets, bombingTargets);
}
}
return new HashSet<>(allowedTargets);
}
// Do not delete, we keep this both for backwards compatibility, and for user convenience when making maps
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setIsAA(final String s) throws GameParseException {
setIsAA(getBool(s));
}
// Do not delete, we keep this both for backwards compatibility, and for user convenience when making maps
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setIsAA(final Boolean s) throws GameParseException {
setIsAAforCombatOnly(s);
setIsAAforBombingThisUnitOnly(s);
setIsAAforFlyOverOnly(s);
setIsAAmovement(s);
setIsRocket(s);
setIsInfrastructure(s);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setAttackAA(final String s) {
m_attackAA = getInt(s);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setAttackAA(final int s) {
m_attackAA = s;
}
public int getAttackAA(final PlayerID player) {
// TODO: this may cause major problems with Low Luck, if they have diceSides equal to something other than 6, or it
// does not divide
// perfectly into attackAAmaxDieSides
return Math.max(0, Math.min(getAttackAAmaxDieSides(),
m_attackAA + TechAbilityAttachment.getRadarBonus((UnitType) this.getAttachedTo(), player, getData())));
}
public void resetAttackAA() {
m_attackAA = 1;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setOffensiveAttackAA(final String s) {
m_offensiveAttackAA = getInt(s);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setOffensiveAttackAA(final Integer s) {
m_offensiveAttackAA = s;
}
public int getOffensiveAttackAA(final PlayerID player) {
// TODO: this may cause major problems with Low Luck, if they have diceSides equal to something other than 6, or it
// does not divide
// perfectly into attackAAmaxDieSides
return Math.max(0, Math.min(getOffensiveAttackAAmaxDieSides(),
m_offensiveAttackAA + TechAbilityAttachment.getRadarBonus((UnitType) this.getAttachedTo(), player, getData())));
}
public void resetOffensiveAttackAA() {
m_offensiveAttackAA = 1;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setAttackAAmaxDieSides(final String s) {
m_attackAAmaxDieSides = getInt(s);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setAttackAAmaxDieSides(final Integer s) {
m_attackAAmaxDieSides = s;
}
public int getAttackAAmaxDieSides() {
if (m_attackAAmaxDieSides < 0) {
return getData().getDiceSides();
}
return m_attackAAmaxDieSides;
}
public void resetAttackAAmaxDieSides() {
m_attackAAmaxDieSides = -1;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setOffensiveAttackAAmaxDieSides(final String s) {
m_offensiveAttackAAmaxDieSides = getInt(s);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setOffensiveAttackAAmaxDieSides(final Integer s) {
m_offensiveAttackAAmaxDieSides = s;
}
public int getOffensiveAttackAAmaxDieSides() {
if (m_offensiveAttackAAmaxDieSides < 0) {
return getData().getDiceSides();
}
return m_offensiveAttackAAmaxDieSides;
}
public void resetOffensiveAttackAAmaxDieSides() {
m_offensiveAttackAAmaxDieSides = -1;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setMaxAAattacks(final String s) throws GameParseException {
final int attacks = getInt(s);
if (attacks < -1) {
throw new GameParseException("maxAAattacks must be positive (or -1 for attacking all) " + thisErrorMsg());
}
m_maxAAattacks = getInt(s);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setMaxAAattacks(final Integer s) {
m_maxAAattacks = s;
}
public int getMaxAAattacks() {
return m_maxAAattacks;
}
public void resetMaxAAattacks() {
m_maxAAattacks = -1;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setMaxRoundsAA(final String s) throws GameParseException {
final int attacks = getInt(s);
if (attacks < -1) {
throw new GameParseException("maxRoundsAA must be positive (or -1 for infinite) " + thisErrorMsg());
}
m_maxRoundsAA = getInt(s);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setMaxRoundsAA(final Integer s) {
m_maxRoundsAA = s;
}
public int getMaxRoundsAA() {
return m_maxRoundsAA;
}
public void resetMaxRoundsAA() {
m_maxRoundsAA = 1;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setMayOverStackAA(final String s) {
m_mayOverStackAA = getBool(s);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setMayOverStackAA(final Boolean s) {
m_mayOverStackAA = s;
}
public boolean getMayOverStackAA() {
return m_mayOverStackAA;
}
public void resetMayOverStackAA() {
m_mayOverStackAA = false;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setDamageableAA(final String s) {
m_damageableAA = getBool(s);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setDamageableAA(final Boolean s) {
m_damageableAA = s;
}
public boolean getDamageableAA() {
return m_damageableAA;
}
public void resetDamageableAA() {
m_damageableAA = false;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setIsAAforCombatOnly(final String s) {
m_isAAforCombatOnly = getBool(s);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setIsAAforCombatOnly(final Boolean s) {
m_isAAforCombatOnly = s;
}
public boolean getIsAAforCombatOnly() {
return m_isAAforCombatOnly;
}
public void resetIsAAforCombatOnly() {
m_isAAforCombatOnly = false;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setIsAAforBombingThisUnitOnly(final String s) {
m_isAAforBombingThisUnitOnly = getBool(s);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setIsAAforBombingThisUnitOnly(final Boolean s) {
m_isAAforBombingThisUnitOnly = s;
}
public boolean getIsAAforBombingThisUnitOnly() {
return m_isAAforBombingThisUnitOnly;
}
public void resetIsAAforBombingThisUnitOnly() {
m_isAAforBombingThisUnitOnly = false;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setIsAAforFlyOverOnly(final String s) {
m_isAAforFlyOverOnly = getBool(s);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setIsAAforFlyOverOnly(final Boolean s) {
m_isAAforFlyOverOnly = s;
}
public boolean getIsAAforFlyOverOnly() {
return m_isAAforFlyOverOnly;
}
public void resetIsAAforFlyOverOnly() {
m_isAAforFlyOverOnly = false;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setIsRocket(final String s) {
m_isRocket = getBool(s);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setIsRocket(final Boolean s) {
m_isRocket = s;
}
public boolean getIsRocket() {
return m_isRocket;
}
public void resetIsRocket() {
m_isRocket = false;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setTypeAA(final String s) {
m_typeAA = s;
}
public String getTypeAA() {
return m_typeAA;
}
public void resetTypeAA() {
m_typeAA = "AA";
}
public static Set<String> getAllOfTypeAAs(final Collection<Unit> aaUnits, final Collection<Unit> targets,
final Match<Unit> typeOfAA, final HashMap<String, HashSet<UnitType>> airborneTechTargetsAllowed) {
final Set<String> rVal = new HashSet<>();
for (final Unit u : Match.getMatches(aaUnits,
Matches.UnitIsAAthatCanHitTheseUnits(targets, typeOfAA, airborneTechTargetsAllowed))) {
rVal.add(UnitAttachment.get(u.getType()).getTypeAA());
}
return rVal;
}
public static List<String> getAllOfTypeAAs(final Collection<Unit> aaUnitsAlreadyVerified) {
final Set<String> aaSet = new HashSet<>();
for (final Unit u : aaUnitsAlreadyVerified) {
aaSet.add(UnitAttachment.get(u.getType()).getTypeAA());
}
final List<String> rVal = new ArrayList<>(aaSet);
Collections.sort(rVal);
return rVal;
}
/**
* Adds to, not sets. Anything that adds to instead of setting needs a clear function as well.
*/
@GameProperty(xmlProperty = true, gameProperty = true, adds = true)
public void setTargetsAA(final String value) throws GameParseException {
if (value == null) {
m_targetsAA = null;
return;
}
if (m_targetsAA == null) {
m_targetsAA = new HashSet<>();
}
final String[] s = value.split(":");
for (final String u : s) {
final UnitType ut = getData().getUnitTypeList().getUnitType(u);
if (ut == null) {
throw new GameParseException("AAtargets: no such unit type: " + u + thisErrorMsg());
}
m_targetsAA.add(ut);
}
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setTargetsAA(final HashSet<UnitType> value) {
m_targetsAA = value;
}
public HashSet<UnitType> getTargetsAA(final GameData data) {
if (m_targetsAA != null) {
return m_targetsAA;
}
final HashSet<UnitType> airTypes = new HashSet<>();
final Iterator<UnitType> utIter = data.getUnitTypeList().iterator();
while (utIter.hasNext()) {
final UnitType ut = utIter.next();
if (UnitAttachment.get(ut).getIsAir()) {
airTypes.add(ut);
}
}
return airTypes;
}
public void clearTargetsAA() {
m_targetsAA.clear();
}
public void resetTargetsAA() {
m_targetsAA = null;
}
/**
* Adds to, not sets. Anything that adds to instead of setting needs a clear function as well.
*/
@GameProperty(xmlProperty = true, gameProperty = true, adds = true)
public void setWillNotFireIfPresent(final String value) throws GameParseException {
final String[] s = value.split(":");
for (final String u : s) {
final UnitType ut = getData().getUnitTypeList().getUnitType(u);
if (ut == null) {
throw new GameParseException("willNotFireIfPresent: no such unit type: " + u + thisErrorMsg());
}
m_willNotFireIfPresent.add(ut);
}
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setWillNotFireIfPresent(final HashSet<UnitType> value) {
m_willNotFireIfPresent = value;
}
public HashSet<UnitType> getWillNotFireIfPresent() {
return m_willNotFireIfPresent;
}
public void clearWillNotFireIfPresent() {
m_willNotFireIfPresent.clear();
}
public void resetWillNotFireIfPresent() {
m_willNotFireIfPresent = new HashSet<>();
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setIsAAmovement(final String s) throws GameParseException {
setIsAAmovement(getBool(s));
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setIsAAmovement(final Boolean s) throws GameParseException {
setCanNotMoveDuringCombatMove(s);
if (s) {
setMovementLimit(Integer.MAX_VALUE + ":allied");
setAttackingLimit(Integer.MAX_VALUE + ":allied");
setPlacementLimit(Integer.MAX_VALUE + ":allied");
} else {
m_movementLimit = null;
m_attackingLimit = null;
m_placementLimit = null;
}
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setCanNotMoveDuringCombatMove(final String s) {
m_canNotMoveDuringCombatMove = getBool(s);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setCanNotMoveDuringCombatMove(final Boolean s) {
m_canNotMoveDuringCombatMove = s;
}
public boolean getCanNotMoveDuringCombatMove() {
return m_canNotMoveDuringCombatMove;
}
public void resetCanNotMoveDuringCombatMove() {
m_canNotMoveDuringCombatMove = false;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setMovementLimit(final String value) throws GameParseException {
if (value == null) {
m_movementLimit = null;
return;
}
final UnitType ut = (UnitType) this.getAttachedTo();
if (ut == null) {
throw new GameParseException("getAttachedTo returned null" + thisErrorMsg());
}
final String[] s = value.split(":");
if (s.length != 2) {
throw new GameParseException("movementLimit must have 2 fields, value and count" + thisErrorMsg());
}
final int max = getInt(s[0]);
if (max < 0) {
throw new GameParseException("movementLimit count must have a positive number" + thisErrorMsg());
}
if (!(s[1].equals("owned") || s[1].equals("allied") || s[1].equals("total"))) {
throw new GameParseException("movementLimit value must owned, allied, or total" + thisErrorMsg());
}
m_movementLimit = Tuple.of(max, s[1]);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setMovementLimit(final Tuple<Integer, String> value) {
m_movementLimit = value;
}
public Tuple<Integer, String> getMovementLimit() {
return m_movementLimit;
}
public void resetMovementLimit() {
m_movementLimit = null;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setAttackingLimit(final String value) throws GameParseException {
if (value == null) {
m_attackingLimit = null;
return;
}
final UnitType ut = (UnitType) this.getAttachedTo();
if (ut == null) {
throw new GameParseException("getAttachedTo returned null" + thisErrorMsg());
}
final String[] s = value.split(":");
if (s.length != 2) {
throw new GameParseException("attackingLimit must have 2 fields, value and count" + thisErrorMsg());
}
final int max = getInt(s[0]);
if (max < 0) {
throw new GameParseException("attackingLimit count must have a positive number" + thisErrorMsg());
}
if (!(s[1].equals("owned") || s[1].equals("allied") || s[1].equals("total"))) {
throw new GameParseException("attackingLimit value must owned, allied, or total" + thisErrorMsg());
}
m_attackingLimit = Tuple.of(max, s[1]);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setAttackingLimit(final Tuple<Integer, String> value) {
m_attackingLimit = value;
}
public Tuple<Integer, String> getAttackingLimit() {
return m_attackingLimit;
}
public void resetAttackingLimit() {
m_attackingLimit = null;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setPlacementLimit(final String value) throws GameParseException {
if (value == null) {
m_placementLimit = null;
return;
}
final UnitType ut = (UnitType) this.getAttachedTo();
if (ut == null) {
throw new GameParseException("getAttachedTo returned null" + thisErrorMsg());
}
final String[] s = value.split(":");
if (s.length != 2) {
throw new GameParseException("placementLimit must have 2 fields, value and count" + thisErrorMsg());
}
final int max = getInt(s[0]);
if (max < 0) {
throw new GameParseException("placementLimit count must have a positive number" + thisErrorMsg());
}
if (!(s[1].equals("owned") || s[1].equals("allied") || s[1].equals("total"))) {
throw new GameParseException("placementLimit value must owned, allied, or total" + thisErrorMsg());
}
m_placementLimit = Tuple.of(max, s[1]);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setPlacementLimit(final Tuple<Integer, String> value) {
m_placementLimit = value;
}
public Tuple<Integer, String> getPlacementLimit() {
return m_placementLimit;
}
public void resetPlacementLimit() {
m_placementLimit = null;
}
public static int getMaximumNumberOfThisUnitTypeToReachStackingLimit(final String limitType, final UnitType ut,
final Territory t, final PlayerID owner, final GameData data) {
final UnitAttachment ua = UnitAttachment.get(ut);
final Tuple<Integer, String> stackingLimit;
if (limitType.equals("movementLimit")) {
stackingLimit = ua.getMovementLimit();
} else if (limitType.equals("attackingLimit")) {
stackingLimit = ua.getAttackingLimit();
} else if (limitType.equals("placementLimit")) {
stackingLimit = ua.getPlacementLimit();
} else {
throw new IllegalStateException(
"getMaximumNumberOfThisUnitTypeToReachStackingLimit does not allow limitType: " + limitType);
}
if (stackingLimit == null) {
return Integer.MAX_VALUE;
}
int max = stackingLimit.getFirst();
if (max == Integer.MAX_VALUE && (ua.getIsAAforBombingThisUnitOnly() || ua.getIsAAforCombatOnly())) {
// under certain rules (classic rules) there can only be 1 aa gun in a territory.
if (!(games.strategy.triplea.Properties.getWW2V2(data) || games.strategy.triplea.Properties.getWW2V3(data)
|| games.strategy.triplea.Properties.getMultipleAAPerTerritory(data))) {
max = 1;
}
}
final CompositeMatchAnd<Unit> stackingMatch = new CompositeMatchAnd<>(Matches.unitIsOfType(ut));
final String stackingType = stackingLimit.getSecond();
if (stackingType.equals("owned")) {
stackingMatch.add(Matches.unitIsOwnedBy(owner));
} else if (stackingType.equals("allied")) {
stackingMatch.add(Matches.isUnitAllied(owner, data));
}
// else if (stackingType.equals("total"))
final int totalInTerritory = Match.countMatches(t.getUnits().getUnits(), stackingMatch);
return Math.max(0, max - totalInTerritory);
}
@Override
public void validate(final GameData data) throws GameParseException {
if (m_isAir) {
if (m_isSea /* || m_isFactory */ || m_isSub || m_transportCost != -1 || m_carrierCapacity != -1 || m_canBlitz
|| m_canBombard || m_isMarine != 0 || m_isInfantry || m_isLandTransport || m_isAirTransportable
|| m_isCombatTransport) {
throw new GameParseException("air units cannot have certain properties, " + thisErrorMsg());
}
} else if (m_isSea) {
if (m_canBlitz || m_isAir /* || m_isFactory */ || m_isStrategicBomber || m_carrierCost != -1
|| m_transportCost != -1 || m_isMarine != 0 || m_isInfantry || m_isLandTransport || m_isAirTransportable
|| m_isAirTransport || m_isKamikaze) {
throw new GameParseException("sea units cannot have certain properties, " + thisErrorMsg());
}
} else { // if land
if (m_canBombard || m_isStrategicBomber || m_isSub || m_carrierCapacity != -1 || m_bombard != -1
|| m_transportCapacity != -1 || m_isAirTransport || m_isCombatTransport || m_isKamikaze) {
throw new GameParseException("land units cannot have certain properties, " + thisErrorMsg());
}
}
if (m_hitPoints < 1) {
throw new GameParseException("hitPoints cannot be zero or negative, " + thisErrorMsg());
}
if (m_attackAA < 0 || m_attackAAmaxDieSides < -1 || m_attackAAmaxDieSides > 200 || m_offensiveAttackAA < 0
|| m_offensiveAttackAAmaxDieSides < -1 || m_offensiveAttackAAmaxDieSides > 200) {
throw new GameParseException(
"attackAA or attackAAmaxDieSides or offensiveAttackAA or offensiveAttackAAmaxDieSides is wrong, "
+ thisErrorMsg());
}
if (m_carrierCapacity != -1 && m_carrierCost != -1) {
throw new GameParseException("carrierCost and carrierCapacity cannot be set at same time, " + thisErrorMsg());
}
if (m_transportCost != -1 && m_transportCapacity != -1) {
throw new GameParseException(
"transportCost and transportCapacity cannot be set at same time, " + thisErrorMsg());
}
if (((m_bombingBonus >= 0 || m_bombingMaxDieSides >= 0) && !(m_isStrategicBomber || m_isRocket))
|| (m_bombingBonus < -1 || m_bombingMaxDieSides < -1)
|| (m_bombingBonus > 10000 || m_bombingMaxDieSides > 200)) {
throw new GameParseException("something wrong with bombingBonus or bombingMaxDieSides, " + thisErrorMsg());
}
if (m_maxBuiltPerPlayer < -1) {
throw new GameParseException("maxBuiltPerPlayer cannot be negative, " + thisErrorMsg());
}
if (m_isCombatTransport && m_transportCapacity < 1) {
throw new GameParseException(
"cannot have isCombatTransport on unit without transportCapacity, " + thisErrorMsg());
}
if (m_isSea && m_transportCapacity != -1 && Properties.getTransportCasualtiesRestricted(data)
&& (m_attack > 0 || m_defense > 0) && !m_isCombatTransport) {
throw new GameParseException("Restricted transports cannot have attack or defense, " + thisErrorMsg());
}
if (m_isConstruction
&& (m_constructionType == null || m_constructionType.equals("none") || m_constructionType.equals("")
|| m_constructionsPerTerrPerTypePerTurn < 0 || m_maxConstructionsPerTypePerTerr < 0)) {
throw new GameParseException("Constructions must have constructionType and positive constructionsPerTerrPerType "
+ "and maxConstructionsPerType, " + thisErrorMsg());
}
if (!m_isConstruction
&& (!(m_constructionType == null || m_constructionType.equals("none") || m_constructionType.equals(""))
|| m_constructionsPerTerrPerTypePerTurn >= 0 || m_maxConstructionsPerTypePerTerr >= 0)) {
throw new GameParseException("Constructions must have isConstruction true, " + thisErrorMsg());
}
if (m_constructionsPerTerrPerTypePerTurn > m_maxConstructionsPerTypePerTerr) {
throw new GameParseException(
"Constructions must have constructionsPerTerrPerTypePerTurn Less than maxConstructionsPerTypePerTerr, "
+ thisErrorMsg());
}
if (m_unitPlacementRestrictions != null) {
getListedTerritories(m_unitPlacementRestrictions);
}
if (m_requiresUnits != null) {
for (final String[] combo : m_requiresUnits) {
getListedUnits(combo);
}
}
if ((m_canBeDamaged && m_maxDamage < 1) || (m_canDieFromReachingMaxDamage && m_maxDamage < 1)
|| (!m_canBeDamaged && m_canDieFromReachingMaxDamage)) {
throw new GameParseException(
"something wrong with canBeDamaged or maxDamage or canDieFromReachingMaxDamage or isFactory, "
+ thisErrorMsg());
}
if (m_canInvadeOnlyFrom != null && !m_canInvadeOnlyFrom[0].equals("all")
&& !m_canInvadeOnlyFrom[0].equals("none")) {
for (final String transport : m_canInvadeOnlyFrom) {
final UnitType ut = getData().getUnitTypeList().getUnitType(transport);
if (ut == null) {
throw new GameParseException("No unit called:" + transport + thisErrorMsg());
}
if (ut.getAttachments() == null || ut.getAttachments().isEmpty()) {
throw new GameParseException(transport + " has no attachments, please declare " + transport
+ " in the xml before using it as a transport" + thisErrorMsg());
// Units may be considered transported if they are on a carrier, or if they are paratroopers, or if they are
// mech infantry. The
// "transporter" may not be an actual transport, so we should not check for that here.
}
}
}
if (!m_receivesAbilityWhenWith.isEmpty()) {
for (final String value : m_receivesAbilityWhenWith) {
// first is ability, second is unit that we get it from
final String[] s = value.split(":");
if (s.length != 2) {
throw new GameParseException("receivesAbilityWhenWith must have 2 parts, 'ability:unit'" + thisErrorMsg());
}
if (getData().getUnitTypeList().getUnitType(s[1]) == null) {
throw new GameParseException("receivesAbilityWhenWith, unit does not exist, name:" + s[1] + thisErrorMsg());
}
// currently only supports canBlitz (m_canBlitz)
if (!s[0].equals("canBlitz")) {
throw new GameParseException("receivesAbilityWhenWith so far only supports: canBlitz" + thisErrorMsg());
}
}
}
if (!m_whenCombatDamaged.isEmpty()) {
for (final Tuple<Tuple<Integer, Integer>, Tuple<String, String>> key : m_whenCombatDamaged) {
final String obj = key.getSecond().getFirst();
if (obj.equals(UNITSMAYNOTLANDONCARRIER)) {
continue;
}
if (obj.equals(UNITSMAYNOTLEAVEALLIEDCARRIER)) {
continue;
}
throw new GameParseException("m_whenCombatDamaged so far only supports: " + UNITSMAYNOTLANDONCARRIER + ", "
+ UNITSMAYNOTLEAVEALLIEDCARRIER + thisErrorMsg());
}
}
}
public Collection<UnitType> getListedUnits(final String[] list) {
final List<UnitType> rVal = new ArrayList<>();
for (final String name : list) {
// Validate all units exist
final UnitType ut = getData().getUnitTypeList().getUnitType(name);
if (ut == null) {
throw new IllegalStateException("No unit called: " + name + thisErrorMsg());
}
rVal.add(ut);
}
return rVal;
}
public Collection<Territory> getListedTerritories(final String[] list) throws GameParseException {
final List<Territory> rVal = new ArrayList<>();
for (final String name : list) {
// Validate all territories exist
final Territory territory = getData().getMap().getTerritory(name);
if (territory == null) {
throw new GameParseException("No territory called: " + name + thisErrorMsg());
}
rVal.add(territory);
}
return rVal;
}
private boolean playerHasRockets(final PlayerID player) {
final TechAttachment ta = (TechAttachment) player.getAttachment(Constants.TECH_ATTACHMENT_NAME);
if (ta == null) {
return false;
}
return ta.getRocket();
}
private boolean playerHasMechInf(final PlayerID player) {
final TechAttachment ta = (TechAttachment) player.getAttachment(Constants.TECH_ATTACHMENT_NAME);
if (ta == null) {
return false;
}
return ta.getMechanizedInfantry();
}
private boolean playerHasParatroopers(final PlayerID player) {
final TechAttachment ta = (TechAttachment) player.getAttachment(Constants.TECH_ATTACHMENT_NAME);
if (ta == null) {
return false;
}
return ta.getParatroopers();
}
@Override
public String toString() {
// Any overriding method for toString on an attachment needs to include at least the Class, m_attachedTo, and
// m_name. Or call
// super.toString()
return super.toString();
}
public String allUnitStatsForExporter() {
// should cover ALL fields stored in UnitAttachment
// remember to test for null and fix arrays
// the stats exporter relies on this toString having two spaces after each entry, so do not change this please,
// except to add new
// abilities onto the end
return this.getAttachedTo().toString().replaceFirst("games.strategy.engine.data.", "") + " with:" + " isAir:"
+ m_isAir + " isSea:" + m_isSea + " movement:" + m_movement + " attack:" + m_attack + " defense:"
+ m_defense + " hitPoints:" + m_hitPoints
// + " isFactory:" + m_isFactory
+ " canBlitz:" + m_canBlitz + " artillerySupportable:" + m_artillerySupportable + " artillery:" + m_artillery
+ " unitSupportCount:" + m_unitSupportCount + " attackRolls:" + m_attackRolls + " defenseRolls:"
+ m_defenseRolls + " chooseBestRoll:" + m_chooseBestRoll + " isMarine:" + m_isMarine + " isInfantry:"
+ m_isInfantry + " isLandTransport:" + m_isLandTransport + " isAirTransportable:" + m_isAirTransportable
+ " isAirTransport:" + m_isAirTransport + " isStrategicBomber:" + m_isStrategicBomber + " transportCapacity:"
+ m_transportCapacity + " transportCost:" + m_transportCost + " carrierCapacity:" + m_carrierCapacity
+ " carrierCost:" + m_carrierCost + " isSub:" + m_isSub + " isDestroyer:" + m_isDestroyer + " canBombard:"
+ m_canBombard + " bombard:" + m_bombard + " isAAforCombatOnly:" + m_isAAforCombatOnly
+ " isAAforBombingThisUnitOnly:" + m_isAAforBombingThisUnitOnly + " isAAforFlyOverOnly:"
+ m_isAAforFlyOverOnly + " attackAA:" + m_attackAA + " offensiveAttackAA:" + m_offensiveAttackAA
+ " attackAAmaxDieSides:" + m_attackAAmaxDieSides + " offensiveAttackAAmaxDieSides:"
+ m_offensiveAttackAAmaxDieSides + " maxAAattacks:" + m_maxAAattacks + " maxRoundsAA:" + m_maxRoundsAA
+ " mayOverStackAA:" + m_mayOverStackAA + " damageableAA:" + m_damageableAA + " typeAA:" + m_typeAA
+ " targetsAA:"
+ (m_targetsAA != null ? (m_targetsAA.size() == 0 ? "empty" : m_targetsAA.toString()) : "all air units")
+ " willNotFireIfPresent:"
+ (m_willNotFireIfPresent != null
? (m_willNotFireIfPresent.size() == 0 ? "empty" : m_willNotFireIfPresent.toString()) : "null")
+ " isRocket:" + m_isRocket + " canProduceUnits:" + m_canProduceUnits + " canProduceXUnits:"
+ m_canProduceXUnits + " createsUnitsList:"
+ (m_createsUnitsList != null ? (m_createsUnitsList.size() == 0 ? "empty" : m_createsUnitsList.toString())
: "null")
+ " createsResourcesList:"
+ (m_createsResourcesList != null
? (m_createsResourcesList.size() == 0 ? "empty" : m_createsResourcesList.toString()) : "null")
+ " fuelCost:" + (m_fuelCost != null ? (m_fuelCost.size() == 0 ? "empty" : m_fuelCost.toString()) : "null")
+ " isInfrastructure:" + m_isInfrastructure + " isConstruction:" + m_isConstruction + " constructionType:"
+ m_constructionType + " constructionsPerTerrPerTypePerTurn:" + m_constructionsPerTerrPerTypePerTurn
+ " maxConstructionsPerTypePerTerr:" + m_maxConstructionsPerTypePerTerr + " destroyedWhenCapturedBy:"
+ (m_destroyedWhenCapturedBy != null
? (m_destroyedWhenCapturedBy.size() == 0 ? "empty" : m_destroyedWhenCapturedBy.toString()) : "null")
+ " canBeCapturedOnEnteringBy:"
+ (m_canBeCapturedOnEnteringBy != null
? (m_canBeCapturedOnEnteringBy.size() == 0 ? "empty" : m_canBeCapturedOnEnteringBy.toString()) : "null")
+ " canBeDamaged:" + m_canBeDamaged + " canDieFromReachingMaxDamage:" + m_canDieFromReachingMaxDamage
+ " maxOperationalDamage:" + m_maxOperationalDamage + " maxDamage:" + m_maxDamage
+ " unitPlacementRestrictions:"
+ (m_unitPlacementRestrictions != null
? (m_unitPlacementRestrictions.length == 0 ? "empty" : Arrays.toString(m_unitPlacementRestrictions))
: "null")
+ " requiresUnits:"
+ (m_requiresUnits != null
? (m_requiresUnits.size() == 0 ? "empty" : MyFormatter.listOfArraysToString(m_requiresUnits)) : "null")
+ " consumesUnits:"
+ (m_consumesUnits != null ? (m_consumesUnits.size() == 0 ? "empty" : m_consumesUnits.toString()) : "null")
+ " canOnlyBePlacedInTerritoryValuedAtX:" + m_canOnlyBePlacedInTerritoryValuedAtX + " maxBuiltPerPlayer:"
+ m_maxBuiltPerPlayer + " special:"
+ (m_special != null ? (m_special.size() == 0 ? "empty" : m_special.toString()) : "null") + " isSuicide:"
+ m_isSuicide + " isSuicide:" + m_isSuicide + " isCombatTransport:" + m_isCombatTransport
+ " canInvadeOnlyFrom:"
+ (m_canInvadeOnlyFrom != null
? (m_canInvadeOnlyFrom.length == 0 ? "empty" : Arrays.toString(m_canInvadeOnlyFrom)) : "null")
+ " canBeGivenByTerritoryTo:"
+ (m_canBeGivenByTerritoryTo != null
? (m_canBeGivenByTerritoryTo.size() == 0 ? "empty" : m_canBeGivenByTerritoryTo.toString()) : "null")
+ " receivesAbilityWhenWith:"
+ (m_receivesAbilityWhenWith != null
? (m_receivesAbilityWhenWith.size() == 0 ? "empty" : m_receivesAbilityWhenWith.toString()) : "null")
+ " whenCombatDamaged:"
+ (m_whenCombatDamaged != null ? (m_whenCombatDamaged.size() == 0 ? "empty" : m_whenCombatDamaged.toString())
: "null")
+ " blockade:" + m_blockade + " bombingMaxDieSides:" + m_bombingMaxDieSides + " bombingBonus:"
+ m_bombingBonus + " bombingTargets:" + m_bombingTargets + " givesMovement:"
+ (m_givesMovement != null ? (m_givesMovement.size() == 0 ? "empty" : m_givesMovement.toString()) : "null")
+ " repairsUnits:"
+ (m_repairsUnits != null ? (m_repairsUnits.isEmpty() ? "empty" : m_repairsUnits.toString()) : "null")
+ " canScramble:" + m_canScramble + " maxScrambleDistance:" + m_maxScrambleDistance + " isAirBase:"
+ m_isAirBase + " maxScrambleCount:" + m_maxScrambleCount + " whenCapturedChangesInto:"
+ (m_whenCapturedChangesInto != null
? (m_whenCapturedChangesInto.size() == 0 ? "empty" : m_whenCapturedChangesInto.toString()) : "null")
+ " canIntercept:" + m_canIntercept + " canEscort:" + m_canEscort + " canAirBattle:" + m_canAirBattle
+ " airDefense:" + m_airDefense + " airAttack:" + m_airAttack + " canNotMoveDuringCombatMove:"
+ m_canNotMoveDuringCombatMove + " movementLimit:"
+ (m_movementLimit != null ? m_movementLimit.toString() : "null") + " attackingLimit:"
+ (m_attackingLimit != null ? m_attackingLimit.toString() : "null") + " placementLimit:"
+ (m_placementLimit != null ? m_placementLimit.toString() : "null");
}
public String toStringShortAndOnlyImportantDifferences(final PlayerID player, final boolean useHTML,
final boolean includeAttachedToName) {
// displays everything in a very short form, in English rather than as xml stuff
// shows all except for: m_constructionType, m_constructionsPerTerrPerTypePerTurn, m_maxConstructionsPerTypePerTerr,
// m_canBeGivenByTerritoryTo, m_destroyedWhenCapturedBy, m_canBeCapturedOnEnteringBy
final StringBuilder stats = new StringBuilder();
final UnitType unitType = (UnitType) this.getAttachedTo();
if (includeAttachedToName && unitType != null) {
stats.append(unitType.getName()).append(": ");
}
if (getIsAir()) {
stats.append("Air unit, ");
} else if (getIsSea()) {
stats.append("Sea unit, ");
} else {
stats.append("Land unit, ");
}
final int attackRolls = getAttackRolls(player);
final int defenseRolls = getDefenseRolls(player);
if (getAttack(player) > 0) {
stats.append(attackRolls > 1 ? (attackRolls + "x ") : "").append(getAttack(player)).append(" Attack, ");
}
if (getDefense(player) > 0) {
stats.append(defenseRolls > 1 ? (defenseRolls + "x ") : "").append(getDefense(player)).append(" Defense, ");
}
if (getMovement(player) > 0) {
stats.append(getMovement(player)).append(" Movement, ");
}
if (getHitPoints() > 1) {
stats.append(getHitPoints()).append(" Hitpoints, ");
}
if (getCanProduceUnits() && getCanProduceXUnits() < 0) {
stats.append("can Produce Units Up To Territory Value, ");
} else if (getCanProduceUnits() && getCanProduceXUnits() > 0) {
stats.append("can Produce ").append(getCanProduceXUnits()).append(" Units, ");
}
if (getCreatesUnitsList() != null && getCreatesUnitsList().size() > 0) {
if (getCreatesUnitsList().size() > 4) {
stats.append("Produces ").append(getCreatesUnitsList().totalValues()).append(" Units Each Turn, ");
} else {
stats.append("Produces ");
for (final Entry<UnitType, Integer> entry : getCreatesUnitsList().entrySet()) {
stats.append(entry.getValue()).append("x").append(entry.getKey().getName()).append(" ");
}
stats.append("Each Turn, ");
}
}
if (getCreatesResourcesList() != null && getCreatesResourcesList().size() > 0) {
if (getCreatesResourcesList().size() > 4) {
stats.append("Produces ").append(getCreatesResourcesList().totalValues()).append(" Resources Each Turn, ");
} else {
stats.append("Produces ");
for (final Entry<Resource, Integer> entry : getCreatesResourcesList().entrySet()) {
stats.append(entry.getValue()).append("x").append(entry.getKey().getName()).append(" ");
}
stats.append("Each Turn, ");
}
}
if (getFuelCost() != null && getFuelCost().size() > 0) {
if (getFuelCost().size() > 4) {
stats.append("Uses ").append(m_fuelCost.totalValues()).append(" Resources Each movement point, ");
} else {
stats.append("Uses ");
for (final Entry<Resource, Integer> entry : getFuelCost().entrySet()) {
stats.append(entry.getValue()).append("x").append(entry.getKey().getName()).append(" ");
}
stats.append("Each movement point, ");
}
}
if ((getIsAAforCombatOnly() || getIsAAforBombingThisUnitOnly() || getIsAAforFlyOverOnly())
&& (getAttackAA(player) > 0 || getOffensiveAttackAA(player) > 0)) {
if (getOffensiveAttackAA(player) > 0) {
stats.append(getOffensiveAttackAA(player)).append("/").append(
getOffensiveAttackAAmaxDieSides() != -1 ? getOffensiveAttackAAmaxDieSides() : getData().getDiceSides())
.append(" att ");
}
if (getAttackAA(player) > 0) {
stats.append(getAttackAA(player)).append("/")
.append(getAttackAAmaxDieSides() != -1 ? getAttackAAmaxDieSides() : getData().getDiceSides())
.append(" def ");
}
if (getIsAAforCombatOnly() && getIsAAforBombingThisUnitOnly() && getIsAAforFlyOverOnly()) {
stats.append(getTypeAA()).append(", ");
} else if (getIsAAforCombatOnly() && getIsAAforFlyOverOnly()
&& !games.strategy.triplea.Properties.getAATerritoryRestricted(getData())) {
stats.append(getTypeAA()).append(" for Combat & Move Through, ");
} else if (getIsAAforBombingThisUnitOnly() && getIsAAforFlyOverOnly()
&& !games.strategy.triplea.Properties.getAATerritoryRestricted(getData())) {
stats.append(getTypeAA()).append(" for Raids & Move Through, ");
} else if (getIsAAforCombatOnly()) {
stats.append(getTypeAA()).append(" for Combat, ");
} else if (getIsAAforBombingThisUnitOnly()) {
stats.append(getTypeAA()).append(" for Raids, ");
} else if (getIsAAforFlyOverOnly()) {
stats.append(getTypeAA()).append(" for Move Through, ");
}
if (getMaxAAattacks() > -1) {
stats.append(getMaxAAattacks()).append(" ").append(getTypeAA()).append(" Attacks, ");
}
}
if (getIsRocket() && playerHasRockets(player)) {
stats.append("can Rocket Attack, ");
final int bombingBonus = getBombingBonus();
if ((getBombingMaxDieSides() != -1 || bombingBonus != -1)
&& games.strategy.triplea.Properties.getUseBombingMaxDiceSidesAndBonus(getData())) {
stats.append(bombingBonus != -1 ? bombingBonus + 1 : 1).append("-")
.append(getBombingMaxDieSides() != -1 ? getBombingMaxDieSides() + (bombingBonus != -1 ? bombingBonus : 0)
: getData().getDiceSides() + (bombingBonus != -1 ? bombingBonus : 0))
.append(" Rocket Damage, ");
} else {
stats.append("1-").append(getData().getDiceSides()).append(" Rocket Damage, ");
}
}
// line break
if (useHTML) {
stats.append("<br /> ");
}
if (getIsInfrastructure()) {
stats.append("can be Captured, ");
}
if (getIsConstruction()) {
stats.append("can be Placed Without Factory, ");
}
if ((getCanBeDamaged())
&& games.strategy.triplea.Properties.getDamageFromBombingDoneToUnitsInsteadOfTerritories(getData())) {
stats.append("can be Damaged By Raids, ");
if (getMaxOperationalDamage() > -1) {
stats.append(getMaxOperationalDamage()).append(" Max Operational Damage, ");
}
if ((getCanProduceUnits()) && getCanProduceXUnits() < 0) {
stats.append("Total Damage up to ").append(getMaxDamage() > -1 ? getMaxDamage() : 2)
.append("x Territory Value, ");
} else if (getMaxDamage() > -1) {
stats.append(getMaxDamage()).append(" Max Total Damage, ");
}
if (getCanDieFromReachingMaxDamage()) {
stats.append("will Die If Max Damage Reached, ");
}
} else if (getCanBeDamaged()) {
stats.append("can be Attacked By Raids, ");
}
if (getIsAirBase() && games.strategy.triplea.Properties.getScramble_Rules_In_Effect(getData())) {
stats.append("can Allow Scrambling, ");
}
if (getCanScramble() && games.strategy.triplea.Properties.getScramble_Rules_In_Effect(getData())) {
stats.append("can Scramble ").append(getMaxScrambleDistance() > 0 ? getMaxScrambleDistance() : 1)
.append(" Distance, ");
}
if (getArtillery()) {
stats.append("can Give Attack Bonus To Other Units, ");
} else {
final List<UnitSupportAttachment> supports =
Match.getMatches(UnitSupportAttachment.get(unitType), Matches.UnitSupportAttachmentCanBeUsedByPlayer(player));
if (supports.size() > 0) {
if (supports.size() > 2) {
stats.append("can Modify Power Of Other Units, ");
} else {
for (final UnitSupportAttachment support : supports) {
if (support.getUnitType() == null || support.getUnitType().isEmpty()) {
continue;
}
stats.append("gives ").append(support.getBonus())
.append(support.getStrength() && support.getRoll() ? " Power&Rolls"
: (support.getStrength() ? " Power" : " Rolls"))
.append(" to ").append(support.getNumber())
.append(support.getAllied() && support.getEnemy() ? " Allied&Enemy "
: (support.getAllied() ? " Allied " : " Enemy "))
.append(support.getUnitType().size() > 4 ? "Units"
: MyFormatter.defaultNamedToTextList(support.getUnitType(), "/", false))
.append(" when ")
.append(support.getOffence() && support.getDefence() ? "Att/Def"
: (support.getOffence() ? "Attacking" : "Defending"))
.append(", ");
}
}
}
}
if (getArtillerySupportable()) {
stats.append("can Receive Attack Bonus From Other Units, ");
}
if (getIsMarine() != 0) {
stats.append(getIsMarine()).append(" Amphibious Attack Modifier, ");
}
if (getCanBlitz(player)) {
stats.append("can Blitz, ");
}
if (!getReceivesAbilityWhenWith().isEmpty()) {
if (getReceivesAbilityWhenWith().size() <= 2) {
for (final String ability : getReceivesAbilityWhenWith()) {
stats.append("receives ").append(ability.split(":")[0]).append(" when paired with ")
.append(ability.split(":")[1]).append(", ");
}
} else {
stats.append("receives Abilities When Paired with Other Units, ");
}
}
if (getIsStrategicBomber()) {
stats.append("can Perform Raids, ");
final int bombingBonus = getBombingBonus();
if ((getBombingMaxDieSides() != -1 || bombingBonus != -1)
&& games.strategy.triplea.Properties.getUseBombingMaxDiceSidesAndBonus(getData())) {
stats.append(bombingBonus != -1 ? bombingBonus + 1 : 1).append("-")
.append(getBombingMaxDieSides() != -1 ? getBombingMaxDieSides() + (bombingBonus != -1 ? bombingBonus : 0)
: getData().getDiceSides() + (bombingBonus != -1 ? bombingBonus : 0))
.append(" Raid Damage, ");
} else {
stats.append("1-").append(getData().getDiceSides()).append(" Raid Damage, ");
}
}
final int airAttack = getAirAttack(player);
final int airDefense = getAirDefense(player);
if (airAttack > 0 && (getIsStrategicBomber() || getCanEscort() || getCanAirBattle())) {
stats.append(attackRolls > 1 ? (attackRolls + "x ") : "").append(airAttack).append(" Air Attack, ");
}
if (airDefense > 0 && (getCanIntercept() || getCanAirBattle())) {
stats.append(defenseRolls > 1 ? (defenseRolls + "x ") : "").append(airAttack).append(" Air Defense, ");
}
if (getIsSub()) {
stats.append("is Stealth, ");
}
if (getIsDestroyer()) {
stats.append("is Anti-Stealth, ");
}
if (getCanBombard(player) && getBombard(player) > 0) {
stats.append(getBombard(player)).append(" Bombard, ");
}
if (getBlockade() > 0) {
stats.append(getBlockade()).append(" Blockade Loss, ");
}
if (getIsSuicide()) {
stats.append("Suicide/Munition Unit, ");
}
if (getIsAir() && (getIsKamikaze() || games.strategy.triplea.Properties.getKamikaze_Airplanes(getData()))) {
stats.append("can use All Movement To Attack Target, ");
}
if (getIsInfantry() && playerHasMechInf(player)) {
stats.append("can be Transported By Land, ");
}
if (getIsLandTransport() && playerHasMechInf(player)) {
stats.append("is a Land Transport, ");
}
if (getIsAirTransportable() && playerHasParatroopers(player)) {
stats.append("can be Transported By Air, ");
}
if (getIsAirTransport() && playerHasParatroopers(player)) {
stats.append("is an Air Transport, ");
}
if (getIsCombatTransport() && getTransportCapacity() > 0) {
stats.append("is a Combat Transport, ");
} else if (getTransportCapacity() > 0 && getIsSea()) {
stats.append("is a Sea Transport, ");
}
if (getTransportCost() > -1) {
stats.append(getTransportCost()).append(" Transporting Cost, ");
}
if (getTransportCapacity() > 0 && getIsSea()) {
stats.append(getTransportCapacity()).append(" Transporting Capacity, ");
} else if (getTransportCapacity() > 0 && getIsAir() && playerHasParatroopers(player)) {
stats.append(getTransportCapacity()).append(" Transporting Capacity, ");
} else if (getTransportCapacity() > 0 && playerHasMechInf(player) && !getIsSea() && !getIsAir()) {
stats.append(getTransportCapacity()).append(" Transporting Capacity, ");
}
if (getCarrierCost() > -1) {
stats.append(getCarrierCost()).append(" Carrier Cost, ");
}
if (getCarrierCapacity() > 0) {
stats.append(getCarrierCapacity()).append(" Carrier Capacity, ");
}
if (!getWhenCombatDamaged().isEmpty()) {
stats.append("when hit this unit loses certain abilities, ");
}
// line break
if (useHTML) {
stats.append("<br /> ");
}
if (getMaxBuiltPerPlayer() > -1) {
stats.append(getMaxBuiltPerPlayer()).append(" Max Built Allowed, ");
}
if (getRepairsUnits() != null && !getRepairsUnits().isEmpty()
&& games.strategy.triplea.Properties.getTwoHitPointUnitsRequireRepairFacilities(getData())
&& (games.strategy.triplea.Properties.getBattleshipsRepairAtBeginningOfRound(getData())
|| games.strategy.triplea.Properties.getBattleshipsRepairAtEndOfRound(getData()))) {
if (getRepairsUnits().size() <= 4) {
stats.append("can Repair: ")
.append(MyFormatter.integerDefaultNamedMapToString(getRepairsUnits(), " ", "=", false)).append(", ");
} else {
stats.append("can Repair Some Units, ");
}
}
if (getGivesMovement() != null && getGivesMovement().totalValues() > 0
&& games.strategy.triplea.Properties.getUnitsMayGiveBonusMovement(getData())) {
if (getGivesMovement().size() <= 4) {
stats.append("can Modify Unit Movement: ")
.append(MyFormatter.integerDefaultNamedMapToString(getGivesMovement(), " ", "=", false)).append(", ");
} else {
stats.append("can Modify Unit Movement, ");
}
}
if (getConsumesUnits() != null && getConsumesUnits().totalValues() == 1) {
stats.append("unit is an Upgrade Of ").append(getConsumesUnits().keySet().iterator().next().getName())
.append(", ");
} else if (getConsumesUnits() != null && getConsumesUnits().totalValues() > 0) {
if (getConsumesUnits().size() <= 4) {
stats.append("unit Consumes On Placement: ")
.append(MyFormatter.integerDefaultNamedMapToString(getConsumesUnits(), " ", "x", true)).append(", ");
} else {
stats.append("unit Consumes Other Units On Placement, ");
}
}
if (getRequiresUnits() != null && getRequiresUnits().size() > 0
&& games.strategy.triplea.Properties.getUnitPlacementRestrictions(getData())) {
final List<String> totalUnitsListed = new ArrayList<>();
for (final String[] list : getRequiresUnits()) {
totalUnitsListed.addAll(Arrays.asList(list));
}
if (totalUnitsListed.size() > 4) {
stats.append("unit Requires Other Units Present To Be Placed, ");
} else {
stats.append("unit can only be Placed Where There Is: ");
final Iterator<String[]> requiredIter = getRequiresUnits().iterator();
while (requiredIter.hasNext()) {
final String[] required = requiredIter.next();
if (required.length == 1) {
stats.append(required[0]);
} else {
stats.append(Arrays.toString(required));
}
if (requiredIter.hasNext()) {
stats.append(" Or ");
}
}
stats.append(", ");
}
}
if (getUnitPlacementRestrictions() != null
&& games.strategy.triplea.Properties.getUnitPlacementRestrictions(getData())) {
stats.append("has Placement Restrictions, ");
}
if (getCanOnlyBePlacedInTerritoryValuedAtX() > 0
&& games.strategy.triplea.Properties.getUnitPlacementRestrictions(getData())) {
stats.append("must be Placed In Territory Valued >=").append(getCanOnlyBePlacedInTerritoryValuedAtX())
.append(", ");
}
if (getCanNotMoveDuringCombatMove()) {
stats.append("cannot Combat Move, ");
}
if (getMovementLimit() != null) {
if (getMovementLimit().getFirst() == Integer.MAX_VALUE
&& (getIsAAforBombingThisUnitOnly() || getIsAAforCombatOnly())
&& !(games.strategy.triplea.Properties.getWW2V2(getData())
|| games.strategy.triplea.Properties.getWW2V3(getData())
|| games.strategy.triplea.Properties.getMultipleAAPerTerritory(getData()))) {
stats.append("max of 1 ").append(getMovementLimit().getSecond()).append(" moving per territory, ");
} else if (getMovementLimit().getFirst() < 10000) {
stats.append("max of ").append(getMovementLimit().getFirst()).append(" ").append(getMovementLimit().getSecond())
.append(" moving per territory, ");
}
}
if (getAttackingLimit() != null) {
if (getAttackingLimit().getFirst() == Integer.MAX_VALUE
&& (getIsAAforBombingThisUnitOnly() || getIsAAforCombatOnly())
&& !(games.strategy.triplea.Properties.getWW2V2(getData())
|| games.strategy.triplea.Properties.getWW2V3(getData())
|| games.strategy.triplea.Properties.getMultipleAAPerTerritory(getData()))) {
stats.append("max of 1 ").append(getAttackingLimit().getSecond()).append(" attacking per territory, ");
} else if (getAttackingLimit().getFirst() < 10000) {
stats.append("max of ").append(getAttackingLimit().getFirst()).append(" ")
.append(getAttackingLimit().getSecond()).append(" attacking per territory, ");
}
}
if (getPlacementLimit() != null) {
if (getPlacementLimit().getFirst() == Integer.MAX_VALUE
&& (getIsAAforBombingThisUnitOnly() || getIsAAforCombatOnly())
&& !(games.strategy.triplea.Properties.getWW2V2(getData())
|| games.strategy.triplea.Properties.getWW2V3(getData())
|| games.strategy.triplea.Properties.getMultipleAAPerTerritory(getData()))) {
stats.append("max of 1 ").append(getPlacementLimit().getSecond()).append(" placed per territory, ");
} else if (getPlacementLimit().getFirst() < 10000) {
stats.append("max of ").append(getPlacementLimit().getFirst()).append(" ")
.append(getPlacementLimit().getSecond()).append(" placed per territory, ");
}
}
if (stats.indexOf(", ") > -1) {
stats.delete(stats.lastIndexOf(", "), stats.length() - 1);
}
return stats.toString();
}
/**
* @deprecated does nothing, kept to avoid breaking maps, do not remove.
*/
@Deprecated
@GameProperty(xmlProperty = true, gameProperty = false, adds = false)
public void setIsParatroop(final String s) {}
/**
* @deprecated does nothing, used to keep compatibility with older xml files, do not remove.
*/
@Deprecated
@GameProperty(xmlProperty = true, gameProperty = false, adds = false)
public void setIsMechanized(final String s) {}
}