package games.strategy.triplea.attachments;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import games.strategy.engine.data.Attachable;
import games.strategy.engine.data.BattleRecordsList;
import games.strategy.engine.data.GameData;
import games.strategy.engine.data.GameMap;
import games.strategy.engine.data.GameParseException;
import games.strategy.engine.data.IAttachment;
import games.strategy.engine.data.PlayerID;
import games.strategy.engine.data.RelationshipTracker.Relationship;
import games.strategy.engine.data.RelationshipType;
import games.strategy.engine.data.Territory;
import games.strategy.engine.data.Unit;
import games.strategy.engine.data.UnitType;
import games.strategy.engine.data.annotations.GameProperty;
import games.strategy.engine.data.annotations.InternalDoNotExport;
import games.strategy.engine.delegate.IDelegateBridge;
import games.strategy.engine.random.IRandomStats.DiceType;
import games.strategy.triplea.Constants;
import games.strategy.triplea.MapSupport;
import games.strategy.triplea.delegate.Matches;
import games.strategy.triplea.delegate.TechAdvance;
import games.strategy.triplea.delegate.TechTracker;
import games.strategy.triplea.formatter.MyFormatter;
import games.strategy.triplea.player.ITripleAPlayer;
import games.strategy.util.CompositeMatchAnd;
import games.strategy.util.IntegerMap;
import games.strategy.util.Match;
import games.strategy.util.ThreadUtil;
import games.strategy.util.Tuple;
@MapSupport
public class RulesAttachment extends AbstractPlayerRulesAttachment {
private static final long serialVersionUID = 7301965634079412516L;
// condition for having techs
private ArrayList<TechAdvance> m_techs = null;
@InternalDoNotExport
// Do Not Export (do not include in IAttachment).
private int m_techCount = -1;
// condition for having specific relationships
private ArrayList<String> m_relationship = new ArrayList<>();
// condition for being at war
private HashSet<PlayerID> m_atWarPlayers = null;
@InternalDoNotExport
// Do Not Export (do not include in IAttachment).
private int m_atWarCount = -1;
// condition for having destroyed at least X enemy non-neutral TUV (total unit value) [according to
// the prices the defender pays for the units]
private String m_destroyedTUV = null;
// condition for having had a battle in some territory, attacker or defender, win
// or lost, etc. these next 9 variables use m_territoryCount for determining the number needed.
private ArrayList<Tuple<String, ArrayList<Territory>>> m_battle =
new ArrayList<>();
// ownership related
private String[] m_alliedOwnershipTerritories = null;
private String[] m_directOwnershipTerritories = null;
// exclusion of units
private String[] m_alliedExclusionTerritories = null;
private String[] m_directExclusionTerritories = null;
private String[] m_enemyExclusionTerritories = null;
private String[] m_enemySurfaceExclusionTerritories = null;
// presence of units
private String[] m_directPresenceTerritories = null;
private String[] m_alliedPresenceTerritories = null;
private String[] m_enemyPresenceTerritories = null;
// used with above 3 to determine the type of unit that must be present
private IntegerMap<String> m_unitPresence = new IntegerMap<>();
/** Creates new RulesAttachment. */
public RulesAttachment(final String name, final Attachable attachable, final GameData gameData) {
super(name, attachable, gameData);
}
/**
* Convenience method, for use with rules attachments, objectives, and condition attachments. Should return
* RulesAttachments.
*
* @param player PlayerID
* @param nameOfAttachment exact full name of attachment
* @return new rule attachment
*/
public static RulesAttachment get(final PlayerID player, final String nameOfAttachment) {
return get(player, nameOfAttachment, null, false);
}
public static RulesAttachment get(final PlayerID player, final String nameOfAttachment,
final Collection<PlayerID> playersToSearch, final boolean allowNull) {
RulesAttachment rVal = (RulesAttachment) player.getAttachment(nameOfAttachment);
if (rVal == null) {
if (playersToSearch == null) {
if (!allowNull) {
throw new IllegalStateException(
"Rules & Conditions: No rule attachment for:" + player.getName() + " with name: " + nameOfAttachment);
}
} else {
for (final PlayerID otherPlayer : playersToSearch) {
if (otherPlayer == player) {
continue;
}
rVal = (RulesAttachment) otherPlayer.getAttachment(nameOfAttachment);
if (rVal != null) {
return rVal;
}
}
if (!allowNull) {
throw new IllegalStateException(
"Rules & Conditions: No rule attachment for:" + player.getName() + " with name: " + nameOfAttachment);
}
}
}
return rVal;
}
/**
* Convenience method, for use returning any RulesAttachment that begins with "objectiveAttachment"
* National Objectives are just conditions that also give money to a player during the end turn delegate. They can be
* used for testing by
* triggers as well.
* Conditions that do not give money are not prefixed with "objectiveAttachment",
* and the trigger attachment that uses these kinds of conditions gets them a different way because they are
* specifically named inside
* that trigger.
*/
public static Set<RulesAttachment> getNationalObjectives(final PlayerID player) {
final Set<RulesAttachment> natObjs = new HashSet<>();
final Map<String, IAttachment> map = player.getAttachments();
for (final Map.Entry<String, IAttachment> entry : map.entrySet()) {
final IAttachment attachment = entry.getValue();
if (attachment instanceof RulesAttachment) {
if (attachment.getName().startsWith(Constants.RULES_OBJECTIVE_PREFIX)) {
natObjs.add((RulesAttachment) attachment);
}
}
}
return natObjs;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setDestroyedTUV(final String value) throws GameParseException {
if (value == null) {
m_destroyedTUV = null;
return;
}
final String[] s = value.split(":");
if (s.length != 2) {
throw new GameParseException("destroyedTUV must have 2 fields, value=currentRound/allRounds, count= the amount "
+ "of TUV that this player must destroy" + thisErrorMsg());
}
final int i = getInt(s[0]);
if (i < -1) {
throw new GameParseException(
"destroyedTUV count cannot be less than -1 [with -1 meaning the condition is not active]" + thisErrorMsg());
}
if (!(s[1].equals("currentRound") || s[1].equals("allRounds"))) {
throw new GameParseException("destroyedTUV value must be currentRound or allRounds" + thisErrorMsg());
}
m_destroyedTUV = value;
}
public String getDestroyedTUV() {
return m_destroyedTUV;
}
public void resetDestroyedTUV() {
m_destroyedTUV = 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 setBattle(final String value) throws GameParseException {
final String[] s = value.split(":");
if (s.length < 5) {
throw new GameParseException(
"battle must have at least 5 fields, attacker:defender:resultType:round:territory1..." + thisErrorMsg());
}
final PlayerID attacker = getData().getPlayerList().getPlayerID(s[0]);
if (attacker == null && !s[0].equalsIgnoreCase("any")) {
throw new GameParseException("no player named: " + s[0] + thisErrorMsg());
}
final PlayerID defender = getData().getPlayerList().getPlayerID(s[1]);
if (defender == null && !s[1].equalsIgnoreCase("any")) {
throw new GameParseException("no player named: " + s[1] + thisErrorMsg());
}
if (!s[2].equalsIgnoreCase("any")) {
throw new GameParseException("battle allows the following for resultType: any" + thisErrorMsg());
}
if (!s[3].equalsIgnoreCase("currentRound")) {
try {
getInt(s[3].split("-")[0]);
getInt(s[3].split("-")[1]);
} catch (final Exception e) {
throw new GameParseException("round must either be currentRound or two numbers like: 2-4" + thisErrorMsg());
}
}
final ArrayList<Territory> terrs = new ArrayList<>();
final GameMap map = getData().getMap();
// this loop starts on 4, so do not replace with an enhanced for loop
for (int i = 4; i < s.length; i++) {
final Territory t = map.getTerritory(s[i]);
if (t == null) {
throw new GameParseException("no such territory called: " + s[i] + thisErrorMsg());
}
terrs.add(t);
}
m_battle.add(Tuple.of((s[0] + ":" + s[1] + ":" + s[2] + ":" + s[3]), terrs));
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setBattle(final ArrayList<Tuple<String, ArrayList<Territory>>> value) {
m_battle = value;
}
public ArrayList<Tuple<String, ArrayList<Territory>>> getBattle() {
return m_battle;
}
public void clearBattle() {
m_battle.clear();
}
public void resetBattle() {
m_battle = new ArrayList<>();
}
/**
* Condition to check if a certain relationship exists between 2 players.
* Adds to, not sets. Anything that adds to instead of setting needs a clear function as well.
*
* @param value should be a string containing: "player:player:relationship"
*/
@GameProperty(xmlProperty = true, gameProperty = true, adds = true)
public void setRelationship(final String value) throws GameParseException {
final String[] s = value.split(":");
if (s.length < 3 || s.length > 4) {
throw new GameParseException(
"relationship should have value=\"playername1:playername2:relationshiptype:numberOfRoundsExisting\""
+ thisErrorMsg());
}
if (getData().getPlayerList().getPlayerID(s[0]) == null) {
throw new GameParseException(
"playername: " + s[0] + " isn't valid in condition with relationship: " + value + thisErrorMsg());
}
if (getData().getPlayerList().getPlayerID(s[1]) == null) {
throw new GameParseException(
"playername: " + s[1] + " isn't valid in condition with relationship: " + value + thisErrorMsg());
}
if (!(s[2].equals(Constants.RELATIONSHIP_CONDITION_ANY_ALLIED)
|| s[2].equals(Constants.RELATIONSHIP_CONDITION_ANY_NEUTRAL)
|| s[2].equals(Constants.RELATIONSHIP_CONDITION_ANY_WAR)
|| Matches.isValidRelationshipName(getData()).match(s[2]))) {
throw new GameParseException(
"relationship: " + s[2] + " isn't valid in condition with relationship: " + value + thisErrorMsg());
}
if (s.length == 4 && Integer.parseInt(s[3]) < -1) {
throw new GameParseException("numberOfRoundsExisting should be a number between -1 and 100000. -1 should be "
+ "default value if you don't know what to put" + thisErrorMsg());
}
m_relationship.add((s.length == 3) ? (value + ":-1") : value);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setRelationship(final ArrayList<String> value) {
m_relationship = value;
}
public ArrayList<String> getRelationship() {
return m_relationship;
}
public void clearRelationship() {
m_relationship.clear();
}
public void resetRelationship() {
m_relationship = new ArrayList<>();
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setAlliedOwnershipTerritories(final String value) {
if (value == null) {
m_alliedOwnershipTerritories = null;
return;
}
m_alliedOwnershipTerritories = value.split(":");
validateNames(m_alliedOwnershipTerritories);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setAlliedOwnershipTerritories(final String[] value) {
m_alliedOwnershipTerritories = value;
}
public String[] getAlliedOwnershipTerritories() {
return m_alliedOwnershipTerritories;
}
public void resetAlliedOwnershipTerritories() {
m_alliedOwnershipTerritories = null;
}
// exclusion types = controlled, controlledNoWater, original, all, or list
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setAlliedExclusionTerritories(final String value) {
if (value == null) {
m_alliedExclusionTerritories = null;
return;
}
m_alliedExclusionTerritories = value.split(":");
validateNames(m_alliedExclusionTerritories);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setAlliedExclusionTerritories(final String[] value) {
m_alliedExclusionTerritories = value;
}
public String[] getAlliedExclusionTerritories() {
return m_alliedExclusionTerritories;
}
public void resetAlliedExclusionTerritories() {
m_alliedExclusionTerritories = null;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setDirectExclusionTerritories(final String value) {
if (value == null) {
m_directExclusionTerritories = null;
return;
}
m_directExclusionTerritories = value.split(":");
validateNames(m_directExclusionTerritories);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setDirectExclusionTerritories(final String[] value) {
m_directExclusionTerritories = value;
}
public String[] getDirectExclusionTerritories() {
return m_directExclusionTerritories;
}
public void resetDirectExclusionTerritories() {
m_directExclusionTerritories = null;
}
// exclusion types = original or list
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setEnemyExclusionTerritories(final String value) {
if (value == null) {
m_enemyExclusionTerritories = null;
return;
}
m_enemyExclusionTerritories = value.split(":");
validateNames(m_enemyExclusionTerritories);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setEnemyExclusionTerritories(final String[] value) {
m_enemyExclusionTerritories = value;
}
public String[] getEnemyExclusionTerritories() {
return m_enemyExclusionTerritories;
}
public void resetEnemyExclusionTerritories() {
m_enemyExclusionTerritories = null;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setDirectPresenceTerritories(final String value) {
if (value == null) {
m_directPresenceTerritories = null;
return;
}
m_directPresenceTerritories = value.split(":");
validateNames(m_directPresenceTerritories);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setDirectPresenceTerritories(final String[] value) {
m_directPresenceTerritories = value;
}
public String[] getDirectPresenceTerritories() {
return m_directPresenceTerritories;
}
public void resetDirectPresenceTerritories() {
m_directPresenceTerritories = null;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setAlliedPresenceTerritories(final String value) {
if (value == null) {
m_alliedPresenceTerritories = null;
return;
}
m_alliedPresenceTerritories = value.split(":");
validateNames(m_alliedPresenceTerritories);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setAlliedPresenceTerritories(final String[] value) {
m_alliedPresenceTerritories = value;
}
public String[] getAlliedPresenceTerritories() {
return m_alliedPresenceTerritories;
}
public void resetAlliedPresenceTerritories() {
m_alliedPresenceTerritories = null;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setEnemyPresenceTerritories(final String value) {
if (value == null) {
m_enemyPresenceTerritories = null;
return;
}
m_enemyPresenceTerritories = value.split(":");
validateNames(m_enemyPresenceTerritories);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setEnemyPresenceTerritories(final String[] value) {
m_enemyPresenceTerritories = value;
}
public String[] getEnemyPresenceTerritories() {
return m_enemyPresenceTerritories;
}
public void resetEnemyPresenceTerritories() {
m_enemyPresenceTerritories = null;
}
// exclusion types = original or list
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setEnemySurfaceExclusionTerritories(final String value) {
if (value == null) {
m_enemySurfaceExclusionTerritories = null;
return;
}
m_enemySurfaceExclusionTerritories = value.split(":");
validateNames(m_enemySurfaceExclusionTerritories);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setEnemySurfaceExclusionTerritories(final String[] value) {
m_enemySurfaceExclusionTerritories = value;
}
public String[] getEnemySurfaceExclusionTerritories() {
return m_enemySurfaceExclusionTerritories;
}
public void resetEnemySurfaceExclusionTerritories() {
m_enemySurfaceExclusionTerritories = null;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setDirectOwnershipTerritories(final String value) {
if (value == null) {
m_directOwnershipTerritories = null;
return;
}
m_directOwnershipTerritories = value.split(":");
validateNames(m_directOwnershipTerritories);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setDirectOwnershipTerritories(final String[] value) {
m_directOwnershipTerritories = value;
}
public String[] getDirectOwnershipTerritories() {
return m_directOwnershipTerritories;
}
public void resetDirectOwnershipTerritories() {
m_directOwnershipTerritories = 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 setUnitPresence(String value) throws GameParseException {
final String[] s = value.split(":");
if (s.length <= 1) {
throw new GameParseException("unitPresence must have at least 2 fields. Format value=unit1 count=number, or "
+ "value=unit1:unit2:unit3 count=number" + thisErrorMsg());
}
final int n = getInt(s[0]);
if (n < 0) {
throw new GameParseException("unitPresence must be a positive integer" + thisErrorMsg());
}
for (int i = 1; i < s.length; i++) {
final String unitTypeToProduce = s[i];
// validate that this unit exists in the xml
final UnitType ut = getData().getUnitTypeList().getUnitType(unitTypeToProduce);
if (ut == null && !(unitTypeToProduce.equals("any") || unitTypeToProduce.equals("ANY"))) {
throw new GameParseException("No unit called: " + unitTypeToProduce + thisErrorMsg());
}
}
value = value.replaceFirst(s[0] + ":", "");
m_unitPresence.put(value, n);
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setUnitPresence(final IntegerMap<String> value) {
m_unitPresence = value;
}
public IntegerMap<String> getUnitPresence() {
return m_unitPresence;
}
public void clearUnitPresence() {
m_unitPresence.clear();
}
public void resetUnitPresence() {
m_unitPresence = new IntegerMap<>();
}
public int getAtWarCount() {
return m_atWarCount;
}
public int getTechCount() {
return m_techCount;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setAtWarPlayers(final String players) throws GameParseException {
if (players == null) {
m_atWarPlayers = null;
return;
}
final String[] s = players.split(":");
int count = -1;
if (s.length < 1) {
throw new GameParseException("Empty enemy list" + thisErrorMsg());
}
try {
count = getInt(s[0]);
m_atWarCount = count;
} catch (final Exception e) {
m_atWarCount = 0;
}
if (s.length < 1 || s.length == 1 && count != -1) {
throw new GameParseException("Empty enemy list" + thisErrorMsg());
}
m_atWarPlayers = new HashSet<>();
for (int i = count == -1 ? 0 : 1; i < s.length; i++) {
final PlayerID player = getData().getPlayerList().getPlayerID(s[i]);
if (player == null) {
throw new GameParseException("Could not find player. name:" + s[i] + thisErrorMsg());
}
m_atWarPlayers.add(player);
}
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setAtWarPlayers(final HashSet<PlayerID> value) {
m_atWarPlayers = value;
}
public HashSet<PlayerID> getAtWarPlayers() {
return m_atWarPlayers;
}
public void resetAtWarPlayers() {
m_atWarPlayers = null;
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setTechs(final String newTechs) throws GameParseException {
if (newTechs == null) {
m_techs = null;
return;
}
final String[] s = newTechs.split(":");
int count = -1;
if (s.length < 1) {
throw new GameParseException("Empty tech list" + thisErrorMsg());
}
try {
count = getInt(s[0]);
m_techCount = count;
} catch (final Exception e) {
m_techCount = 0;
}
if (s.length < 1 || s.length == 1 && count != -1) {
throw new GameParseException("Empty tech list" + thisErrorMsg());
}
m_techs = new ArrayList<>();
for (int i = count == -1 ? 0 : 1; i < s.length; i++) {
TechAdvance ta = getData().getTechnologyFrontier().getAdvanceByProperty(s[i]);
if (ta == null) {
ta = getData().getTechnologyFrontier().getAdvanceByName(s[i]);
}
if (ta == null) {
throw new GameParseException("Technology not found :" + Arrays.toString(s) + thisErrorMsg());
}
m_techs.add(ta);
}
}
@GameProperty(xmlProperty = true, gameProperty = true, adds = false)
public void setTechs(final ArrayList<TechAdvance> value) {
m_techs = value;
}
public ArrayList<TechAdvance> getTechs() {
return m_techs;
}
public void resetTechs() {
m_techs = null;
}
@Override
public boolean isSatisfied(final HashMap<ICondition, Boolean> testedConditions) {
checkNotNull(testedConditions);
checkState(testedConditions.containsKey(this));
return testedConditions.get(this);
}
@Override
public boolean isSatisfied(HashMap<ICondition, Boolean> testedConditions, final IDelegateBridge aBridge) {
if (testedConditions != null) {
if (testedConditions.containsKey(this)) {
return testedConditions.get(this);
}
}
boolean objectiveMet = true;
final List<PlayerID> players = getPlayers();
final GameData data = aBridge.getData();
// check meta conditions (conditions which hold other conditions)
if (objectiveMet && m_conditions.size() > 0) {
if (testedConditions == null) {
testedConditions = testAllConditionsRecursive(
getAllConditionsRecursive(new HashSet<>(m_conditions), null), null, aBridge);
}
objectiveMet = areConditionsMet(new ArrayList<>(m_conditions), testedConditions, m_conditionType);
}
// check switch (on/off)
if (objectiveMet) {
objectiveMet = m_switch;
}
// check turn limits
if (objectiveMet && m_turns != null) {
objectiveMet = checkTurns(data);
}
// check custom game property options
if (objectiveMet && m_gameProperty != null) {
objectiveMet = this.getGamePropertyState(data);
}
// Check for unit presence (Veqryn)
if (objectiveMet && getDirectPresenceTerritories() != null) {
// Get the listed territories
final String[] terrs = getDirectPresenceTerritories();
objectiveMet = checkUnitPresence(getTerritoryListBasedOnInputFromXML(terrs, players, data),
"direct", getTerritoryCount(), players, data);
}
// Check for unit presence (Veqryn)
if (objectiveMet && getAlliedPresenceTerritories() != null) {
// Get the listed territories
final String[] terrs = getAlliedPresenceTerritories();
objectiveMet = checkUnitPresence(getTerritoryListBasedOnInputFromXML(terrs, players, data),
"allied", getTerritoryCount(), players, data);
}
// Check for unit presence (Veqryn)
if (objectiveMet && getEnemyPresenceTerritories() != null) {
// Get the listed territories
final String[] terrs = getEnemyPresenceTerritories();
objectiveMet = checkUnitPresence(getTerritoryListBasedOnInputFromXML(terrs, players, data), "enemy",
getTerritoryCount(), players, data);
}
// Check for direct unit exclusions (veqryn)
if (objectiveMet && getDirectExclusionTerritories() != null) {
// Get the listed territories
final String[] terrs = getDirectExclusionTerritories();
objectiveMet = checkUnitExclusions(getTerritoryListBasedOnInputFromXML(terrs, players, data),
"direct", getTerritoryCount(), players, data);
}
// Check for allied unit exclusions
if (objectiveMet && getAlliedExclusionTerritories() != null) {
// Get the listed territories
final String[] terrs = getAlliedExclusionTerritories();
objectiveMet = checkUnitExclusions(getTerritoryListBasedOnInputFromXML(terrs, players, data),
"allied", getTerritoryCount(), players, data);
}
// Check for enemy unit exclusions (ANY UNITS)
if (objectiveMet && getEnemyExclusionTerritories() != null) {
// Get the listed territories
final String[] terrs = getEnemyExclusionTerritories();
objectiveMet = checkUnitExclusions(getTerritoryListBasedOnInputFromXML(terrs, players, data),
"enemy", getTerritoryCount(), players, data);
}
// Check for enemy unit exclusions (SURFACE UNITS with ATTACK POWER)
if (objectiveMet && getEnemySurfaceExclusionTerritories() != null) {
// Get the listed territories
final String[] terrs = getEnemySurfaceExclusionTerritories();
objectiveMet = checkUnitExclusions(getTerritoryListBasedOnInputFromXML(terrs, players, data),
"enemy_surface", getTerritoryCount(), players, data);
}
// Check for Territory Ownership rules
if (objectiveMet && getAlliedOwnershipTerritories() != null) {
// Get the listed territories
final String[] terrs = getAlliedOwnershipTerritories();
Set<Territory> listedTerritories;
if (terrs.length == 1) {
if (terrs[0].equals("original")) {
final Collection<PlayerID> allies =
Match.getMatches(data.getPlayerList().getPlayers(), Matches.isAlliedWithAnyOfThesePlayers(players, data));
listedTerritories = getTerritoryListBasedOnInputFromXML(terrs, allies, data);
} else if (terrs[0].equals("enemy")) {
final Collection<PlayerID> enemies =
Match.getMatches(data.getPlayerList().getPlayers(), Matches.isAtWarWithAnyOfThesePlayers(players, data));
listedTerritories = getTerritoryListBasedOnInputFromXML(terrs, enemies, data);
} else {
listedTerritories = getTerritoryListBasedOnInputFromXML(terrs, players, data);
}
} else if (terrs.length == 2) {
if (terrs[1].equals("original")) {
final Collection<PlayerID> allies =
Match.getMatches(data.getPlayerList().getPlayers(), Matches.isAlliedWithAnyOfThesePlayers(players, data));
listedTerritories = getTerritoryListBasedOnInputFromXML(terrs, allies, data);
} else if (terrs[1].equals("enemy")) {
final Collection<PlayerID> enemies =
Match.getMatches(data.getPlayerList().getPlayers(), Matches.isAtWarWithAnyOfThesePlayers(players, data));
listedTerritories = getTerritoryListBasedOnInputFromXML(terrs, enemies, data);
} else {
listedTerritories = getTerritoryListBasedOnInputFromXML(terrs, players, data);
}
} else {
listedTerritories = getTerritoryListBasedOnInputFromXML(terrs, players, data);
}
objectiveMet = checkAlliedOwnership(listedTerritories, getTerritoryCount(), players, data);
}
// Check for Direct Territory Ownership rules
if (objectiveMet && getDirectOwnershipTerritories() != null) {
// Get the listed territories
final String[] terrs = getDirectOwnershipTerritories();
Set<Territory> listedTerritories;
if (terrs.length == 1) {
if (terrs[0].equals("original")) {
listedTerritories = getTerritoryListBasedOnInputFromXML(terrs, players, data);
} else if (terrs[0].equals("enemy")) {
final Collection<PlayerID> enemies =
Match.getMatches(data.getPlayerList().getPlayers(), Matches.isAtWarWithAnyOfThesePlayers(players, data));
listedTerritories = getTerritoryListBasedOnInputFromXML(terrs, enemies, data);
} else {
listedTerritories = getTerritoryListBasedOnInputFromXML(terrs, players, data);
}
} else if (terrs.length == 2) {
if (terrs[1].equals("original")) {
listedTerritories = getTerritoryListBasedOnInputFromXML(terrs, players, data);
} else if (terrs[1].equals("enemy")) {
final Collection<PlayerID> enemies =
Match.getMatches(data.getPlayerList().getPlayers(), Matches.isAtWarWithAnyOfThesePlayers(players, data));
listedTerritories = getTerritoryListBasedOnInputFromXML(terrs, enemies, data);
} else {
listedTerritories = getTerritoryListBasedOnInputFromXML(terrs, players, data);
}
} else {
listedTerritories = getTerritoryListBasedOnInputFromXML(terrs, players, data);
}
objectiveMet = checkDirectOwnership(listedTerritories, getTerritoryCount(), players);
}
// get attached to player
final PlayerID playerAttachedTo = (PlayerID) getAttachedTo();
if (objectiveMet && getAtWarPlayers() != null) {
objectiveMet = checkAtWar(playerAttachedTo, getAtWarPlayers(), getAtWarCount(), data);
}
if (objectiveMet && m_techs != null) {
objectiveMet = checkTechs(playerAttachedTo, data);
}
// check for relationships
if (objectiveMet && m_relationship.size() > 0) {
objectiveMet = checkRelationships();
}
// check for battle stats
if (objectiveMet && m_destroyedTUV != null) {
final String[] s = m_destroyedTUV.split(":");
final int requiredDestroyedTUV = getInt(s[0]);
if (requiredDestroyedTUV >= 0) {
final boolean justCurrentRound = s[1].equals("currentRound");
final int destroyedTUVforThisRoundSoFar = BattleRecordsList.getTUVdamageCausedByPlayer(playerAttachedTo,
data.getBattleRecordsList(), 0, data.getSequence().getRound(), justCurrentRound, false);
if (requiredDestroyedTUV > destroyedTUVforThisRoundSoFar) {
objectiveMet = false;
}
if (getCountEach()) {
m_eachMultiple = destroyedTUVforThisRoundSoFar;
}
}
}
// check for battles
if (objectiveMet && !m_battle.isEmpty()) {
final BattleRecordsList brl = data.getBattleRecordsList();
final int round = data.getSequence().getRound();
for (final Tuple<String, ArrayList<Territory>> entry : m_battle) {
final String[] type = entry.getFirst().split(":");
// they could be "any", and if they are "any" then this would be null, which is good!
final PlayerID attacker = data.getPlayerList().getPlayerID(type[0]);
final PlayerID defender = data.getPlayerList().getPlayerID(type[1]);
final String resultType = type[2];
final String roundType = type[3];
int start = 0;
int end = round;
final boolean currentRound = roundType.equalsIgnoreCase("currentRound");
if (!currentRound) {
final String[] rounds = roundType.split("-");
start = getInt(rounds[0]);
end = getInt(rounds[1]);
}
objectiveMet = BattleRecordsList.getWereThereBattlesInTerritoriesMatching(attacker, defender, resultType,
entry.getSecond(), brl, start, end, currentRound);
if (!objectiveMet) {
break;
}
}
}
// "chance" should ALWAYS be checked last!
final int hitTarget = getChanceToHit();
final int diceSides = getChanceDiceSides();
final int incrementOnFailure = this.getChanceIncrementOnFailure();
final int decrementOnSuccess = this.getChanceDecrementOnSuccess();
if (objectiveMet && (hitTarget != diceSides || incrementOnFailure != 0 || decrementOnSuccess != 0)) {
if (diceSides <= 0 || hitTarget >= diceSides) {
objectiveMet = true;
changeChanceDecrementOrIncrementOnSuccessOrFailure(aBridge, objectiveMet, false);
} else if (hitTarget <= 0) {
objectiveMet = false;
changeChanceDecrementOrIncrementOnSuccessOrFailure(aBridge, objectiveMet, false);
} else {
// there is an issue with maps using thousands of chance triggers: they are causing the cypted random source
// (ie: live and pbem
// games) to lock up or error out
// so we need to slow them down a bit, until we come up with a better solution (like aggregating all the chances
// together, then
// getting a ton of random numbers at once instead of one at a time)
ThreadUtil.sleep(100);
final int rollResult = aBridge.getRandom(diceSides, null, DiceType.ENGINE,
"Attempting the Condition: " + MyFormatter.attachmentNameToText(this.getName())) + 1;
objectiveMet = rollResult <= hitTarget;
final String notificationMessage = (objectiveMet ? TRIGGER_CHANCE_SUCCESSFUL : TRIGGER_CHANCE_FAILURE)
+ " (Rolled at " + hitTarget + " out of " + diceSides + " Result: " + rollResult + " for "
+ MyFormatter.attachmentNameToText(this.getName()) + ")";
aBridge.getHistoryWriter().startEvent(notificationMessage);
changeChanceDecrementOrIncrementOnSuccessOrFailure(aBridge, objectiveMet, true);
((ITripleAPlayer) aBridge.getRemotePlayer(aBridge.getPlayerID())).reportMessage(notificationMessage,
notificationMessage);
}
}
return objectiveMet != m_invert;
}
/**
* checks if all relationship requirements are set
*
* @return whether all relationships as are required are set correctly.
*/
private boolean checkRelationships() {
for (final String aRelationCheck : m_relationship) {
final String[] relationCheck = aRelationCheck.split(":");
final PlayerID p1 = getData().getPlayerList().getPlayerID(relationCheck[0]);
final PlayerID p2 = getData().getPlayerList().getPlayerID(relationCheck[1]);
final int relationshipsExistance = Integer.parseInt(relationCheck[3]);
final Relationship currentRelationship = getData().getRelationshipTracker().getRelationship(p1, p2);
final RelationshipType currentRelationshipType = currentRelationship.getRelationshipType();
if (!relationShipExistsLongEnnough(currentRelationship, relationshipsExistance)) {
return false;
}
if (!(relationCheck[2].equals(Constants.RELATIONSHIP_CONDITION_ANY_ALLIED)
&& Matches.RelationshipTypeIsAllied.match(currentRelationshipType)
|| relationCheck[2].equals(Constants.RELATIONSHIP_CONDITION_ANY_NEUTRAL)
&& Matches.RelationshipTypeIsNeutral.match(currentRelationshipType)
|| relationCheck[2].equals(Constants.RELATIONSHIP_CONDITION_ANY_WAR)
&& Matches.RelationshipTypeIsAtWar.match(currentRelationshipType)
|| currentRelationshipType
.equals(getData().getRelationshipTypeList().getRelationshipType(relationCheck[2])))) {
return false;
}
}
return true;
}
private boolean relationShipExistsLongEnnough(final Relationship r, final int relationshipsExistance) {
int roundCurrentRelationshipWasCreated = r.getRoundCreated();
roundCurrentRelationshipWasCreated += games.strategy.triplea.Properties.getRelationshipsLastExtraRounds(getData());
return getData().getSequence().getRound() - roundCurrentRelationshipWasCreated >= relationshipsExistance;
}
/**
* Checks for the collection of territories to see if they have units owned by the exclType alliance.
*/
private boolean checkUnitPresence(final Collection<Territory> Territories, final String exclType,
final int numberNeeded, final List<PlayerID> players, final GameData data) {
int numberMet = 0;
boolean satisfied = false;
boolean useSpecific = false;
if (getUnitPresence() != null && !getUnitPresence().keySet().isEmpty()) {
useSpecific = true;
}
for (final Territory terr : Territories) {
final Collection<Unit> allUnits =
Match.getMatches(terr.getUnits().getUnits(), Matches.unitIsBeingTransported().invert());
if (exclType.equals("direct")) {
allUnits.removeAll(Match.getMatches(allUnits, Matches.unitIsOwnedByOfAnyOfThesePlayers(players).invert()));
} else if (exclType.equals("allied")) {
allUnits.retainAll(Match.getMatches(allUnits, Matches.alliedUnitOfAnyOfThesePlayers(players, data)));
} else if (exclType.equals("enemy")) {
allUnits.retainAll(Match.getMatches(allUnits, Matches.enemyUnitOfAnyOfThesePlayers(players, data)));
} else {
return false;
}
if (allUnits.size() > 0) {
if (!useSpecific) {
numberMet += 1;
if (numberMet >= numberNeeded) {
satisfied = true;
if (!getCountEach()) {
break;
}
}
} else if (useSpecific) {
final IntegerMap<String> unitComboMap = getUnitPresence();
final Set<String> unitCombos = unitComboMap.keySet();
boolean hasEnough = false;
for (final String uc : unitCombos) {
final int unitsNeeded = unitComboMap.getInt(uc);
if (uc == null || uc.equals("ANY") || uc.equals("any")) {
hasEnough = allUnits.size() >= unitsNeeded;
} else {
final Set<UnitType> typesAllowed = data.getUnitTypeList().getUnitTypes(uc.split(":"));
hasEnough = Match.getMatches(allUnits, Matches.unitIsOfTypes(typesAllowed)).size() >= unitsNeeded;
}
if (!hasEnough) {
break;
}
}
if (hasEnough) {
numberMet += 1;
if (numberMet >= numberNeeded) {
satisfied = true;
if (!getCountEach()) {
break;
}
}
}
}
}
}
if (getCountEach()) {
m_eachMultiple = numberMet;
}
return satisfied;
}
/**
* Checks for the collection of territories to see if they have units owned by the exclType alliance.
* It doesn't yet threshold the data
*/
private boolean checkUnitExclusions(final Collection<Territory> Territories, final String exclType,
final int numberNeeded, final List<PlayerID> players, final GameData data) {
int numberMet = 0;
boolean satisfied = false;
boolean useSpecific = false;
if (getUnitPresence() != null && !getUnitPresence().keySet().isEmpty()) {
useSpecific = true;
}
final Iterator<Territory> ownedTerrIter = Territories.iterator();
// Go through the owned territories and see if there are any units owned by allied/enemy based on exclType
while (ownedTerrIter.hasNext()) {
// get all the units in the territory
final Territory terr = ownedTerrIter.next();
final Collection<Unit> allUnits =
Match.getMatches(terr.getUnits().getUnits(), Matches.unitIsBeingTransported().invert());
if (exclType.equals("allied")) { // any allied units in the territory. (does not include owned units)
allUnits.removeAll(Match.getMatches(allUnits, Matches.unitIsOwnedByOfAnyOfThesePlayers(players)));
allUnits.retainAll(Match.getMatches(allUnits, Matches.alliedUnitOfAnyOfThesePlayers(players, data)));
} else if (exclType.equals("direct")) {
allUnits.removeAll(Match.getMatches(allUnits, Matches.unitIsOwnedByOfAnyOfThesePlayers(players).invert()));
} else if (exclType.equals("enemy")) { // any enemy units in the territory
allUnits.retainAll(Match.getMatches(allUnits, Matches.enemyUnitOfAnyOfThesePlayers(players, data)));
} else if (exclType.equals("enemy_surface")) { // any enemy units (not trn/sub) in the territory
allUnits.retainAll(
Match.getMatches(allUnits, new CompositeMatchAnd<>(Matches.enemyUnitOfAnyOfThesePlayers(players, data),
Matches.UnitIsNotSub, Matches.UnitIsNotTransportButCouldBeCombatTransport)));
} else {
return false;
}
if (allUnits.size() == 0) {
numberMet += 1;
if (numberMet >= numberNeeded) {
satisfied = true;
if (!getCountEach()) {
break;
}
}
} else if (useSpecific) {
final IntegerMap<String> unitComboMap = getUnitPresence();
final Set<String> unitCombos = unitComboMap.keySet();
boolean hasLess = false;
for (final String uc : unitCombos) {
final int unitsMax = unitComboMap.getInt(uc);
if (uc == null || uc.equals("ANY") || uc.equals("any")) {
hasLess = allUnits.size() <= unitsMax;
} else {
final Set<UnitType> typesAllowed = data.getUnitTypeList().getUnitTypes(uc.split(":"));
hasLess = Match.getMatches(allUnits, Matches.unitIsOfTypes(typesAllowed)).size() <= unitsMax;
}
if (!hasLess) {
break;
}
}
if (hasLess) {
numberMet += 1;
if (numberMet >= numberNeeded) {
satisfied = true;
if (!getCountEach()) {
break;
}
}
}
}
}
if (getCountEach()) {
m_eachMultiple = numberMet;
}
return satisfied;
}
/**
* Checks for allied ownership of the collection of territories. Once the needed number threshold is reached, the
* satisfied flag is set
* to true and returned
*/
private boolean checkAlliedOwnership(final Collection<Territory> listedTerrs,
final int numberNeeded, final Collection<PlayerID> players, final GameData data) {
int numberMet = 0;
boolean satisfied = false;
final Collection<PlayerID> allies =
Match.getMatches(data.getPlayerList().getPlayers(), Matches.isAlliedWithAnyOfThesePlayers(players, data));
for (final Territory listedTerr : listedTerrs) {
// if the territory owner is an ally
if (Matches.isTerritoryOwnedBy(allies).match(listedTerr)) {
numberMet += 1;
if (numberMet >= numberNeeded) {
satisfied = true;
if (!getCountEach()) {
break;
}
}
}
}
if (getCountEach()) {
m_eachMultiple = numberMet;
}
return satisfied;
}
/**
* Checks for direct ownership of the collection of territories. Once the needed number threshold is reached, return
* true.
*/
private boolean checkDirectOwnership(final Collection<Territory> listedTerrs,
final int numberNeeded, final Collection<PlayerID> players) {
int numberMet = 0;
boolean satisfied = false;
for (final Territory listedTerr : listedTerrs) {
if (Matches.isTerritoryOwnedBy(players).match(listedTerr)) {
numberMet += 1;
if (numberMet >= numberNeeded) {
satisfied = true;
if (!getCountEach()) {
break;
}
}
}
}
if (getCountEach()) {
m_eachMultiple = numberMet;
}
return satisfied;
}
private boolean checkAtWar(final PlayerID player, final Set<PlayerID> enemies, final int count, final GameData data) {
int found = 0;
for (final PlayerID e : enemies) {
if (data.getRelationshipTracker().isAtWar(player, e)) {
found++;
}
}
if (count == 0) {
return count == found;
}
if (getCountEach()) {
m_eachMultiple = found;
}
return found >= count;
}
private boolean checkTechs(final PlayerID player, final GameData data) {
int found = 0;
for (final TechAdvance a : TechTracker.getCurrentTechAdvances(player, data)) {
if (m_techs.contains(a)) {
found++;
}
}
if (m_techCount == 0) {
return m_techCount == found;
}
if (getCountEach()) {
m_eachMultiple = found;
}
return found >= m_techCount;
}
/**
* Called after the attachment is created.
*
* @throws GameParseException validation failed
*/
@Override
public void validate(final GameData data) throws GameParseException {
super.validate(data);
validateNames(m_alliedOwnershipTerritories);
validateNames(m_enemyExclusionTerritories);
validateNames(m_enemySurfaceExclusionTerritories);
validateNames(m_alliedExclusionTerritories);
validateNames(m_directExclusionTerritories);
validateNames(m_directOwnershipTerritories);
validateNames(m_directPresenceTerritories);
validateNames(m_alliedPresenceTerritories);
validateNames(m_enemyPresenceTerritories);
}
}