Commit e46fbb4d authored by CCTV-1's avatar CCTV-1

Merge remote-tracking branch 'upstream/master'

parents 3768074f a2da91eb
......@@ -156,7 +156,7 @@ public class DamageDealAi extends DamageAiBase {
}
}
}
if (ai.getAttackedWithCreatureThisTurn()) {
if (!ai.getCreaturesAttackedThisTurn().isEmpty()) {
dmg = Integer.parseInt(logic.substring(logic.indexOf(".") + 1));
}
} else if ("WildHunt".equals(logic)) {
......
......@@ -63,7 +63,7 @@ public class DestroyAi extends SpellAbilityAi {
}
} else if ("AtEOTIfNotAttacking".equals(sa.getParam("AILogic"))) {
PhaseHandler ph = ai.getGame().getPhaseHandler();
if (!ph.is(PhaseType.END_OF_TURN) || ai.getAttackedWithCreatureThisTurn()) {
if (!ph.is(PhaseType.END_OF_TURN) || !ai.getCreaturesAttackedThisTurn().isEmpty()) {
return false;
}
}
......
......@@ -54,6 +54,8 @@ import org.apache.commons.lang3.tuple.ImmutablePair;
import java.util.*;
import static forge.util.EnumMapUtil.toStringMap;
/**
* Methods for common actions performed during a game.
*
......@@ -297,7 +299,7 @@ public class GameAction {
repParams.putAll(params);
}
ReplacementResult repres = game.getReplacementHandler().run(EnumMapUtil.toStringMap(repParams));
ReplacementResult repres = game.getReplacementHandler().run(toStringMap(repParams));
if (repres != ReplacementResult.NotReplaced) {
// reset failed manifested Cards back to original
if (c.isManifested()) {
......
......@@ -10,8 +10,9 @@ public enum AbilityKey {
AbilityMana("AbilityMana"),
Activator("Activator"),
Affected("Affected"),
Attach("Attach"),
AllVotes("AllVotes"),
Amount("Amount"),
Attach("Attach"),
AttachSource("AttachSource"),
AttachTarget("AttachTarget"),
Attacked("Attacked"),
......@@ -51,14 +52,12 @@ public enum AbilityKey {
Devoured("Devoured"),
EchoPaid("EchoPaid"),
Exploited("Exploited"),
Explorer("Explorer"),
Event("Event"),
Fighter("Fighter"),
FirstTime("FirstTime"),
Fizzle("Fizzle"),
IsCombatDamage("IsCombatDamage"),
PayingMana("PayingMana"),
Phase("Phase"),
Player("Player"),
IndividualCostPaymentInstance("IndividualCostPaymentInstance"),
IsMadness("IsMadness"),
LifeAmount("LifeAmount"),
......@@ -71,14 +70,23 @@ public enum AbilityKey {
Object("Object"),
Objects("Objects"),
OtherAttackers("OtherAttackers"),
OtherVoters("OtherVoters"),
Origin("Origin"),
OriginalController("OriginalController"),
OriginalDefender("OriginalDefender"),
PayingMana("PayingMana"),
Phase("Phase"),
Player("Player"),
Produced("Produced"),
Result("Result"),
Scheme("Scheme"),
Source("Source"),
Sources("Sources"),
SourceSA("SourceSA"),
SpellAbility("SpellAbility"),
SpellAbilityStackInstance("SpellAbilityStackInstance"),
SpellAbilityTargetingCards("SpellAbilityTargetingCards"),
StackInstance("StackInstance"),
StackSa("StackSa"),
StackSi("StackSi"),
Target("Target"),
......@@ -99,10 +107,204 @@ public enum AbilityKey {
return key;
}
/**
* @param s A string that would be output from toString
* @return the corresponding key if there is one or null otherwise
*/
public static AbilityKey fromString(String s) {
switch (s) {
case "AbilityMana":
return AbilityMana;
case "Activator":
return Activator;
case "Affected":
return Affected;
case "AllVotes":
return AllVotes;
case "Amount":
return Amount;
case "Attach":
return Attach;
case "AttachSource":
return AttachSource;
case "AttachTarget":
return AttachTarget;
case "Attacked":
return Attacked;
case "Attacker":
return Attacker;
case "Attackers":
return Attackers;
case "AttackingPlayer":
return AttackingPlayer;
case "AttackedTarget":
return AttackedTarget;
case "Blocker":
return Blocker;
case "Blockers":
return Blockers;
case "CastSA":
return CastSA;
case "CastSACMC":
return CastSACMC;
case "Card":
return Card;
case "Cards":
return Cards;
case "CardLKI":
return CardLKI;
case "Cause":
return Cause;
case "Causer":
return Causer;
case "Championed":
return Championed;
case "CopySA":
return CopySA;
case "Cost":
return Cost;
case "CostStack":
return CostStack;
case "CounterAmount":
return CounterAmount;
case "CounteredSA":
return CounteredSA;
case "CounterType":
return CounterType;
case "Crew":
return Crew;
case "CumulativeUpkeepPaid":
return CumulativeUpkeepPaid;
case "CurrentCastSpells":
return CurrentCastSpells;
case "CurrentStormCount":
return CurrentStormCount;
case "DamageAmount":
return DamageAmount;
case "DamageSource":
return DamageSource;
case "DamageSources":
return DamageSources;
case "DamageTarget":
return DamageTarget;
case "DamageTargets":
return DamageTargets;
case "Defender":
return Defender;
case "Defenders":
return Defenders;
case "DefendingPlayer":
return DefendingPlayer;
case "Destination":
return Destination;
case "Devoured":
return Devoured;
case "EchoPaid":
return EchoPaid;
case "Exploited":
return Exploited;
case "Explorer":
return Explorer;
case "Event":
return Event;
case "Fighter":
return Fighter;
case "FirstTime":
return FirstTime;
case "Fizzle":
return Fizzle;
case "IsCombatDamage":
return IsCombatDamage;
case "IndividualCostPaymentInstance":
return IndividualCostPaymentInstance;
case "IsMadness":
return IsMadness;
case "LifeAmount":
return LifeAmount;
case "MonstrosityAmount":
return MonstrosityAmount;
case "NewCounterAmount":
return NewCounterAmount;
case "Num":
return Num;
case "NumBlockers":
return NumBlockers;
case "NumThisTurn":
return NumThisTurn;
case "Number":
return Number;
case "Object":
return Object;
case "Objects":
return Objects;
case "OtherAttackers":
return OtherAttackers;
case "OtherVoters":
return OtherVoters;
case "Origin":
return Origin;
case "OriginalController":
return OriginalController;
case "OriginalDefender":
return OriginalDefender;
case "PayingMana":
return PayingMana;
case "Phase":
return Phase;
case "Player":
return Player;
case "Produced":
return Produced;
case "Result":
return Result;
case "Scheme":
return Scheme;
case "Source":
return Source;
case "Sources":
return Sources;
case "SourceSA":
return SourceSA;
case "SpellAbility":
return SpellAbility;
case "SpellAbilityStackInstance":
return SpellAbilityStackInstance;
case "SpellAbilityTargetingCards":
return SpellAbilityTargetingCards;
case "StackInstance":
return StackInstance;
case "StackSa":
return StackSa;
case "StackSi":
return StackSi;
case "Target":
return Target;
case "Targets":
return Targets;
case "Transformer":
return Transformer;
case "Vehicle":
return Vehicle;
case "Won":
return Won;
default:
return null;
}
}
public static <V> EnumMap<AbilityKey, V> newMap() {
return new EnumMap<>(AbilityKey.class);
}
public static <V> EnumMap<AbilityKey, V> newMap(Map<AbilityKey, V> map) {
// The EnumMap constructor throws IllegalArgumentException if the map is empty.
if (map.isEmpty()) {
return newMap();
}
return new EnumMap<>(map);
}
public static Map<AbilityKey, Object> mapFromCard(forge.game.card.Card card) {
final Map<AbilityKey, Object> runParams = newMap();
......
......@@ -139,13 +139,15 @@ public class AbilityUtils {
final SpellAbility root = sa.getRootAbility();
if (defined.contains("LKICopy")) { //Triggered*LKICopy
int lkiPosition = defined.indexOf("LKICopy");
final Object crd = root.getTriggeringObject(defined.substring(9, lkiPosition));
AbilityKey type = AbilityKey.fromString(defined.substring(9, lkiPosition));
final Object crd = root.getTriggeringObject(type);
if (crd instanceof Card) {
c = (Card) crd;
}
}
else {
final Object crd = root.getTriggeringObject(defined.substring(9));
AbilityKey type = AbilityKey.fromString(defined.substring(9));
final Object crd = root.getTriggeringObject(type);
if (crd instanceof Card) {
c = game.getCardState((Card) crd);
} else if (crd instanceof Iterable) {
......@@ -462,12 +464,9 @@ public class AbilityUtils {
players.remove(game.getPhaseHandler().getPlayerTurn());
val = CardFactoryUtil.playerXCount(players, calcX[1], card);
}
else if (hType.startsWith("PropertyYou") && !(ability instanceof SpellAbility)) {
// Related to the controller of the card with ability when the ability is static (or otherwise not a SpellAbility)
// TODO: This doesn't work in situations when the controller of the card is different from the spell caster
// (e.g. opponent's Hollow One exiled by Hostage Taker - cost reduction will not work in this scenario, requires
// a more significant rework).
players.add(card.getController());
else if (hType.startsWith("PropertyYou") && ability instanceof SpellAbility) {
// Hollow One
players.add(((SpellAbility) ability).getActivatingPlayer());
val = CardFactoryUtil.playerXCount(players, calcX[1], card);
}
else if (hType.startsWith("Property") && ability instanceof SpellAbility) {
......@@ -641,12 +640,12 @@ public class AbilityUtils {
}
if (calcX[0].startsWith("TriggeredPlayer") || calcX[0].startsWith("TriggeredTarget")) {
final SpellAbility root = sa.getRootAbility();
Object o = root.getTriggeringObject(calcX[0].substring(9));
Object o = root.getTriggeringObject(AbilityKey.fromString(calcX[0].substring(9)));
return o instanceof Player ? CardFactoryUtil.playerXProperty((Player) o, calcX[1], card) * multiplier : 0;
}
if (calcX[0].equals("TriggeredSpellAbility")) {
final SpellAbility root = sa.getRootAbility();
SpellAbility sat = (SpellAbility) root.getTriggeringObject("SpellAbility");
SpellAbility sat = (SpellAbility) root.getTriggeringObject(AbilityKey.SpellAbility);
return calculateAmount(sat.getHostCard(), calcX[1], sat);
}
// Added on 9/30/12 (ArsenalNut) - Ended up not using but might be useful in future
......@@ -695,11 +694,11 @@ public class AbilityUtils {
}
else if (calcX[0].startsWith("TriggerObjects")) {
final SpellAbility root = sa.getRootAbility();
list = (CardCollection) root.getTriggeringObject(calcX[0].substring(14));
list = (CardCollection) root.getTriggeringObject(AbilityKey.fromString(calcX[0].substring(14)));
}
else if (calcX[0].startsWith("Triggered")) {
final SpellAbility root = sa.getRootAbility();
list = new CardCollection((Card) root.getTriggeringObject(calcX[0].substring(9)));
list = new CardCollection((Card) root.getTriggeringObject(AbilityKey.fromString(calcX[0].substring(9))));
}
else if (calcX[0].startsWith("TriggerCount")) {
// TriggerCount is similar to a regular Count, but just
......@@ -707,7 +706,7 @@ public class AbilityUtils {
final SpellAbility root = sa.getRootAbility();
final String[] l = calcX[1].split("/");
final String m = CardFactoryUtil.extractOperators(calcX[1]);
final int count = (Integer) root.getTriggeringObject(l[0]);
final int count = (Integer) root.getTriggeringObject(AbilityKey.fromString(l[0]));
return CardFactoryUtil.doXMath(count, m, card) * multiplier;
}
......@@ -777,19 +776,19 @@ public class AbilityUtils {
final Object o;
if (type.startsWith("Triggered")) {
if (type.contains("Card")) {
o = sa.getTriggeringObject("Card");
o = sa.getTriggeringObject(AbilityKey.Card);
}
else if (type.contains("Object")) {
o = sa.getTriggeringObject("Object");
o = sa.getTriggeringObject(AbilityKey.Object);
}
else if (type.contains("Attacker")) {
o = sa.getTriggeringObject("Attacker");
o = sa.getTriggeringObject(AbilityKey.Attacker);
}
else if (type.contains("Blocker")) {
o = sa.getTriggeringObject("Blocker");
o = sa.getTriggeringObject(AbilityKey.Blocker);
}
else {
o = sa.getTriggeringObject("Card");
o = sa.getTriggeringObject(AbilityKey.Card);
}
if (!(o instanceof Card)) {
......@@ -1004,7 +1003,7 @@ public class AbilityUtils {
if (defParsed.endsWith("Controller")) {
String triggeringType = defParsed.substring(9);
triggeringType = triggeringType.substring(0, triggeringType.length() - 10);
final Object c = root.getTriggeringObject(triggeringType);
final Object c = root.getTriggeringObject(AbilityKey.fromString(triggeringType));
if (c instanceof Card) {
o = ((Card) c).getController();
}
......@@ -1015,7 +1014,7 @@ public class AbilityUtils {
else if (defParsed.endsWith("Opponent")) {
String triggeringType = defParsed.substring(9);
triggeringType = triggeringType.substring(0, triggeringType.length() - 8);
final Object c = root.getTriggeringObject(triggeringType);
final Object c = root.getTriggeringObject(AbilityKey.fromString(triggeringType));
if (c instanceof Card) {
o = ((Card) c).getController().getOpponents();
}
......@@ -1026,14 +1025,14 @@ public class AbilityUtils {
else if (defParsed.endsWith("Owner")) {
String triggeringType = defParsed.substring(9);
triggeringType = triggeringType.substring(0, triggeringType.length() - 5);
final Object c = root.getTriggeringObject(triggeringType);
final Object c = root.getTriggeringObject(AbilityKey.fromString(triggeringType));
if (c instanceof Card) {
o = ((Card) c).getOwner();
}
}
else {
final String triggeringType = defParsed.substring(9);
o = root.getTriggeringObject(triggeringType);
o = root.getTriggeringObject(AbilityKey.fromString(triggeringType));
}
if (o != null) {
if (o instanceof Player) {
......@@ -1252,13 +1251,13 @@ public class AbilityUtils {
final SpellAbility root = sa.getRootAbility();
final String triggeringType = defined.substring(9);
final Object o = root.getTriggeringObject(triggeringType);
final Object o = root.getTriggeringObject(AbilityKey.fromString(triggeringType));
if (o instanceof SpellAbility) {
s = (SpellAbility) o;
// if there is no target information in SA but targets are listed in SpellAbilityTargeting cards, copy that
// information so it's not lost if the calling code is interested in targets of the triggered SA.
if (triggeringType.equals("SpellAbility")) {
final CardCollectionView tgtList = (CardCollectionView)root.getTriggeringObject("SpellAbilityTargetingCards");
final CardCollectionView tgtList = (CardCollectionView)root.getTriggeringObject(AbilityKey.SpellAbilityTargetingCards);
if (s.getTargets() != null && s.getTargets().getNumTargeted() == 0) {
if (tgtList != null && tgtList.size() > 0) {
TargetChoices tc = new TargetChoices();
......@@ -1390,7 +1389,7 @@ public class AbilityUtils {
cost = new Cost(source.getManaCost(), true);
}
else if (unlessCost.equals("TriggeredSpellManaCost")) {
SpellAbility triggered = (SpellAbility) sa.getRootAbility().getTriggeringObject("SpellAbility");
SpellAbility triggered = (SpellAbility) sa.getRootAbility().getTriggeringObject(AbilityKey.SpellAbility);
Card triggeredCard = triggered.getHostCard();
if (triggeredCard.getManaCost() == null) {
cost = new Cost(ManaCost.ZERO, true);
......@@ -1608,7 +1607,7 @@ public class AbilityUtils {
// Count$TriggeredPayingMana.<Color1>.<Color2>
if (sq[0].startsWith("TriggeredPayingMana")) {
final SpellAbility root = sa.getRootAbility();
String mana = (String) root.getTriggeringObject("PayingMana");
String mana = (String) root.getTriggeringObject(AbilityKey.PayingMana);
int count = 0;
Matcher mat = Pattern.compile(StringUtils.join(sq, "|", 1, sq.length)).matcher(mana);
while (mat.find()) {
......
......@@ -2,6 +2,7 @@ package forge.game.ability.effects;
import forge.game.Game;
import forge.game.GameEntity;
import forge.game.ability.AbilityKey;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.combat.AttackingBand;
......@@ -54,12 +55,12 @@ public class ChangeCombatantsEffect extends SpellAbilityEffect {
// retarget triggers to the new defender (e.g. Ulamog, Ceaseless Hunger + Portal Mage)
for (SpellAbilityStackInstance si : game.getStack()) {
if (si.isTrigger() && c.equals(si.getSourceCard())
&& si.getTriggeringObject("Attacker") != null) {
si.addTriggeringObject("OriginalDefender", originalDefender);
&& si.getTriggeringObject(AbilityKey.Attacker) != null) {
si.addTriggeringObject(AbilityKey.OriginalDefender, originalDefender);
if (defender instanceof Player) {
si.updateTriggeringObject("DefendingPlayer", defender);
si.updateTriggeringObject(AbilityKey.DefendingPlayer, defender);
} else if (defender instanceof Card) {
si.updateTriggeringObject("DefendingPlayer", ((Card)defender).getController());
si.updateTriggeringObject(AbilityKey.DefendingPlayer, ((Card)defender).getController());
}
}
}
......
......@@ -980,7 +980,10 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
}
c.setController(newController, game.getNextTimestamp());
}
if (sa.hasParam("WithCounters")) {
String[] parse = sa.getParam("WithCounters").split("_");
c.addEtbCounter(CounterType.getType(parse[0]), Integer.parseInt(parse[1]), player);
}
if (sa.hasParam("Transformed")) {
if (c.isDoubleFaced()) {
c.changeCardState("Transform", null);
......
......@@ -70,6 +70,7 @@ public class RestartGameEffect extends SpellAbilityEffect {
player.setStartingLife(psc.getStartingLife());
player.setPoisonCounters(0, sa.getHostCard());
player.resetSpellCastThisGame();
player.setLandsPlayedLastTurn(0);
player.resetLandsPlayedThisTurn();
player.resetInvestigatedThisTurn();
......
......@@ -1048,7 +1048,7 @@ public class Card extends GameEntity implements Comparable<Card> {
}
}
public final Object getTriggeringObject(final String typeIn) {
public final Object getTriggeringObject(final AbilityKey typeIn) {
Object triggered = null;
if (!currentState.getTriggers().isEmpty()) {
for (final Trigger t : currentState.getTriggers()) {
......
......@@ -35,6 +35,7 @@ import forge.game.GameEntity;
import forge.game.GameEntityCounterTable;
import forge.game.GameLogEntryType;
import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.card.CardPredicates.Presets;
......@@ -917,6 +918,10 @@ public class CardFactoryUtil {
return doXMath(cc.getSurveilThisTurn(), m, c);
}
if (sq[0].equals("YouCastThisGame")) {
return doXMath(cc.getSpellsCastThisGame(), m, c);
}
if (sq[0].equals("FirstSpellTotalManaSpent")) {
try{
return doXMath(c.getFirstSpellAbility().getTotalManaSpent(), m, c);
......@@ -948,7 +953,7 @@ public class CardFactoryUtil {
// TriggeringObjects
if (sq[0].startsWith("Triggered")) {
return doXMath(xCount((Card) c.getTriggeringObject("Card"), sq[0].substring(9)), m, c);
return doXMath(xCount((Card) c.getTriggeringObject(AbilityKey.Card), sq[0].substring(9)), m, c);
}
if (sq[0].contains("YourStartingLife")) {
......@@ -1329,6 +1334,13 @@ public class CardFactoryUtil {
return doXMath(cc.getAttackersDeclaredThisTurn(), m, c);
}
// Count$CardAttackedThisTurn_<Valid>
if (sq[0].contains("CreaturesAttackedThisTurn")) {
final String[] workingCopy = l[0].split("_");
final String validFilter = workingCopy[1];
return doXMath(CardLists.getType(cc.getCreaturesAttackedThisTurn(), validFilter).size(), m, c);
}
// Count$ThisTurnCast <Valid>
// Count$LastTurnCast <Valid>
if (sq[0].contains("ThisTurnCast") || sq[0].contains("LastTurnCast")) {
......
......@@ -6,6 +6,7 @@ import forge.card.MagicColor;
import forge.game.Direction;
import forge.game.Game;
import forge.game.GameEntity;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils;
import forge.game.card.CardPredicates.Presets;
import forge.game.combat.AttackingBand;
......@@ -874,7 +875,7 @@ public class CardProperty {
}
return false;
case "TriggeredCard":
final Object triggeringObject = source.getTriggeringObject(restriction.substring("Triggered".length()));
final Object triggeringObject = source.getTriggeringObject(AbilityKey.fromString(restriction.substring("Triggered".length())));
if (!(triggeringObject instanceof Card)) {
return false;
}
......@@ -957,7 +958,7 @@ public class CardProperty {
if (spellAbility == null) {
System.out.println("Looking at TriggeredCard but no SA?");
} else {
Card triggeredCard = ((Card)spellAbility.getTriggeringObject("Card"));
Card triggeredCard = ((Card) spellAbility.getTriggeringObject(AbilityKey.Card));
if (triggeredCard != null && card.sharesNameWith(triggeredCard)) {
return true;
}
......
......@@ -25,6 +25,7 @@ import forge.card.ColorSet;
import forge.card.MagicColor;
import forge.game.Game;
import forge.game.GameObject;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.player.Player;
......@@ -412,7 +413,7 @@ public final class CardUtil {
}
} else if (reflectProperty.equals("Produced")) {
// Why is this name so similar to the one below?
final String producedColors = abMana instanceof AbilitySub ? (String) abMana.getRootAbility().getTriggeringObject("Produced") : (String) abMana.getTriggeringObject("Produced");
final String producedColors = abMana instanceof AbilitySub ? (String) abMana.getRootAbility().getTriggeringObject(AbilityKey.Produced) : (String) abMana.getTriggeringObject(AbilityKey.Produced);
for (final String col : MagicColor.Constant.ONLY_COLORS) {
final String s = MagicColor.toShortString(col);
if (producedColors.contains(s)) {
......
......@@ -546,13 +546,13 @@ public class Combat {
Game game = c.getGame();
for (SpellAbilityStackInstance si : game.getStack()) {
if (si.isTrigger() && c.equals(si.getSourceCard())) {
GameEntity origDefender = (GameEntity)si.getTriggeringObject("OriginalDefender");
GameEntity origDefender = (GameEntity)si.getTriggeringObject(AbilityKey.OriginalDefender);
if (origDefender != null) {
si.updateTriggeringObject("Defender", origDefender);
si.updateTriggeringObject(AbilityKey.Defender, origDefender);
if (origDefender instanceof Player) {
si.updateTriggeringObject("DefendingPlayer", origDefender);
si.updateTriggeringObject(AbilityKey.DefendingPlayer, origDefender);
} else if (origDefender instanceof Card) {
si.updateTriggeringObject("DefendingPlayer", ((Card)origDefender).getController());
si.updateTriggeringObject(AbilityKey.DefendingPlayer, ((Card)origDefender).getController());
}
}
}
......
......@@ -317,7 +317,7 @@ public class CombatUtil {
c.getDamageHistory().setCreatureAttackedThisCombat(true);
c.getDamageHistory().clearNotAttackedSinceLastUpkeepOf();
c.getController().setAttackedWithCreatureThisTurn(true);
c.getController().addCreaturesAttackedThisTurn(c);
c.getController().incrementAttackersDeclaredThisTurn();
if (combat.getDefenderByAttacker(c) != null && combat.getDefenderByAttacker(c) instanceof Player) {
......
......@@ -150,7 +150,7 @@ public class GlobalAttackRestrictions {
final Game game = attackingPlayer.getGame();