package games.strategy.triplea.attachments;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
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.ResourceCollection;
import games.strategy.engine.data.Territory;
import games.strategy.engine.data.TerritoryEffect;
import games.strategy.engine.data.annotations.GameProperty;
import games.strategy.triplea.Constants;
import games.strategy.triplea.MapSupport;
import games.strategy.triplea.formatter.MyFormatter;
@MapSupport
public class TerritoryAttachment extends DefaultAttachment {
private static final long serialVersionUID = 9102862080104655281L;
public static boolean doWeHaveEnoughCapitalsToProduce(final PlayerID player, final GameData data) {
final List<Territory> capitalsListOriginal =
new ArrayList<>(TerritoryAttachment.getAllCapitals(player, data));
final List<Territory> capitalsListOwned =
new ArrayList<>(TerritoryAttachment.getAllCurrentlyOwnedCapitals(player, data));
final PlayerAttachment pa = PlayerAttachment.get(player);
if (pa == null) {
if (!capitalsListOriginal.isEmpty() && capitalsListOwned.isEmpty()) {
return false;
}
} else {
if (pa.getRetainCapitalProduceNumber() > capitalsListOwned.size()) {
return false;
}
}
return true;
}
/**
* If we own one of our capitals, return the first one found, otherwise return the first capital we find that we don't
* own.
* If a capital has no neighbor connections, it will be sent last.
*/
public static Territory getFirstOwnedCapitalOrFirstUnownedCapital(final PlayerID player, final GameData data) {
final List<Territory> capitals = new ArrayList<>();
final List<Territory> noNeighborCapitals = new ArrayList<>();
for (final Territory current : data.getMap().getTerritories()) {
final TerritoryAttachment ta = TerritoryAttachment.get(current);
if (ta != null && ta.getCapital() != null) {
final PlayerID whoseCapital = data.getPlayerList().getPlayerID(ta.getCapital());
if (whoseCapital == null) {
throw new IllegalStateException("Invalid capital for player name:" + ta.getCapital());
}
if (player.equals(whoseCapital)) {
if (player.equals(current.getOwner())) {
if (data.getMap().getNeighbors(current).size() > 0) {
return current;
} else {
noNeighborCapitals.add(current);
}
} else {
capitals.add(current);
}
}
}
}
if (!capitals.isEmpty()) {
return capitals.iterator().next();
}
if (!noNeighborCapitals.isEmpty()) {
return noNeighborCapitals.iterator().next();
}
// Added check for optional players- no error thrown for them
if (player.getOptional()) {
return null;
}
throw new IllegalStateException("Capital not found for:" + player);
}
/**
* will return empty list if none controlled, never returns null.
*/
public static List<Territory> getAllCapitals(final PlayerID player, final GameData data) {
final List<Territory> capitals = new ArrayList<>();
for (final Territory current : data.getMap().getTerritories()) {
final TerritoryAttachment ta = TerritoryAttachment.get(current);
if (ta != null && ta.getCapital() != null) {
final PlayerID whoseCapital = data.getPlayerList().getPlayerID(ta.getCapital());
if (whoseCapital == null) {
throw new IllegalStateException("Invalid capital for player name:" + ta.getCapital());
}
if (player.equals(whoseCapital)) {
capitals.add(current);
}
}
}
if (!capitals.isEmpty()) {
return capitals;
}
// Added check for optional players- no error thrown for them
if (player.getOptional()) {
return capitals;
}
throw new IllegalStateException("Capital not found for:" + player);
}
/**
* will return empty list if none controlled, never returns null.
*/
public static List<Territory> getAllCurrentlyOwnedCapitals(final PlayerID player, final GameData data) {
final List<Territory> capitals = new ArrayList<>();
for (final Territory current : data.getMap().getTerritories()) {
final TerritoryAttachment ta = TerritoryAttachment.get(current);
if (ta != null && ta.getCapital() != null) {
final PlayerID whoseCapital = data.getPlayerList().getPlayerID(ta.getCapital());
if (whoseCapital == null) {
throw new IllegalStateException("Invalid capital for player name:" + ta.getCapital());
}
if (player.equals(whoseCapital) && player.equals(current.getOwner())) {
capitals.add(current);
}
}
}
return capitals;
}
/**
* Convenience method. Can return null.
*/
public static TerritoryAttachment get(final Territory t) {
return (TerritoryAttachment) t.getAttachment(Constants.TERRITORY_ATTACHMENT_NAME);
}
public static TerritoryAttachment get(final Territory t, final String nameOfAttachment) {
final TerritoryAttachment rVal = (TerritoryAttachment) t.getAttachment(nameOfAttachment);
if (rVal == null && !t.isWater()) {
throw new IllegalStateException("No territory attachment for:" + t.getName() + " with name:" + nameOfAttachment);
}
return rVal;
}
/**
* Convenience method since TerritoryAttachment.get could return null.
*/
public static int getProduction(final Territory t) {
final TerritoryAttachment ta = TerritoryAttachment.get(t);
if (ta == null) {
return 0;
}
return ta.getProduction();
}
/**
* Convenience method since TerritoryAttachment.get could return null.
*/
public static int getUnitProduction(final Territory t) {
final TerritoryAttachment ta = TerritoryAttachment.get(t);
if (ta == null) {
return 0;
}
return ta.getUnitProduction();
}
private String m_capital = null;
private boolean m_originalFactory = false;
// "setProduction" will set both m_production and m_unitProduction.
// While "setProductionOnly" sets only m_production.
private int m_production = 0;
private int m_victoryCity = 0;
private boolean m_isImpassable = false;
private PlayerID m_originalOwner = null;
private boolean m_convoyRoute = false;
private HashSet<Territory> m_convoyAttached = new HashSet<>();
private ArrayList<PlayerID> m_changeUnitOwners = new ArrayList<>();
private ArrayList<PlayerID> m_captureUnitOnEnteringBy = new ArrayList<>();
private boolean m_navalBase = false;
private boolean m_airBase = false;
private boolean m_kamikazeZone = false;
private int m_unitProduction = 0;
private boolean m_blockadeZone = false;
private ArrayList<TerritoryEffect> m_territoryEffect = new ArrayList<>();
private ArrayList<String> m_whenCapturedByGoesTo = new ArrayList<>();
private ResourceCollection m_resources = null;
/** Creates new TerritoryAttachment. */
public TerritoryAttachment(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 setResources(final String value) throws GameParseException {
if (value == null) {
m_resources = null;
return;
}
if (m_resources == null) {
m_resources = new ResourceCollection(getData());
}
final String[] s = value.split(":");
final int amount = getInt(s[0]);
if (s[1].equals(Constants.PUS)) {
throw new GameParseException("Please set PUs using production, not resource" + thisErrorMsg());
}
final Resource resource = getData().getResourceList().getResource(s[1]);
if (resource == null) {
throw new GameParseException("No resource named: " + s[1] + thisErrorMsg());
}
m_resources.putResource(resource, amount);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setResources(final ResourceCollection value) {
m_resources = value;
}
public ResourceCollection getResources() {
return m_resources;
}
public void clearResources() {
m_resources = new ResourceCollection(getData());
}
public void resetResources() {
m_resources = null;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setIsImpassable(final String value) {
m_isImpassable = getBool(value);
}
public boolean getIsImpassable() {
return m_isImpassable;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setCapital(final String value) throws GameParseException {
if (value == null) {
m_capital = null;
return;
}
final PlayerID p = getData().getPlayerList().getPlayerID(value);
if (p == null) {
throw new GameParseException("No Player named: " + value + thisErrorMsg());
}
m_capital = value;
}
public boolean isCapital() {
return m_capital != null;
}
public String getCapital() {
return m_capital;
}
public void resetCapital() {
m_capital = null;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setVictoryCity(final String value) {
m_victoryCity = getInt(value);
}
public int getVictoryCity() {
return m_victoryCity;
}
public void resetVictoryCity() {
m_victoryCity = 0;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setOriginalFactory(final String value) {
m_originalFactory = getBool(value);
}
public boolean getOriginalFactory() {
return m_originalFactory;
}
/**
* setProduction (or just "production" in a map xml) sets both the m_production AND the m_unitProduction of a
* territory to be equal to the
* String value passed.
*/
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setProduction(final String value) {
m_production = getInt(value);
// do NOT remove. unitProduction should always default to production
m_unitProduction = m_production;
}
/**
* Sets only m_production.
*/
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setProductionOnly(final String value) {
m_production = getInt(value);
}
public int getProduction() {
return m_production;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setUnitProduction(final String value) {
m_unitProduction = Integer.parseInt(value);
}
public int getUnitProduction() {
return m_unitProduction;
}
/**
* Should not be set by a game xml during attachment parsing, but CAN be set by initialization parsing and/or Property
* Utils.
*/
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setOriginalOwner(final PlayerID player) {
m_originalOwner = player;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setOriginalOwner(final String player) throws GameParseException {
if (player == null) {
m_originalOwner = null;
}
final PlayerID tempPlayer = getData().getPlayerList().getPlayerID(player);
if (tempPlayer == null) {
throw new GameParseException("No player named: " + player + thisErrorMsg());
}
m_originalOwner = tempPlayer;
}
public PlayerID getOriginalOwner() {
return m_originalOwner;
}
public void resetOriginalOwner() {
m_originalOwner = null;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setConvoyRoute(final String value) {
m_convoyRoute = getBool(value);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setConvoyRoute(final Boolean value) {
m_convoyRoute = value;
}
public boolean getConvoyRoute() {
return m_convoyRoute;
}
public void resetConvoyRoute() {
m_convoyRoute = 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 setChangeUnitOwners(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_changeUnitOwners.add(tempPlayer);
} else if (name.equalsIgnoreCase("true") || name.equalsIgnoreCase("false")) {
m_changeUnitOwners.clear();
} else {
throw new GameParseException("No player named: " + name + thisErrorMsg());
}
}
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setChangeUnitOwners(final ArrayList<PlayerID> value) {
m_changeUnitOwners = value;
}
public ArrayList<PlayerID> getChangeUnitOwners() {
return m_changeUnitOwners;
}
public void clearChangeUnitOwners() {
m_changeUnitOwners.clear();
}
public void resetChangeUnitOwners() {
m_changeUnitOwners = 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 setWhenCapturedByGoesTo(final String value) throws GameParseException {
final String[] s = value.split(":");
if (s.length != 2) {
throw new GameParseException(
"whenCapturedByGoesTo must have 2 player names separated by a colon" + thisErrorMsg());
}
for (final String name : s) {
final PlayerID player = getData().getPlayerList().getPlayerID(name);
if (player == null) {
throw new GameParseException("No player named: " + name + thisErrorMsg());
}
}
m_whenCapturedByGoesTo.add(value);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setWhenCapturedByGoesTo(final ArrayList<String> value) {
m_whenCapturedByGoesTo = value;
}
public ArrayList<String> getWhenCapturedByGoesTo() {
return m_whenCapturedByGoesTo;
}
public void clearWhenCapturedByGoesTo() {
m_whenCapturedByGoesTo.clear();
}
public void resetWhenCapturedByGoesTo() {
m_whenCapturedByGoesTo = 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 setTerritoryEffect(final String value) throws GameParseException {
final String[] s = value.split(":");
for (final String name : s) {
final TerritoryEffect effect = getData().getTerritoryEffectList().get(name);
if (effect != null) {
m_territoryEffect.add(effect);
} else {
throw new GameParseException("No TerritoryEffect named: " + name + thisErrorMsg());
}
}
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setTerritoryEffect(final ArrayList<TerritoryEffect> value) {
m_territoryEffect = value;
}
public ArrayList<TerritoryEffect> getTerritoryEffect() {
return m_territoryEffect;
}
public void clearTerritoryEffect() {
m_territoryEffect.clear();
}
public void resetTerritoryEffect() {
m_territoryEffect = 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 setConvoyAttached(final String value) throws GameParseException {
if (value.length() <= 0) {
return;
}
for (final String subString : value.split(":")) {
final Territory territory = getData().getMap().getTerritory(subString);
if (territory == null) {
throw new GameParseException("No territory called:" + subString + thisErrorMsg());
}
m_convoyAttached.add(territory);
}
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setConvoyAttached(final HashSet<Territory> value) {
m_convoyAttached = value;
}
public HashSet<Territory> getConvoyAttached() {
return m_convoyAttached;
}
public void clearConvoyAttached() {
m_convoyAttached.clear();
}
public void resetConvoyAttached() {
m_convoyAttached = new HashSet<>();
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setNavalBase(final String value) {
m_navalBase = getBool(value);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setNavalBase(final Boolean value) {
m_navalBase = value;
}
public boolean getNavalBase() {
return m_navalBase;
}
public void resetNavalBase() {
m_navalBase = false;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setAirBase(final String value) {
m_airBase = getBool(value);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setAirBase(final Boolean value) {
m_airBase = value;
}
public boolean getAirBase() {
return m_airBase;
}
public void resetAirBase() {
m_airBase = false;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setKamikazeZone(final String value) {
m_kamikazeZone = getBool(value);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setKamikazeZone(final Boolean value) {
m_kamikazeZone = value;
}
public boolean getKamikazeZone() {
return m_kamikazeZone;
}
public void resetKamikazeZone() {
m_kamikazeZone = false;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setBlockadeZone(final String value) {
m_blockadeZone = getBool(value);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setBlockadeZone(final Boolean value) {
m_blockadeZone = value;
}
public boolean getBlockadeZone() {
return m_blockadeZone;
}
public void resetBlockadeZone() {
m_blockadeZone = false;
}
public static Set<Territory> getWhatTerritoriesThisIsUsedInConvoysFor(final Territory t, final GameData data) {
final Set<Territory> rVal = new HashSet<>();
final TerritoryAttachment ta = TerritoryAttachment.get(t);
if (ta == null || !ta.getConvoyRoute()) {
return null;
}
for (final Territory current : data.getMap().getTerritories()) {
final TerritoryAttachment cta = TerritoryAttachment.get(current);
if (cta == null || !cta.getConvoyRoute()) {
continue;
}
if (cta.getConvoyAttached().contains(t)) {
rVal.add(current);
}
}
return rVal;
}
public String toStringForInfo(final boolean useHTML, final boolean includeAttachedToName) {
final StringBuilder sb = new StringBuilder("");
final String br = (useHTML ? "<br>" : ", ");
final Territory t = (Territory) this.getAttachedTo();
if (t == null) {
return sb.toString();
}
if (includeAttachedToName) {
sb.append(t.getName());
sb.append(br);
if (t.isWater()) {
sb.append("Water Territory");
} else {
sb.append("Land Territory");
}
sb.append(br);
final PlayerID owner = t.getOwner();
if (owner != null && !owner.isNull()) {
sb.append("Current Owner: ").append(t.getOwner().getName());
sb.append(br);
}
final PlayerID originalOwner = getOriginalOwner();
if (originalOwner != null) {
sb.append("Original Owner: ").append(originalOwner.getName());
sb.append(br);
}
}
if (m_isImpassable) {
sb.append("Is Impassable");
sb.append(br);
}
if (m_capital != null && m_capital.length() > 0) {
sb.append("A Capital of ").append(m_capital);
sb.append(br);
}
if (m_victoryCity != 0) {
sb.append("Is a Victory location");
sb.append(br);
}
if (m_kamikazeZone) {
sb.append("Is Kamikaze Zone");
sb.append(br);
}
if (m_blockadeZone) {
sb.append("Is a Blockade Zone");
sb.append(br);
}
if (m_convoyRoute) {
if (!m_convoyAttached.isEmpty()) {
sb.append("Needs: ").append(MyFormatter.defaultNamedToTextList(m_convoyAttached)).append(br);
}
final Set<Territory> requiredBy = getWhatTerritoriesThisIsUsedInConvoysFor(t, getData());
if (!requiredBy.isEmpty()) {
sb.append("Required By: ").append(MyFormatter.defaultNamedToTextList(requiredBy)).append(br);
}
}
if (m_changeUnitOwners != null && !m_changeUnitOwners.isEmpty()) {
sb.append("Units May Change Ownership Here");
sb.append(br);
}
if (m_captureUnitOnEnteringBy != null && !m_captureUnitOnEnteringBy.isEmpty()) {
sb.append("May Allow The Capture of Some Units");
sb.append(br);
}
if (m_whenCapturedByGoesTo != null && !m_whenCapturedByGoesTo.isEmpty()) {
sb.append("Captured By -> Ownership Goes To");
sb.append(br);
for (final String value : m_whenCapturedByGoesTo) {
final String[] s = value.split(":");
sb.append(s[0]).append(" -> ").append(s[1]);
sb.append(br);
}
}
sb.append(br);
if (!t.isWater() && m_unitProduction > 0
&& games.strategy.triplea.Properties.getDamageFromBombingDoneToUnitsInsteadOfTerritories(getData())) {
sb.append("Base Unit Production: ");
sb.append(m_unitProduction);
sb.append(br);
}
if (m_production > 0 || (m_resources != null && m_resources.toString().length() > 0)) {
sb.append("Production: ");
sb.append(br);
if (m_production > 0) {
sb.append(" ").append(m_production).append(" PUs");
sb.append(br);
}
if (m_resources != null) {
if (useHTML) {
sb.append(" ")
.append((m_resources.toStringForHTML()).replaceAll("<br>", "<br> "));
} else {
sb.append(m_resources.toString());
}
sb.append(br);
}
}
final Iterator<TerritoryEffect> iter = m_territoryEffect.iterator();
if (iter.hasNext()) {
sb.append("Territory Effects: ");
sb.append(br);
}
while (iter.hasNext()) {
sb.append(" ").append(iter.next().getName());
sb.append(br);
}
return sb.toString();
}
@Override
public void validate(final GameData data) throws GameParseException {}
}