package games.strategy.triplea.attachments; import java.util.ArrayList; 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; import java.util.Set; import games.strategy.debug.ClientLogger; import games.strategy.engine.data.Attachable; import games.strategy.engine.data.Change; import games.strategy.engine.data.CompositeChange; import games.strategy.engine.data.GameData; import games.strategy.engine.data.GameParseException; import games.strategy.engine.data.IAttachment; import games.strategy.engine.data.PlayerID; import games.strategy.engine.data.ProductionFrontier; import games.strategy.engine.data.ProductionRule; import games.strategy.engine.data.RelationshipType; import games.strategy.engine.data.Resource; import games.strategy.engine.data.TechnologyFrontier; import games.strategy.engine.data.Territory; import games.strategy.engine.data.TerritoryEffect; import games.strategy.engine.data.Unit; import games.strategy.engine.data.UnitType; import games.strategy.engine.data.annotations.GameProperty; import games.strategy.engine.data.changefactory.ChangeFactory; import games.strategy.engine.delegate.IDelegate; import games.strategy.engine.delegate.IDelegateBridge; import games.strategy.sound.SoundPath; import games.strategy.triplea.Constants; import games.strategy.triplea.MapSupport; import games.strategy.triplea.Properties; import games.strategy.triplea.delegate.AbstractMoveDelegate; import games.strategy.triplea.delegate.BattleTracker; import games.strategy.triplea.delegate.DelegateFinder; import games.strategy.triplea.delegate.EndRoundDelegate; import games.strategy.triplea.delegate.Matches; import games.strategy.triplea.delegate.OriginalOwnerTracker; import games.strategy.triplea.delegate.TechAdvance; import games.strategy.triplea.delegate.TechTracker; import games.strategy.triplea.formatter.MyFormatter; import games.strategy.triplea.ui.NotificationMessages; import games.strategy.triplea.ui.display.ITripleADisplay; import games.strategy.util.CompositeMatchAnd; import games.strategy.util.IntegerMap; import games.strategy.util.Match; import games.strategy.util.Tuple; @MapSupport public class TriggerAttachment extends AbstractTriggerAttachment { private static final long serialVersionUID = -3327739180569606093L; private static final String PREFIX_CLEAR = "-clear-"; private static final String PREFIX_RESET = "-reset-"; private ProductionFrontier m_frontier = null; private ArrayList<String> m_productionRule = null; private ArrayList<TechAdvance> m_tech = new ArrayList<>(); private HashMap<String, LinkedHashMap<TechAdvance, Boolean>> m_availableTech = null; private HashMap<Territory, IntegerMap<UnitType>> m_placement = null; private HashMap<Territory, IntegerMap<UnitType>> m_removeUnits = null; private IntegerMap<UnitType> m_purchase = null; private String m_resource = null; private int m_resourceCount = 0; // never use a map of other attachments, inside of an attachment. java will not be able to deserialize it. private LinkedHashMap<String, Boolean> m_support = null; // List of relationshipChanges that should be executed when this trigger hits. private ArrayList<String> m_relationshipChange = new ArrayList<>(); private String m_victory = null; private ArrayList<Tuple<String, String>> m_activateTrigger = new ArrayList<>(); private ArrayList<String> m_changeOwnership = new ArrayList<>(); // raw property changes below: // // really m_unitTypes, but we are not going to rename because it will break all existing maps private ArrayList<UnitType> m_unitType = new ArrayList<>(); private Tuple<String, String> m_unitAttachmentName = null; // covers UnitAttachment, UnitSupportAttachment private ArrayList<Tuple<String, String>> m_unitProperty = null; private ArrayList<Territory> m_territories = new ArrayList<>(); private Tuple<String, String> m_territoryAttachmentName = null; // covers TerritoryAttachment, CanalAttachment private ArrayList<Tuple<String, String>> m_territoryProperty = null; private ArrayList<PlayerID> m_players = new ArrayList<>(); // covers PlayerAttachment, TriggerAttachment, RulesAttachment, TechAttachment, UserActionAttachment private Tuple<String, String> m_playerAttachmentName = null; private ArrayList<Tuple<String, String>> m_playerProperty = null; private ArrayList<RelationshipType> m_relationshipTypes = new ArrayList<>(); private Tuple<String, String> m_relationshipTypeAttachmentName = null; // covers RelationshipTypeAttachment private ArrayList<Tuple<String, String>> m_relationshipTypeProperty = null; private ArrayList<TerritoryEffect> m_territoryEffects = new ArrayList<>(); private Tuple<String, String> m_territoryEffectAttachmentName = null; // covers TerritoryEffectAttachment private ArrayList<Tuple<String, String>> m_territoryEffectProperty = null; public TriggerAttachment(final String name, final Attachable attachable, final GameData gameData) { super(name, attachable, gameData); } /** * Convenience method for returning TriggerAttachments. * * @return a new trigger attachment */ public static TriggerAttachment get(final PlayerID player, final String nameOfAttachment) { return get(player, nameOfAttachment, null); } public static TriggerAttachment get(final PlayerID player, final String nameOfAttachment, final Collection<PlayerID> playersToSearch) { TriggerAttachment rVal = (TriggerAttachment) player.getAttachment(nameOfAttachment); if (rVal == null) { if (playersToSearch == null) { throw new IllegalStateException( "Triggers: No trigger attachment for:" + player.getName() + " with name: " + nameOfAttachment); } else { for (final PlayerID otherPlayer : playersToSearch) { if (otherPlayer == player) { continue; } rVal = (TriggerAttachment) otherPlayer.getAttachment(nameOfAttachment); if (rVal != null) { return rVal; } } throw new IllegalStateException( "Triggers: No trigger attachment for:" + player.getName() + " with name: " + nameOfAttachment); } } return rVal; } /** * Convenience method for return all TriggerAttachments attached to a player. * * @return set of trigger attachments (If you use null for the match condition, you will get all triggers for this * player) */ public static Set<TriggerAttachment> getTriggers(final PlayerID player, final GameData data, final Match<TriggerAttachment> cond) { final Set<TriggerAttachment> trigs = new HashSet<>(); final Map<String, IAttachment> map = player.getAttachments(); final Iterator<String> iter = map.keySet().iterator(); while (iter.hasNext()) { final IAttachment a = map.get(iter.next()); if (a instanceof TriggerAttachment) { if (cond == null || cond.match((TriggerAttachment) a)) { trigs.add((TriggerAttachment) a); } } } return trigs; } /** * This will collect all triggers for the desired players, based on a match provided, * and then it will gather all the conditions necessary, then test all the conditions, * and then it will fire all the conditions which are satisfied. */ public static void collectAndFireTriggers(final HashSet<PlayerID> players, final Match<TriggerAttachment> triggerMatch, final IDelegateBridge aBridge, final String beforeOrAfter, final String stepName) { final HashSet<TriggerAttachment> toFirePossible = collectForAllTriggersMatching(players, triggerMatch, aBridge); if (toFirePossible.isEmpty()) { return; } final HashMap<ICondition, Boolean> testedConditions = collectTestsForAllTriggers(toFirePossible, aBridge); final List<TriggerAttachment> toFireTestedAndSatisfied = Match.getMatches(toFirePossible, AbstractTriggerAttachment.isSatisfiedMatch(testedConditions)); if (toFireTestedAndSatisfied.isEmpty()) { return; } TriggerAttachment.fireTriggers(new HashSet<>(toFireTestedAndSatisfied), testedConditions, aBridge, beforeOrAfter, stepName, true, true, true, true); } public static HashSet<TriggerAttachment> collectForAllTriggersMatching(final HashSet<PlayerID> players, final Match<TriggerAttachment> triggerMatch, final IDelegateBridge aBridge) { final GameData data = aBridge.getData(); final HashSet<TriggerAttachment> toFirePossible = new HashSet<>(); for (final PlayerID player : players) { toFirePossible.addAll(TriggerAttachment.getTriggers(player, data, triggerMatch)); } return toFirePossible; } public static HashMap<ICondition, Boolean> collectTestsForAllTriggers(final HashSet<TriggerAttachment> toFirePossible, final IDelegateBridge aBridge) { return collectTestsForAllTriggers(toFirePossible, aBridge, null, null); } public static HashMap<ICondition, Boolean> collectTestsForAllTriggers(final HashSet<TriggerAttachment> toFirePossible, final IDelegateBridge aBridge, final HashSet<ICondition> allConditionsNeededSoFar, final HashMap<ICondition, Boolean> allConditionsTestedSoFar) { final HashSet<ICondition> allConditionsNeeded = AbstractConditionsAttachment .getAllConditionsRecursive(new HashSet<>(toFirePossible), allConditionsNeededSoFar); return AbstractConditionsAttachment.testAllConditionsRecursive(allConditionsNeeded, allConditionsTestedSoFar, aBridge); } /** * This will fire all triggers, and it will not test to see if they are satisfied or not first. Please use * collectAndFireTriggers instead * of using this directly. * To see if they are satisfied, first create the list of triggers using Matches + TriggerAttachment.getTriggers. * Then test the triggers using RulesAttachment.getAllConditionsRecursive, and RulesAttachment.testAllConditions */ public static void fireTriggers(final HashSet<TriggerAttachment> triggersToBeFired, final HashMap<ICondition, Boolean> testedConditionsSoFar, final IDelegateBridge aBridge, final String beforeOrAfter, final String stepName, final boolean useUses, final boolean testUses, final boolean testChance, final boolean testWhen) { // all triggers at this point have their conditions satisfied // so we now test chance (because we test chance last), and remove any conditions that do not succeed in their dice // rolls final HashSet<TriggerAttachment> triggersToFire = new HashSet<>(); for (final TriggerAttachment t : triggersToBeFired) { if (testChance && !t.testChance(aBridge)) { continue; } triggersToFire.add(t); } // Order: Notifications, Attachment Property Changes (Player, Relationship, Territory, TerritoryEffect, Unit), // Relationship, // AvailableTech, Tech, ProductionFrontier, ProductionEdit, Support, Purchase, UnitPlacement, Resource, Victory // Notifications to current player triggerNotifications(triggersToFire, aBridge, beforeOrAfter, stepName, useUses, testUses, false, testWhen); // Attachment property changes triggerPlayerPropertyChange(triggersToFire, aBridge, beforeOrAfter, stepName, useUses, testUses, false, testWhen); triggerRelationshipTypePropertyChange(triggersToFire, aBridge, beforeOrAfter, stepName, useUses, testUses, false, testWhen); triggerTerritoryPropertyChange(triggersToFire, aBridge, beforeOrAfter, stepName, useUses, testUses, false, testWhen); triggerTerritoryEffectPropertyChange(triggersToFire, aBridge, beforeOrAfter, stepName, useUses, testUses, false, testWhen); triggerUnitPropertyChange(triggersToFire, aBridge, beforeOrAfter, stepName, useUses, testUses, false, testWhen); // Misc changes that only need to happen once (twice or more is meaningless) triggerRelationshipChange(triggersToFire, aBridge, beforeOrAfter, stepName, useUses, testUses, false, testWhen); triggerAvailableTechChange(triggersToFire, aBridge, beforeOrAfter, stepName, useUses, testUses, false, testWhen); triggerTechChange(triggersToFire, aBridge, beforeOrAfter, stepName, useUses, testUses, false, testWhen); triggerProductionChange(triggersToFire, aBridge, beforeOrAfter, stepName, useUses, testUses, false, testWhen); triggerProductionFrontierEditChange(triggersToFire, aBridge, beforeOrAfter, stepName, useUses, testUses, false, testWhen); triggerSupportChange(triggersToFire, aBridge, beforeOrAfter, stepName, useUses, testUses, false, testWhen); triggerChangeOwnership(triggersToFire, aBridge, beforeOrAfter, stepName, useUses, testUses, false, testWhen); // Misc changes that can happen multiple times, because they add or subtract, something from the game (and therefore // can use "each") triggerUnitRemoval(triggersToFire, aBridge, beforeOrAfter, stepName, useUses, testUses, false, testWhen); triggerPurchase(triggersToFire, aBridge, beforeOrAfter, stepName, useUses, testUses, false, testWhen); triggerUnitPlacement(triggersToFire, aBridge, beforeOrAfter, stepName, useUses, testUses, false, testWhen); triggerResourceChange(triggersToFire, aBridge, beforeOrAfter, stepName, useUses, testUses, false, testWhen); // Activating other triggers, and trigger victory, should ALWAYS be LAST in this list! triggerActivateTriggerOther(testedConditionsSoFar, triggersToFire, aBridge, beforeOrAfter, stepName, useUses, testUses, false, testWhen); // Triggers firing other triggers // Victory messages and recording of winners triggerVictory(triggersToFire, aBridge, beforeOrAfter, stepName, useUses, testUses, false, testWhen); // for both 'when' and 'activated triggers', we can change the uses now. (for other triggers, we change at end of // each round) if (useUses) { setUsesForWhenTriggers(triggersToFire, aBridge, useUses); } } protected static void setUsesForWhenTriggers(final HashSet<TriggerAttachment> triggersToBeFired, final IDelegateBridge aBridge, final boolean useUses) { if (!useUses) { return; } final CompositeChange change = new CompositeChange(); for (final TriggerAttachment trig : triggersToBeFired) { final int currentUses = trig.getUses(); // we only care about triggers that have WHEN set. Triggers without When set are changed during EndRoundDelegate. if (currentUses > 0 && !trig.getWhen().isEmpty()) { change.add(ChangeFactory.attachmentPropertyChange(trig, Integer.toString(currentUses - 1), "uses")); if (trig.getUsedThisRound()) { change.add(ChangeFactory.attachmentPropertyChange(trig, false, "usedThisRound")); } } } if (!change.isEmpty()) { aBridge.getHistoryWriter().startEvent("Setting uses for triggers used this phase."); aBridge.addChange(change); } } /** * 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 setActivateTrigger(final String value) throws GameParseException { // triggerName:numberOfTimes:useUses:testUses:testConditions:testChance final String[] s = value.split(":"); if (s.length != 6) { throw new GameParseException( "activateTrigger must have 6 parts: triggerName:numberOfTimes:useUses:testUses:testConditions:testChance" + thisErrorMsg()); } TriggerAttachment trigger = null; for (final PlayerID player : getData().getPlayerList().getPlayers()) { for (final TriggerAttachment ta : getTriggers(player, getData(), null)) { if (ta.getName().equals(s[0])) { trigger = ta; break; } } if (trigger != null) { break; } } if (trigger == null) { throw new GameParseException("No TriggerAttachment named: " + s[0] + thisErrorMsg()); } if (trigger == this) { throw new GameParseException("Cannot have a trigger activate itself!" + thisErrorMsg()); } String options = value; options = options.replaceFirst((s[0] + ":"), ""); final int numberOfTimes = getInt(s[1]); if (numberOfTimes < 0) { throw new GameParseException( "activateTrigger must be positive for the number of times to fire: " + s[1] + thisErrorMsg()); } getBool(s[2]); getBool(s[3]); getBool(s[4]); getBool(s[5]); m_activateTrigger.add(Tuple.of(s[0], options)); } @GameProperty(xmlProperty = true, gameProperty = true, adds = false) public void setActivateTrigger(final ArrayList<Tuple<String, String>> value) { m_activateTrigger = value; } public ArrayList<Tuple<String, String>> getActivateTrigger() { return m_activateTrigger; } public void clearActivateTrigger() { m_activateTrigger.clear(); } public void resetActivateTrigger() { m_activateTrigger = new ArrayList<>(); } @GameProperty(xmlProperty = true, gameProperty = true, adds = false) public void setFrontier(final String s) throws GameParseException { if (s == null) { m_frontier = null; return; } final ProductionFrontier front = getData().getProductionFrontierList().getProductionFrontier(s); if (front == null) { throw new GameParseException("Could not find frontier. name:" + s + thisErrorMsg()); } m_frontier = front; } @GameProperty(xmlProperty = true, gameProperty = true, adds = false) public void setFrontier(final ProductionFrontier value) { m_frontier = value; } public ProductionFrontier getFrontier() { return m_frontier; } public void resetFrontier() { m_frontier = 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 setProductionRule(final String prop) throws GameParseException { if (prop == null) { m_productionRule = null; return; } final String[] s = prop.split(":"); if (s.length != 2) { throw new GameParseException("Invalid productionRule declaration: " + prop + thisErrorMsg()); } if (m_productionRule == null) { m_productionRule = new ArrayList<>(); } if (getData().getProductionFrontierList().getProductionFrontier(s[0]) == null) { throw new GameParseException("Could not find frontier. name:" + s[0] + thisErrorMsg()); } String rule = s[1]; if (rule.startsWith("-")) { rule = rule.replaceFirst("-", ""); } if (getData().getProductionRuleList().getProductionRule(rule) == null) { throw new GameParseException("Could not find production rule. name:" + rule + thisErrorMsg()); } m_productionRule.add(prop); } @GameProperty(xmlProperty = true, gameProperty = true, adds = false) public void setProductionRule(final ArrayList<String> value) { m_productionRule = value; } public ArrayList<String> getProductionRule() { return m_productionRule; } public void clearProductionRule() { m_productionRule.clear(); } public void resetProductionRule() { m_productionRule = null; } @GameProperty(xmlProperty = true, gameProperty = true, adds = false) public void setResourceCount(final String s) { m_resourceCount = getInt(s); } @GameProperty(xmlProperty = true, gameProperty = true, adds = false) public void setResourceCount(final Integer s) { m_resourceCount = s; } public int getResourceCount() { return m_resourceCount; } public void resetResourceCount() { m_resourceCount = 0; } @GameProperty(xmlProperty = true, gameProperty = true, adds = false) public void setVictory(final String s) { if (s == null) { m_victory = null; return; } m_victory = s; } public String getVictory() { return m_victory; } public void resetVictory() { m_victory = 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 setTech(final String techs) throws GameParseException { for (final String subString : techs.split(":")) { TechAdvance ta = getData().getTechnologyFrontier().getAdvanceByProperty(subString); if (ta == null) { ta = getData().getTechnologyFrontier().getAdvanceByName(subString); } if (ta == null) { throw new GameParseException("Technology not found :" + subString + thisErrorMsg()); } m_tech.add(ta); } } @GameProperty(xmlProperty = true, gameProperty = true, adds = false) public void setTech(final ArrayList<TechAdvance> value) { m_tech = value; } public ArrayList<TechAdvance> getTech() { return m_tech; } public void clearTech() { m_tech.clear(); } public void resetTech() { m_tech = 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 setAvailableTech(final String techs) throws GameParseException { if (techs == null) { m_availableTech = null; return; } final String[] s = techs.split(":"); if (s.length < 2) { throw new GameParseException( "Invalid tech availability: " + techs + " should be category:techs" + thisErrorMsg()); } final String cat = s[0]; final LinkedHashMap<TechAdvance, Boolean> tlist = new LinkedHashMap<>(); for (int i = 1; i < s.length; i++) { boolean add = true; if (s[i].startsWith("-")) { add = false; s[i] = s[i].substring(1); } 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 :" + s[i] + thisErrorMsg()); } tlist.put(ta, add); } if (m_availableTech == null) { m_availableTech = new HashMap<>(); } if (m_availableTech.containsKey(cat)) { tlist.putAll(m_availableTech.get(cat)); } m_availableTech.put(cat, tlist); } @GameProperty(xmlProperty = true, gameProperty = true, adds = false) public void setAvailableTech(final HashMap<String, LinkedHashMap<TechAdvance, Boolean>> value) { m_availableTech = value; } public HashMap<String, LinkedHashMap<TechAdvance, Boolean>> getAvailableTech() { return m_availableTech; } public void clearAvailableTech() { m_availableTech.clear(); } public void resetAvailableTech() { m_availableTech = 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 setSupport(final String sup) throws GameParseException { if (sup == null) { m_support = null; return; } final String[] s = sup.split(":"); for (int i = 0; i < s.length; i++) { boolean add = true; if (s[i].startsWith("-")) { add = false; s[i] = s[i].substring(1); } boolean found = false; for (final UnitSupportAttachment support : UnitSupportAttachment.get(getData())) { if (support.getName().equals(s[i])) { found = true; if (m_support == null) { m_support = new LinkedHashMap<>(); } m_support.put(s[i], add); break; } } if (!found) { throw new GameParseException("Could not find unitSupportAttachment. name:" + s[i] + thisErrorMsg()); } } } @GameProperty(xmlProperty = true, gameProperty = true, adds = false) public void setSupport(final LinkedHashMap<String, Boolean> value) { m_support = value; } public LinkedHashMap<String, Boolean> getSupport() { return m_support; } public void clearSupport() { m_support.clear(); } public void resetSupport() { m_support = null; } @GameProperty(xmlProperty = true, gameProperty = true, adds = false) public void setResource(final String s) throws GameParseException { if (s == null) { m_resource = null; return; } final Resource r = getData().getResourceList().getResource(s); if (r == null) { throw new GameParseException("Invalid resource: " + s + thisErrorMsg()); } else { m_resource = s; } } public String getResource() { return m_resource; } public void resetResource() { m_resource = 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 setRelationshipChange(final String relChange) throws GameParseException { final String[] s = relChange.split(":"); if (s.length != 4) { throw new GameParseException("Invalid relationshipChange declaration: " + relChange + " \n Use: player1:player2:oldRelation:newRelation\n" + thisErrorMsg()); } if (getData().getPlayerList().getPlayerID(s[0]) == null) { throw new GameParseException("Invalid relationshipChange declaration: " + relChange + " \n player: " + s[0] + " unknown " + thisErrorMsg()); } if (getData().getPlayerList().getPlayerID(s[1]) == null) { throw new GameParseException("Invalid relationshipChange declaration: " + relChange + " \n player: " + s[1] + " unknown " + thisErrorMsg()); } if (!(s[2].equals(Constants.RELATIONSHIP_CONDITION_ANY_NEUTRAL) || s[2].equals(Constants.RELATIONSHIP_CONDITION_ANY) || s[2].equals(Constants.RELATIONSHIP_CONDITION_ANY_ALLIED) || s[2].equals(Constants.RELATIONSHIP_CONDITION_ANY_WAR) || Matches.isValidRelationshipName(getData()).match(s[2]))) { throw new GameParseException("Invalid relationshipChange declaration: " + relChange + " \n relationshipType: " + s[2] + " unknown " + thisErrorMsg()); } if (Matches.isValidRelationshipName(getData()).invert().match(s[3])) { throw new GameParseException("Invalid relationshipChange declaration: " + relChange + " \n relationshipType: " + s[3] + " unknown " + thisErrorMsg()); } m_relationshipChange.add(relChange); } @GameProperty(xmlProperty = true, gameProperty = true, adds = false) public void setRelationshipChange(final ArrayList<String> value) { m_relationshipChange = value; } public ArrayList<String> getRelationshipChange() { return m_relationshipChange; } public void clearRelationshipChange() { m_relationshipChange.clear(); } public void resetRelationshipChange() { m_relationshipChange = 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 setUnitType(final String names) throws GameParseException { final String[] s = names.split(":"); for (final String element : s) { final UnitType type = getData().getUnitTypeList().getUnitType(element); if (type == null) { throw new GameParseException("Could not find unitType. name:" + element + thisErrorMsg()); } m_unitType.add(type); } } @GameProperty(xmlProperty = true, gameProperty = true, adds = false) public void setUnitType(final ArrayList<UnitType> value) { m_unitType = value; } public ArrayList<UnitType> getUnitType() { return m_unitType; } public void clearUnitType() { m_unitType.clear(); } public void resetUnitType() { m_unitType = new ArrayList<>(); } @GameProperty(xmlProperty = true, gameProperty = true, adds = false) public void setUnitAttachmentName(final String name) throws GameParseException { if (name == null) { m_unitAttachmentName = null; return; } final String[] s = name.split(":"); if (s.length != 2) { throw new GameParseException( "unitAttachmentName must have 2 entries, the type of attachment and the name of the attachment." + thisErrorMsg()); } // covers UnitAttachment, UnitSupportAttachment if (!(s[1].equals("UnitAttachment") || s[1].equals("UnitSupportAttachment"))) { throw new GameParseException( "unitAttachmentName value must be UnitAttachment or UnitSupportAttachment" + thisErrorMsg()); } // TODO validate attachment exists? if (s[0].length() < 1) { throw new GameParseException("unitAttachmentName count must be a valid attachment name" + thisErrorMsg()); } if (s[1].equals("UnitAttachment") && !s[0].startsWith(Constants.UNIT_ATTACHMENT_NAME)) { throw new GameParseException("attachment incorrectly named:" + s[0] + thisErrorMsg()); } if (s[1].equals("UnitSupportAttachment") && !s[0].startsWith(Constants.SUPPORT_ATTACHMENT_PREFIX)) { throw new GameParseException("attachment incorrectly named:" + s[0] + thisErrorMsg()); } m_unitAttachmentName = Tuple.of(s[1], s[0]); } @GameProperty(xmlProperty = true, gameProperty = true, adds = false) public void setUnitAttachmentName(final Tuple<String, String> value) { m_unitAttachmentName = value; } public Tuple<String, String> getUnitAttachmentName() { if (m_unitAttachmentName == null) { return Tuple.of("UnitAttachment", Constants.UNIT_ATTACHMENT_NAME); } return m_unitAttachmentName; } public void resetUnitAttachmentName() { m_unitAttachmentName = 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 setUnitProperty(final String prop) { if (prop == null) { m_unitProperty = null; return; } final String[] s = prop.split(":"); if (m_unitProperty == null) { m_unitProperty = new ArrayList<>(); } // the last one is the property we are changing, while the rest is the string we are changing it to final String property = s[s.length - 1]; m_unitProperty.add(Tuple.of(property, getValueFromStringArrayForAllExceptLastSubstring(s))); } @GameProperty(xmlProperty = true, gameProperty = true, adds = false) public void setUnitProperty(final ArrayList<Tuple<String, String>> value) { m_unitProperty = value; } public ArrayList<Tuple<String, String>> getUnitProperty() { return m_unitProperty; } public void clearUnitProperty() { m_unitProperty.clear(); } public void resetUnitProperty() { m_unitProperty = 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 setTerritories(final String names) throws GameParseException { final String[] s = names.split(":"); for (final String element : s) { final Territory terr = getData().getMap().getTerritory(element); if (terr == null) { throw new GameParseException("Could not find territory. name:" + element + thisErrorMsg()); } m_territories.add(terr); } } @GameProperty(xmlProperty = true, gameProperty = true, adds = false) public void setTerritories(final ArrayList<Territory> value) { m_territories = value; } public ArrayList<Territory> getTerritories() { return m_territories; } public void clearTerritories() { m_territories.clear(); } public void resetTerritories() { m_territories = new ArrayList<>(); } @GameProperty(xmlProperty = true, gameProperty = true, adds = false) public void setTerritoryAttachmentName(final String name) throws GameParseException { if (name == null) { m_territoryAttachmentName = null; return; } final String[] s = name.split(":"); if (s.length != 2) { throw new GameParseException( "territoryAttachmentName must have 2 entries, the type of attachment and the name of the attachment." + thisErrorMsg()); } // covers TerritoryAttachment, CanalAttachment if (!(s[1].equals("TerritoryAttachment") || s[1].equals("CanalAttachment"))) { throw new GameParseException( "territoryAttachmentName value must be TerritoryAttachment or CanalAttachment" + thisErrorMsg()); } // TODO validate attachment exists? if (s[0].length() < 1) { throw new GameParseException("territoryAttachmentName count must be a valid attachment name" + thisErrorMsg()); } if (s[1].equals("TerritoryAttachment") && !s[0].startsWith(Constants.TERRITORY_ATTACHMENT_NAME)) { throw new GameParseException("attachment incorrectly named:" + s[0] + thisErrorMsg()); } if (s[1].equals("CanalAttachment") && !s[0].startsWith(Constants.CANAL_ATTACHMENT_PREFIX)) { throw new GameParseException("attachment incorrectly named:" + s[0] + thisErrorMsg()); } m_territoryAttachmentName = Tuple.of(s[1], s[0]); } @GameProperty(xmlProperty = true, gameProperty = true, adds = false) public void setTerritoryAttachmentName(final Tuple<String, String> value) { m_territoryAttachmentName = value; } public Tuple<String, String> getTerritoryAttachmentName() { if (m_territoryAttachmentName == null) { return Tuple.of("TerritoryAttachment", Constants.TERRITORY_ATTACHMENT_NAME); } return m_territoryAttachmentName; } public void resetTerritoryAttachmentName() { m_territoryAttachmentName = 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 setTerritoryProperty(final String prop) { if (prop == null) { m_territoryProperty = null; return; } final String[] s = prop.split(":"); if (m_territoryProperty == null) { m_territoryProperty = new ArrayList<>(); } // the last one is the property we are changing, while the rest is the string we are changing it to final String property = s[s.length - 1]; m_territoryProperty.add(Tuple.of(property, getValueFromStringArrayForAllExceptLastSubstring(s))); } @GameProperty(xmlProperty = true, gameProperty = true, adds = false) public void setTerritoryProperty(final ArrayList<Tuple<String, String>> value) { m_territoryProperty = value; } public ArrayList<Tuple<String, String>> getTerritoryProperty() { return m_territoryProperty; } public void clearTerritoryProperty() { m_territoryProperty.clear(); } public void resetTerritoryProperty() { m_territoryProperty = 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 setPlayers(final String names) throws GameParseException { final String[] s = names.split(":"); for (final String element : s) { final PlayerID player = getData().getPlayerList().getPlayerID(element); if (player == null) { throw new GameParseException("Could not find player. name:" + element + thisErrorMsg()); } m_players.add(player); } } @GameProperty(xmlProperty = true, gameProperty = true, adds = false) public void setPlayers(final ArrayList<PlayerID> value) { m_players = value; } public ArrayList<PlayerID> getPlayers() { if (m_players.isEmpty()) { return new ArrayList<>(Collections.singletonList((PlayerID) getAttachedTo())); } else { return m_players; } } public void clearPlayers() { m_players.clear(); } public void resetPlayers() { m_players = new ArrayList<>(); } @GameProperty(xmlProperty = true, gameProperty = true, adds = false) public void setPlayerAttachmentName(final String name) throws GameParseException { if (name == null) { m_playerAttachmentName = null; return; } final String[] s = name.split(":"); if (s.length != 2) { throw new GameParseException( "playerAttachmentName must have 2 entries, the type of attachment and the name of the attachment." + thisErrorMsg()); } // covers PlayerAttachment, TriggerAttachment, RulesAttachment, TechAttachment if (!(s[1].equals("PlayerAttachment") || s[1].equals("RulesAttachment") || s[1].equals("TriggerAttachment") || s[1].equals("TechAttachment") || s[1].equals("PoliticalActionAttachment") || s[1].equals("UserActionAttachment"))) { throw new GameParseException("playerAttachmentName value must be PlayerAttachment or RulesAttachment or " + "TriggerAttachment or TechAttachment or PoliticalActionAttachment or UserActionAttachment" + thisErrorMsg()); } // TODO validate attachment exists? if (s[0].length() < 1) { throw new GameParseException("playerAttachmentName count must be a valid attachment name" + thisErrorMsg()); } if (s[1].equals("PlayerAttachment") && !s[0].startsWith(Constants.PLAYER_ATTACHMENT_NAME)) { throw new GameParseException("attachment incorrectly named:" + s[0] + thisErrorMsg()); } if (s[1].equals("RulesAttachment") && !(s[0].startsWith(Constants.RULES_ATTACHMENT_NAME) || s[0].startsWith(Constants.RULES_OBJECTIVE_PREFIX) || s[0].startsWith(Constants.RULES_CONDITION_PREFIX))) { throw new GameParseException("attachment incorrectly named:" + s[0] + thisErrorMsg()); } if (s[1].equals("TriggerAttachment") && !s[0].startsWith(Constants.TRIGGER_ATTACHMENT_PREFIX)) { throw new GameParseException("attachment incorrectly named:" + s[0] + thisErrorMsg()); } if (s[1].equals("TechAttachment") && !s[0].startsWith(Constants.TECH_ATTACHMENT_NAME)) { throw new GameParseException("attachment incorrectly named:" + s[0] + thisErrorMsg()); } if (s[1].equals("PoliticalActionAttachment") && !s[0].startsWith(Constants.POLITICALACTION_ATTACHMENT_PREFIX)) { throw new GameParseException("attachment incorrectly named:" + s[0] + thisErrorMsg()); } if (s[1].equals("UserActionAttachment") && !s[0].startsWith(Constants.USERACTION_ATTACHMENT_PREFIX)) { throw new GameParseException("attachment incorrectly named:" + s[0] + thisErrorMsg()); } m_playerAttachmentName = Tuple.of(s[1], s[0]); } @GameProperty(xmlProperty = true, gameProperty = true, adds = false) public void setPlayerAttachmentName(final Tuple<String, String> value) { m_playerAttachmentName = value; } public Tuple<String, String> getPlayerAttachmentName() { if (m_playerAttachmentName == null) { return Tuple.of("PlayerAttachment", Constants.PLAYER_ATTACHMENT_NAME); } return m_playerAttachmentName; } public void resetPlayerAttachmentName() { m_playerAttachmentName = 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 setPlayerProperty(final String prop) { if (prop == null) { m_playerProperty = null; return; } final String[] s = prop.split(":"); if (m_playerProperty == null) { m_playerProperty = new ArrayList<>(); } // the last one is the property we are changing, while the rest is the string we are changing it to final String property = s[s.length - 1]; m_playerProperty.add(Tuple.of(property, getValueFromStringArrayForAllExceptLastSubstring(s))); } @GameProperty(xmlProperty = true, gameProperty = true, adds = false) public void setPlayerProperty(final ArrayList<Tuple<String, String>> value) { m_playerProperty = value; } public ArrayList<Tuple<String, String>> getPlayerProperty() { return m_playerProperty; } public void clearPlayerProperty() { m_playerProperty.clear(); } public void resetPlayerProperty() { m_playerProperty = 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 setRelationshipTypes(final String names) throws GameParseException { final String[] s = names.split(":"); for (final String element : s) { final RelationshipType relation = getData().getRelationshipTypeList().getRelationshipType(element); if (relation == null) { throw new GameParseException("Could not find relationshipType. name:" + element + thisErrorMsg()); } m_relationshipTypes.add(relation); } } @GameProperty(xmlProperty = true, gameProperty = true, adds = false) public void setRelationshipTypes(final ArrayList<RelationshipType> value) { m_relationshipTypes = value; } public ArrayList<RelationshipType> getRelationshipTypes() { return m_relationshipTypes; } public void clearRelationshipTypes() { m_relationshipTypes.clear(); } public void resetRelationshipTypes() { m_relationshipTypes = new ArrayList<>(); } @GameProperty(xmlProperty = true, gameProperty = true, adds = false) public void setRelationshipTypeAttachmentName(final String name) throws GameParseException { if (name == null) { m_relationshipTypeAttachmentName = null; return; } final String[] s = name.split(":"); if (s.length != 2) { throw new GameParseException( "relationshipTypeAttachmentName must have 2 entries, the type of attachment and the name of the attachment." + thisErrorMsg()); } // covers RelationshipTypeAttachment if (!(s[1].equals("RelationshipTypeAttachment"))) { throw new GameParseException( "relationshipTypeAttachmentName value must be RelationshipTypeAttachment" + thisErrorMsg()); } // TODO validate attachment exists? if (s[0].length() < 1) { throw new GameParseException( "relationshipTypeAttachmentName count must be a valid attachment name" + thisErrorMsg()); } if (s[1].equals("RelationshipTypeAttachment") && !s[0].startsWith(Constants.RELATIONSHIPTYPE_ATTACHMENT_NAME)) { throw new GameParseException("attachment incorrectly named:" + s[0] + thisErrorMsg()); } m_relationshipTypeAttachmentName = Tuple.of(s[1], s[0]); } @GameProperty(xmlProperty = true, gameProperty = true, adds = false) public void setRelationshipTypeAttachmentName(final Tuple<String, String> value) { m_relationshipTypeAttachmentName = value; } public Tuple<String, String> getRelationshipTypeAttachmentName() { if (m_relationshipTypeAttachmentName == null) { return Tuple.of("RelationshipTypeAttachment", Constants.RELATIONSHIPTYPE_ATTACHMENT_NAME); } return m_relationshipTypeAttachmentName; } public void resetRelationshipTypeAttachmentName() { m_relationshipTypeAttachmentName = 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 setRelationshipTypeProperty(final String prop) { if (prop == null) { m_relationshipTypeProperty = null; return; } final String[] s = prop.split(":"); if (m_relationshipTypeProperty == null) { m_relationshipTypeProperty = new ArrayList<>(); } // the last one is the property we are changing, while the rest is the string we are changing it to final String property = s[s.length - 1]; m_relationshipTypeProperty .add(Tuple.of(property, getValueFromStringArrayForAllExceptLastSubstring(s))); } @GameProperty(xmlProperty = true, gameProperty = true, adds = false) public void setRelationshipTypeProperty(final ArrayList<Tuple<String, String>> value) { m_relationshipTypeProperty = value; } public ArrayList<Tuple<String, String>> getRelationshipTypeProperty() { return m_relationshipTypeProperty; } public void clearRelationshipTypeProperty() { m_relationshipTypeProperty.clear(); } public void resetRelationshipTypeProperty() { m_relationshipTypeProperty = 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 setTerritoryEffects(final String names) throws GameParseException { final String[] s = names.split(":"); for (final String element : s) { final TerritoryEffect effect = getData().getTerritoryEffectList().get(element); if (effect == null) { throw new GameParseException("Could not find territoryEffect. name:" + element + thisErrorMsg()); } m_territoryEffects.add(effect); } } @GameProperty(xmlProperty = true, gameProperty = true, adds = false) public void setTerritoryEffects(final ArrayList<TerritoryEffect> value) { m_territoryEffects = value; } public ArrayList<TerritoryEffect> getTerritoryEffects() { return m_territoryEffects; } public void clearTerritoryEffects() { m_territoryEffects.clear(); } public void resetTerritoryEffects() { m_territoryEffects = new ArrayList<>(); } @GameProperty(xmlProperty = true, gameProperty = true, adds = false) public void setTerritoryEffectAttachmentName(final String name) throws GameParseException { if (name == null) { m_territoryEffectAttachmentName = null; return; } final String[] s = name.split(":"); if (s.length != 2) { throw new GameParseException( "territoryEffectAttachmentName must have 2 entries, the type of attachment and the name of the attachment." + thisErrorMsg()); } // covers TerritoryEffectAttachment if (!(s[1].equals("TerritoryEffectAttachment"))) { throw new GameParseException( "territoryEffectAttachmentName value must be TerritoryEffectAttachment" + thisErrorMsg()); } // TODO validate attachment exists? if (s[0].length() < 1) { throw new GameParseException( "territoryEffectAttachmentName count must be a valid attachment name" + thisErrorMsg()); } if (s[1].equals("TerritoryEffectAttachment") && !s[0].startsWith(Constants.TERRITORYEFFECT_ATTACHMENT_NAME)) { throw new GameParseException("attachment incorrectly named:" + s[0] + thisErrorMsg()); } m_territoryEffectAttachmentName = Tuple.of(s[1], s[0]); } @GameProperty(xmlProperty = true, gameProperty = true, adds = false) public void setTerritoryEffectAttachmentName(final Tuple<String, String> value) { m_territoryEffectAttachmentName = value; } public Tuple<String, String> getTerritoryEffectAttachmentName() { if (m_territoryEffectAttachmentName == null) { return Tuple.of("TerritoryEffectAttachment", Constants.TERRITORYEFFECT_ATTACHMENT_NAME); } return m_territoryEffectAttachmentName; } public void resetTerritoryEffectAttachmentName() { m_territoryEffectAttachmentName = 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 setTerritoryEffectProperty(final String prop) { if (prop == null) { m_territoryEffectProperty = null; return; } final String[] s = prop.split(":"); if (m_territoryEffectProperty == null) { m_territoryEffectProperty = new ArrayList<>(); } // the last one is the property we are changing, while the rest is the string we are changing it to final String property = s[s.length - 1]; m_territoryEffectProperty .add(Tuple.of(property, getValueFromStringArrayForAllExceptLastSubstring(s))); } @GameProperty(xmlProperty = true, gameProperty = true, adds = false) public void setTerritoryEffectProperty(final ArrayList<Tuple<String, String>> value) { m_territoryEffectProperty = value; } public ArrayList<Tuple<String, String>> getTerritoryEffectProperty() { return m_territoryEffectProperty; } public void clearTerritoryEffectProperty() { m_territoryEffectProperty.clear(); } public void resetTerritoryEffectProperty() { m_territoryEffectProperty = null; } /** * Fudging this, it really represents adding placements. * 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 setPlacement(final String place) throws GameParseException { if (place == null) { m_placement = null; return; } final String[] s = place.split(":"); int count = -1; int i = 0; if (s.length < 1) { throw new GameParseException("Empty placement list" + thisErrorMsg()); } try { count = getInt(s[0]); i++; } catch (final Exception e) { count = 1; } if (s.length < 1 || s.length == 1 && count != -1) { throw new GameParseException("Empty placement list" + thisErrorMsg()); } final Territory territory = getData().getMap().getTerritory(s[i]); if (territory == null) { throw new GameParseException("Territory does not exist " + s[i] + thisErrorMsg()); } else { i++; final IntegerMap<UnitType> map = new IntegerMap<>(); for (; i < s.length; i++) { final UnitType type = getData().getUnitTypeList().getUnitType(s[i]); if (type == null) { throw new GameParseException("UnitType does not exist " + s[i] + thisErrorMsg()); } else { map.add(type, count); } } if (m_placement == null) { m_placement = new HashMap<>(); } if (m_placement.containsKey(territory)) { map.add(m_placement.get(territory)); } m_placement.put(territory, map); } } @GameProperty(xmlProperty = true, gameProperty = true, adds = false) public void setPlacement(final HashMap<Territory, IntegerMap<UnitType>> value) { m_placement = value; } public HashMap<Territory, IntegerMap<UnitType>> getPlacement() { return m_placement; } public void clearPlacement() { m_placement.clear(); } public void resetPlacement() { m_placement = 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 setRemoveUnits(final String value) throws GameParseException { if (value == null) { m_removeUnits = null; return; } if (m_removeUnits == null) { m_removeUnits = new HashMap<>(); } final String[] s = value.split(":"); int count = -1; int i = 0; if (s.length < 1) { throw new GameParseException("Empty removeUnits list" + thisErrorMsg()); } try { count = getInt(s[0]); i++; } catch (final Exception e) { count = 1; } if (s.length < 1 || s.length == 1 && count != -1) { throw new GameParseException("Empty removeUnits list" + thisErrorMsg()); } final Collection<Territory> territories = new ArrayList<>(); final Territory terr = getData().getMap().getTerritory(s[i]); if (terr == null) { if (s[i].equalsIgnoreCase("all")) { territories.addAll(getData().getMap().getTerritories()); } else { throw new GameParseException("Territory does not exist " + s[i] + thisErrorMsg()); } } else { territories.add(terr); } i++; final IntegerMap<UnitType> map = new IntegerMap<>(); for (; i < s.length; i++) { final Collection<UnitType> types = new ArrayList<>(); final UnitType tp = getData().getUnitTypeList().getUnitType(s[i]); if (tp == null) { if (s[i].equalsIgnoreCase("all")) { types.addAll(getData().getUnitTypeList().getAllUnitTypes()); } else { throw new GameParseException("UnitType does not exist " + s[i] + thisErrorMsg()); } } else { types.add(tp); } for (final UnitType type : types) { map.add(type, count); } } for (final Territory t : territories) { if (m_removeUnits.containsKey(t)) { map.add(m_removeUnits.get(t)); } m_removeUnits.put(t, map); } } @GameProperty(xmlProperty = true, gameProperty = true, adds = false) public void setRemoveUnits(final HashMap<Territory, IntegerMap<UnitType>> value) { m_removeUnits = value; } public HashMap<Territory, IntegerMap<UnitType>> getRemoveUnits() { return m_removeUnits; } public void clearRemoveUnits() { m_removeUnits.clear(); } public void resetRemoveUnits() { m_removeUnits = 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 setPurchase(final String place) throws GameParseException { if (place == null) { m_purchase = null; return; } final String[] s = place.split(":"); int count = -1; int i = 0; if (s.length < 1) { throw new GameParseException("Empty purchase list" + thisErrorMsg()); } try { count = getInt(s[0]); i++; } catch (final Exception e) { count = 1; } if (s.length < 1 || s.length == 1 && count != -1) { throw new GameParseException("Empty purchase list" + thisErrorMsg()); } else { if (m_purchase == null) { m_purchase = new IntegerMap<>(); } for (; i < s.length; i++) { final UnitType type = getData().getUnitTypeList().getUnitType(s[i]); if (type == null) { throw new GameParseException("UnitType does not exist " + s[i] + thisErrorMsg()); } else { m_purchase.add(type, count); } } } } @GameProperty(xmlProperty = true, gameProperty = true, adds = false) public void setPurchase(final IntegerMap<UnitType> value) { m_purchase = value; } public IntegerMap<UnitType> getPurchase() { return m_purchase; } public void clearPurchase() { m_purchase.clear(); } public void resetPurchase() { m_purchase = 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 setChangeOwnership(final String value) throws GameParseException { // territory:oldOwner:newOwner:booleanConquered // can have "all" for territory and "any" for oldOwner final String[] s = value.split(":"); if (s.length < 4) { throw new GameParseException( "changeOwnership must have 4 fields: territory:oldOwner:newOwner:booleanConquered" + thisErrorMsg()); } if (!s[0].equalsIgnoreCase("all")) { final Territory t = getData().getMap().getTerritory(s[0]); if (t == null) { throw new GameParseException("No such territory: " + s[0] + thisErrorMsg()); } } if (!s[1].equalsIgnoreCase("any")) { final PlayerID oldOwner = getData().getPlayerList().getPlayerID(s[1]); if (oldOwner == null) { throw new GameParseException("No such player: " + s[1] + thisErrorMsg()); } } final PlayerID newOwner = getData().getPlayerList().getPlayerID(s[2]); if (newOwner == null) { throw new GameParseException("No such player: " + s[2] + thisErrorMsg()); } getBool(s[3]); m_changeOwnership.add(value); } @GameProperty(xmlProperty = true, gameProperty = true, adds = false) public void setChangeOwnership(final ArrayList<String> value) { m_changeOwnership = value; } public ArrayList<String> getChangeOwnership() { return m_changeOwnership; } public void clearChangeOwnership() { m_changeOwnership.clear(); } public void resetChangeOwnership() { m_changeOwnership = new ArrayList<>(); } private static void removeUnits(final TriggerAttachment t, final Territory terr, final IntegerMap<UnitType> uMap, final PlayerID player, final IDelegateBridge aBridge) { final CompositeChange change = new CompositeChange(); final Collection<Unit> totalRemoved = new ArrayList<>(); for (final UnitType ut : uMap.keySet()) { final int removeNum = uMap.getInt(ut); final Collection<Unit> toRemove = Match.getNMatches(terr.getUnits().getUnits(), removeNum, new CompositeMatchAnd<>(Matches.unitIsOwnedBy(player), Matches.unitIsOfType(ut))); if (!toRemove.isEmpty()) { totalRemoved.addAll(toRemove); change.add(ChangeFactory.removeUnits(terr, toRemove)); } } if (!change.isEmpty()) { final String transcriptText = MyFormatter.attachmentNameToText(t.getName()) + ": has removed " + MyFormatter.unitsToTextNoOwner(totalRemoved) + " owned by " + player.getName() + " in " + terr.getName(); aBridge.getHistoryWriter().startEvent(transcriptText, totalRemoved); aBridge.addChange(change); } } private static void placeUnits(final TriggerAttachment t, final Territory terr, final IntegerMap<UnitType> uMap, final PlayerID player, final IDelegateBridge aBridge) { // createUnits final List<Unit> units = new ArrayList<>(); for (final UnitType u : uMap.keySet()) { units.addAll(u.create(uMap.getInt(u), player)); } final CompositeChange change = new CompositeChange(); // mark no movement for (final Unit unit : units) { change.add(ChangeFactory.markNoMovementChange(unit)); } // place units final Collection<Unit> factoryAndInfrastructure = Match.getMatches(units, Matches.UnitIsInfrastructure); change.add(OriginalOwnerTracker.addOriginalOwnerChange(factoryAndInfrastructure, player)); final String transcriptText = MyFormatter.attachmentNameToText(t.getName()) + ": " + player.getName() + " has " + MyFormatter.unitsToTextNoOwner(units) + " placed in " + terr.getName(); aBridge.getHistoryWriter().startEvent(transcriptText, units); final Change place = ChangeFactory.addUnits(terr, units); change.add(place); aBridge.addChange(change); } // And now for the actual triggers, as called throughout the engine. // Each trigger should be called exactly twice, once in BaseDelegate (for use with 'when'), and a second time as the // default location for // when 'when' is not used. // Should be void. public static void triggerNotifications(final Set<TriggerAttachment> satisfiedTriggers, final IDelegateBridge aBridge, final String beforeOrAfter, final String stepName, final boolean useUses, final boolean testUses, final boolean testChance, final boolean testWhen) { final GameData data = aBridge.getData(); Collection<TriggerAttachment> trigs = Match.getMatches(satisfiedTriggers, notificationMatch()); if (testWhen) { trigs = Match.getMatches(trigs, whenOrDefaultMatch(beforeOrAfter, stepName)); } if (testUses) { trigs = Match.getMatches(trigs, availableUses); } final Set<String> notifications = new HashSet<>(); for (final TriggerAttachment t : trigs) { if (testChance && !t.testChance(aBridge)) { continue; } if (useUses) { t.use(aBridge); } if (!notifications.contains(t.getNotification())) { notifications.add(t.getNotification()); final String notificationMessageKey = t.getNotification().trim(); final String sounds = NotificationMessages.getInstance().getSoundsKey(notificationMessageKey); if (sounds != null) { // play to observers if we are playing to everyone aBridge.getSoundChannelBroadcaster().playSoundToPlayers( SoundPath.CLIP_TRIGGERED_NOTIFICATION_SOUND + sounds.trim(), t.getPlayers(), null, t.getPlayers().containsAll(data.getPlayerList().getPlayers())); } final String message = NotificationMessages.getInstance().getMessage(notificationMessageKey); if (message != null) { String messageForRecord = message.trim(); if (messageForRecord.length() > 190) { // We don't want to record a giant string in the history panel, so just put a shortened version in instead. messageForRecord = messageForRecord.replaceAll("\\<br.*?>", " "); messageForRecord = messageForRecord.replaceAll("\\<.*?>", ""); if (messageForRecord.length() > 195) { messageForRecord = messageForRecord.substring(0, 190) + "...."; } } aBridge.getHistoryWriter().startEvent( "Note to players " + MyFormatter.defaultNamedToTextList(t.getPlayers()) + ": " + messageForRecord); ((ITripleADisplay) aBridge.getDisplayChannelBroadcaster()).reportMessageToPlayers(t.getPlayers(), null, ("<html>" + message.trim() + "</html>"), NOTIFICATION); } } } } public static void triggerPlayerPropertyChange(final Set<TriggerAttachment> satisfiedTriggers, final IDelegateBridge aBridge, final String beforeOrAfter, final String stepName, final boolean useUses, final boolean testUses, final boolean testChance, final boolean testWhen) { Collection<TriggerAttachment> trigs = Match.getMatches(satisfiedTriggers, playerPropertyMatch()); if (testWhen) { trigs = Match.getMatches(trigs, whenOrDefaultMatch(beforeOrAfter, stepName)); } if (testUses) { trigs = Match.getMatches(trigs, availableUses); } final CompositeChange change = new CompositeChange(); for (final TriggerAttachment t : trigs) { if (testChance && !t.testChance(aBridge)) { continue; } if (useUses) { t.use(aBridge); } for (final Tuple<String, String> property : t.getPlayerProperty()) { for (final PlayerID aPlayer : t.getPlayers()) { String newValue = property.getSecond(); boolean clearFirst = false; // test if we are resetting the variable first, and if so, remove the leading "-reset-" or "-clear-" if (newValue.length() > 0 && (newValue.startsWith(PREFIX_CLEAR) || newValue.startsWith(PREFIX_RESET))) { newValue = newValue.replaceFirst(PREFIX_CLEAR, "").replaceFirst(PREFIX_RESET, ""); clearFirst = true; } // covers PlayerAttachment, TriggerAttachment, RulesAttachment, TechAttachment if (t.getPlayerAttachmentName().getFirst().equals("PlayerAttachment")) { final PlayerAttachment attachment = PlayerAttachment.get(aPlayer, t.getPlayerAttachmentName().getSecond()); if (newValue.equals(attachment.getRawPropertyString(property.getFirst()))) { continue; } if (clearFirst && newValue.length() < 1) { change.add(ChangeFactory.attachmentPropertyReset(attachment, property.getFirst())); } else { change.add( ChangeFactory.attachmentPropertyChange(attachment, newValue, property.getFirst(), clearFirst)); } aBridge.getHistoryWriter() .startEvent(MyFormatter.attachmentNameToText(t.getName()) + ": Setting " + property.getFirst() + (newValue.length() > 0 ? " to " + newValue : " cleared ") + " for " + t.getPlayerAttachmentName().getSecond() + " attached to " + aPlayer.getName()); } else if (t.getPlayerAttachmentName().getFirst().equals("RulesAttachment")) { final RulesAttachment attachment = RulesAttachment.get(aPlayer, t.getPlayerAttachmentName().getSecond()); if (newValue.equals(attachment.getRawPropertyString(property.getFirst()))) { continue; } if (clearFirst && newValue.length() < 1) { change.add(ChangeFactory.attachmentPropertyReset(attachment, property.getFirst())); } else { change.add( ChangeFactory.attachmentPropertyChange(attachment, newValue, property.getFirst(), clearFirst)); } aBridge.getHistoryWriter() .startEvent(MyFormatter.attachmentNameToText(t.getName()) + ": Setting " + property.getFirst() + (newValue.length() > 0 ? " to " + newValue : " cleared ") + " for " + t.getPlayerAttachmentName().getSecond() + " attached to " + aPlayer.getName()); } else if (t.getPlayerAttachmentName().getFirst().equals("TriggerAttachment")) { final TriggerAttachment attachment = TriggerAttachment.get(aPlayer, t.getPlayerAttachmentName().getSecond()); if (newValue.equals(attachment.getRawPropertyString(property.getFirst()))) { continue; } if (clearFirst && newValue.length() < 1) { change.add(ChangeFactory.attachmentPropertyReset(attachment, property.getFirst())); } else { change.add( ChangeFactory.attachmentPropertyChange(attachment, newValue, property.getFirst(), clearFirst)); } aBridge.getHistoryWriter() .startEvent(MyFormatter.attachmentNameToText(t.getName()) + ": Setting " + property.getFirst() + (newValue.length() > 0 ? " to " + newValue : " cleared ") + " for " + t.getPlayerAttachmentName().getSecond() + " attached to " + aPlayer.getName()); } else if (t.getPlayerAttachmentName().getFirst().equals("TechAttachment")) { final TechAttachment attachment = TechAttachment.get(aPlayer, t.getPlayerAttachmentName().getSecond()); if (newValue.equals(attachment.getRawPropertyString(property.getFirst()))) { continue; } if (clearFirst && newValue.length() < 1) { change.add(ChangeFactory.attachmentPropertyReset(attachment, property.getFirst())); } else { change.add( ChangeFactory.attachmentPropertyChange(attachment, newValue, property.getFirst(), clearFirst)); } aBridge.getHistoryWriter() .startEvent(MyFormatter.attachmentNameToText(t.getName()) + ": Setting " + property.getFirst() + (newValue.length() > 0 ? " to " + newValue : " cleared ") + " for " + t.getPlayerAttachmentName().getSecond() + " attached to " + aPlayer.getName()); } else if (t.getPlayerAttachmentName().getFirst().equals("PoliticalActionAttachment")) { final PoliticalActionAttachment attachment = PoliticalActionAttachment.get(aPlayer, t.getPlayerAttachmentName().getSecond()); if (newValue.equals(attachment.getRawPropertyString(property.getFirst()))) { continue; } if (clearFirst && newValue.length() < 1) { change.add(ChangeFactory.attachmentPropertyReset(attachment, property.getFirst())); } else { change.add( ChangeFactory.attachmentPropertyChange(attachment, newValue, property.getFirst(), clearFirst)); } aBridge.getHistoryWriter() .startEvent(MyFormatter.attachmentNameToText(t.getName()) + ": Setting " + property.getFirst() + (newValue.length() > 0 ? " to " + newValue : " cleared ") + " for " + t.getPlayerAttachmentName().getSecond() + " attached to " + aPlayer.getName()); } else if (t.getPlayerAttachmentName().getFirst().equals("UserActionAttachment")) { final UserActionAttachment attachment = UserActionAttachment.get(aPlayer, t.getPlayerAttachmentName().getSecond()); if (newValue.equals(attachment.getRawPropertyString(property.getFirst()))) { continue; } if (clearFirst && newValue.length() < 1) { change.add(ChangeFactory.attachmentPropertyReset(attachment, property.getFirst())); } else { change.add( ChangeFactory.attachmentPropertyChange(attachment, newValue, property.getFirst(), clearFirst)); } aBridge.getHistoryWriter() .startEvent(MyFormatter.attachmentNameToText(t.getName()) + ": Setting " + property.getFirst() + (newValue.length() > 0 ? " to " + newValue : " cleared ") + " for " + t.getPlayerAttachmentName().getSecond() + " attached to " + aPlayer.getName()); } // TODO add other attachment changes here if they attach to a player } } } if (!change.isEmpty()) { aBridge.addChange(change); } } public static void triggerRelationshipTypePropertyChange(final Set<TriggerAttachment> satisfiedTriggers, final IDelegateBridge aBridge, final String beforeOrAfter, final String stepName, final boolean useUses, final boolean testUses, final boolean testChance, final boolean testWhen) { Collection<TriggerAttachment> trigs = Match.getMatches(satisfiedTriggers, relationshipTypePropertyMatch()); if (testWhen) { trigs = Match.getMatches(trigs, whenOrDefaultMatch(beforeOrAfter, stepName)); } if (testUses) { trigs = Match.getMatches(trigs, availableUses); } final CompositeChange change = new CompositeChange(); for (final TriggerAttachment t : trigs) { if (testChance && !t.testChance(aBridge)) { continue; } if (useUses) { t.use(aBridge); } for (final Tuple<String, String> property : t.getRelationshipTypeProperty()) { for (final RelationshipType aRelationshipType : t.getRelationshipTypes()) { String newValue = property.getSecond(); boolean clearFirst = false; // test if we are resetting the variable first, and if so, remove the leading "-reset-" or "-clear-" if (newValue.length() > 0 && (newValue.startsWith(PREFIX_CLEAR) || newValue.startsWith(PREFIX_RESET))) { newValue = newValue.replaceFirst(PREFIX_CLEAR, "").replaceFirst(PREFIX_RESET, ""); clearFirst = true; } // covers RelationshipTypeAttachment if (t.getRelationshipTypeAttachmentName().getFirst().equals("RelationshipTypeAttachment")) { final RelationshipTypeAttachment attachment = RelationshipTypeAttachment.get(aRelationshipType, t.getRelationshipTypeAttachmentName().getSecond()); if (newValue.equals(attachment.getRawPropertyString(property.getFirst()))) { continue; } if (clearFirst && newValue.length() < 1) { change.add(ChangeFactory.attachmentPropertyReset(attachment, property.getFirst())); } else { change.add( ChangeFactory.attachmentPropertyChange(attachment, newValue, property.getFirst(), clearFirst)); } aBridge.getHistoryWriter().startEvent(MyFormatter.attachmentNameToText(t.getName()) + ": Setting " + property.getFirst() + (newValue.length() > 0 ? " to " + newValue : " cleared ") + " for " + t.getRelationshipTypeAttachmentName().getSecond() + " attached to " + aRelationshipType.getName()); } // TODO add other attachment changes here if they attach to a territory } } } if (!change.isEmpty()) { aBridge.addChange(change); } } public static void triggerTerritoryPropertyChange(final Set<TriggerAttachment> satisfiedTriggers, final IDelegateBridge aBridge, final String beforeOrAfter, final String stepName, final boolean useUses, final boolean testUses, final boolean testChance, final boolean testWhen) { Collection<TriggerAttachment> trigs = Match.getMatches(satisfiedTriggers, territoryPropertyMatch()); if (testWhen) { trigs = Match.getMatches(trigs, whenOrDefaultMatch(beforeOrAfter, stepName)); } if (testUses) { trigs = Match.getMatches(trigs, availableUses); } final CompositeChange change = new CompositeChange(); final HashSet<Territory> territoriesNeedingReDraw = new HashSet<>(); for (final TriggerAttachment t : trigs) { if (testChance && !t.testChance(aBridge)) { continue; } if (useUses) { t.use(aBridge); } for (final Tuple<String, String> property : t.getTerritoryProperty()) { for (final Territory aTerritory : t.getTerritories()) { territoriesNeedingReDraw.add(aTerritory); String newValue = property.getSecond(); boolean clearFirst = false; // test if we are resetting the variable first, and if so, remove the leading "-reset-" or "-clear-" if (newValue.length() > 0 && (newValue.startsWith(PREFIX_CLEAR) || newValue.startsWith(PREFIX_RESET))) { newValue = newValue.replaceFirst(PREFIX_CLEAR, "").replaceFirst(PREFIX_RESET, ""); clearFirst = true; } // covers TerritoryAttachment, CanalAttachment if (t.getTerritoryAttachmentName().getFirst().equals("TerritoryAttachment")) { final TerritoryAttachment attachment = TerritoryAttachment.get(aTerritory, t.getTerritoryAttachmentName().getSecond()); if (attachment == null) { // water territories may not have an attachment, so this could be null throw new IllegalStateException("Triggers: No territory attachment for:" + aTerritory.getName()); } if (newValue.equals(attachment.getRawPropertyString(property.getFirst()))) { continue; } if (clearFirst && newValue.length() < 1) { change.add(ChangeFactory.attachmentPropertyReset(attachment, property.getFirst())); } else { change.add( ChangeFactory.attachmentPropertyChange(attachment, newValue, property.getFirst(), clearFirst)); } aBridge.getHistoryWriter() .startEvent(MyFormatter.attachmentNameToText(t.getName()) + ": Setting " + property.getFirst() + (newValue.length() > 0 ? " to " + newValue : " cleared ") + " for " + t.getTerritoryAttachmentName().getSecond() + " attached to " + aTerritory.getName()); } else if (t.getTerritoryAttachmentName().getFirst().equals("CanalAttachment")) { final CanalAttachment attachment = CanalAttachment.get(aTerritory, t.getTerritoryAttachmentName().getSecond()); if (newValue.equals(attachment.getRawPropertyString(property.getFirst()))) { continue; } if (clearFirst && newValue.length() < 1) { change.add(ChangeFactory.attachmentPropertyReset(attachment, property.getFirst())); } else { change.add( ChangeFactory.attachmentPropertyChange(attachment, newValue, property.getFirst(), clearFirst)); } aBridge.getHistoryWriter() .startEvent(MyFormatter.attachmentNameToText(t.getName()) + ": Setting " + property.getFirst() + (newValue.length() > 0 ? " to " + newValue : " cleared ") + " for " + t.getTerritoryAttachmentName().getSecond() + " attached to " + aTerritory.getName()); } // TODO add other attachment changes here if they attach to a territory } } } if (!change.isEmpty()) { aBridge.addChange(change); for (final Territory aTerritory : territoriesNeedingReDraw) { aTerritory.notifyAttachmentChanged(); } } } public static void triggerTerritoryEffectPropertyChange(final Set<TriggerAttachment> satisfiedTriggers, final IDelegateBridge aBridge, final String beforeOrAfter, final String stepName, final boolean useUses, final boolean testUses, final boolean testChance, final boolean testWhen) { Collection<TriggerAttachment> trigs = Match.getMatches(satisfiedTriggers, territoryEffectPropertyMatch()); if (testWhen) { trigs = Match.getMatches(trigs, whenOrDefaultMatch(beforeOrAfter, stepName)); } if (testUses) { trigs = Match.getMatches(trigs, availableUses); } final CompositeChange change = new CompositeChange(); for (final TriggerAttachment t : trigs) { if (testChance && !t.testChance(aBridge)) { continue; } if (useUses) { t.use(aBridge); } for (final Tuple<String, String> property : t.getTerritoryEffectProperty()) { for (final TerritoryEffect aTerritoryEffect : t.getTerritoryEffects()) { String newValue = property.getSecond(); boolean clearFirst = false; // test if we are resetting the variable first, and if so, remove the leading "-reset-" or "-clear-" if (newValue.length() > 0 && (newValue.startsWith(PREFIX_CLEAR) || newValue.startsWith(PREFIX_RESET))) { newValue = newValue.replaceFirst(PREFIX_CLEAR, "").replaceFirst(PREFIX_RESET, ""); clearFirst = true; } // covers TerritoryEffectAttachment if (t.getTerritoryEffectAttachmentName().getFirst().equals("TerritoryEffectAttachment")) { final TerritoryEffectAttachment attachment = TerritoryEffectAttachment.get(aTerritoryEffect, t.getTerritoryEffectAttachmentName().getSecond()); if (newValue.equals(attachment.getRawPropertyString(property.getFirst()))) { continue; } if (clearFirst && newValue.length() < 1) { change.add(ChangeFactory.attachmentPropertyReset(attachment, property.getFirst())); } else { change.add( ChangeFactory.attachmentPropertyChange(attachment, newValue, property.getFirst(), clearFirst)); } aBridge.getHistoryWriter() .startEvent(MyFormatter.attachmentNameToText(t.getName()) + ": Setting " + property.getFirst() + (newValue.length() > 0 ? " to " + newValue : " cleared ") + " for " + t.getTerritoryEffectAttachmentName().getSecond() + " attached to " + aTerritoryEffect.getName()); } // TODO add other attachment changes here if they attach to a territory } } } if (!change.isEmpty()) { aBridge.addChange(change); } } public static void triggerUnitPropertyChange(final Set<TriggerAttachment> satisfiedTriggers, final IDelegateBridge aBridge, final String beforeOrAfter, final String stepName, final boolean useUses, final boolean testUses, final boolean testChance, final boolean testWhen) { Collection<TriggerAttachment> trigs = Match.getMatches(satisfiedTriggers, unitPropertyMatch()); if (testWhen) { trigs = Match.getMatches(trigs, whenOrDefaultMatch(beforeOrAfter, stepName)); } if (testUses) { trigs = Match.getMatches(trigs, availableUses); } final CompositeChange change = new CompositeChange(); for (final TriggerAttachment t : trigs) { if (testChance && !t.testChance(aBridge)) { continue; } if (useUses) { t.use(aBridge); } for (final Tuple<String, String> property : t.getUnitProperty()) { for (final UnitType aUnitType : t.getUnitType()) { String newValue = property.getSecond(); boolean clearFirst = false; // test if we are resetting the variable first, and if so, remove the leading "-reset-" or "-clear-" if (newValue.length() > 0 && (newValue.startsWith(PREFIX_CLEAR) || newValue.startsWith(PREFIX_RESET))) { newValue = newValue.replaceFirst(PREFIX_CLEAR, "").replaceFirst(PREFIX_RESET, ""); clearFirst = true; } // covers UnitAttachment, UnitSupportAttachment if (t.getUnitAttachmentName().getFirst().equals("UnitAttachment")) { final UnitAttachment attachment = UnitAttachment.get(aUnitType, t.getUnitAttachmentName().getSecond()); if (newValue.equals(attachment.getRawPropertyString(property.getFirst()))) { continue; } if (clearFirst && newValue.length() < 1) { change.add(ChangeFactory.attachmentPropertyReset(attachment, property.getFirst())); } else { change.add( ChangeFactory.attachmentPropertyChange(attachment, newValue, property.getFirst(), clearFirst)); } aBridge.getHistoryWriter() .startEvent(MyFormatter.attachmentNameToText(t.getName()) + ": Setting " + property.getFirst() + (newValue.length() > 0 ? " to " + newValue : " cleared ") + " for " + t.getUnitAttachmentName().getSecond() + " attached to " + aUnitType.getName()); } else if (t.getUnitAttachmentName().getFirst().equals("UnitSupportAttachment")) { final UnitSupportAttachment attachment = UnitSupportAttachment.get(aUnitType, t.getUnitAttachmentName().getSecond()); if (newValue.equals(attachment.getRawPropertyString(property.getFirst()))) { continue; } if (clearFirst && newValue.length() < 1) { change.add(ChangeFactory.attachmentPropertyReset(attachment, property.getFirst())); } else { change.add( ChangeFactory.attachmentPropertyChange(attachment, newValue, property.getFirst(), clearFirst)); } aBridge.getHistoryWriter() .startEvent(MyFormatter.attachmentNameToText(t.getName()) + ": Setting " + property.getFirst() + (newValue.length() > 0 ? " to " + newValue : " cleared ") + " for " + t.getUnitAttachmentName().getSecond() + " attached to " + aUnitType.getName()); } // TODO add other attachment changes here if they attach to a unitType } } } if (!change.isEmpty()) { aBridge.addChange(change); } } public static void triggerRelationshipChange(final Set<TriggerAttachment> satisfiedTriggers, final IDelegateBridge aBridge, final String beforeOrAfter, final String stepName, final boolean useUses, final boolean testUses, final boolean testChance, final boolean testWhen) { final GameData data = aBridge.getData(); Collection<TriggerAttachment> trigs = Match.getMatches(satisfiedTriggers, relationshipChangeMatch()); if (testWhen) { trigs = Match.getMatches(trigs, whenOrDefaultMatch(beforeOrAfter, stepName)); } if (testUses) { trigs = Match.getMatches(trigs, availableUses); } final CompositeChange change = new CompositeChange(); for (final TriggerAttachment t : trigs) { if (testChance && !t.testChance(aBridge)) { continue; } if (useUses) { t.use(aBridge); } for (final String relationshipChange : t.getRelationshipChange()) { final String[] s = relationshipChange.split(":"); final PlayerID player1 = data.getPlayerList().getPlayerID(s[0]); final PlayerID player2 = data.getPlayerList().getPlayerID(s[1]); final RelationshipType currentRelation = data.getRelationshipTracker().getRelationshipType(player1, player2); if (s[2].equals(Constants.RELATIONSHIP_CONDITION_ANY) || (s[2].equals(Constants.RELATIONSHIP_CONDITION_ANY_NEUTRAL) && Matches.RelationshipTypeIsNeutral.match(currentRelation)) || (s[2].equals(Constants.RELATIONSHIP_CONDITION_ANY_ALLIED) && Matches.RelationshipTypeIsAllied.match(currentRelation)) || (s[2].equals(Constants.RELATIONSHIP_CONDITION_ANY_WAR) && Matches.RelationshipTypeIsAtWar.match(currentRelation)) || currentRelation.equals(data.getRelationshipTypeList().getRelationshipType(s[2]))) { final RelationshipType triggerNewRelation = data.getRelationshipTypeList().getRelationshipType(s[3]); change.add(ChangeFactory.relationshipChange(player1, player2, currentRelation, triggerNewRelation)); aBridge.getHistoryWriter() .startEvent(MyFormatter.attachmentNameToText(t.getName()) + ": Changing Relationship for " + player1.getName() + " and " + player2.getName() + " from " + currentRelation.getName() + " to " + triggerNewRelation.getName()); AbstractMoveDelegate.getBattleTracker(data).addRelationshipChangesThisTurn(player1, player2, currentRelation, triggerNewRelation); /* * creation of new battles is handled at the beginning of the battle delegate, in * "setupUnitsInSameTerritoryBattles", not here. * if (Matches.RelationshipTypeIsAtWar.match(triggerNewRelation)) * triggerMustFightBattle(player1, player2, aBridge); */ } } } if (!change.isEmpty()) { aBridge.addChange(change); } } public static void triggerAvailableTechChange(final Set<TriggerAttachment> satisfiedTriggers, final IDelegateBridge aBridge, final String beforeOrAfter, final String stepName, final boolean useUses, final boolean testUses, final boolean testChance, final boolean testWhen) { Collection<TriggerAttachment> trigs = Match.getMatches(satisfiedTriggers, techAvailableMatch()); if (testWhen) { trigs = Match.getMatches(trigs, whenOrDefaultMatch(beforeOrAfter, stepName)); } if (testUses) { trigs = Match.getMatches(trigs, availableUses); } for (final TriggerAttachment t : trigs) { if (testChance && !t.testChance(aBridge)) { continue; } if (useUses) { t.use(aBridge); } for (final PlayerID aPlayer : t.getPlayers()) { for (final String cat : t.getAvailableTech().keySet()) { final TechnologyFrontier tf = aPlayer.getTechnologyFrontierList().getTechnologyFrontier(cat); if (tf == null) { throw new IllegalStateException("Triggers: tech category doesn't exist:" + cat + " for player:" + aPlayer); } for (final TechAdvance ta : t.getAvailableTech().get(cat).keySet()) { if (t.getAvailableTech().get(cat).get(ta)) { aBridge.getHistoryWriter().startEvent( MyFormatter.attachmentNameToText(t.getName()) + ": " + aPlayer.getName() + " gains access to " + ta); final Change change = ChangeFactory.addAvailableTech(tf, ta, aPlayer); aBridge.addChange(change); } else { aBridge.getHistoryWriter().startEvent( MyFormatter.attachmentNameToText(t.getName()) + ": " + aPlayer.getName() + " loses access to " + ta); final Change change = ChangeFactory.removeAvailableTech(tf, ta, aPlayer); aBridge.addChange(change); } } } } } } public static void triggerTechChange(final Set<TriggerAttachment> satisfiedTriggers, final IDelegateBridge aBridge, final String beforeOrAfter, final String stepName, final boolean useUses, final boolean testUses, final boolean testChance, final boolean testWhen) { // final GameData data = aBridge.getData(); Collection<TriggerAttachment> trigs = Match.getMatches(satisfiedTriggers, techMatch()); if (testWhen) { trigs = Match.getMatches(trigs, whenOrDefaultMatch(beforeOrAfter, stepName)); } if (testUses) { trigs = Match.getMatches(trigs, availableUses); } for (final TriggerAttachment t : trigs) { if (testChance && !t.testChance(aBridge)) { continue; } if (useUses) { t.use(aBridge); } for (final PlayerID aPlayer : t.getPlayers()) { for (final TechAdvance ta : t.getTech()) { if (ta.hasTech(TechAttachment.get(aPlayer))) { continue; } aBridge.getHistoryWriter().startEvent( MyFormatter.attachmentNameToText(t.getName()) + ": " + aPlayer.getName() + " activates " + ta); TechTracker.addAdvance(aPlayer, aBridge, ta); } } } } public static void triggerProductionChange(final Set<TriggerAttachment> satisfiedTriggers, final IDelegateBridge aBridge, final String beforeOrAfter, final String stepName, final boolean useUses, final boolean testUses, final boolean testChance, final boolean testWhen) { Collection<TriggerAttachment> trigs = Match.getMatches(satisfiedTriggers, prodMatch()); if (testWhen) { trigs = Match.getMatches(trigs, whenOrDefaultMatch(beforeOrAfter, stepName)); } if (testUses) { trigs = Match.getMatches(trigs, availableUses); } final CompositeChange change = new CompositeChange(); for (final TriggerAttachment t : trigs) { if (testChance && !t.testChance(aBridge)) { continue; } if (useUses) { t.use(aBridge); } for (final PlayerID aPlayer : t.getPlayers()) { change.add(ChangeFactory.changeProductionFrontier(aPlayer, t.getFrontier())); aBridge.getHistoryWriter().startEvent(MyFormatter.attachmentNameToText(t.getName()) + ": " + aPlayer.getName() + " has their production frontier changed to: " + t.getFrontier().getName()); } } if (!change.isEmpty()) { aBridge.addChange(change); } } public static void triggerProductionFrontierEditChange(final Set<TriggerAttachment> satisfiedTriggers, final IDelegateBridge aBridge, final String beforeOrAfter, final String stepName, final boolean useUses, final boolean testUses, final boolean testChance, final boolean testWhen) { final GameData data = aBridge.getData(); Collection<TriggerAttachment> trigs = Match.getMatches(satisfiedTriggers, prodFrontierEditMatch()); if (testWhen) { trigs = Match.getMatches(trigs, whenOrDefaultMatch(beforeOrAfter, stepName)); } if (testUses) { trigs = Match.getMatches(trigs, availableUses); } final CompositeChange change = new CompositeChange(); for (final TriggerAttachment t : trigs) { if (testChance && !t.testChance(aBridge)) { continue; } if (useUses) { t.use(aBridge); } final Iterator<String> iter = t.getProductionRule().iterator(); while (iter.hasNext()) { boolean add = true; final String[] s = iter.next().split(":"); final ProductionFrontier front = data.getProductionFrontierList().getProductionFrontier(s[0]); String rule = s[1]; if (rule.startsWith("-")) { rule = rule.replaceFirst("-", ""); add = false; } final ProductionRule pRule = data.getProductionRuleList().getProductionRule(rule); if (add) { if (!front.getRules().contains(pRule)) { change.add(ChangeFactory.addProductionRule(pRule, front)); aBridge.getHistoryWriter().startEvent(MyFormatter.attachmentNameToText(t.getName()) + ": " + pRule.getName() + " added to " + front.getName()); } } else { if (front.getRules().contains(pRule)) { change.add(ChangeFactory.removeProductionRule(pRule, front)); aBridge.getHistoryWriter().startEvent(MyFormatter.attachmentNameToText(t.getName()) + ": " + pRule.getName() + " removed from " + front.getName()); } } } } if (!change.isEmpty()) { aBridge.addChange(change); // TODO: we should sort the frontier list if we make changes to it... } } public static void triggerSupportChange(final Set<TriggerAttachment> satisfiedTriggers, final IDelegateBridge aBridge, final String beforeOrAfter, final String stepName, final boolean useUses, final boolean testUses, final boolean testChance, final boolean testWhen) { final GameData data = aBridge.getData(); Collection<TriggerAttachment> trigs = Match.getMatches(satisfiedTriggers, supportMatch()); if (testWhen) { trigs = Match.getMatches(trigs, whenOrDefaultMatch(beforeOrAfter, stepName)); } if (testUses) { trigs = Match.getMatches(trigs, availableUses); } final CompositeChange change = new CompositeChange(); for (final TriggerAttachment t : trigs) { if (testChance && !t.testChance(aBridge)) { continue; } if (useUses) { t.use(aBridge); } for (final PlayerID aPlayer : t.getPlayers()) { for (final String usaString : t.getSupport().keySet()) { UnitSupportAttachment usa = null; for (final UnitSupportAttachment support : UnitSupportAttachment.get(data)) { if (support.getName().equals(usaString)) { usa = support; break; } } if (usa == null) { throw new IllegalStateException("Could not find unitSupportAttachment. name:" + usaString); } final List<PlayerID> p = new ArrayList<>(usa.getPlayers()); if (p.contains(aPlayer)) { if (!t.getSupport().get(usa.getName())) { p.remove(aPlayer); change.add(ChangeFactory.attachmentPropertyChange(usa, p, "players")); aBridge.getHistoryWriter().startEvent(MyFormatter.attachmentNameToText(t.getName()) + ": " + aPlayer.getName() + " is removed from " + usa.toString()); } } else { if (t.getSupport().get(usa.getName())) { p.add(aPlayer); change.add(ChangeFactory.attachmentPropertyChange(usa, p, "players")); aBridge.getHistoryWriter().startEvent(MyFormatter.attachmentNameToText(t.getName()) + ": " + aPlayer.getName() + " is added to " + usa.toString()); } } } } } if (!change.isEmpty()) { aBridge.addChange(change); } } public static void triggerChangeOwnership(final Set<TriggerAttachment> satisfiedTriggers, final IDelegateBridge aBridge, final String beforeOrAfter, final String stepName, final boolean useUses, final boolean testUses, final boolean testChance, final boolean testWhen) { final GameData data = aBridge.getData(); Collection<TriggerAttachment> trigs = Match.getMatches(satisfiedTriggers, changeOwnershipMatch()); if (testWhen) { trigs = Match.getMatches(trigs, whenOrDefaultMatch(beforeOrAfter, stepName)); } if (testUses) { trigs = Match.getMatches(trigs, availableUses); } final BattleTracker bt = DelegateFinder.battleDelegate(data).getBattleTracker(); for (final TriggerAttachment t : trigs) { if (testChance && !t.testChance(aBridge)) { continue; } if (useUses) { t.use(aBridge); } for (final String value : t.getChangeOwnership()) { final String[] s = value.split(":"); final Collection<Territory> territories = new ArrayList<>(); if (s[0].equalsIgnoreCase("all")) { territories.addAll(data.getMap().getTerritories()); } else { final Territory territorySet = data.getMap().getTerritory(s[0]); territories.add(territorySet); } // if null, then is must be "any", so then any player final PlayerID oldOwner = data.getPlayerList().getPlayerID(s[1]); final PlayerID newOwner = data.getPlayerList().getPlayerID(s[2]); final boolean captured = getBool(s[3]); for (final Territory terr : territories) { final PlayerID currentOwner = terr.getOwner(); if (TerritoryAttachment.get(terr) == null) { continue; // any territory that has no territory attachment should definitely not be changed } if (oldOwner != null && !oldOwner.equals(currentOwner)) { continue; } aBridge.getHistoryWriter() .startEvent(MyFormatter.attachmentNameToText(t.getName()) + ": " + newOwner.getName() + (captured ? " captures territory " : " takes ownership of territory ") + terr.getName()); if (!captured) { aBridge.addChange(ChangeFactory.changeOwner(terr, newOwner)); } else { bt.takeOver(terr, newOwner, aBridge, null, null); } } } } } public static void triggerPurchase(final Set<TriggerAttachment> satisfiedTriggers, final IDelegateBridge aBridge, final String beforeOrAfter, final String stepName, final boolean useUses, final boolean testUses, final boolean testChance, final boolean testWhen) { Collection<TriggerAttachment> trigs = Match.getMatches(satisfiedTriggers, purchaseMatch()); if (testWhen) { trigs = Match.getMatches(trigs, whenOrDefaultMatch(beforeOrAfter, stepName)); } if (testUses) { trigs = Match.getMatches(trigs, availableUses); } for (final TriggerAttachment t : trigs) { if (testChance && !t.testChance(aBridge)) { continue; } if (useUses) { t.use(aBridge); } final int eachMultiple = getEachMultiple(t); for (final PlayerID aPlayer : t.getPlayers()) { for (int i = 0; i < eachMultiple; ++i) { final List<Unit> units = new ArrayList<>(); for (final UnitType u : t.getPurchase().keySet()) { units.addAll(u.create(t.getPurchase().getInt(u), aPlayer)); } if (!units.isEmpty()) { final String transcriptText = MyFormatter.attachmentNameToText(t.getName()) + ": " + MyFormatter.unitsToTextNoOwner(units) + " gained by " + aPlayer; aBridge.getHistoryWriter().startEvent(transcriptText, units); final Change place = ChangeFactory.addUnits(aPlayer, units); aBridge.addChange(place); } } } } } public static void triggerUnitRemoval(final Set<TriggerAttachment> satisfiedTriggers, final IDelegateBridge aBridge, final String beforeOrAfter, final String stepName, final boolean useUses, final boolean testUses, final boolean testChance, final boolean testWhen) { Collection<TriggerAttachment> trigs = Match.getMatches(satisfiedTriggers, removeUnitsMatch()); if (testWhen) { trigs = Match.getMatches(trigs, whenOrDefaultMatch(beforeOrAfter, stepName)); } if (testUses) { trigs = Match.getMatches(trigs, availableUses); } for (final TriggerAttachment t : trigs) { if (testChance && !t.testChance(aBridge)) { continue; } if (useUses) { t.use(aBridge); } final int eachMultiple = getEachMultiple(t); for (final PlayerID aPlayer : t.getPlayers()) { for (final Territory ter : t.getRemoveUnits().keySet()) { for (int i = 0; i < eachMultiple; ++i) { removeUnits(t, ter, t.getRemoveUnits().get(ter), aPlayer, aBridge); } } } } } public static void triggerUnitPlacement(final Set<TriggerAttachment> satisfiedTriggers, final IDelegateBridge aBridge, final String beforeOrAfter, final String stepName, final boolean useUses, final boolean testUses, final boolean testChance, final boolean testWhen) { Collection<TriggerAttachment> trigs = Match.getMatches(satisfiedTriggers, placeMatch()); if (testWhen) { trigs = Match.getMatches(trigs, whenOrDefaultMatch(beforeOrAfter, stepName)); } if (testUses) { trigs = Match.getMatches(trigs, availableUses); } for (final TriggerAttachment t : trigs) { if (testChance && !t.testChance(aBridge)) { continue; } if (useUses) { t.use(aBridge); } final int eachMultiple = getEachMultiple(t); for (final PlayerID aPlayer : t.getPlayers()) { for (final Territory ter : t.getPlacement().keySet()) { for (int i = 0; i < eachMultiple; ++i) { placeUnits(t, ter, t.getPlacement().get(ter), aPlayer, aBridge); } } } } } public static String triggerResourceChange(final Set<TriggerAttachment> satisfiedTriggers, final IDelegateBridge aBridge, final String beforeOrAfter, final String stepName, final boolean useUses, final boolean testUses, final boolean testChance, final boolean testWhen) { final GameData data = aBridge.getData(); Collection<TriggerAttachment> trigs = Match.getMatches(satisfiedTriggers, resourceMatch()); if (testWhen) { trigs = Match.getMatches(trigs, whenOrDefaultMatch(beforeOrAfter, stepName)); } if (testUses) { trigs = Match.getMatches(trigs, availableUses); } final StringBuilder strbuf = new StringBuilder(); for (final TriggerAttachment t : trigs) { if (testChance && !t.testChance(aBridge)) { continue; } if (useUses) { t.use(aBridge); } final int eachMultiple = getEachMultiple(t); for (final PlayerID aPlayer : t.getPlayers()) { for (int i = 0; i < eachMultiple; ++i) { int toAdd = t.getResourceCount(); if (t.getResource().equals(Constants.PUS)) { toAdd *= Properties.getPU_Multiplier(data); } int total = aPlayer.getResources().getQuantity(t.getResource()) + toAdd; if (total < 0) { toAdd -= total; total = 0; } aBridge.addChange( ChangeFactory.changeResourcesChange(aPlayer, data.getResourceList().getResource(t.getResource()), toAdd)); final String PUMessage = MyFormatter.attachmentNameToText(t.getName()) + ": " + aPlayer.getName() + " met a national objective for an additional " + t.getResourceCount() + " " + t.getResource() + "; end with " + total + " " + t.getResource(); aBridge.getHistoryWriter().startEvent(PUMessage); strbuf.append(PUMessage).append(" <br />"); } } } return strbuf.toString(); } public static void triggerActivateTriggerOther(final HashMap<ICondition, Boolean> testedConditionsSoFar, final Set<TriggerAttachment> satisfiedTriggers, final IDelegateBridge aBridge, final String beforeOrAfter, final String stepName, final boolean useUses, final boolean testUses, final boolean testChance, final boolean testWhen) { final GameData data = aBridge.getData(); Collection<TriggerAttachment> trigs = Match.getMatches(satisfiedTriggers, activateTriggerMatch()); if (testWhen) { trigs = Match.getMatches(trigs, whenOrDefaultMatch(beforeOrAfter, stepName)); } if (testUses) { trigs = Match.getMatches(trigs, availableUses); } for (final TriggerAttachment t : trigs) { if (testChance && !t.testChance(aBridge)) { continue; } if (useUses) { t.use(aBridge); } final int eachMultiple = getEachMultiple(t); for (final Tuple<String, String> tuple : t.getActivateTrigger()) { // numberOfTimes:useUses:testUses:testConditions:testChance TriggerAttachment toFire = null; for (final PlayerID player : data.getPlayerList().getPlayers()) { for (final TriggerAttachment ta : TriggerAttachment.getTriggers(player, data, null)) { if (ta.getName().equals(tuple.getFirst())) { toFire = ta; break; } } if (toFire != null) { break; } } final HashSet<TriggerAttachment> toFireSet = new HashSet<>(); toFireSet.add(toFire); final String[] options = tuple.getSecond().split(":"); final int numberOfTimesToFire = getInt(options[0]); final boolean useUsesToFire = getBool(options[1]); final boolean testUsesToFire = getBool(options[2]); final boolean testConditionsToFire = getBool(options[3]); final boolean testChanceToFire = getBool(options[4]); if (testConditionsToFire) { if (!testedConditionsSoFar.containsKey(toFire)) { // this should directly add the new tests to testConditionsToFire... collectTestsForAllTriggers(toFireSet, aBridge, new HashSet<>(testedConditionsSoFar.keySet()), testedConditionsSoFar); } if (!isSatisfiedMatch(testedConditionsSoFar).match(toFire)) { continue; } } for (int i = 0; i < numberOfTimesToFire * eachMultiple; ++i) { aBridge.getHistoryWriter().startEvent(MyFormatter.attachmentNameToText(t.getName()) + " activates a trigger called: " + MyFormatter.attachmentNameToText(toFire.getName())); fireTriggers(toFireSet, testedConditionsSoFar, aBridge, beforeOrAfter, stepName, useUsesToFire, testUsesToFire, testChanceToFire, false); } } } } public static void triggerVictory(final Set<TriggerAttachment> satisfiedTriggers, final IDelegateBridge aBridge, final String beforeOrAfter, final String stepName, final boolean useUses, final boolean testUses, final boolean testChance, final boolean testWhen) { final GameData data = aBridge.getData(); Collection<TriggerAttachment> trigs = Match.getMatches(satisfiedTriggers, victoryMatch()); if (testWhen) { trigs = Match.getMatches(trigs, whenOrDefaultMatch(beforeOrAfter, stepName)); } if (testUses) { trigs = Match.getMatches(trigs, availableUses); } for (final TriggerAttachment t : trigs) { if (testChance && !t.testChance(aBridge)) { continue; } if (useUses) { t.use(aBridge); } if (t.getVictory() == null || t.getPlayers() == null) { continue; } final String victoryMessage = NotificationMessages.getInstance().getMessage(t.getVictory().trim()); final String sounds = NotificationMessages.getInstance().getSoundsKey(t.getVictory().trim()); if (victoryMessage != null) { if (sounds != null) { // only play the sound if we are also notifying everyone aBridge.getSoundChannelBroadcaster().playSoundToPlayers( SoundPath.CLIP_TRIGGERED_VICTORY_SOUND + sounds.trim(), t.getPlayers(), null, true); aBridge.getSoundChannelBroadcaster().playSoundToPlayers(SoundPath.CLIP_TRIGGERED_DEFEAT_SOUND + sounds.trim(), data.getPlayerList().getPlayers(), t.getPlayers(), false); } String messageForRecord = victoryMessage.trim(); if (messageForRecord.length() > 150) { messageForRecord = messageForRecord.replaceAll("\\<br.*?>", " "); messageForRecord = messageForRecord.replaceAll("\\<.*?>", ""); if (messageForRecord.length() > 155) { messageForRecord = messageForRecord.substring(0, 150) + "...."; } } try { aBridge.getHistoryWriter().startEvent("Players: " + MyFormatter.defaultNamedToTextList(t.getPlayers()) + " have just won the game, with this victory: " + messageForRecord); final IDelegate delegateEndRound = data.getDelegateList().getDelegate("endRound"); ((EndRoundDelegate) delegateEndRound).signalGameOver(victoryMessage.trim(), t.getPlayers(), aBridge); } catch (final Exception e) { ClientLogger.logQuietly(e); } } } } public static Match<TriggerAttachment> prodMatch() { return new Match<TriggerAttachment>() { @Override public boolean match(final TriggerAttachment t) { return t.getFrontier() != null; } }; } public static Match<TriggerAttachment> prodFrontierEditMatch() { return new Match<TriggerAttachment>() { @Override public boolean match(final TriggerAttachment t) { return t.getProductionRule() != null && t.getProductionRule().size() > 0; } }; } public static Match<TriggerAttachment> techMatch() { return new Match<TriggerAttachment>() { @Override public boolean match(final TriggerAttachment t) { return !t.getTech().isEmpty(); } }; } public static Match<TriggerAttachment> techAvailableMatch() { return new Match<TriggerAttachment>() { @Override public boolean match(final TriggerAttachment t) { return t.getAvailableTech() != null; } }; } public static Match<TriggerAttachment> removeUnitsMatch() { return new Match<TriggerAttachment>() { @Override public boolean match(final TriggerAttachment t) { return t.getRemoveUnits() != null; } }; } public static Match<TriggerAttachment> placeMatch() { return new Match<TriggerAttachment>() { @Override public boolean match(final TriggerAttachment t) { return t.getPlacement() != null; } }; } public static Match<TriggerAttachment> purchaseMatch() { return new Match<TriggerAttachment>() { @Override public boolean match(final TriggerAttachment t) { return t.getPurchase() != null; } }; } public static Match<TriggerAttachment> resourceMatch() { return new Match<TriggerAttachment>() { @Override public boolean match(final TriggerAttachment t) { return t.getResource() != null && t.getResourceCount() != 0; } }; } public static Match<TriggerAttachment> supportMatch() { return new Match<TriggerAttachment>() { @Override public boolean match(final TriggerAttachment t) { return t.getSupport() != null; } }; } public static Match<TriggerAttachment> changeOwnershipMatch() { return new Match<TriggerAttachment>() { @Override public boolean match(final TriggerAttachment t) { return !t.getChangeOwnership().isEmpty(); } }; } public static Match<TriggerAttachment> unitPropertyMatch() { return new Match<TriggerAttachment>() { @Override public boolean match(final TriggerAttachment t) { return !t.getUnitType().isEmpty() && t.getUnitProperty() != null; } }; } public static Match<TriggerAttachment> territoryPropertyMatch() { return new Match<TriggerAttachment>() { @Override public boolean match(final TriggerAttachment t) { return !t.getTerritories().isEmpty() && t.getTerritoryProperty() != null; } }; } public static Match<TriggerAttachment> playerPropertyMatch() { return new Match<TriggerAttachment>() { @Override public boolean match(final TriggerAttachment t) { return t.getPlayerProperty() != null; } }; } public static Match<TriggerAttachment> relationshipTypePropertyMatch() { return new Match<TriggerAttachment>() { @Override public boolean match(final TriggerAttachment t) { return !t.getRelationshipTypes().isEmpty() && t.getRelationshipTypeProperty() != null; } }; } public static Match<TriggerAttachment> territoryEffectPropertyMatch() { return new Match<TriggerAttachment>() { @Override public boolean match(final TriggerAttachment t) { return !t.getTerritoryEffects().isEmpty() && t.getTerritoryEffectProperty() != null; } }; } public static Match<TriggerAttachment> relationshipChangeMatch() { return new Match<TriggerAttachment>() { @Override public boolean match(final TriggerAttachment t) { return !t.getRelationshipChange().isEmpty(); } }; } public static Match<TriggerAttachment> victoryMatch() { return new Match<TriggerAttachment>() { @Override public boolean match(final TriggerAttachment t) { return t.getVictory() != null && t.getVictory().length() > 0; } }; } public static Match<TriggerAttachment> activateTriggerMatch() { return new Match<TriggerAttachment>() { @Override public boolean match(final TriggerAttachment t) { return !t.getActivateTrigger().isEmpty(); } }; } @Override public void validate(final GameData data) throws GameParseException { super.validate(data); } }