package games.strategy.triplea.attachments;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
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.MapSupport;
import games.strategy.triplea.delegate.Matches;
import games.strategy.util.IntegerMap;
import games.strategy.util.Match;
import games.strategy.util.Triple;
@MapSupport
public class PlayerAttachment extends DefaultAttachment {
private static final long serialVersionUID = 1880755875866426270L;
/**
* Convenience method. can be null
*/
public static PlayerAttachment get(final PlayerID p) {
// allow null
return p.getPlayerAttachment();
}
public static PlayerAttachment get(final PlayerID p, final String nameOfAttachment) {
final PlayerAttachment rVal = p.getPlayerAttachment(); // (PlayerAttachment) p.getAttachment(nameOfAttachment);
if (rVal == null) {
throw new IllegalStateException("No player attachment for:" + p.getName() + " with name:" + nameOfAttachment);
}
return rVal;
}
private int m_vps = 0;
// need to store some data during a turn
private int m_captureVps = 0;
// number of capitals needed before we lose all our money
private int m_retainCapitalNumber = 1;
// number of capitals needed before we lose ability to gain money and produce units
private int m_retainCapitalProduceNumber = 1;
private ArrayList<PlayerID> m_giveUnitControl = new ArrayList<>();
private ArrayList<PlayerID> m_captureUnitOnEnteringBy = new ArrayList<>();
// gives any technology researched to this player automatically
private ArrayList<PlayerID> m_shareTechnology = new ArrayList<>();
// allows these players to help pay for technology
private ArrayList<PlayerID> m_helpPayTechCost = new ArrayList<>();
// do we lose our money and have it disappear or is that money captured?
private boolean m_destroysPUs = false;
// are we immune to being blockaded?
private boolean m_immuneToBlockade = false;
// what resources can be used for suicide attacks, and
private IntegerMap<Resource> m_suicideAttackResources = new IntegerMap<>();
// at what attack power
// what can be hit by suicide attacks
private HashSet<UnitType> m_suicideAttackTargets = null;
// placement limits on a flexible per player basis
private HashSet<Triple<Integer, String, HashSet<UnitType>>> m_placementLimit =
new HashSet<>();
// movement limits on a flexible per player basis
private HashSet<Triple<Integer, String, HashSet<UnitType>>> m_movementLimit =
new HashSet<>();
// attacking limits on a flexible per player basis
private HashSet<Triple<Integer, String, HashSet<UnitType>>> m_attackingLimit =
new HashSet<>();
/** Creates new PlayerAttachment. */
public PlayerAttachment(final String name, final Attachable attachable, final GameData gameData) {
super(name, attachable, gameData);
}
/**
* 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 setPlacementLimit(final String value) throws GameParseException {
final String[] s = value.split(":");
if (s.length < 3) {
throw new GameParseException("placementLimit must have 3 parts: count, type, unit list" + 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 type must be: owned, allied, or total" + thisErrorMsg());
}
final HashSet<UnitType> types = new HashSet<>();
if (s[2].equalsIgnoreCase("all")) {
types.addAll(getData().getUnitTypeList().getAllUnitTypes());
} else {
for (int i = 2; i < s.length; i++) {
final UnitType ut = getData().getUnitTypeList().getUnitType(s[i]);
if (ut == null) {
throw new GameParseException("No unit called: " + s[i] + thisErrorMsg());
} else {
types.add(ut);
}
}
}
m_placementLimit.add(Triple.of(max, s[1], types));
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setPlacementLimit(final HashSet<Triple<Integer, String, HashSet<UnitType>>> value) {
m_placementLimit = value;
}
public HashSet<Triple<Integer, String, HashSet<UnitType>>> getPlacementLimit() {
return m_placementLimit;
}
public void clearPlacementLimit() {
m_placementLimit.clear();
}
public void resetPlacementLimit() {
m_placementLimit = new HashSet<>();
}
/**
* 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 setMovementLimit(final String value) throws GameParseException {
final String[] s = value.split(":");
if (s.length < 3) {
throw new GameParseException("movementLimit must have 3 parts: count, type, unit list" + 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 type must be: owned, allied, or total" + thisErrorMsg());
}
final HashSet<UnitType> types = new HashSet<>();
if (s[2].equalsIgnoreCase("all")) {
types.addAll(getData().getUnitTypeList().getAllUnitTypes());
} else {
for (int i = 2; i < s.length; i++) {
final UnitType ut = getData().getUnitTypeList().getUnitType(s[i]);
if (ut == null) {
throw new GameParseException("No unit called: " + s[i] + thisErrorMsg());
} else {
types.add(ut);
}
}
}
m_movementLimit.add(Triple.of(max, s[1], types));
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setMovementLimit(final HashSet<Triple<Integer, String, HashSet<UnitType>>> value) {
m_movementLimit = value;
}
public HashSet<Triple<Integer, String, HashSet<UnitType>>> getMovementLimit() {
return m_movementLimit;
}
public void clearMovementLimit() {
m_movementLimit.clear();
}
public void resetMovementLimit() {
m_movementLimit = new HashSet<>();
}
/**
* 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 setAttackingLimit(final String value) throws GameParseException {
final String[] s = value.split(":");
if (s.length < 3) {
throw new GameParseException("attackingLimit must have 3 parts: count, type, unit list" + 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 type must be: owned, allied, or total" + thisErrorMsg());
}
final HashSet<UnitType> types = new HashSet<>();
if (s[2].equalsIgnoreCase("all")) {
types.addAll(getData().getUnitTypeList().getAllUnitTypes());
} else {
for (int i = 2; i < s.length; i++) {
final UnitType ut = getData().getUnitTypeList().getUnitType(s[i]);
if (ut == null) {
throw new GameParseException("No unit called: " + s[i] + thisErrorMsg());
} else {
types.add(ut);
}
}
}
m_attackingLimit.add(Triple.of(max, s[1], types));
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setAttackingLimit(final HashSet<Triple<Integer, String, HashSet<UnitType>>> value) {
m_attackingLimit = value;
}
public HashSet<Triple<Integer, String, HashSet<UnitType>>> getAttackingLimit() {
return m_attackingLimit;
}
public void clearAttackingLimit() {
m_attackingLimit.clear();
}
public void resetAttackingLimit() {
m_attackingLimit = new HashSet<>();
}
public static boolean getCanTheseUnitsMoveWithoutViolatingStackingLimit(final String limitType,
final Collection<Unit> unitsMoving, final Territory toMoveInto, final PlayerID owner, final GameData data) {
final PlayerAttachment pa = PlayerAttachment.get(owner);
if (pa == null) {
return true;
}
final HashSet<Triple<Integer, String, HashSet<UnitType>>> stackingLimits;
if (limitType.equals("movementLimit")) {
stackingLimits = pa.getMovementLimit();
} else if (limitType.equals("attackingLimit")) {
stackingLimits = pa.getAttackingLimit();
} else if (limitType.equals("placementLimit")) {
stackingLimits = pa.getPlacementLimit();
} else {
throw new IllegalStateException(
"getCanTheseUnitsMoveWithoutViolatingStackingLimit does not allow limitType: " + limitType);
}
if (stackingLimits.isEmpty()) {
return true;
}
for (final Triple<Integer, String, HashSet<UnitType>> currentLimit : stackingLimits) {
// first make a copy of unitsMoving
final Collection<Unit> copyUnitsMoving = new ArrayList<>(unitsMoving);
final int max = currentLimit.getFirst();
final String type = currentLimit.getSecond();
final HashSet<UnitType> unitsToTest = currentLimit.getThird();
final Collection<Unit> currentInTerritory = toMoveInto.getUnits().getUnits();
// first remove units that do not apply to our current type
if (type.equals("owned")) {
currentInTerritory.removeAll(Match.getMatches(currentInTerritory, Matches.unitIsOwnedBy(owner).invert()));
copyUnitsMoving.removeAll(Match.getMatches(copyUnitsMoving, Matches.unitIsOwnedBy(owner).invert()));
} else if (type.equals("allied")) {
currentInTerritory.removeAll(Match.getMatches(currentInTerritory, Matches.alliedUnit(owner, data).invert()));
copyUnitsMoving.removeAll(Match.getMatches(copyUnitsMoving, Matches.alliedUnit(owner, data).invert()));
}
// else if (type.equals("total"))
// now remove units that are not part of our list
currentInTerritory.retainAll(Match.getMatches(currentInTerritory, Matches.unitIsOfTypes(unitsToTest)));
copyUnitsMoving.retainAll(Match.getMatches(copyUnitsMoving, Matches.unitIsOfTypes(unitsToTest)));
// now test
if (max < (currentInTerritory.size() + copyUnitsMoving.size())) {
return false;
}
}
return true;
}
/**
* 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 setSuicideAttackTargets(final String value) throws GameParseException {
if (value == null) {
m_suicideAttackTargets = null;
return;
}
if (m_suicideAttackTargets == null) {
m_suicideAttackTargets = 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("suicideAttackTargets: no such unit called " + u + thisErrorMsg());
}
m_suicideAttackTargets.add(ut);
}
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setSuicideAttackTargets(final HashSet<UnitType> value) {
m_suicideAttackTargets = value;
}
public HashSet<UnitType> getSuicideAttackTargets() {
return m_suicideAttackTargets;
}
public void clearSuicideAttackTargets() {
m_suicideAttackTargets.clear();
}
public void resetSuicideAttackTargets() {
m_suicideAttackTargets = 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 setSuicideAttackResources(final String value) throws GameParseException {
final String[] s = value.split(":");
if (s.length != 2) {
throw new GameParseException("suicideAttackResources must have exactly 2 fields" + thisErrorMsg());
}
final int attackValue = getInt(s[0]);
if (attackValue < 0) {
throw new GameParseException("suicideAttackResources attack value must be positive" + thisErrorMsg());
}
final Resource r = getData().getResourceList().getResource(s[1]);
if (r == null) {
throw new GameParseException("no such resource: " + s[1] + thisErrorMsg());
}
m_suicideAttackResources.put(r, attackValue);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setSuicideAttackResources(final IntegerMap<Resource> value) {
m_suicideAttackResources = value;
}
public IntegerMap<Resource> getSuicideAttackResources() {
return m_suicideAttackResources;
}
public void clearSuicideAttackResources() {
m_suicideAttackResources.clear();
}
public void resetSuicideAttackResources() {
m_suicideAttackResources = new IntegerMap<>();
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setVps(final String value) {
m_vps = getInt(value);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setVps(final Integer value) {
m_vps = value;
}
public int getVps() {
return m_vps;
}
public void resetVps() {
m_vps = 0;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setCaptureVps(final String value) {
m_captureVps = getInt(value);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setCaptureVps(final Integer value) {
m_captureVps = value;
}
public int getCaptureVps() {
return m_captureVps;
}
public void resetCaptureVps() {
m_captureVps = 0;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setRetainCapitalNumber(final String value) {
m_retainCapitalNumber = getInt(value);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setRetainCapitalNumber(final Integer value) {
m_retainCapitalNumber = value;
}
public int getRetainCapitalNumber() {
return m_retainCapitalNumber;
}
public void resetRetainCapitalNumber() {
m_retainCapitalNumber = 1;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setRetainCapitalProduceNumber(final String value) {
m_retainCapitalProduceNumber = getInt(value);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setRetainCapitalProduceNumber(final Integer value) {
m_retainCapitalProduceNumber = value;
}
public int getRetainCapitalProduceNumber() {
return m_retainCapitalProduceNumber;
}
public void resetRetainCapitalProduceNumber() {
m_retainCapitalProduceNumber = 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 setGiveUnitControl(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_giveUnitControl.add(tempPlayer);
} else {
throw new GameParseException("No player named: " + name + thisErrorMsg());
}
}
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setGiveUnitControl(final ArrayList<PlayerID> value) {
m_giveUnitControl = value;
}
public ArrayList<PlayerID> getGiveUnitControl() {
return m_giveUnitControl;
}
public void clearGiveUnitControl() {
m_giveUnitControl.clear();
}
public void resetGiveUnitControl() {
m_giveUnitControl = 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 setCaptureUnitOnEnteringBy(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_captureUnitOnEnteringBy.add(tempPlayer);
} else {
throw new GameParseException("No player named: " + name + thisErrorMsg());
}
}
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setCaptureUnitOnEnteringBy(final ArrayList<PlayerID> value) {
m_captureUnitOnEnteringBy = value;
}
public ArrayList<PlayerID> getCaptureUnitOnEnteringBy() {
return m_captureUnitOnEnteringBy;
}
public void clearCaptureUnitOnEnteringBy() {
m_captureUnitOnEnteringBy.clear();
}
public void resetCaptureUnitOnEnteringBy() {
m_captureUnitOnEnteringBy = 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 setShareTechnology(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_shareTechnology.add(tempPlayer);
} else {
throw new GameParseException("No player named: " + name + thisErrorMsg());
}
}
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setShareTechnology(final ArrayList<PlayerID> value) {
m_shareTechnology = value;
}
public ArrayList<PlayerID> getShareTechnology() {
return m_shareTechnology;
}
public void clearShareTechnology() {
m_shareTechnology.clear();
}
public void resetShareTechnology() {
m_shareTechnology = 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 setHelpPayTechCost(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_helpPayTechCost.add(tempPlayer);
} else {
throw new GameParseException("No player named: " + name + thisErrorMsg());
}
}
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setHelpPayTechCost(final ArrayList<PlayerID> value) {
m_helpPayTechCost = value;
}
public ArrayList<PlayerID> getHelpPayTechCost() {
return m_helpPayTechCost;
}
public void clearHelpPayTechCost() {
m_helpPayTechCost.clear();
}
public void resetHelpPayTechCost() {
m_helpPayTechCost = new ArrayList<>();
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setDestroysPUs(final String value) {
m_destroysPUs = getBool(value);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setDestroysPUs(final Boolean value) {
m_destroysPUs = value;
}
public boolean getDestroysPUs() {
return m_destroysPUs;
}
public void resetDestroysPUs() {
m_destroysPUs = false;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setImmuneToBlockade(final String value) {
m_immuneToBlockade = getBool(value);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setImmuneToBlockade(final Boolean value) {
m_immuneToBlockade = value;
}
public boolean getImmuneToBlockade() {
return m_immuneToBlockade;
}
public void resetImmuneToBlockade() {
m_immuneToBlockade = false;
}
@Override
public void validate(final GameData data) throws GameParseException {}
}