params) {
return spells.get(0);
}
}
diff --git a/forge-ai/src/main/java/forge/ai/ability/AnimateAi.java b/forge-ai/src/main/java/forge/ai/ability/AnimateAi.java
index 28e8fb5bb9c..76fdbbd3d0d 100644
--- a/forge-ai/src/main/java/forge/ai/ability/AnimateAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/AnimateAi.java
@@ -23,12 +23,12 @@ import forge.game.staticability.StaticAbilityLayer;
import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerHandler;
import forge.game.zone.ZoneType;
-import forge.util.collect.FCollectionView;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
+import forge.game.ability.effects.AnimateEffectBase;
/**
*
@@ -363,11 +363,11 @@ public class AnimateAi extends SpellAbilityAi {
card.setSickness(hasOriginalCardSickness);
// AF specific sa
- int power = -1;
+ Integer power = null;
if (sa.hasParam("Power")) {
power = AbilityUtils.calculateAmount(source, sa.getParam("Power"), sa);
}
- int toughness = -1;
+ Integer toughness = null;
if (sa.hasParam("Toughness")) {
toughness = AbilityUtils.calculateAmount(source, sa.getParam("Toughness"), sa);
}
@@ -453,65 +453,7 @@ public class AnimateAi extends SpellAbilityAi {
sVars.addAll(Arrays.asList(sa.getParam("sVars").split(",")));
}
- // duplicating AnimateEffectBase.doAnimate
- boolean removeSuperTypes = false;
- boolean removeCardTypes = false;
- boolean removeSubTypes = false;
- boolean removeCreatureTypes = false;
- boolean removeArtifactTypes = false;
-
- if (sa.hasParam("OverwriteTypes")) {
- removeSuperTypes = true;
- removeCardTypes = true;
- removeSubTypes = true;
- removeCreatureTypes = true;
- removeArtifactTypes = true;
- }
-
- if (sa.hasParam("KeepSupertypes")) {
- removeSuperTypes = false;
- }
-
- if (sa.hasParam("KeepCardTypes")) {
- removeCardTypes = false;
- }
-
- if (sa.hasParam("RemoveSuperTypes")) {
- removeSuperTypes = true;
- }
-
- if (sa.hasParam("RemoveCardTypes")) {
- removeCardTypes = true;
- }
-
- if (sa.hasParam("RemoveSubTypes")) {
- removeSubTypes = true;
- }
-
- if (sa.hasParam("RemoveCreatureTypes")) {
- removeCreatureTypes = true;
- }
-
- if (sa.hasParam("RemoveArtifactTypes")) {
- removeArtifactTypes = true;
- }
-
- if ((power != -1) || (toughness != -1)) {
- card.addNewPT(power, toughness, timestamp);
- }
-
- if (!types.isEmpty() || !removeTypes.isEmpty() || removeCreatureTypes) {
- card.addChangedCardTypes(types, removeTypes, removeSuperTypes, removeCardTypes, removeSubTypes,
- removeCreatureTypes, removeArtifactTypes, timestamp);
- }
-
- card.addChangedCardKeywords(keywords, removeKeywords, sa.hasParam("RemoveAllAbilities"), timestamp);
-
- for (final String k : hiddenKeywords) {
- card.addHiddenExtrinsicKeyword(k);
- }
-
- card.addColor(finalDesc, !sa.hasParam("OverwriteColors"), timestamp);
+ AnimateEffectBase.doAnimate(card, sa, power, toughness, types, removeTypes, finalDesc, keywords, removeKeywords, hiddenKeywords, timestamp);
// back to duplicating AnimateEffect.resolve
// TODO will all these abilities/triggers/replacements/etc. lead to
@@ -521,10 +463,14 @@ public class AnimateAi extends SpellAbilityAi {
boolean clearAbilities = sa.hasParam("OverwriteAbilities");
boolean clearSpells = sa.hasParam("OverwriteSpells");
boolean removeAll = sa.hasParam("RemoveAllAbilities");
+ boolean removeIntrinsic = sa.hasParam("RemoveIntrinsicAbilities");
if (clearAbilities || clearSpells || removeAll) {
for (final SpellAbility ab : card.getSpellAbilities()) {
- if (removeAll || (ab.isAbility() && clearAbilities) || (ab.isSpell() && clearSpells)) {
+ if (removeAll
+ || (ab.isIntrinsic() && removeIntrinsic && !ab.isBasicLandAbility())
+ || (ab.isAbility() && clearAbilities)
+ || (ab.isSpell() && clearSpells)) {
card.removeSpellAbility(ab);
removedAbilities.add(ab);
}
@@ -565,9 +511,11 @@ public class AnimateAi extends SpellAbilityAi {
// suppress triggers from the animated card
final List removedTriggers = Lists.newArrayList();
- if (sa.hasParam("OverwriteTriggers") || removeAll) {
- final FCollectionView triggersToRemove = card.getTriggers();
- for (final Trigger trigger : triggersToRemove) {
+ if (sa.hasParam("OverwriteTriggers") || removeAll || removeIntrinsic) {
+ for (final Trigger trigger : card.getTriggers()) {
+ if (removeIntrinsic && !trigger.isIntrinsic()) {
+ continue;
+ }
trigger.setSuppressed(true);
removedTriggers.add(trigger);
}
@@ -603,9 +551,11 @@ public class AnimateAi extends SpellAbilityAi {
// suppress static abilities from the animated card
final List removedStatics = Lists.newArrayList();
- if (sa.hasParam("OverwriteStatics") || removeAll) {
- final FCollectionView staticsToRemove = card.getStaticAbilities();
- for (final StaticAbility stAb : staticsToRemove) {
+ if (sa.hasParam("OverwriteStatics") || removeAll || removeIntrinsic) {
+ for (final StaticAbility stAb : card.getStaticAbilities()) {
+ if (removeIntrinsic && !stAb.isIntrinsic()) {
+ continue;
+ }
stAb.setTemporarilySuppressed(true);
removedStatics.add(stAb);
}
@@ -613,8 +563,11 @@ public class AnimateAi extends SpellAbilityAi {
// suppress static abilities from the animated card
final List removedReplacements = Lists.newArrayList();
- if (sa.hasParam("OverwriteReplacements") || removeAll) {
+ if (sa.hasParam("OverwriteReplacements") || removeAll || removeIntrinsic) {
for (final ReplacementEffect re : card.getReplacementEffects()) {
+ if (removeIntrinsic && !re.isIntrinsic()) {
+ continue;
+ }
re.setTemporarilySuppressed(true);
removedReplacements.add(re);
}
diff --git a/forge-ai/src/main/java/forge/ai/ability/AssignGroupAi.java b/forge-ai/src/main/java/forge/ai/ability/AssignGroupAi.java
new file mode 100644
index 00000000000..691a21904ce
--- /dev/null
+++ b/forge-ai/src/main/java/forge/ai/ability/AssignGroupAi.java
@@ -0,0 +1,33 @@
+package forge.ai.ability;
+
+import java.util.List;
+import java.util.Map;
+
+import com.google.common.collect.Iterables;
+
+import forge.ai.SpellAbilityAi;
+import forge.game.player.Player;
+import forge.game.spellability.SpellAbility;
+
+public class AssignGroupAi extends SpellAbilityAi {
+
+ protected boolean canPlayAI(Player ai, SpellAbility sa) {
+ // TODO: Currently this AI relies on the card-specific limiting hints (NeedsToPlay / NeedsToPlayVar),
+ // otherwise the AI considers the card playable.
+
+ return true;
+ }
+
+ public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List spells, Map params) {
+ final String logic = sa.getParamOrDefault("AILogic", "");
+
+ if (logic.equals("FriendOrFoe")) {
+ if (params.containsKey("Affected") && spells.size() >= 2) {
+ Player t = (Player) params.get("Affected");
+ return spells.get(player.isOpponentOf(t) ? 1 : 0);
+ }
+ }
+
+ return Iterables.getFirst(spells, null);
+ }
+}
diff --git a/forge-ai/src/main/java/forge/ai/ability/AttachAi.java b/forge-ai/src/main/java/forge/ai/ability/AttachAi.java
index 902691478f8..cdd19a65318 100644
--- a/forge-ai/src/main/java/forge/ai/ability/AttachAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/AttachAi.java
@@ -510,7 +510,7 @@ public class AttachAi extends SpellAbilityAi {
// Prefer "tap to deal damage"
// TODO : Skip this one if triggers on combat damage only?
for (SpellAbility sa2 : card.getSpellAbilities()) {
- if ((sa2.getApi().equals(ApiType.DealDamage))
+ if (ApiType.DealDamage.equals(sa2.getApi())
&& (sa2.getTargetRestrictions().canTgtPlayer())) {
cardPriority += 300;
}
@@ -971,8 +971,8 @@ public class AttachAi extends SpellAbilityAi {
continue;
}
if ((affected.contains(stCheck) || affected.contains("AttachedBy"))) {
- totToughness += AbilityUtils.calculateAmount(attachSource, stabMap.get("AddToughness"), sa);
- totPower += AbilityUtils.calculateAmount(attachSource, stabMap.get("AddPower"), sa);
+ totToughness += AbilityUtils.calculateAmount(attachSource, stabMap.get("AddToughness"), stAbility);
+ totPower += AbilityUtils.calculateAmount(attachSource, stabMap.get("AddPower"), stAbility);
grantingAbilities |= stabMap.containsKey("AddAbility");
diff --git a/forge-ai/src/main/java/forge/ai/ability/CanPlayAsDrawbackAi.java b/forge-ai/src/main/java/forge/ai/ability/CanPlayAsDrawbackAi.java
index 0b4cf77f7c9..96876913dd9 100644
--- a/forge-ai/src/main/java/forge/ai/ability/CanPlayAsDrawbackAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/CanPlayAsDrawbackAi.java
@@ -6,6 +6,7 @@ import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import java.util.List;
+import java.util.Map;
public class CanPlayAsDrawbackAi extends SpellAbilityAi {
@@ -37,7 +38,8 @@ public class CanPlayAsDrawbackAi extends SpellAbilityAi {
@Override
- public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List spells) {
+ public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List spells,
+ Map params) {
// This might be called from CopySpellAbilityEffect - to hide warning (for having no overload) use this simple overload
return spells.get(0);
}
diff --git a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java
index 2eb6f30a6f2..ab2ef609098 100644
--- a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java
@@ -152,7 +152,9 @@ public class ChangeZoneAi extends SpellAbilityAi {
return doReturnCommanderLogic(sa, aiPlayer);
}
- if ("IfNotBuffed".equals(sa.getParam("AILogic"))) {
+ if ("Always".equals(sa.getParam("AILogic"))) {
+ return true;
+ } else if ("IfNotBuffed".equals(sa.getParam("AILogic"))) {
if (ComputerUtilCard.isUselessCreature(aiPlayer, sa.getHostCard())) {
return true; // debuffed by opponent's auras to the level that it becomes useless
}
diff --git a/forge-ai/src/main/java/forge/ai/ability/ChooseGenericEffectAi.java b/forge-ai/src/main/java/forge/ai/ability/ChooseGenericEffectAi.java
index 05ba5338e55..af986809b36 100644
--- a/forge-ai/src/main/java/forge/ai/ability/ChooseGenericEffectAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/ChooseGenericEffectAi.java
@@ -1,6 +1,7 @@
package forge.ai.ability;
import java.util.List;
+import java.util.Map;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
@@ -79,7 +80,8 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
}
@Override
- public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List spells) {
+ public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List spells,
+ Map params) {
Card host = sa.getHostCard();
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
final Game game = host.getGame();
diff --git a/forge-ai/src/main/java/forge/ai/ability/CopySpellAbilityAi.java b/forge-ai/src/main/java/forge/ai/ability/CopySpellAbilityAi.java
index 4471074c3f5..55a0804a562 100644
--- a/forge-ai/src/main/java/forge/ai/ability/CopySpellAbilityAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/CopySpellAbilityAi.java
@@ -7,6 +7,7 @@ import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.SpellAbility;
import java.util.List;
+import java.util.Map;
public class CopySpellAbilityAi extends SpellAbilityAi {
@@ -36,7 +37,8 @@ public class CopySpellAbilityAi extends SpellAbilityAi {
}
@Override
- public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List spells) {
+ public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List spells,
+ Map params) {
return spells.get(0);
}
diff --git a/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java b/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java
index 3a2f2777dc1..171d3431506 100644
--- a/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java
@@ -125,7 +125,7 @@ public class CountersPutAi extends SpellAbilityAi {
CardCollection list;
Card choice = null;
final String type = sa.getParam("CounterType");
- final String amountStr = sa.getParam("CounterNum");
+ final String amountStr = sa.getParamOrDefault("CounterNum", "1");
final boolean divided = sa.hasParam("DividedAsYouChoose");
final String logic = sa.getParamOrDefault("AILogic", "");
PhaseHandler ph = ai.getGame().getPhaseHandler();
@@ -220,13 +220,9 @@ public class CountersPutAi extends SpellAbilityAi {
if ("Never".equals(logic)) {
return false;
- }
-
- if ("PayEnergy".equals(logic)) {
+ } else if ("PayEnergy".equals(logic)) {
return true;
- }
-
- if ("PayEnergyConservatively".equals(logic)) {
+ } else if ("PayEnergyConservatively".equals(logic)) {
boolean onlyInCombat = ai.getController().isAI()
&& ((PlayerControllerAi) ai.getController()).getAi().getBooleanProperty(AiProps.CONSERVATIVE_ENERGY_PAYMENT_ONLY_IN_COMBAT);
boolean onlyDefensive = ai.getController().isAI()
@@ -266,9 +262,7 @@ public class CountersPutAi extends SpellAbilityAi {
return true;
}
}
- }
-
- if (logic.equals("MarkOppCreature")) {
+ } else if (logic.equals("MarkOppCreature")) {
if (!ph.is(PhaseType.END_OF_TURN)) {
return false;
}
@@ -283,6 +277,11 @@ public class CountersPutAi extends SpellAbilityAi {
sa.getTargets().add(bestCreat);
return true;
}
+ } else if (logic.equals("CheckDFC")) {
+ // for cards like Ludevic's Test Subject
+ if (!source.canTransform()) {
+ return false;
+ }
}
if (sa.getConditions() != null && !sa.getConditions().areMet(sa) && sa.getSubAbility() == null) {
@@ -581,7 +580,7 @@ public class CountersPutAi extends SpellAbilityAi {
final String type = sa.getParam("CounterType");
final String logic = sa.getParamOrDefault("AILogic", "");
- final String amountStr = sa.getParam("CounterNum");
+ final String amountStr = sa.getParamOrDefault("CounterNum", "1");
final boolean divided = sa.hasParam("DividedAsYouChoose");
final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
@@ -661,7 +660,7 @@ public class CountersPutAi extends SpellAbilityAi {
boolean preferred = true;
CardCollection list;
final String type = sa.getParam("CounterType");
- final String amountStr = sa.getParam("CounterNum");
+ final String amountStr = sa.getParamOrDefault("CounterNum", "1");
final boolean divided = sa.hasParam("DividedAsYouChoose");
final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
int left = amount;
@@ -804,7 +803,8 @@ public class CountersPutAi extends SpellAbilityAi {
if (mode == PlayerActionConfirmMode.Tribute) {
// add counter if that opponent has a giant creature
final List creats = player.getCreaturesInPlay();
- final int tributeAmount = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("CounterNum"), sa);
+ final String amountStr = sa.getParamOrDefault("CounterNum", "1");
+ final int tributeAmount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
final boolean isHaste = source.hasKeyword(Keyword.HASTE);
List threatening = CardLists.filter(creats, new Predicate() {
@@ -863,7 +863,7 @@ public class CountersPutAi extends SpellAbilityAi {
}
final CounterType type = CounterType.valueOf(sa.getParam("CounterType"));
- final String amountStr = sa.getParam("CounterNum");
+ final String amountStr = sa.getParamOrDefault("CounterNum", "1");
final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
final boolean isCurse = sa.isCurse();
diff --git a/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java b/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java
index 1e78c806dda..d55ad8aa463 100644
--- a/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java
@@ -481,10 +481,11 @@ public class DamageDealAi extends DamageAiBase {
final PhaseHandler phase = game.getPhaseHandler();
final boolean divided = sa.hasParam("DividedAsYouChoose");
final boolean oppTargetsChoice = sa.hasParam("TargetingPlayer");
+ final String logic = sa.getParamOrDefault("AILogic", "");
Player enemy = ComputerUtil.getOpponentFor(ai);
- if ("PowerDmg".equals(sa.getParam("AILogic"))) {
+ if ("PowerDmg".equals(logic)) {
// check if it is better to target the player instead, the original target is already set in PumpAi.pumpTgtAI()
if (tgt.canTgtCreatureAndPlayer() && this.shouldTgtP(ai, sa, dmg, noPrevention)){
sa.resetTargets();
@@ -504,11 +505,11 @@ public class DamageDealAi extends DamageAiBase {
TargetChoices tcs = sa.getTargets();
// Do not use if would kill self
- if (("SelfDamage".equals(sa.getParam("AILogic"))) && (ai.getLife() <= Integer.parseInt(source.getSVar("SelfDamageAmount")))) {
+ if (("SelfDamage".equals(logic)) && (ai.getLife() <= Integer.parseInt(source.getSVar("SelfDamageAmount")))) {
return false;
}
- if ("ChoiceBurn".equals(sa.getParam("AILogic"))) {
+ if ("ChoiceBurn".equals(logic)) {
// do not waste burns on player if other choices are present
if (this.shouldTgtP(ai, sa, dmg, noPrevention)) {
tcs.add(enemy);
@@ -517,7 +518,7 @@ public class DamageDealAi extends DamageAiBase {
return false;
}
}
- if ("Polukranos".equals(sa.getParam("AILogic"))) {
+ if ("Polukranos".equals(logic)) {
int dmgTaken = 0;
CardCollection humCreatures = enemy.getCreaturesInPlay();
Card lastTgt = null;
@@ -681,7 +682,7 @@ public class DamageDealAi extends DamageAiBase {
}
continue;
}
- } else if ("OppAtTenLife".equals(sa.getParam("AILogic"))) {
+ } else if ("OppAtTenLife".equals(logic)) {
for (final Player p : ai.getOpponents()) {
if (sa.canTarget(p) && p.getLife() == 10 && tcs.getNumTargeted() < tgt.getMaxTargets(source, sa)) {
tcs.add(p);
@@ -690,9 +691,10 @@ public class DamageDealAi extends DamageAiBase {
}
// TODO: Improve Damage, we shouldn't just target the player just
// because we can
- else if (sa.canTarget(enemy)) {
+ if (sa.canTarget(enemy) && tcs.getNumTargeted() < tgt.getMaxTargets(source, sa)) {
if (((phase.is(PhaseType.END_OF_TURN) && phase.getNextTurn().equals(ai))
|| (SpellAbilityAi.isSorcerySpeed(sa) && phase.is(PhaseType.MAIN2))
+ || ("PingAfterAttack".equals(logic) && phase.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) && phase.isPlayerTurn(ai))
|| sa.getPayCosts() == null || immediately
|| this.shouldTgtP(ai, sa, dmg, noPrevention)) &&
(!avoidTargetP(ai, sa))) {
diff --git a/forge-ai/src/main/java/forge/ai/ability/DrawAi.java b/forge-ai/src/main/java/forge/ai/ability/DrawAi.java
index 0c020491679..2a2967f317b 100644
--- a/forge-ai/src/main/java/forge/ai/ability/DrawAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/DrawAi.java
@@ -26,10 +26,7 @@ import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.card.CounterType;
-import forge.game.cost.Cost;
-import forge.game.cost.CostDiscard;
-import forge.game.cost.CostPart;
-import forge.game.cost.PaymentDecision;
+import forge.game.cost.*;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
@@ -252,19 +249,34 @@ public class DrawAi extends SpellAbilityAi {
}
if (num != null && num.equals("ChosenX")) {
- // Necrologia, Pay X Life : Draw X Cards
if (sa.getSVar("X").equals("XChoice")) {
// Draw up to max hand size but leave at least 3 in library
numCards = Math.min(computerMaxHandSize - computerHandSize, computerLibrarySize - 3);
- // But no more than what's "safe" and doesn't risk a near death experience
- // Maybe would be better to check for "serious danger" and take more risk?
- while ((ComputerUtil.aiLifeInDanger(ai, false, numCards) && (numCards > 0))) {
- numCards--;
+
+ if (sa.getPayCosts() != null) {
+ if (sa.getPayCosts().hasSpecificCostType(CostPayLife.class)) {
+ // [Necrologia, Pay X Life : Draw X Cards]
+ // Don't draw more than what's "safe" and don't risk a near death experience
+ // Maybe would be better to check for "serious danger" and take more risk?
+ while ((ComputerUtil.aiLifeInDanger(ai, false, numCards) && (numCards > 0))) {
+ numCards--;
+ }
+ } else if (sa.getPayCosts().hasSpecificCostType(CostSacrifice.class)) {
+ // [e.g. Krav, the Unredeemed and other cases which say "Sacrifice X creatures: draw X cards]
+ // TODO: Add special logic to limit/otherwise modify the ChosenX value here
+
+ // Skip this ability if nothing is to be chosen for sacrifice
+ if (numCards <= 0) {
+ return false;
+ }
+ }
}
+
sa.setSVar("ChosenX", Integer.toString(numCards));
source.setSVar("ChosenX", Integer.toString(numCards));
}
}
+
// Logic for cards that require special handling
if ("YawgmothsBargain".equals(logic)) {
return SpecialCardAi.YawgmothsBargain.consider(ai, sa);
diff --git a/forge-ai/src/main/java/forge/ai/ability/FightAi.java b/forge-ai/src/main/java/forge/ai/ability/FightAi.java
index f9805e09954..3a1ad5b5528 100644
--- a/forge-ai/src/main/java/forge/ai/ability/FightAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/FightAi.java
@@ -33,6 +33,12 @@ public class FightAi extends SpellAbilityAi {
sa.resetTargets();
final Card source = sa.getHostCard();
+ // everything is defined or targeted above, can't do anything there?
+ if (sa.hasParam("Defined") && !sa.usesTargeting()) {
+ // TODO extend Logic for cards like Arena or Grothama
+ return true;
+ }
+
// Get creature lists
CardCollectionView aiCreatures = ai.getCreaturesInPlay();
aiCreatures = CardLists.getTargetableCards(aiCreatures, sa);
diff --git a/forge-ai/src/main/java/forge/ai/ability/LifeExchangeVariantAi.java b/forge-ai/src/main/java/forge/ai/ability/LifeExchangeVariantAi.java
index a34f5e0d801..633dfadf4e7 100644
--- a/forge-ai/src/main/java/forge/ai/ability/LifeExchangeVariantAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/LifeExchangeVariantAi.java
@@ -9,6 +9,7 @@ import forge.game.card.Card;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
+import forge.game.zone.MagicStack;
public class LifeExchangeVariantAi extends SpellAbilityAi {
@@ -83,7 +84,25 @@ public class LifeExchangeVariantAi extends SpellAbilityAi {
return shouldDo;
}
else if ("Evra, Halcyon Witness".equals(sourceName)) {
- // TODO add logic
+ if (!ai.canGainLife())
+ return false;
+
+ int aiLife = ai.getLife();
+
+ if (source.getNetPower() > aiLife) {
+ if (ComputerUtilCombat.lifeInSeriousDanger(ai, ai.getGame().getCombat())) {
+ return true;
+ }
+
+ // check the top of stack
+ MagicStack stack = ai.getGame().getStack();
+ if (!stack.isEmpty()) {
+ SpellAbility saTop = stack.peekAbility();
+ if (ComputerUtil.predictDamageFromSpell(saTop, ai) >= aiLife) {
+ return true;
+ }
+ }
+ }
}
return false;
diff --git a/forge-ai/src/main/java/forge/ai/ability/ScryAi.java b/forge-ai/src/main/java/forge/ai/ability/ScryAi.java
index a831818c7da..ea10f386b9e 100644
--- a/forge-ai/src/main/java/forge/ai/ability/ScryAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/ScryAi.java
@@ -11,7 +11,6 @@ import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.SpellAbility;
-import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
@@ -22,9 +21,8 @@ public class ScryAi extends SpellAbilityAi {
*/
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
- final TargetRestrictions tgt = sa.getTargetRestrictions();
- if (tgt != null) { // It doesn't appear that Scry ever targets
+ if (sa.usesTargeting()) { // It doesn't appear that Scry ever targets
// ability is targeted
sa.resetTargets();
diff --git a/forge-ai/src/main/java/forge/ai/ability/SetStateAi.java b/forge-ai/src/main/java/forge/ai/ability/SetStateAi.java
index 350f368a817..2a2a5d7493e 100644
--- a/forge-ai/src/main/java/forge/ai/ability/SetStateAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/SetStateAi.java
@@ -3,7 +3,6 @@ package forge.ai.ability;
import com.google.common.base.Predicate;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
-import forge.card.CardSplitType;
import forge.card.CardStateName;
import forge.game.Game;
import forge.game.GlobalRuleChange;
@@ -65,8 +64,6 @@ public class SetStateAi extends SpellAbilityAi {
@Override
protected boolean checkAiLogic(final Player aiPlayer, final SpellAbility sa, final String aiLogic) {
- final Card source = sa.getHostCard();
-
return super.checkAiLogic(aiPlayer, sa, aiLogic);
}
@@ -87,7 +84,7 @@ public class SetStateAi extends SpellAbilityAi {
if("Transform".equals(mode)) {
if (!sa.usesTargeting()) {
// no Transform with Defined which is not Self
- if (source.hasKeyword("CARDNAME can't transform")) {
+ if (!source.canTransform()) {
return false;
}
return shouldTransformCard(source, ai, ph) || "Always".equals(logic);
@@ -96,15 +93,13 @@ public class SetStateAi extends SpellAbilityAi {
sa.resetTargets();
CardCollection list = CardLists.getValidCards(CardLists.filter(game.getCardsIn(ZoneType.Battlefield), Presets.CREATURES), tgt.getValidTgts(), ai, source, sa);
- // select only cards with Transform as SplitType
+ // select only the ones that can transform
list = CardLists.filter(list, new Predicate() {
@Override
public boolean apply(Card c) {
- return c.hasAlternateState() && c.getRules().getSplitType() == CardSplitType.Transform;
+ return c.canTransform();
}
});
- // select only the ones that can transform
- list = CardLists.getNotKeyword(list, "CARDNAME can't transform");
list = CardLists.getTargetableCards(list, sa);
if (list.isEmpty()) {
diff --git a/forge-ai/src/main/java/forge/ai/ability/SurveilAi.java b/forge-ai/src/main/java/forge/ai/ability/SurveilAi.java
new file mode 100644
index 00000000000..fe7b1671e67
--- /dev/null
+++ b/forge-ai/src/main/java/forge/ai/ability/SurveilAi.java
@@ -0,0 +1,104 @@
+package forge.ai.ability;
+
+import forge.ai.SpellAbilityAi;
+import forge.game.phase.PhaseHandler;
+import forge.game.phase.PhaseType;
+import forge.game.player.Player;
+import forge.game.player.PlayerActionConfirmMode;
+import forge.game.spellability.SpellAbility;
+import forge.game.zone.ZoneType;
+import forge.util.MyRandom;
+
+public class SurveilAi extends SpellAbilityAi {
+
+ /*
+ * (non-Javadoc)
+ * @see forge.ai.SpellAbilityAi#doTriggerAINoCost(forge.game.player.Player, forge.game.spellability.SpellAbility, boolean)
+ */
+ @Override
+ protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
+
+ if (sa.usesTargeting()) { // TODO: It doesn't appear that Surveil ever targets, is this necessary?
+ sa.resetTargets();
+ sa.getTargets().add(ai);
+ }
+
+ return true;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see forge.ai.SpellAbilityAi#chkAIDrawback(forge.game.spellability.SpellAbility, forge.game.player.Player)
+ */
+ @Override
+ public boolean chkAIDrawback(SpellAbility sa, Player ai) {
+ return doTriggerAINoCost(ai, sa, false);
+ }
+
+ /**
+ * Checks if the AI will play a SpellAbility based on its phase restrictions
+ */
+ @Override
+ protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
+ // if the Surveil ability requires tapping and has a mana cost, it's best done at the end of opponent's turn
+ // and right before the beginning of AI's turn, if possible, to avoid mana locking the AI and also to
+ // try to scry right before drawing a card. Also, avoid tapping creatures in the AI's turn, if possible,
+ // even if there's no mana cost.
+ if (sa.getPayCosts() != null) {
+ if (sa.getPayCosts().hasTapCost()
+ && (sa.getPayCosts().hasManaCost() || (sa.getHostCard() != null && sa.getHostCard().isCreature()))
+ && !SpellAbilityAi.isSorcerySpeed(sa)) {
+ return ph.getNextTurn() == ai && ph.is(PhaseType.END_OF_TURN);
+ }
+ }
+
+ // in the player's turn Surveil should only be done in Main1 or in Upkeep if able
+ if (ph.isPlayerTurn(ai)) {
+ if (SpellAbilityAi.isSorcerySpeed(sa)) {
+ return ph.is(PhaseType.MAIN1) || sa.hasParam("Planeswalker");
+ } else {
+ return ph.is(PhaseType.UPKEEP);
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Checks if the AI will play a SpellAbility with the specified AiLogic
+ */
+ @Override
+ protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
+ if ("Never".equals(aiLogic)) {
+ return false;
+ }
+
+ // TODO: add card-specific Surveil AI logic here when/if necessary
+
+ return true;
+ }
+
+ @Override
+ protected boolean checkApiLogic(Player ai, SpellAbility sa) {
+ // Makes no sense to do Surveil when there's nothing in the library
+ if (ai.getCardsIn(ZoneType.Library).isEmpty()) {
+ return false;
+ }
+
+ double chance = .4; // 40 percent chance for instant speed
+ if (SpellAbilityAi.isSorcerySpeed(sa)) {
+ chance = .667; // 66.7% chance for sorcery speed (since it will never activate EOT)
+ }
+
+ boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(chance, sa.getActivationsThisTurn() + 1);
+ if (SpellAbilityAi.playReusable(ai, sa)) {
+ randomReturn = true;
+ }
+
+ return randomReturn;
+ }
+
+ @Override
+ public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
+ return true;
+ }
+}
diff --git a/forge-ai/src/main/java/forge/ai/ability/TokenAi.java b/forge-ai/src/main/java/forge/ai/ability/TokenAi.java
index ec86828e6d8..56d88681c72 100644
--- a/forge-ai/src/main/java/forge/ai/ability/TokenAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/TokenAi.java
@@ -8,6 +8,7 @@ import forge.game.GameEntity;
import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
+import forge.game.ability.effects.TokenEffect;
import forge.game.card.*;
import forge.game.card.token.TokenInfo;
import forge.game.combat.Combat;
@@ -42,14 +43,11 @@ import java.util.List;
* @version $Id: AbilityFactoryToken.java 17656 2012-10-22 19:32:56Z Max mtg $
*/
public class TokenAi extends SpellAbilityAi {
-
-
private String tokenAmount;
- private String tokenName;
- private String[] tokenTypes;
- private String[] tokenKeywords;
private String tokenPower;
private String tokenToughness;
+
+ private Card actualToken;
/**
*
* Constructor for AbilityFactory_Token.
@@ -58,23 +56,28 @@ public class TokenAi extends SpellAbilityAi {
* a {@link forge.game.ability.AbilityFactory} object.
*/
private void readParameters(final SpellAbility mapParams) {
- String[] keywords;
-
- if (mapParams.hasParam("TokenKeywords")) {
- // TODO: Change this Split to a semicolon or something else
- keywords = mapParams.getParam("TokenKeywords").split("<>");
- } else {
- keywords = new String[0];
- }
-
-
this.tokenAmount = mapParams.getParamOrDefault("TokenAmount", "1");
- this.tokenPower = mapParams.getParam("TokenPower");
- this.tokenToughness = mapParams.getParam("TokenToughness");
- this.tokenName = mapParams.getParam("TokenName");
- this.tokenTypes = mapParams.getParam("TokenTypes").split(",");
- this.tokenKeywords = keywords;
+ TokenEffect effect = new TokenEffect();
+
+ this.actualToken = effect.loadTokenPrototype(mapParams);
+
+ if (actualToken == null) {
+ String[] keywords;
+
+ if (mapParams.hasParam("TokenKeywords")) {
+ // TODO: Change this Split to a semicolon or something else
+ keywords = mapParams.getParam("TokenKeywords").split("<>");
+ } else {
+ keywords = new String[0];
+ }
+
+ this.tokenPower = mapParams.getParam("TokenPower");
+ this.tokenToughness = mapParams.getParam("TokenToughness");
+ } else {
+ this.tokenPower = actualToken.getBasePowerString();
+ this.tokenToughness = actualToken.getBaseToughnessString();
+ }
}
@Override
@@ -103,8 +106,11 @@ public class TokenAi extends SpellAbilityAi {
}
}
- final Card token = spawnToken(ai, sa);
- if (token == null) {
+ if (actualToken == null) {
+ actualToken = spawnToken(ai, sa);
+ }
+
+ if (actualToken == null) {
final AbilitySub sub = sa.getSubAbility();
if (pwPlus || (sub != null && SpellApiToAi.Converter.get(sub.getApi()).chkAIDrawback(sub, ai))) {
return true; // planeswalker plus ability or sub-ability is
@@ -130,24 +136,21 @@ public class TokenAi extends SpellAbilityAi {
}
}
- if (canInterruptSacrifice(ai, sa, token)) {
+ if (canInterruptSacrifice(ai, sa, actualToken)) {
return true;
}
- boolean haste = false;
+ boolean haste = this.actualToken.hasKeyword(Keyword.HASTE);
boolean oneShot = sa.getSubAbility() != null
&& sa.getSubAbility().getApi() == ApiType.DelayedTrigger;
- for (final String kw : this.tokenKeywords) {
- if (kw.equals("Haste")) {
- haste = true;
- }
- }
+ boolean isCreature = this.actualToken.getType().isCreature();
+
// Don't generate tokens without haste before main 2 if possible
if (ph.getPhase().isBefore(PhaseType.MAIN2) && ph.isPlayerTurn(ai) && !haste && !sa.hasParam("ActivationPhases")
&& !ComputerUtil.castSpellInMain1(ai, sa)) {
boolean buff = false;
for (Card c : ai.getCardsIn(ZoneType.Battlefield)) {
- if ("Creature".equals(c.getSVar("BuffedBy"))) {
+ if (isCreature && "Creature".equals(c.getSVar("BuffedBy"))) {
buff = true;
}
}
@@ -180,12 +183,9 @@ public class TokenAi extends SpellAbilityAi {
}
// Don't kill AIs Legendary tokens
- for (final String type : this.tokenTypes) {
- if (type.equals("Legendary")) {
- if (ai.isCardInPlay(this.tokenName)) {
- return false;
- }
- }
+ if (this.actualToken.getType().isLegendary() && ai.isCardInPlay(this.actualToken.getName())) {
+ // TODO Check if Token is useless due to an aura or counters?
+ return false;
}
final TargetRestrictions tgt = sa.getTargetRestrictions();
@@ -311,6 +311,18 @@ public class TokenAi extends SpellAbilityAi {
}
}
+ if (mandatory) {
+ // Necessary because the AI goes into this method twice, first to set up targets (with mandatory=true)
+ // and then the second time to confirm the trigger (where mandatory may be set to false).
+ return true;
+ }
+
+ if ("OnlyOnAlliedAttack".equals(sa.getParam("AILogic"))) {
+ Combat combat = ai.getGame().getCombat();
+ return combat != null && combat.getAttackingPlayer() != null
+ && !combat.getAttackingPlayer().isOpponentOf(ai);
+ }
+
return true;
}
/* (non-Javadoc)
@@ -388,6 +400,7 @@ public class TokenAi extends SpellAbilityAi {
* @param notNull if the token would not survive, still return it
* @return token creature created by ability
*/
+ // TODO Is this just completely copied from TokenEffect? Let's just call that thing
public static Card spawnToken(Player ai, SpellAbility sa, boolean notNull) {
final Card host = sa.getHostCard();
@@ -511,7 +524,7 @@ public class TokenAi extends SpellAbilityAi {
// Apply static abilities and prune dead tokens
final Game game = ai.getGame();
ComputerUtilCard.applyStaticContPT(game, token, null);
- if (!notNull && token.getNetToughness() < 1) {
+ if (!notNull && token.isCreature() && token.getNetToughness() < 1) {
return null;
} else {
return token;
diff --git a/forge-core/.classpath b/forge-core/.classpath
index f7bf6c0c0ab..95651cd7399 100644
--- a/forge-core/.classpath
+++ b/forge-core/.classpath
@@ -1,7 +1,6 @@
-
diff --git a/forge-core/pom.xml b/forge-core/pom.xml
index c3dfef97b0a..75670239273 100644
--- a/forge-core/pom.xml
+++ b/forge-core/pom.xml
@@ -6,7 +6,7 @@
forge
forge
- 1.6.11-SNAPSHOT
+ 1.6.16-SNAPSHOT
forge-core
@@ -16,7 +16,7 @@
com.google.guava
guava
- 24.1-jre
+ 24.1-android
org.apache.commons
@@ -24,4 +24,31 @@
3.7
+
+
+
+
+ org.apache.maven.plugins
+ maven-checkstyle-plugin
+ 3.0.0
+
+
+ checkstyle-validation
+ validate
+
+ ../checkstyle.xml
+ true
+ UTF-8
+ true
+ true
+ true
+
+
+ check
+
+
+
+
+
+
diff --git a/forge-core/src/main/java/forge/CardStorageReader.java b/forge-core/src/main/java/forge/CardStorageReader.java
index 0f99bf6b87d..92b30bce7ac 100644
--- a/forge-core/src/main/java/forge/CardStorageReader.java
+++ b/forge-core/src/main/java/forge/CardStorageReader.java
@@ -413,6 +413,9 @@ public class CardStorageReader {
return reader.readCard(lines, Files.getNameWithoutExtension(file.getName()));
} catch (final FileNotFoundException ex) {
throw new RuntimeException("CardReader : run error -- file not found: " + file.getPath(), ex);
+ } catch (final Exception ex) {
+ System.out.println("Error loading cardscript " + file.getName() + ". Please close Forge and resolve this.");
+ throw ex;
} finally {
try {
assert fileInputStream != null;
diff --git a/forge-core/src/main/java/forge/StaticData.java b/forge-core/src/main/java/forge/StaticData.java
index 6c7d931d7b3..9e8629baf20 100644
--- a/forge-core/src/main/java/forge/StaticData.java
+++ b/forge-core/src/main/java/forge/StaticData.java
@@ -37,6 +37,8 @@ public class StaticData {
private Predicate brawlPredicate;
private Predicate modernPredicate;
+ private boolean filteredHandsEnabled = false;
+
// Loaded lazily:
private IStorage boosters;
private IStorage specialBoosters;
@@ -209,6 +211,14 @@ public class StaticData {
return brawlPredicate;
}
+ public void setFilteredHandsEnabled(boolean filteredHandsEnabled){
+ this.filteredHandsEnabled = filteredHandsEnabled;
+ }
+
+ public boolean getFilteredHandsEnabled(){
+ return filteredHandsEnabled;
+ }
+
public PaperCard getCardByEditionDate(PaperCard card, Date editionDate) {
PaperCard c = this.getCommonCards().getCardFromEdition(card.getName(), editionDate, CardDb.SetPreference.LatestCoreExp, card.getArtIndex());
diff --git a/forge-core/src/main/java/forge/card/CardChangedType.java b/forge-core/src/main/java/forge/card/CardChangedType.java
index 1aae3bd41fa..6e855548598 100644
--- a/forge-core/src/main/java/forge/card/CardChangedType.java
+++ b/forge-core/src/main/java/forge/card/CardChangedType.java
@@ -32,19 +32,24 @@ public class CardChangedType {
private final boolean removeSuperTypes;
private final boolean removeCardTypes;
private final boolean removeSubTypes;
+ private final boolean removeLandTypes;
private final boolean removeCreatureTypes;
private final boolean removeArtifactTypes;
+ private final boolean removeEnchantmentTypes;
public CardChangedType(final CardType addType0, final CardType removeType0, final boolean removeSuperType0,
- final boolean removeCardType0, final boolean removeSubType0, final boolean removeCreatureType0,
- final boolean removeArtifactType0) {
+ final boolean removeCardType0, final boolean removeSubType0, final boolean removeLandType0,
+ final boolean removeCreatureType0, final boolean removeArtifactType0,
+ final boolean removeEnchantmentTypes0) {
addType = addType0;
removeType = removeType0;
removeSuperTypes = removeSuperType0;
removeCardTypes = removeCardType0;
removeSubTypes = removeSubType0;
+ removeLandTypes = removeLandType0;
removeCreatureTypes = removeCreatureType0;
removeArtifactTypes = removeArtifactType0;
+ removeEnchantmentTypes = removeEnchantmentTypes0;
}
public final CardType getAddType() {
@@ -67,6 +72,10 @@ public class CardChangedType {
return removeSubTypes;
}
+ public final boolean isRemoveLandTypes() {
+ return removeLandTypes;
+ }
+
public final boolean isRemoveCreatureTypes() {
return removeCreatureTypes;
}
@@ -74,4 +83,8 @@ public class CardChangedType {
public final boolean isRemoveArtifactTypes() {
return removeArtifactTypes;
}
+
+ public final boolean isRemoveEnchantmentTypes() {
+ return removeEnchantmentTypes;
+ }
}
diff --git a/forge-core/src/main/java/forge/card/CardEdition.java b/forge-core/src/main/java/forge/card/CardEdition.java
index 022a915fdb8..d9afaef296b 100644
--- a/forge-core/src/main/java/forge/card/CardEdition.java
+++ b/forge-core/src/main/java/forge/card/CardEdition.java
@@ -123,12 +123,19 @@ public final class CardEdition implements Comparable { // immutable
private boolean smallSetOverride = false;
private String boosterMustContain = "";
private final CardInSet[] cards;
+ private final String[] tokenNormalized;
private int boosterArts = 1;
private SealedProduct.Template boosterTpl = null;
private CardEdition(CardInSet[] cards) {
this.cards = cards;
+ tokenNormalized = null;
+ }
+
+ private CardEdition(CardInSet[] cards, String[] tokens) {
+ this.cards = cards;
+ this.tokenNormalized = tokens;
}
/**
@@ -254,6 +261,7 @@ public final class CardEdition implements Comparable { // immutable
protected CardEdition read(File file) {
final Map> contents = FileSection.parseSections(FileUtil.readFile(file));
+ List tokenNormalized = new ArrayList<>();
List processedCards = new ArrayList<>();
if (contents.containsKey("cards")) {
for(String line : contents.get("cards")) {
@@ -277,7 +285,19 @@ public final class CardEdition implements Comparable { // immutable
}
}
- CardEdition res = new CardEdition(processedCards.toArray(new CardInSet[processedCards.size()]));
+ if (contents.containsKey("tokens")) {
+ for(String line : contents.get("tokens")) {
+ if (StringUtils.isBlank(line))
+ continue;
+
+ tokenNormalized.add(line);
+ }
+ }
+
+ CardEdition res = new CardEdition(
+ processedCards.toArray(new CardInSet[processedCards.size()]),
+ tokenNormalized.toArray(new String[tokenNormalized.size()])
+ );
FileSection section = FileSection.parse(contents.get("metadata"), "=");
res.name = section.get("name");
diff --git a/forge-core/src/main/java/forge/card/CardFace.java b/forge-core/src/main/java/forge/card/CardFace.java
index 041e9251378..cfb3205c44b 100644
--- a/forge-core/src/main/java/forge/card/CardFace.java
+++ b/forge-core/src/main/java/forge/card/CardFace.java
@@ -87,21 +87,27 @@ final class CardFace implements ICardFace {
void setInitialLoyalty(int value) { this.initialLoyalty = value; }
void setPtText(String value) {
- final int slashPos = value.indexOf('/');
- if (slashPos == -1) {
+ final String k[] = value.split("/");
+
+ if (k.length != 2) {
throw new RuntimeException("Creature '" + this.getName() + "' has bad p/t stats");
}
- boolean negPower = value.charAt(0) == '-';
- boolean negToughness = value.charAt(slashPos + 1) == '-';
- this.power = negPower ? value.substring(1, slashPos) : value.substring(0, slashPos);
- this.toughness = negToughness ? value.substring(slashPos + 2) : value.substring(slashPos + 1);
+ this.power = k[0];
+ this.toughness = k[1];
- this.iPower = StringUtils.isNumeric(this.power) ? Integer.parseInt(this.power) : 0;
- this.iToughness = StringUtils.isNumeric(this.toughness) ? Integer.parseInt(this.toughness) : 0;
+ this.iPower = parsePT(k[0]);
+ this.iToughness = parsePT(k[1]);
+ }
- if (negPower) { this.iPower *= -1; }
- if (negToughness) { this.iToughness *= -1; }
+ static int parsePT(String val) {
+ // normalize PT value
+ if (val.contains("*")) {
+ val = val.replace("+*", "");
+ val = val.replace("-*", "");
+ val = val.replace("*", "0");
+ }
+ return Integer.parseInt(val);
}
// Raw fields used for Card creation
diff --git a/forge-core/src/main/java/forge/card/CardFacePredicates.java b/forge-core/src/main/java/forge/card/CardFacePredicates.java
index 80209bb43b1..ee83dda3787 100644
--- a/forge-core/src/main/java/forge/card/CardFacePredicates.java
+++ b/forge-core/src/main/java/forge/card/CardFacePredicates.java
@@ -76,6 +76,51 @@ public final class CardFacePredicates {
};
}
+ static class ValidPredicate implements Predicate {
+ private String valid;
+
+ public ValidPredicate(final String valid) {
+ this.valid = valid;
+ }
+
+ @Override
+ public boolean apply(ICardFace input) {
+ String k[] = valid.split("\\.", 2);
+
+ if ("Card".equals(k[0])) {
+ // okay
+ } else if ("Permanent".equals(k[0])) {
+ if (input.getType().isInstant() || input.getType().isSorcery()) {
+ return false;
+ }
+ } else if (!input.getType().hasStringType(k[0])) {
+ return false;
+ }
+ if (k.length > 1) {
+ for (final String m : k[1].split("\\+")) {
+ if (!hasProperty(input, m)) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ static protected boolean hasProperty(ICardFace input, final String v) {
+ if (v.startsWith("non")) {
+ return !hasProperty(input, v.substring(3));
+ } else if (!input.getType().hasStringType(v)) {
+ return false;
+ }
+ return true;
+ }
+ }
+
+ public static Predicate valid(final String val) {
+ return new ValidPredicate(val);
+ }
+
public static class Presets {
/** The Constant isBasicLand. */
public static final Predicate IS_BASIC_LAND = new Predicate() {
diff --git a/forge-core/src/main/java/forge/card/CardRules.java b/forge-core/src/main/java/forge/card/CardRules.java
index a16a8152179..f96411d4dc7 100644
--- a/forge-core/src/main/java/forge/card/CardRules.java
+++ b/forge-core/src/main/java/forge/card/CardRules.java
@@ -41,6 +41,7 @@ public final class CardRules implements ICardCharacteristics {
private CardAiHints aiHints;
private ColorSet colorIdentity;
private String meldWith;
+ private String partnerWith;
private CardRules(ICardFace[] faces, CardSplitType altMode, CardAiHints cah) {
splitType = altMode;
@@ -48,6 +49,7 @@ public final class CardRules implements ICardCharacteristics {
otherPart = faces[1];
aiHints = cah;
meldWith = "";
+ partnerWith = "";
//calculate color identity
byte colMask = calculateColorIdentity(mainPart);
@@ -68,6 +70,7 @@ public final class CardRules implements ICardCharacteristics {
aiHints = newRules.aiHints;
colorIdentity = newRules.colorIdentity;
meldWith = newRules.meldWith;
+ partnerWith = newRules.partnerWith;
}
private static byte calculateColorIdentity(final ICardFace face) {
@@ -204,7 +207,7 @@ public final class CardRules implements ICardCharacteristics {
}
public boolean canBePartnerCommander() {
- return canBeCommander() && Iterables.contains(mainPart.getKeywords(), "Partner");
+ return canBeCommander() && (hasKeyword("Partner") || !this.partnerWith.isEmpty());
}
public boolean canBeBrawlCommander() {
@@ -216,6 +219,10 @@ public final class CardRules implements ICardCharacteristics {
return meldWith;
}
+ public String getParterWith() {
+ return partnerWith;
+ }
+
// vanguard card fields, they don't use sides.
private int deltaHand;
private int deltaLife;
@@ -262,6 +269,7 @@ public final class CardRules implements ICardCharacteristics {
private int curFace = 0;
private CardSplitType altMode = CardSplitType.None;
private String meldWith = "";
+ private String partnerWith = "";
private String handLife = null;
private String normalizedName = "";
@@ -291,6 +299,7 @@ public final class CardRules implements ICardCharacteristics {
this.hints = null;
this.has = null;
this.meldWith = "";
+ this.partnerWith = "";
this.normalizedName = "";
}
@@ -307,6 +316,7 @@ public final class CardRules implements ICardCharacteristics {
result.setNormalizedName(this.normalizedName);
result.meldWith = this.meldWith;
+ result.partnerWith = this.partnerWith;
result.setDlUrls(pictureUrl);
if (StringUtils.isNotBlank(handLife))
result.setVanguardProperties(handLife);
@@ -382,6 +392,9 @@ public final class CardRules implements ICardCharacteristics {
case 'K':
if ("K".equals(key)) {
this.faces[this.curFace].addKeyword(value);
+ if (value.startsWith("Partner:")) {
+ this.partnerWith = value.split(":")[1];
+ }
}
break;
@@ -533,4 +546,8 @@ public final class CardRules implements ICardCharacteristics {
return result;
}
+
+ public boolean hasKeyword(final String k) {
+ return Iterables.contains(mainPart.getKeywords(), k);
+ }
}
diff --git a/forge-core/src/main/java/forge/card/CardRulesPredicates.java b/forge-core/src/main/java/forge/card/CardRulesPredicates.java
index cd8a3ec4e23..dba9ccad197 100644
--- a/forge-core/src/main/java/forge/card/CardRulesPredicates.java
+++ b/forge-core/src/main/java/forge/card/CardRulesPredicates.java
@@ -10,7 +10,6 @@ import com.google.common.collect.Iterables;
import forge.util.ComparableOp;
import forge.util.PredicateString;
-import forge.util.PredicateString.StringOp;
/**
* Filtering conditions specific for CardRules class, defined here along with
@@ -558,6 +557,19 @@ public final class CardRulesPredicates {
}
};
+ public static final Predicate CAN_BE_COMMANDER = new Predicate() {
+ @Override
+ public boolean apply(final CardRules subject) {
+ return subject.canBeCommander();
+ }
+ };
+ public static final Predicate CAN_BE_PARTNER_COMMANDER = new Predicate() {
+ @Override
+ public boolean apply(final CardRules subject) {
+ return subject.canBePartnerCommander();
+ }
+ };
+
public static final Predicate IS_PLANESWALKER = CardRulesPredicates.coreType(true, CardType.CoreType.Planeswalker);
public static final Predicate IS_INSTANT = CardRulesPredicates.coreType(true, CardType.CoreType.Instant);
public static final Predicate IS_SORCERY = CardRulesPredicates.coreType(true, CardType.CoreType.Sorcery);
@@ -570,13 +582,10 @@ public final class CardRulesPredicates {
public static final Predicate IS_CONSPIRACY = CardRulesPredicates.coreType(true, CardType.CoreType.Conspiracy);
public static final Predicate IS_NON_LAND = CardRulesPredicates.coreType(false, CardType.CoreType.Land);
public static final Predicate IS_NON_CREATURE_SPELL = Predicates.not(Predicates.or(Presets.IS_CREATURE, Presets.IS_LAND));
- public static final Predicate CAN_BE_COMMANDER = Predicates.or(CardRulesPredicates.rules(StringOp.CONTAINS_IC, "can be your commander"),
- Predicates.and(Presets.IS_CREATURE, Presets.IS_LEGENDARY));
public static final Predicate CAN_BE_BRAWL_COMMANDER = Predicates.or(Presets.IS_PLANESWALKER,
Predicates.and(Presets.IS_CREATURE, Presets.IS_LEGENDARY));
/** The Constant IS_NONCREATURE_SPELL_FOR_GENERATOR. **/
- @SuppressWarnings("unchecked")
public static final Predicate IS_NONCREATURE_SPELL_FOR_GENERATOR = com.google.common.base.Predicates
.or(Presets.IS_SORCERY, Presets.IS_INSTANT, Presets.IS_PLANESWALKER, Presets.IS_ENCHANTMENT,
Predicates.and(Presets.IS_ARTIFACT, Predicates.not(Presets.IS_CREATURE)));
diff --git a/forge-core/src/main/java/forge/card/CardType.java b/forge-core/src/main/java/forge/card/CardType.java
index f01b9e59413..e3713cf1f1b 100644
--- a/forge-core/src/main/java/forge/card/CardType.java
+++ b/forge-core/src/main/java/forge/card/CardType.java
@@ -190,7 +190,7 @@ public final class CardType implements Comparable, CardTypeView {
public boolean setCreatureTypes(Collection ctypes) {
// if it isn't a creature then this has no effect
- if (!coreTypes.contains(CoreType.Creature)) {
+ if (!isCreature() && !isTribal()) {
return false;
}
boolean changed = Iterables.removeIf(subtypes, Predicates.IS_CREATURE_TYPE);
@@ -236,7 +236,7 @@ public final class CardType implements Comparable, CardTypeView {
final Set landTypes = Sets.newHashSet();
if (isLand()) {
for (final String t : subtypes) {
- if (isALandType(t) || isABasicLandType(t)) {
+ if (isALandType(t)) {
landTypes.add(t);
}
}
@@ -435,6 +435,9 @@ public final class CardType implements Comparable, CardTypeView {
@Override
public CardTypeView getTypeWithChanges(final Iterable changedCardTypes) {
CardType newType = null;
+ if (Iterables.isEmpty(changedCardTypes)) {
+ return this;
+ }
// we assume that changes are already correctly ordered (taken from TreeMap.values())
for (final CardChangedType ct : changedCardTypes) {
if(null == newType)
@@ -449,7 +452,10 @@ public final class CardType implements Comparable, CardTypeView {
if (ct.isRemoveSubTypes()) {
newType.subtypes.clear();
}
- else {
+ else if (!newType.subtypes.isEmpty()) {
+ if (ct.isRemoveLandTypes()) {
+ Iterables.removeIf(newType.subtypes, Predicates.IS_LAND_TYPE);
+ }
if (ct.isRemoveCreatureTypes()) {
Iterables.removeIf(newType.subtypes, Predicates.IS_CREATURE_TYPE);
// need to remove AllCreatureTypes too when removing creature Types
@@ -458,6 +464,9 @@ public final class CardType implements Comparable, CardTypeView {
if (ct.isRemoveArtifactTypes()) {
Iterables.removeIf(newType.subtypes, Predicates.IS_ARTIFACT_TYPE);
}
+ if (ct.isRemoveEnchantmentTypes()) {
+ Iterables.removeIf(newType.subtypes, Predicates.IS_ENCHANTMENT_TYPE);
+ }
}
if (ct.getRemoveType() != null) {
newType.removeAll(ct.getRemoveType());
@@ -466,6 +475,28 @@ public final class CardType implements Comparable, CardTypeView {
newType.addAll(ct.getAddType());
}
}
+ // sanisfy subtypes
+ if (newType != null && !newType.subtypes.isEmpty()) {
+ if (!newType.isCreature() && !newType.isTribal()) {
+ Iterables.removeIf(newType.subtypes, Predicates.IS_CREATURE_TYPE);
+ newType.subtypes.remove("AllCreatureTypes");
+ }
+ if (!newType.isLand()) {
+ Iterables.removeIf(newType.subtypes, Predicates.IS_LAND_TYPE);
+ }
+ if (!newType.isArtifact()) {
+ Iterables.removeIf(newType.subtypes, Predicates.IS_ARTIFACT_TYPE);
+ }
+ if (!newType.isEnchantment()) {
+ Iterables.removeIf(newType.subtypes, Predicates.IS_ENCHANTMENT_TYPE);
+ }
+ if (!newType.isInstant() && !newType.isSorcery()) {
+ Iterables.removeIf(newType.subtypes, Predicates.IS_SPELL_TYPE);
+ }
+ if (!newType.isPlaneswalker() && !newType.isEmblem()) {
+ Iterables.removeIf(newType.subtypes, Predicates.IS_WALKER_TYPE);
+ }
+ }
return newType == null ? this : newType;
}
@@ -574,6 +605,13 @@ public final class CardType implements Comparable, CardTypeView {
public static final BiMap singularTypes = pluralTypes.inverse();
}
public static class Predicates {
+ public static Predicate IS_LAND_TYPE = new Predicate() {
+ @Override
+ public boolean apply(String input) {
+ return CardType.isALandType(input);
+ }
+ };
+
public static Predicate IS_ARTIFACT_TYPE = new Predicate() {
@Override
public boolean apply(String input) {
@@ -587,6 +625,27 @@ public final class CardType implements Comparable, CardTypeView {
return CardType.isACreatureType(input);
}
};
+
+ public static Predicate IS_ENCHANTMENT_TYPE = new Predicate() {
+ @Override
+ public boolean apply(String input) {
+ return CardType.isAnEnchantmentType(input);
+ }
+ };
+
+ public static Predicate IS_SPELL_TYPE = new Predicate() {
+ @Override
+ public boolean apply(String input) {
+ return CardType.isASpellType(input);
+ }
+ };
+
+ public static Predicate IS_WALKER_TYPE = new Predicate() {
+ @Override
+ public boolean apply(String input) {
+ return CardType.isAPlaneswalkerType(input);
+ }
+ };
}
@@ -656,7 +715,7 @@ public final class CardType implements Comparable, CardTypeView {
}
public static boolean isALandType(final String cardType) {
- return (Constant.LAND_TYPES.contains(cardType));
+ return Constant.LAND_TYPES.contains(cardType) || isABasicLandType(cardType);
}
public static boolean isAPlaneswalkerType(final String cardType) {
@@ -667,6 +726,13 @@ public final class CardType implements Comparable, CardTypeView {
return (Constant.BASIC_TYPES.contains(cardType));
}
+ public static boolean isAnEnchantmentType(final String cardType) {
+ return (Constant.ENCHANTMENT_TYPES.contains(cardType));
+ }
+
+ public static boolean isASpellType(final String cardType) {
+ return (Constant.SPELL_TYPES.contains(cardType));
+ }
/**
* If the input is a plural type, return the corresponding singular form.
diff --git a/forge-core/src/main/java/forge/deck/Deck.java b/forge-core/src/main/java/forge/deck/Deck.java
index 6f0be2bbd95..a4b43b51efd 100644
--- a/forge-core/src/main/java/forge/deck/Deck.java
+++ b/forge-core/src/main/java/forge/deck/Deck.java
@@ -291,17 +291,13 @@ public class Deck extends DeckBase implements Iterable.
*/
package forge.deck;
import forge.item.InventoryItem;
-
import java.io.Serializable;
-
public abstract class DeckBase implements Serializable, Comparable, InventoryItem {
private static final long serialVersionUID = -7538150536939660052L;
// gameType is from Constant.GameType, like GameType.Regular
@@ -59,7 +57,7 @@ public abstract class DeckBase implements Serializable, Comparable, In
/*
* (non-Javadoc)
- *
+ *
* @see java.lang.Object#hashCode()
*/
@Override
@@ -74,6 +72,7 @@ public abstract class DeckBase implements Serializable, Comparable, In
public String getDirectory() {
return directory;
}
+
public void setDirectory(String directory0) {
directory = directory0;
}
@@ -101,7 +100,7 @@ public abstract class DeckBase implements Serializable, Comparable, In
*
* getComment.
*
- *
+ *
* @return a {@link java.lang.String} object.
*/
public String getComment() {
@@ -149,5 +148,5 @@ public abstract class DeckBase implements Serializable, Comparable, In
public abstract boolean isEmpty();
- public abstract void importDeck(Deck deck);
+ public abstract Deck getHumanDeck();
}
diff --git a/forge-core/src/main/java/forge/deck/DeckFormat.java b/forge-core/src/main/java/forge/deck/DeckFormat.java
index b1494cbc322..131b4360e42 100644
--- a/forge-core/src/main/java/forge/deck/DeckFormat.java
+++ b/forge-core/src/main/java/forge/deck/DeckFormat.java
@@ -248,41 +248,33 @@ public enum DeckFormat {
return "too many commanders";
}
- // Bring values up to 100
- min++;
- max++;
-
byte cmdCI = 0;
- Boolean hasPartner = null;
for (PaperCard pc : commanders) {
- // For each commander decrement size by 1 (99 for 1, 98 for 2)
- min--;
- max--;
-
if (!isLegalCommander(pc.getRules())) {
return "has an illegal commander";
}
-
- if (hasPartner != null && !hasPartner) {
- return "has an illegal commander partnership";
- }
-
- boolean isPartner = false;
- for(String s : pc.getRules().getMainPart().getKeywords()) {
- if (s.equals("Partner")) {
- isPartner = true;
- break;
- }
- }
- if (hasPartner == null) {
- hasPartner = isPartner;
- } else if (!isPartner) {
- return "has an illegal commander partnership";
- }
-
cmdCI |= pc.getRules().getColorIdentity().getColor();
}
+ // special check for Partner
+ if (commanders.size() == 2) {
+ // two commander = 98 cards
+ min--;
+ max--;
+
+ PaperCard a = commanders.get(0);
+ PaperCard b = commanders.get(1);
+
+ if (a.getRules().hasKeyword("Partner") && b.getRules().hasKeyword("Partner")) {
+ // normal partner commander
+ } else if (a.getName().equals(b.getRules().getParterWith())
+ && b.getName().equals(a.getRules().getParterWith())) {
+ // paired partner commander
+ } else {
+ return "has an illegal commander partnership";
+ }
+ }
+
final List erroneousCI = new ArrayList();
Set basicLandNames = new HashSet<>();
@@ -521,6 +513,8 @@ public enum DeckFormat {
for (final PaperCard p : commanders) {
cmdCI |= p.getRules().getColorIdentity().getColor();
}
- return Predicates.compose(Predicates.or(CardRulesPredicates.hasColorIdentity(cmdCI), CardRulesPredicates.hasKeyword("Partner")), PaperCard.FN_GET_RULES);
+ // TODO : check commander what kind of Partner it needs
+ return Predicates.compose(Predicates.or(CardRulesPredicates.hasColorIdentity(cmdCI),
+ CardRulesPredicates.Presets.CAN_BE_PARTNER_COMMANDER), PaperCard.FN_GET_RULES);
}
}
diff --git a/forge-core/src/main/java/forge/deck/DeckGroup.java b/forge-core/src/main/java/forge/deck/DeckGroup.java
index 599449d69a2..1a48f019d25 100644
--- a/forge-core/src/main/java/forge/deck/DeckGroup.java
+++ b/forge-core/src/main/java/forge/deck/DeckGroup.java
@@ -18,9 +18,6 @@
package forge.deck;
import com.google.common.base.Function;
-import forge.StaticData;
-import forge.item.PaperCard;
-
import java.util.*;
/**
@@ -47,7 +44,8 @@ public class DeckGroup extends DeckBase {
*
* @return the human deck
*/
- public final Deck getHumanDeck() {
+ @Override
+ public Deck getHumanDeck() {
return humanDeck;
}
@@ -160,100 +158,4 @@ public class DeckGroup extends DeckBase {
public String getImageKey(boolean altState) {
return null;
}
-
-
- @Override
- public void importDeck(Deck deck) {
- CardPool draftedCards = this.getHumanDeck().getAllCardsInASinglePool(false);
-
- this.getHumanDeck().putSection(DeckSection.Main, new CardPool());
- this.getHumanDeck().putSection(DeckSection.Sideboard, new CardPool());
-
- HashMap countByName = getCountByName(deck);
-
- addFromDraftedCardPool(countByName, draftedCards);
- addBasicLands(deck, countByName, draftedCards);
- }
-
- private HashMap getCountByName(Deck deck) {
- HashMap result = new HashMap();
-
- for (Map.Entry entry: deck.getMain()) {
- PaperCard importedCard = entry.getKey();
-
- Integer previousCount = result.getOrDefault(importedCard.getName(), 0);
- int countToAdd = entry.getValue();
-
- result.put(importedCard.getName(), countToAdd + previousCount);
- }
-
- return result;
- }
-
- private void addFromDraftedCardPool(HashMap countByName, CardPool availableCards) {
- for (Map.Entry entry: availableCards) {
-
- PaperCard availableCard = entry.getKey();
- Integer availableCount = entry.getValue();
- int countToAdd = countByName.getOrDefault(availableCard.getName(), 0);
-
- if (availableCard.getRules().getType().isBasicLand()) {
- // basic lands are added regardless from drafted cards
- continue;
- }
-
- int countMain = Math.min(availableCount, countToAdd);
-
- if (countMain > 0) {
- this.getHumanDeck().getMain().add(availableCard, countMain);
- countByName.put(availableCard.getName(), countToAdd - countMain);
- }
-
- int countSideboard = availableCount - countMain;
-
- if (countSideboard > 0) {
- CardPool sideboard = this.getHumanDeck().getOrCreate(DeckSection.Sideboard);
- sideboard.add(availableCard, countSideboard);
- }
- }
- }
-
- private void addBasicLands(Deck deck, HashMap countByName, CardPool availableCards) {
- HashMap basicLandsByName = getBasicLandsByName(deck, countByName);
-
- Date dateWithAllCards = StaticData.instance().getEditions().getEarliestDateWithAllCards(availableCards);
- for (String cardName: countByName.keySet()) {
-
- PaperCard card = basicLandsByName.getOrDefault(cardName, null);
-
- if (card == null) {
- continue;
- }
-
- int countToAdd = countByName.get(cardName);
-
- card = StaticData.instance().getCardByEditionDate(card, dateWithAllCards);
- this.getHumanDeck().getMain().add(card.getName(), card.getEdition(), countToAdd);
- }
- }
-
- private HashMap getBasicLandsByName(Deck deck, HashMap countByName) {
- HashMap result = new HashMap();
-
- for (Map.Entry entry: deck.getMain()) {
- PaperCard card = entry.getKey();
-
- if (!card.getRules().getType().isBasicLand()) {
- continue;
- }
-
- if (result.containsKey(card.getName())) {
- continue;
- }
-
- result.put(card.getName(), card);
- }
-
- return result;
- }
}
diff --git a/forge-core/src/main/java/forge/deck/DeckSection.java b/forge-core/src/main/java/forge/deck/DeckSection.java
index 7974d1e1677..90d2b91d17e 100644
--- a/forge-core/src/main/java/forge/deck/DeckSection.java
+++ b/forge-core/src/main/java/forge/deck/DeckSection.java
@@ -21,9 +21,9 @@ public enum DeckSection {
return null;
}
- final String valToCompate = value.trim();
+ final String valToCompare = value.trim();
for (final DeckSection v : DeckSection.values()) {
- if (v.name().compareToIgnoreCase(valToCompate) == 0) {
+ if (v.name().compareToIgnoreCase(valToCompare) == 0) {
return v;
}
}
diff --git a/forge-core/src/main/java/forge/item/PaperToken.java b/forge-core/src/main/java/forge/item/PaperToken.java
index f958afdfac0..59e2e0015ae 100644
--- a/forge-core/src/main/java/forge/item/PaperToken.java
+++ b/forge-core/src/main/java/forge/item/PaperToken.java
@@ -137,6 +137,6 @@ public class PaperToken implements InventoryItemFromSet, IPaperCard {
@Override
public String getImageKey(boolean altState) {
- return ImageKeys.TOKEN_PREFIX + imageFileName;
+ return ImageKeys.TOKEN_PREFIX + imageFileName.replace(" ", "_");
}
}
diff --git a/forge-core/src/main/java/forge/token/TokenDb.java b/forge-core/src/main/java/forge/token/TokenDb.java
index d3fe2aac9ce..f4275e4c86b 100644
--- a/forge-core/src/main/java/forge/token/TokenDb.java
+++ b/forge-core/src/main/java/forge/token/TokenDb.java
@@ -39,13 +39,19 @@ public class TokenDb implements ITokenDatabase {
@Override
public PaperToken getToken(String tokenName, String edition) {
- try {
- PaperToken pt = new PaperToken(rulesByName.get(tokenName), editions.get(edition));
- // TODO Cache the token after it's referenced
- return pt;
- } catch(Exception e) {
- return null;
+ String fullName = String.format("%s_%s", tokenName, edition.toLowerCase());
+
+ if (!tokensByName.containsKey(fullName)) {
+ try {
+ PaperToken pt = new PaperToken(rulesByName.get(tokenName), editions.get(edition));
+ tokensByName.put(fullName, pt);
+ return pt;
+ } catch(Exception e) {
+ return null;
+ }
}
+
+ return tokensByName.get(fullName);
}
@Override
diff --git a/forge-game/pom.xml b/forge-game/pom.xml
index bdd76921006..363022a7855 100644
--- a/forge-game/pom.xml
+++ b/forge-game/pom.xml
@@ -6,7 +6,7 @@
forge
forge
- 1.6.11-SNAPSHOT
+ 1.6.16-SNAPSHOT
forge-game
@@ -30,5 +30,37 @@
test
jar
+
+ io.sentry
+ sentry-log4j
+ 1.7.5
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-checkstyle-plugin
+ 3.0.0
+
+
+ checkstyle-validation
+ validate
+
+ ../checkstyle.xml
+ true
+ UTF-8
+ true
+ true
+ true
+
+
+ check
+
+
+
+
+
+
diff --git a/forge-game/src/main/java/forge/game/CardTraitBase.java b/forge-game/src/main/java/forge/game/CardTraitBase.java
index d3ff7689545..b59c994a80c 100644
--- a/forge-game/src/main/java/forge/game/CardTraitBase.java
+++ b/forge-game/src/main/java/forge/game/CardTraitBase.java
@@ -314,7 +314,11 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView {
list.addAll(p.getCardsIn(presentZone));
}
}
-
+ if (presentPlayer.equals("Any")) {
+ for (final Player p : this.getHostCard().getController().getAllies()) {
+ list.addAll(p.getCardsIn(presentZone));
+ }
+ }
list = CardLists.getValidCards(list, sIsPresent.split(","), this.getHostCard().getController(), this.getHostCard(), null);
int right = 1;
diff --git a/forge-game/src/main/java/forge/game/ForgeScript.java b/forge-game/src/main/java/forge/game/ForgeScript.java
index aec9121c12d..59870a830de 100644
--- a/forge-game/src/main/java/forge/game/ForgeScript.java
+++ b/forge-game/src/main/java/forge/game/ForgeScript.java
@@ -168,6 +168,10 @@ public class ForgeScript {
if (!sa.isFlashBackAbility()) {
return false;
}
+ } else if (property.equals("Jumpstart")) {
+ if (!sa.isJumpstart()) {
+ return false;
+ }
} else if (property.equals("Kicked")) {
if (!sa.isKicked()) {
return false;
@@ -204,6 +208,12 @@ public class ForgeScript {
if (!found) {
return false;
}
+ } else if (property.equals("YouCtrl")) {
+ return sa.getActivatingPlayer().equals(sourceController);
+ } else if (sa.getHostCard() != null) {
+ if (!sa.getHostCard().hasProperty(property, sourceController, source, spellAbility)) {
+ return false;
+ }
}
return true;
diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java
index 3bf8a391842..2741c83f892 100644
--- a/forge-game/src/main/java/forge/game/GameAction.java
+++ b/forge-game/src/main/java/forge/game/GameAction.java
@@ -20,6 +20,7 @@ package forge.game;
import com.google.common.base.Predicate;
import com.google.common.collect.*;
import forge.GameCommand;
+import forge.StaticData;
import forge.card.CardStateName;
import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityUtils;
@@ -150,8 +151,9 @@ public class GameAction {
// Cards returned from exile face-down must be reset to their original state, otherwise
// all sort of funky shenanigans may happen later (e.g. their ETB replacement effects are set
// up on the wrong card state etc.).
- if (zoneTo.is(ZoneType.Hand) && zoneFrom.is(ZoneType.Exile) && c.isFaceDown()) {
+ if (c.isFaceDown() && (fromBattlefield || (toHand && zoneFrom.is(ZoneType.Exile)))) {
c.setState(CardStateName.Original, true);
+ c.runFaceupCommands();
}
// Clean up the temporary Dash SVar when the Dashed card leaves the battlefield
@@ -190,7 +192,7 @@ public class GameAction {
if (!c.isToken()) {
if (c.isCloned()) {
- c.switchStates(CardStateName.Cloner, CardStateName.Original, false);
+ c.switchStates(CardStateName.Original, CardStateName.Cloner, false);
c.setState(CardStateName.Original, false);
c.clearStates(CardStateName.Cloner, false);
if (c.isFlipCard()) {
@@ -250,11 +252,24 @@ public class GameAction {
Card noLandLKI = CardUtil.getLKICopy(c);
// this check needs to check if this card would be on the battlefield
noLandLKI.setLastKnownZone(zoneTo);
-
+
CardCollection preList = new CardCollection(noLandLKI);
checkStaticAbilities(false, Sets.newHashSet(noLandLKI), preList);
+ // fake etb counters thing, then if something changed,
+ // need to apply checkStaticAbilities again
+ if(!noLandLKI.isLand()) {
+ if (noLandLKI.putEtbCounters()) {
+ // counters are added need to check again
+ checkStaticAbilities(false, Sets.newHashSet(noLandLKI), preList);
+ }
+ }
+
if(noLandLKI.isLand()) {
+ // if it isn't on the Stack, it stays in that Zone
+ if (!c.getZone().is(ZoneType.Stack)) {
+ return c;
+ }
// if something would only be a land when entering the battlefield and not before
// put it into the graveyard instead
zoneTo = c.getOwner().getZone(ZoneType.Graveyard);
@@ -262,6 +277,9 @@ public class GameAction {
copied.setState(CardStateName.Original, false);
copied.setManifested(false);
copied.updateStateForView();
+
+ // not to battlefield anymore!
+ toBattlefield = false;
}
}
@@ -357,7 +375,10 @@ public class GameAction {
// do ETB counters after StaticAbilities check
if (!suppress) {
if (toBattlefield) {
- copied.putEtbCounters();
+ if (copied.putEtbCounters()) {
+ // if counter where put of card, call checkStaticAbilities again
+ checkStaticAbilities();
+ }
}
copied.clearEtbCounters();
}
@@ -367,6 +388,7 @@ public class GameAction {
final Map runParams = Maps.newHashMap();
runParams.put("Card", lastKnownInfo);
+ runParams.put("Cause", cause);
runParams.put("Origin", zoneFrom != null ? zoneFrom.getZoneType().name() : null);
runParams.put("Destination", zoneTo.getZoneType().name());
runParams.put("SpellAbilityStackInstance", game.stack.peek());
@@ -873,9 +895,13 @@ public class GameAction {
}
}
- if (runEvents) {
+ // preList means that this is run by a pre Check with LKI objects
+ // in that case Always trigger should not Run
+ if (preList.isEmpty()) {
final Map runParams = Maps.newHashMap();
game.getTriggerHandler().runTrigger(TriggerType.Always, runParams, false);
+
+ game.getTriggerHandler().runTrigger(TriggerType.Immediate, runParams, false);
}
// Update P/T and type in the view only once after all the cards have been processed, to avoid flickering
@@ -1562,6 +1588,53 @@ public class GameAction {
}
}
+ private void drawStartingHand(Player p1){
+
+ //check initial hand
+ List lib1 = Lists.newArrayList(p1.getZone(ZoneType.Library).getCards().threadSafeIterable());
+ List hand1 = lib1.subList(0,p1.getMaxHandSize());
+ System.out.println(hand1.toString());
+
+ //shuffle
+ List shuffledCards = Lists.newArrayList(p1.getZone(ZoneType.Library).getCards().threadSafeIterable());
+ Collections.shuffle(shuffledCards);
+
+ //check a second hand
+ List hand2 = shuffledCards.subList(0,p1.getMaxHandSize());
+ System.out.println(hand2.toString());
+
+ //choose better hand according to land count
+ float averageLandRatio = getLandRatio(lib1);
+ if(getHandScore(hand1, averageLandRatio)>getHandScore(hand2, averageLandRatio)){
+ p1.getZone(ZoneType.Library).setCards(shuffledCards);
+ }
+ p1.drawCards(p1.getMaxHandSize());
+ }
+
+ private float getLandRatio(List deck){
+ int landCount = 0;
+ for(Card c:deck){
+ if(c.isLand()){
+ landCount++;
+ }
+ }
+ if (landCount == 0 ){
+ return 0;
+ }
+ return Float.valueOf(landCount)/Float.valueOf(deck.size());
+ }
+
+ private float getHandScore(List hand, float landRatio){
+ int landCount = 0;
+ for(Card c:hand){
+ if(c.isLand()){
+ landCount++;
+ }
+ }
+ float averageCount = landRatio * hand.size();
+ return Math.abs(averageCount-landCount);
+ }
+
public void startGame(GameOutcome lastGameOutcome) {
startGame(lastGameOutcome, null);
}
@@ -1582,7 +1655,11 @@ public class GameAction {
game.setAge(GameStage.Mulligan);
for (final Player p1 : game.getPlayers()) {
- p1.drawCards(p1.getMaxHandSize());
+ if (StaticData.instance().getFilteredHandsEnabled() ) {
+ drawStartingHand(p1);
+ } else {
+ p1.drawCards(p1.getStartingHandSize());
+ }
// If pl has Backup Plan as a Conspiracy draw that many extra hands
@@ -1702,6 +1779,11 @@ public class GameAction {
boolean isMultiPlayer = game.getPlayers().size() > 2;
int mulliganDelta = isMultiPlayer ? 0 : 1;
+ // https://magic.wizards.com/en/articles/archive/feature/checking-brawl-2018-07-09
+ if (game.getRules().hasAppliedVariant(GameType.Brawl) && !isMultiPlayer){
+ mulliganDelta = 0;
+ }
+
boolean allKept;
do {
allKept = true;
@@ -1766,7 +1848,7 @@ public class GameAction {
//Vancouver Mulligan
for(Player p : whoCanMulligan) {
if (p.getStartingHandSize() > p.getZone(ZoneType.Hand).size()) {
- p.scry(1);
+ p.scry(1, null);
}
}
}
diff --git a/forge-game/src/main/java/forge/game/GameActionUtil.java b/forge-game/src/main/java/forge/game/GameActionUtil.java
index 2336b55b8d2..79824f924f7 100644
--- a/forge-game/src/main/java/forge/game/GameActionUtil.java
+++ b/forge-game/src/main/java/forge/game/GameActionUtil.java
@@ -109,7 +109,7 @@ public final class GameActionUtil {
final SpellAbility newSA = sa.copy(activator);
final SpellAbilityRestriction sar = newSA.getRestrictions();
if (o.isWithFlash()) {
- sar.setInstantSpeed(true);
+ sar.setInstantSpeed(true);
}
sar.setZone(null);
newSA.setMayPlay(o.getAbility());
@@ -174,7 +174,8 @@ public final class GameActionUtil {
}
if (sa.isCycling() && activator.hasKeyword("CyclingForZero")) {
- final SpellAbility newSA = sa.copyWithNoManaCost();
+ // set the cost to this directly to buypass non mana cost
+ final SpellAbility newSA = sa.copyWithDefinedCost("Discard<1/CARDNAME>");
newSA.setBasicSpell(false);
newSA.getMapParams().put("CostDesc", ManaCostParser.parse("0"));
// makes new SpellDescription
@@ -186,6 +187,15 @@ public final class GameActionUtil {
alternatives.add(newSA);
}
+ if (sa.hasParam("Equip") && activator.hasKeyword("EquipInstantSpeed")) {
+ final SpellAbility newSA = sa.copy(activator);
+ SpellAbilityRestriction sar = newSA.getRestrictions();
+ sar.setSorcerySpeed(false);
+ sar.setInstantSpeed(true);
+ newSA.setDescription(sa.getDescription() + " (you may activate any time you could cast an instant )");
+ alternatives.add(newSA);
+ }
+
for (final KeywordInterface inst : source.getKeywords()) {
final String keyword = inst.getOriginal();
if (sa.isSpell() && keyword.startsWith("Flashback")) {
@@ -201,20 +211,12 @@ public final class GameActionUtil {
flashback.getRestrictions().setZone(ZoneType.Graveyard);
// there is a flashback cost (and not the cards cost)
- if (!keyword.equals("Flashback")) {
- flashback.setPayCosts(new Cost(keyword.substring(10), false));
+ if (keyword.contains(":")) {
+ final String k[] = keyword.split(":");
+ flashback.setPayCosts(new Cost(k[1], false));
}
alternatives.add(flashback);
}
-
- if (sa.hasParam("Equip") && sa instanceof AbilityActivated && keyword.equals("EquipInstantSpeed")) {
- final SpellAbility newSA = sa.copy(activator);
- SpellAbilityRestriction sar = newSA.getRestrictions();
- sar.setSorcerySpeed(false);
- sar.setInstantSpeed(true);
- newSA.setDescription(sa.getDescription() + " (you may activate any time you could cast an instant )");
- alternatives.add(newSA);
- }
}
return alternatives;
}
@@ -259,6 +261,11 @@ public final class GameActionUtil {
final Cost cost = new Cost("Discard<1/Land>", false);
costs.add(new OptionalCostValue(OptionalCost.Retrace, cost));
}
+ } else if (keyword.equals("Jump-start")) {
+ if (source.getZone().is(ZoneType.Graveyard)) {
+ final Cost cost = new Cost("Discard<1/Card>", false);
+ costs.add(new OptionalCostValue(OptionalCost.Jumpstart, cost));
+ }
} else if (keyword.startsWith("MayFlashCost")) {
String[] k = keyword.split(":");
final Cost cost = new Cost(k[1], false);
@@ -286,6 +293,7 @@ public final class GameActionUtil {
result.addConspireInstance();
break;
case Retrace:
+ case Jumpstart:
result.getRestrictions().setZone(ZoneType.Graveyard);
break;
case Flash:
@@ -389,8 +397,8 @@ public final class GameActionUtil {
}
}
} else if (keyword.startsWith("Kicker")) {
- String[] sCosts = TextUtil.split(keyword.substring(6), ':');
- boolean generic = "Generic".equals(sCosts[sCosts.length - 1]);
+ String[] sCosts = TextUtil.split(keyword.substring(6), ':');
+ boolean generic = "Generic".equals(sCosts[sCosts.length - 1]);
// If this is a "generic kicker" (Undergrowth), ignore value for kicker creations
int numKickers = sCosts.length - (generic ? 1 : 0);
for (int i = 0; i < abilities.size(); i++) {
diff --git a/forge-game/src/main/java/forge/game/GameEntity.java b/forge-game/src/main/java/forge/game/GameEntity.java
index 61c5ad6457a..37a27344866 100644
--- a/forge-game/src/main/java/forge/game/GameEntity.java
+++ b/forge-game/src/main/java/forge/game/GameEntity.java
@@ -25,7 +25,9 @@ import forge.game.card.CounterType;
import forge.game.event.GameEventCardAttachment;
import forge.game.event.GameEventCardAttachment.AttachMethod;
import forge.game.keyword.Keyword;
+import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
+import forge.game.spellability.TargetRestrictions;
import forge.game.trigger.TriggerType;
import forge.util.collect.FCollection;
@@ -337,6 +339,17 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
}
}
+ public boolean canBeEnchantedBy(final Card aura) {
+ SpellAbility sa = aura.getFirstAttachSpell();
+ TargetRestrictions tgt = null;
+ if (sa != null) {
+ tgt = sa.getTargetRestrictions();
+ }
+
+ return !(hasProtectionFrom(aura)
+ || ((tgt != null) && !isValid(tgt.getValidTgts(), aura.getController(), aura, sa)));
+ }
+
public abstract boolean hasProtectionFrom(final Card source);
// Counters!
@@ -365,7 +378,7 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
abstract public void setCounters(final Map allCounters);
abstract public boolean canReceiveCounters(final CounterType type);
- abstract public void addCounter(final CounterType counterType, final int n, final Card source, final boolean applyMultiplier, final boolean fireEvents);
+ abstract public int addCounter(final CounterType counterType, final int n, final Player source, final boolean applyMultiplier, final boolean fireEvents);
abstract public void subtractCounter(final CounterType counterName, final int n);
abstract public void clearCounters();
diff --git a/forge-game/src/main/java/forge/game/GameLogFormatter.java b/forge-game/src/main/java/forge/game/GameLogFormatter.java
index 9756a7b79b6..6ad9bd1cf8f 100644
--- a/forge-game/src/main/java/forge/game/GameLogFormatter.java
+++ b/forge-game/src/main/java/forge/game/GameLogFormatter.java
@@ -24,6 +24,7 @@ import forge.game.event.GameEventPlayerPoisoned;
import forge.game.event.GameEventScry;
import forge.game.event.GameEventSpellAbilityCast;
import forge.game.event.GameEventSpellResolved;
+import forge.game.event.GameEventSurveil;
import forge.game.event.GameEventTurnBegan;
import forge.game.event.GameEventTurnPhase;
import forge.game.event.IGameEventVisitor;
@@ -65,7 +66,24 @@ public class GameLogFormatter extends IGameEventVisitor.Base {
return new GameLogEntry(GameLogEntryType.STACK_RESOLVE, scryOutcome);
}
-
+
+ @Override
+ public GameLogEntry visit(GameEventSurveil ev) {
+ String surveilOutcome = "";
+ String toLibrary = Lang.nounWithAmount(ev.toLibrary, "card") + " to the top of the library";
+ String toGraveyard = Lang.nounWithAmount(ev.toGraveyard, "card") + " to the graveyard";
+
+ if (ev.toLibrary > 0 && ev.toGraveyard > 0) {
+ surveilOutcome = ev.player.toString() + " surveiled " + toLibrary + " and " + toGraveyard;
+ } else if (ev.toGraveyard == 0) {
+ surveilOutcome = ev.player.toString() + " surveiled " + toLibrary;
+ } else {
+ surveilOutcome = ev.player.toString() + " surveiled " + toGraveyard;
+ }
+
+ return new GameLogEntry(GameLogEntryType.STACK_RESOLVE, surveilOutcome);
+ }
+
@Override
public GameLogEntry visit(GameEventSpellResolved ev) {
String messageForLog = ev.hasFizzled ? ev.spell.getHostCard().getName() + " ability fizzles." : ev.spell.getStackDescription();
diff --git a/forge-game/src/main/java/forge/game/StaticEffect.java b/forge-game/src/main/java/forge/game/StaticEffect.java
index 4462acce247..b2c925c748e 100644
--- a/forge-game/src/main/java/forge/game/StaticEffect.java
+++ b/forge-game/src/main/java/forge/game/StaticEffect.java
@@ -58,9 +58,6 @@ public class StaticEffect {
private String chosenType;
private Map mapParams = Maps.newTreeMap();
- // for P/T
- private final Map originalPT = Maps.newTreeMap();
-
// for types
private boolean overwriteTypes = false;
private boolean keepSupertype = false;
@@ -101,7 +98,6 @@ public class StaticEffect {
copy.xValueMap = this.xValueMap;
copy.chosenType = this.chosenType;
copy.mapParams = this.mapParams;
- map.fillKeyedMap(copy.originalPT, this.originalPT);
copy.overwriteTypes = this.overwriteTypes;
copy.keepSupertype = this.keepSupertype;
copy.removeSubTypes = this.removeSubTypes;
@@ -345,68 +341,6 @@ public class StaticEffect {
this.originalKeywords.clear();
}
- // original power/toughness
- /**
- *
- * addOriginalPT.
- *
- *
- * @param c
- * a {@link forge.game.card.Card} object.
- * @param power
- * a int.
- * @param toughness
- * a int.
- */
- public final void addOriginalPT(final Card c, final int power, final int toughness) {
- final String pt = power + "/" + toughness;
- if (!this.originalPT.containsKey(c)) {
- this.originalPT.put(c, pt);
- }
- }
-
- /**
- *
- * getOriginalPower.
- *
- *
- * @param c
- * a {@link forge.game.card.Card} object.
- * @return a int.
- */
- public final int getOriginalPower(final Card c) {
- int power = -1;
- if (this.originalPT.containsKey(c)) {
- power = Integer.parseInt(this.originalPT.get(c).split("/")[0]);
- }
- return power;
- }
-
- /**
- *
- * getOriginalToughness.
- *
- *
- * @param c
- * a {@link forge.game.card.Card} object.
- * @return a int.
- */
- public final int getOriginalToughness(final Card c) {
- int tough = -1;
- if (this.originalPT.containsKey(c)) {
- tough = Integer.parseInt(this.originalPT.get(c).split("/")[1]);
- }
- return tough;
- }
-
- /**
- *
- * clearAllOriginalPTs.
- *
- */
- public final void clearAllOriginalPTs() {
- this.originalPT.clear();
- }
// should we overwrite types?
/**
@@ -995,7 +929,7 @@ public class StaticEffect {
}
// remove set P/T
- if (!params.containsKey("CharacteristicDefining") && setPT) {
+ if (setPT) {
affectedCard.removeNewPT(getTimestamp());
}
@@ -1036,7 +970,7 @@ public class StaticEffect {
}
// remove abilities
- if (params.containsKey("RemoveAllAbilities")) {
+ if (params.containsKey("RemoveAllAbilities") || params.containsKey("RemoveIntrinsicAbilities")) {
affectedCard.unSuppressCardTraits();
}
diff --git a/forge-game/src/main/java/forge/game/ability/AbilityFactory.java b/forge-game/src/main/java/forge/game/ability/AbilityFactory.java
index e68d8fb454a..b068872a5b4 100644
--- a/forge-game/src/main/java/forge/game/ability/AbilityFactory.java
+++ b/forge-game/src/main/java/forge/game/ability/AbilityFactory.java
@@ -256,7 +256,7 @@ public final class AbilityFactory {
}
}
- if (api == ApiType.Charm || api == ApiType.GenericChoice) {
+ if (api == ApiType.Charm || api == ApiType.GenericChoice || api == ApiType.AssignGroup) {
final String key = "Choices";
if (mapParams.containsKey(key)) {
List names = Lists.newArrayList(mapParams.get(key).split(","));
diff --git a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java
index cb8ae2e50e4..3401a5d6a80 100644
--- a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java
+++ b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java
@@ -381,7 +381,7 @@ public class AbilityUtils {
svarval = ability.getSVar(amount);
}
if (StringUtils.isBlank(svarval)) {
- if ((ability != null) && (ability instanceof SpellAbility) && !(ability instanceof SpellPermanent)) {
+ if ((ability != null) && (ability instanceof SpellAbility) && !(ability instanceof SpellPermanent) && !amount.equals("ChosenX")) {
System.err.printf("SVar '%s' not found in ability, fallback to Card (%s). Ability is (%s)%n", amount, card.getName(), ability);
}
svarval = card.getSVar(amount);
@@ -439,6 +439,10 @@ public class AbilityUtils {
players.addAll(game.getPlayers());
val = CardFactoryUtil.playerXCount(players, calcX[1], card);
}
+ else if (hType.equals("YourTeam")) {
+ players.addAll(player.getYourTeam());
+ val = CardFactoryUtil.playerXCount(players, calcX[1], card);
+ }
else if (hType.equals("Opponents")) {
players.addAll(player.getOpponents());
val = CardFactoryUtil.playerXCount(players, calcX[1], card);
@@ -891,7 +895,11 @@ public class AbilityUtils {
final Player player = sa == null ? card.getController() : sa.getActivatingPlayer();
- if (defined.equals("Targeted") || defined.equals("TargetedPlayer")) {
+ if (defined.equals("TargetedOrController")) {
+ players.addAll(getDefinedPlayers(card, "Targeted", sa));
+ players.addAll(getDefinedPlayers(card, "TargetedController", sa));
+ }
+ else if (defined.equals("Targeted") || defined.equals("TargetedPlayer")) {
final SpellAbility saTargeting = sa.getSATargetingPlayer();
if (saTargeting != null) {
players.addAll(saTargeting.getTargets().getTargetPlayers());
@@ -1672,7 +1680,10 @@ public class AbilityUtils {
res.setZone(null);
newSA.setRestrictions(res);
// timing restrictions still apply
- if (res.checkTimingRestrictions(tgtCard, newSA) && newSA.checkOtherRestrictions()) {
+ if (res.checkTimingRestrictions(tgtCard, newSA)
+ // still need to check the other restrictions like Aftermath
+ && res.checkOtherRestrictions(tgtCard, newSA, controller)
+ && newSA.checkOtherRestrictions()) {
sas.add(newSA);
}
}
diff --git a/forge-game/src/main/java/forge/game/ability/ApiType.java b/forge-game/src/main/java/forge/game/ability/ApiType.java
index ad4f48c739c..a5d9572fde2 100644
--- a/forge-game/src/main/java/forge/game/ability/ApiType.java
+++ b/forge-game/src/main/java/forge/game/ability/ApiType.java
@@ -21,6 +21,7 @@ public enum ApiType {
AnimateAll (AnimateAllEffect.class),
Attach (AttachEffect.class),
Ascend (AscendEffect.class),
+ AssignGroup (AssignGroupEffect.class),
Balance (BalanceEffect.class),
BecomeMonarch (BecomeMonarchEffect.class),
BecomesBlocked (BecomesBlockedEffect.class),
@@ -81,6 +82,7 @@ public enum ApiType {
GenericChoice (ChooseGenericEffect.class),
Goad (GoadEffect.class),
Haunt (HauntEffect.class),
+ ImmediateTrigger (ImmediateTriggerEffect.class),
LookAt (LookAtEffect.class),
LoseLife (LifeLoseEffect.class),
LosesGame (GameLossEffect.class),
@@ -142,6 +144,7 @@ public enum ApiType {
SkipTurn (SkipTurnEffect.class),
StoreSVar (StoreSVarEffect.class),
StoreMap (StoreMapEffect.class),
+ Surveil (SurveilEffect.class),
Tap (TapEffect.class),
TapAll (TapAllEffect.class),
TapOrUntap (TapOrUntapEffect.class),
diff --git a/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java b/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java
index 6670b9bab6d..cff15f16e83 100644
--- a/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java
@@ -10,6 +10,7 @@ import org.apache.commons.lang3.StringUtils;
import com.google.common.collect.Lists;
+import forge.GameCommand;
import forge.card.CardType;
import forge.game.Game;
import forge.game.GameObject;
@@ -385,4 +386,83 @@ public abstract class SpellAbilityEffect {
return eff;
}
+
+ protected static void replaceDying(final SpellAbility sa) {
+ if (sa.hasParam("ReplaceDyingDefined") || sa.hasParam("ReplaceDyingValid")) {
+
+ if (sa.hasParam("ReplaceDyingCondition")) {
+ // currently there is only one with Kicker
+ final String condition = sa.getParam("ReplaceDyingCondition");
+ if ("Kicked".equals(condition)) {
+ if (!sa.isKicked()) {
+ return;
+ }
+ }
+ }
+
+ final Card host = sa.getHostCard();
+ final Player controller = sa.getActivatingPlayer();
+ final Game game = host.getGame();
+ String zone = sa.getParamOrDefault("ReplaceDyingZone", "Exile");
+
+ CardCollection cards = null;
+
+ if (sa.hasParam("ReplaceDyingDefined")) {
+ cards = AbilityUtils.getDefinedCards(host, sa.getParam("ReplaceDyingDefined"), sa);
+ // no cards, no need for Effect
+ if (cards.isEmpty()) {
+ return;
+ }
+ }
+
+ // build an Effect with that infomation
+ String name = host.getName() + "'s Effect";
+
+ final Card eff = createEffect(host, controller, name, host.getImageKey());
+ if (cards != null) {
+ eff.addRemembered(cards);
+ }
+
+ String valid = sa.getParamOrDefault("ReplaceDyingValid", "Card.IsRemembered");
+
+ String repeffstr = "Event$ Moved | ValidCard$ " + valid +
+ "| Origin$ Battlefield | Destination$ Graveyard " +
+ "| Description$ If the creature would die this turn, exile it instead.";
+ String effect = "DB$ ChangeZone | Defined$ ReplacedCard | Origin$ Battlefield | Destination$ " + zone;
+
+ ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, eff, true);
+ re.setLayer(ReplacementLayer.Other);
+
+ re.setOverridingAbility(AbilityFactory.getAbility(effect, eff));
+ eff.addReplacementEffect(re);
+
+ if (cards != null) {
+ // Add forgot trigger
+ addForgetOnMovedTrigger(eff, "Battlefield");
+ }
+
+ // Copy text changes
+ if (sa.isIntrinsic()) {
+ eff.copyChangedTextFrom(host);
+ }
+
+ final GameCommand endEffect = new GameCommand() {
+ private static final long serialVersionUID = -5861759814760561373L;
+
+ @Override
+ public void run() {
+ game.getAction().exile(eff, null);
+ }
+ };
+
+ game.getEndOfTurn().addUntil(endEffect);
+
+ eff.updateStateForView();
+
+ // TODO: Add targeting to the effect so it knows who it's dealing with
+ game.getTriggerHandler().suppressMode(TriggerType.ChangesZone);
+ game.getAction().moveTo(ZoneType.Command, eff, sa);
+ game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone);
+ }
+ }
}
diff --git a/forge-game/src/main/java/forge/game/ability/effects/ActivateAbilityEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ActivateAbilityEffect.java
index fc0aae36d17..3114cf48501 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/ActivateAbilityEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/ActivateAbilityEffect.java
@@ -12,6 +12,7 @@ import forge.util.Lang;
import org.apache.commons.lang3.StringUtils;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import java.util.List;
@@ -49,7 +50,8 @@ public class ActivateAbilityEffect extends SpellAbilityEffect {
if (possibleAb.isEmpty()) {
continue;
}
- SpellAbility manaAb = p.getController().chooseSingleSpellForEffect(possibleAb, sa, "Choose a mana ability:");
+ SpellAbility manaAb = p.getController().chooseSingleSpellForEffect(
+ possibleAb, sa, "Choose a mana ability:", ImmutableMap.of());
p.getController().playChosenSpellAbility(manaAb);
}
}
diff --git a/forge-game/src/main/java/forge/game/ability/effects/AnimateAllEffect.java b/forge-game/src/main/java/forge/game/ability/effects/AnimateAllEffect.java
index 17b5a336eb1..0f3219c7e55 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/AnimateAllEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/AnimateAllEffect.java
@@ -25,6 +25,8 @@ import java.util.Arrays;
import java.util.List;
import java.util.Map;
+import com.google.common.collect.ImmutableList;
+
public class AnimateAllEffect extends AnimateEffectBase {
@Override
@@ -144,6 +146,9 @@ public class AnimateAllEffect extends AnimateEffectBase {
list = CardLists.getValidCards(list, valid.split(","), host.getController(), host, sa);
+ boolean removeAll = sa.hasParam("RemoveAllAbilities");
+ boolean removeIntrinsic = sa.hasParam("RemoveIntrinsicAbilities");
+
for (final Card c : list) {
doAnimate(c, sa, power, toughness, types, removeTypes, finalDesc,
keywords, removeKeywords, hiddenKeywords, timestamp);
@@ -161,11 +166,14 @@ public class AnimateAllEffect extends AnimateEffectBase {
// remove abilities
final List removedAbilities = new ArrayList();
- if (sa.hasParam("OverwriteAbilities") || sa.hasParam("RemoveAllAbilities")) {
+ if (sa.hasParam("OverwriteAbilities") || removeAll || removeIntrinsic) {
for (final SpellAbility ab : c.getSpellAbilities()) {
if (ab.isAbility()) {
- c.removeSpellAbility(ab);
- removedAbilities.add(ab);
+ if (removeAll
+ || (ab.isIntrinsic() && removeIntrinsic && !ab.isBasicLandAbility())) {
+ ab.setTemporarilySuppressed(true);
+ removedAbilities.add(ab);
+ }
}
}
}
@@ -190,19 +198,24 @@ public class AnimateAllEffect extends AnimateEffectBase {
// suppress triggers from the animated card
final List removedTriggers = new ArrayList();
- if (sa.hasParam("OverwriteTriggers") || sa.hasParam("RemoveAllAbilities")) {
+ if (sa.hasParam("OverwriteTriggers") || removeAll || removeIntrinsic) {
final FCollectionView triggersToRemove = c.getTriggers();
for (final Trigger trigger : triggersToRemove) {
- trigger.setSuppressed(true);
+ if (removeIntrinsic && !trigger.isIntrinsic()) {
+ continue;
+ }
+ trigger.setSuppressed(true); // why this not TemporarilySuppressed?
removedTriggers.add(trigger);
}
}
// suppress static abilities from the animated card
final List removedStatics = new ArrayList();
- if (sa.hasParam("OverwriteStatics") || sa.hasParam("RemoveAllAbilities")) {
- final FCollectionView staticsToRemove = c.getStaticAbilities();
- for (final StaticAbility stAb : staticsToRemove) {
+ if (sa.hasParam("OverwriteStatics") || removeAll || removeIntrinsic) {
+ for (final StaticAbility stAb : c.getStaticAbilities()) {
+ if (removeIntrinsic && !stAb.isIntrinsic()) {
+ continue;
+ }
stAb.setTemporarilySuppressed(true);
removedStatics.add(stAb);
}
@@ -210,9 +223,11 @@ public class AnimateAllEffect extends AnimateEffectBase {
// suppress static abilities from the animated card
final List removedReplacements = new ArrayList();
- if (sa.hasParam("OverwriteReplacements") || sa.hasParam("RemoveAllAbilities")) {
- final FCollectionView replacementsToRemove = c.getReplacementEffects();
- for (final ReplacementEffect re : replacementsToRemove) {
+ if (sa.hasParam("OverwriteReplacements") || removeAll || removeIntrinsic) {
+ for (final ReplacementEffect re : c.getReplacementEffects()) {
+ if (removeIntrinsic && !re.isIntrinsic()) {
+ continue;
+ }
re.setTemporarilySuppressed(true);
removedReplacements.add(re);
}
@@ -234,8 +249,11 @@ public class AnimateAllEffect extends AnimateEffectBase {
public void run() {
doUnanimate(c, sa, finalDesc, hiddenKeywords,
addedAbilities, addedTriggers, addedReplacements,
- false, removedAbilities, timestamp);
+ ImmutableList.of(), timestamp);
+ for (final SpellAbility sa : removedAbilities) {
+ sa.setTemporarilySuppressed(false);
+ }
// give back suppressed triggers
for (final Trigger t : removedTriggers) {
t.setSuppressed(false);
diff --git a/forge-game/src/main/java/forge/game/ability/effects/AnimateEffect.java b/forge-game/src/main/java/forge/game/ability/effects/AnimateEffect.java
index e2de4258f82..59c9f4a5a93 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/AnimateEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/AnimateEffect.java
@@ -18,7 +18,6 @@ import forge.game.spellability.SpellAbility;
import forge.game.staticability.StaticAbility;
import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerHandler;
-import forge.util.collect.FCollectionView;
import java.util.Arrays;
import java.util.List;
@@ -162,21 +161,20 @@ public class AnimateEffect extends AnimateEffectBase {
boolean clearAbilities = sa.hasParam("OverwriteAbilities");
boolean clearSpells = sa.hasParam("OverwriteSpells");
boolean removeAll = sa.hasParam("RemoveAllAbilities");
+ boolean removeIntrinsic = sa.hasParam("RemoveIntrinsicAbilities");
if (clearAbilities || clearSpells || removeAll) {
for (final SpellAbility ab : c.getSpellAbilities()) {
- if (removeAll || (ab.isAbility() && clearAbilities)
+ if (removeAll
+ || (ab.isIntrinsic() && removeIntrinsic && !ab.isBasicLandAbility())
+ || (ab.isAbility() && clearAbilities)
|| (ab.isSpell() && clearSpells)) {
+ ab.setTemporarilySuppressed(true);
removedAbilities.add(ab);
}
}
}
- // Can't rmeove SAs in foreach loop that finds them
- for (final SpellAbility ab : removedAbilities) {
- c.removeSpellAbility(ab);
- }
-
if (sa.hasParam("RemoveThisAbility") && !removedAbilities.contains(sa)) {
c.removeSpellAbility(sa);
removedAbilities.add(sa);
@@ -215,20 +213,23 @@ public class AnimateEffect extends AnimateEffectBase {
// suppress triggers from the animated card
final List removedTriggers = Lists.newArrayList();
- if (sa.hasParam("OverwriteTriggers") || removeAll) {
- final FCollectionView triggersToRemove = c.getTriggers();
- for (final Trigger trigger : triggersToRemove) {
- trigger.setSuppressed(true);
+ if (sa.hasParam("OverwriteTriggers") || removeAll || removeIntrinsic) {
+ for (final Trigger trigger : c.getTriggers()) {
+ if (removeIntrinsic && !trigger.isIntrinsic()) {
+ continue;
+ }
+ trigger.setSuppressed(true); // why this not TemporarilySuppressed?
removedTriggers.add(trigger);
}
}
// give static abilities (should only be used by cards to give
// itself a static ability)
+ final List addedStaticAbilities = Lists.newArrayList();
if (stAbs.size() > 0) {
for (final String s : stAbs) {
final String actualAbility = source.getSVar(s);
- c.addStaticAbility(actualAbility);
+ addedStaticAbilities.add(c.addStaticAbility(actualAbility));
}
}
@@ -248,9 +249,11 @@ public class AnimateEffect extends AnimateEffectBase {
// suppress static abilities from the animated card
final List removedStatics = Lists.newArrayList();
- if (sa.hasParam("OverwriteStatics") || removeAll) {
- final FCollectionView staticsToRemove = c.getStaticAbilities();
- for (final StaticAbility stAb : staticsToRemove) {
+ if (sa.hasParam("OverwriteStatics") || removeAll || removeIntrinsic) {
+ for (final StaticAbility stAb : c.getStaticAbilities()) {
+ if (removeIntrinsic && !stAb.isIntrinsic()) {
+ continue;
+ }
stAb.setTemporarilySuppressed(true);
removedStatics.add(stAb);
}
@@ -258,8 +261,11 @@ public class AnimateEffect extends AnimateEffectBase {
// suppress static abilities from the animated card
final List removedReplacements = Lists.newArrayList();
- if (sa.hasParam("OverwriteReplacements") || removeAll) {
+ if (sa.hasParam("OverwriteReplacements") || removeAll || removeIntrinsic) {
for (final ReplacementEffect re : c.getReplacementEffects()) {
+ if (removeIntrinsic && !re.isIntrinsic()) {
+ continue;
+ }
re.setTemporarilySuppressed(true);
removedReplacements.add(re);
}
@@ -272,8 +278,6 @@ public class AnimateEffect extends AnimateEffectBase {
}
}
- final boolean givesStAbs = (stAbs.size() > 0);
-
final GameCommand unanimate = new GameCommand() {
private static final long serialVersionUID = -5861759814760561373L;
@@ -281,9 +285,13 @@ public class AnimateEffect extends AnimateEffectBase {
public void run() {
doUnanimate(c, sa, finalDesc, hiddenKeywords,
addedAbilities, addedTriggers, addedReplacements,
- givesStAbs, removedAbilities, timestamp);
+ addedStaticAbilities, timestamp);
game.fireEvent(new GameEventCardStatsChanged(c));
+
+ for (final SpellAbility sa : removedAbilities) {
+ sa.setTemporarilySuppressed(false);
+ }
// give back suppressed triggers
for (final Trigger t : removedTriggers) {
t.setSuppressed(false);
@@ -458,6 +466,8 @@ public class AnimateEffect extends AnimateEffectBase {
sb.append(" until ").append(host).append(" leaves the battlefield.");
} else if (sa.hasParam("UntilYourNextUpkeep")) {
sb.append(" until your next upkeep.");
+ } else if (sa.hasParam("UntilYourNextTurn")) {
+ sb.append(" until your next turn.");
} else if (sa.hasParam("UntilControllerNextUntap")) {
sb.append(" until its controller's next untap step.");
} else {
diff --git a/forge-game/src/main/java/forge/game/ability/effects/AnimateEffectBase.java b/forge-game/src/main/java/forge/game/ability/effects/AnimateEffectBase.java
index 627e5e6e84b..e6f814bb780 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/AnimateEffectBase.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/AnimateEffectBase.java
@@ -24,11 +24,10 @@ import forge.game.replacement.ReplacementEffect;
import forge.game.spellability.SpellAbility;
import forge.game.staticability.StaticAbility;
import forge.game.trigger.Trigger;
-import java.util.ArrayList;
import java.util.List;
public abstract class AnimateEffectBase extends SpellAbilityEffect {
- void doAnimate(final Card c, final SpellAbility sa, final Integer power, final Integer toughness,
+ public static void doAnimate(final Card c, final SpellAbility sa, final Integer power, final Integer toughness,
final CardType addType, final CardType removeType, final String colors,
final List keywords, final List removeKeywords,
final List hiddenKeywords, final long timestamp) {
@@ -36,15 +35,19 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
boolean removeSuperTypes = false;
boolean removeCardTypes = false;
boolean removeSubTypes = false;
+ boolean removeLandTypes = false;
boolean removeCreatureTypes = false;
boolean removeArtifactTypes = false;
+ boolean removeEnchantmentTypes = false;
if (sa.hasParam("OverwriteTypes")) {
removeSuperTypes = true;
removeCardTypes = true;
removeSubTypes = true;
+ removeLandTypes = true;
removeCreatureTypes = true;
removeArtifactTypes = true;
+ removeEnchantmentTypes = true;
}
if (sa.hasParam("KeepSupertypes")) {
@@ -57,6 +60,10 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
if (sa.hasParam("KeepSubtypes")) {
removeSubTypes = false;
+ removeLandTypes = false;
+ removeCreatureTypes = false;
+ removeArtifactTypes = false;
+ removeEnchantmentTypes = false;
}
if (sa.hasParam("RemoveSuperTypes")) {
@@ -71,23 +78,30 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
removeSubTypes = true;
}
+ if (sa.hasParam("RemoveLandTypes")) {
+ removeCreatureTypes = true;
+ }
if (sa.hasParam("RemoveCreatureTypes")) {
removeCreatureTypes = true;
}
-
if (sa.hasParam("RemoveArtifactTypes")) {
removeArtifactTypes = true;
}
+ if (sa.hasParam("RemoveEnchantmentTypes")) {
+ removeEnchantmentTypes = true;
+ }
+
if ((power != null) || (toughness != null)) {
c.addNewPT(power, toughness, timestamp);
}
if (!addType.isEmpty() || !removeType.isEmpty() || removeCreatureTypes) {
c.addChangedCardTypes(addType, removeType, removeSuperTypes, removeCardTypes, removeSubTypes,
- removeCreatureTypes, removeArtifactTypes, timestamp);
+ removeLandTypes, removeCreatureTypes, removeArtifactTypes, removeEnchantmentTypes, timestamp);
}
- c.addChangedCardKeywords(keywords, removeKeywords, sa.hasParam("RemoveAllAbilities"), timestamp);
+ c.addChangedCardKeywords(keywords, removeKeywords,
+ sa.hasParam("RemoveAllAbilities"), sa.hasParam("RemoveIntrinsicAbilities"), timestamp);
for (final String k : hiddenKeywords) {
c.addHiddenExtrinsicKeyword(k);
@@ -114,10 +128,10 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
* @param timestamp
* a long.
*/
- void doUnanimate(final Card c, SpellAbility sa, final String colorDesc,
+ static void doUnanimate(final Card c, SpellAbility sa, final String colorDesc,
final List hiddenKeywords, final List addedAbilities,
final List addedTriggers, final List addedReplacements,
- final boolean givesStAbs, final List removedAbilities, final long timestamp) {
+ final List addedStaticAbilities, final long timestamp) {
if (sa.hasParam("LastsIndefinitely")) {
return;
@@ -127,16 +141,7 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
c.removeChangedCardKeywords(timestamp);
- // remove all static abilities
- if (givesStAbs) {
- c.setStaticAbilities(new ArrayList());
- }
-
- if (sa.hasParam("Types") || sa.hasParam("RemoveTypes")
- || sa.hasParam("RemoveCreatureTypes") || sa.hasParam("RemoveArtifactTypes")) {
- c.removeChangedCardTypes(timestamp);
- }
-
+ c.removeChangedCardTypes(timestamp);
c.removeColor(timestamp);
for (final String k : hiddenKeywords) {
@@ -147,10 +152,6 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
c.removeSpellAbility(saAdd);
}
- for (final SpellAbility saRem : removedAbilities) {
- c.addSpellAbility(saRem);
- }
-
for (final Trigger t : addedTriggers) {
c.removeTrigger(t);
}
@@ -159,6 +160,10 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
c.removeReplacementEffect(rep);
}
+ for (final StaticAbility stAb : addedStaticAbilities) {
+ c.removeStaticAbility(stAb);
+ }
+
// any other unanimate cleanup
if (!c.isCreature()) {
c.unEquipAllCards();
diff --git a/forge-game/src/main/java/forge/game/ability/effects/AssignGroupEffect.java b/forge-game/src/main/java/forge/game/ability/effects/AssignGroupEffect.java
new file mode 100644
index 00000000000..eb53cecf76b
--- /dev/null
+++ b/forge-game/src/main/java/forge/game/ability/effects/AssignGroupEffect.java
@@ -0,0 +1,74 @@
+package forge.game.ability.effects;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+
+import forge.game.Game;
+import forge.game.GameObject;
+import forge.game.ability.AbilityUtils;
+import forge.game.ability.SpellAbilityEffect;
+import forge.game.card.Card;
+import forge.game.player.Player;
+import forge.game.spellability.SpellAbility;
+
+public class AssignGroupEffect extends SpellAbilityEffect {
+
+ /* (non-Javadoc)
+ * @see forge.game.ability.SpellAbilityEffect#getStackDescription(forge.game.spellability.SpellAbility)
+ */
+ @Override
+ protected String getStackDescription(SpellAbility sa) {
+ return sa.getDescription();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see forge.game.ability.SpellAbilityEffect#resolve(forge.game.spellability.SpellAbility)
+ */
+ @Override
+ public void resolve(SpellAbility sa) {
+ final Card host = sa.getHostCard();
+ final Game game = host.getGame();
+
+ List defined = getDefinedOrTargeted(sa, "Defined");
+
+ final List abilities = Lists.newArrayList(sa.getAdditionalAbilityList("Choices"));
+
+ Player chooser = sa.getActivatingPlayer();
+ if (sa.hasParam("Chooser")) {
+ final String choose = sa.getParam("Chooser");
+ chooser = AbilityUtils.getDefinedPlayers(sa.getHostCard(), choose, sa).get(0);
+ }
+
+ Multimap result = ArrayListMultimap.create();
+
+ for (GameObject g : defined) {
+ final String title = "Choose ability for " + g.toString();
+ Map params = Maps.newHashMap();
+ params.put("Affected", g);
+
+ result.put(chooser.getController().chooseSingleSpellForEffect(abilities, sa, title, params), g);
+ }
+
+ // in order of choice list
+ for (SpellAbility s : abilities) {
+ // is that in Player order?
+ Collection l = result.get(s);
+
+ host.addRemembered(l);
+ AbilityUtils.resolve(s);
+ host.removeRemembered(l);
+
+ // this will refresh continuous abilities for players and permanents.
+ game.getAction().checkStaticAbilities();
+ game.getTriggerHandler().resetActiveTriggers(false);
+ }
+ }
+
+}
diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneAllEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneAllEffect.java
index 57c6a945fdf..4ed23fa7392 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneAllEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneAllEffect.java
@@ -156,7 +156,7 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect {
// Auras without Candidates stay in their current location
if (c.isAura()) {
final SpellAbility saAura = c.getFirstAttachSpell();
- if (!saAura.getTargetRestrictions().hasCandidates(saAura, false)) {
+ if (saAura != null && !saAura.getTargetRestrictions().hasCandidates(saAura, false)) {
continue;
}
}
diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java
index df665e7a7cb..749b72a277e 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java
@@ -6,7 +6,9 @@ import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
+import forge.GameCommand;
import forge.card.CardStateName;
+import forge.card.CardType;
import forge.game.Game;
import forge.game.GameEntity;
import forge.game.GameObject;
@@ -32,6 +34,7 @@ import forge.util.collect.*;
import forge.util.Lang;
import forge.util.MessageUtil;
+import java.util.Arrays;
import java.util.List;
import java.util.Map;
@@ -484,7 +487,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
}
if (sa.hasParam("WithCounters")) {
String[] parse = sa.getParam("WithCounters").split("_");
- tgtC.addEtbCounter(CounterType.getType(parse[0]), Integer.parseInt(parse[1]), hostCard);
+ tgtC.addEtbCounter(CounterType.getType(parse[0]), Integer.parseInt(parse[1]), player);
}
if (sa.hasParam("GainControl")) {
if (sa.hasParam("NewController")) {
@@ -552,9 +555,11 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
// location
if (tgtC.isAura()) {
final SpellAbility saAura = tgtC.getFirstAttachSpell();
- saAura.setActivatingPlayer(sa.getActivatingPlayer());
- if (!saAura.getTargetRestrictions().hasCandidates(saAura, false)) {
- continue;
+ if (saAura != null) {
+ saAura.setActivatingPlayer(sa.getActivatingPlayer());
+ if (!saAura.getTargetRestrictions().hasCandidates(saAura, false)) {
+ continue;
+ }
}
}
@@ -865,7 +870,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
final boolean champion = sa.hasParam("Champion");
final boolean forget = sa.hasParam("ForgetChanged");
final boolean imprint = sa.hasParam("Imprint");
- final String selectPrompt = sa.hasParam("SelectPrompt") ? sa.getParam("SelectPrompt") : MessageUtil.formatMessage("Select a card from {player's} " + Lang.joinHomogenous(origin).toLowerCase(), decider, player);
+ String selectPrompt = sa.hasParam("SelectPrompt") ? sa.getParam("SelectPrompt") : MessageUtil.formatMessage("Select a card from {player's} " + Lang.joinHomogenous(origin).toLowerCase(), decider, player);
final String totalcmc = sa.getParam("WithTotalCMC");
int totcmc = AbilityUtils.calculateAmount(source, totalcmc, sa);
@@ -874,7 +879,20 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
CardCollection chosenCards = new CardCollection();
// only multi-select if player can select more than one
if (changeNum > 1 && allowMultiSelect(decider, sa)) {
- for (Card card : decider.getController().chooseCardsForZoneChange(destination, origin, sa, fetchList, delayedReveal, selectPrompt, decider)) {
+ List selectedCards;
+ if (! sa.hasParam("SelectPrompt")) {
+ // new default messaging for multi select
+ if (fetchList.size() > changeNum) {
+ selectPrompt = MessageUtil.formatMessage("Select up to " + changeNum + " cards from {player's} " + Lang.joinHomogenous(origin).toLowerCase(), decider, player);
+ } else {
+ selectPrompt = MessageUtil.formatMessage("Select cards from {player's} " + Lang.joinHomogenous(origin).toLowerCase(), decider, player);
+ }
+ }
+ // ensure that selection is within maximum allowed changeNum
+ do {
+ selectedCards = decider.getController().chooseCardsForZoneChange(destination, origin, sa, fetchList, delayedReveal, selectPrompt, decider);
+ } while (selectedCards != null && selectedCards.size() > changeNum);
+ for (Card card : selectedCards) {
chosenCards.add(card);
};
// maybe prompt the user if they selected fewer than the maximum possible?
@@ -1101,11 +1119,49 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
}
}
}
+ // need to be facedown before it hits the battlefield in case of Replacement Effects or Trigger
+ if (sa.hasParam("FaceDown") && ZoneType.Battlefield.equals(destination)) {
+ c.setState(CardStateName.FaceDown, true);
+
+ // set New Pt doesn't work because this values need to be copyable for clone effects
+ if (sa.hasParam("FaceDownPower") || sa.hasParam("FaceDownToughness")) {
+ if (sa.hasParam("FaceDownPower")) {
+ c.setBasePower(AbilityUtils.calculateAmount(
+ source, sa.getParam("FaceDownPower"), sa));
+ }
+ if (sa.hasParam("FaceDownToughness")) {
+ c.setBaseToughness(AbilityUtils.calculateAmount(
+ source, sa.getParam("FaceDownToughness"), sa));
+ }
+ }
+
+ if (sa.hasParam("FaceDownAddType")) {
+ CardType t = new CardType(c.getCurrentState().getType());
+ t.addAll(Arrays.asList(sa.getParam("FaceDownAddType").split(",")));
+ c.getCurrentState().setType(t);
+ }
+
+ if (sa.hasParam("FaceDownPower") || sa.hasParam("FaceDownToughness")
+ || sa.hasParam("FaceDownAddType")) {
+ final GameCommand unanimate = new GameCommand() {
+ private static final long serialVersionUID = 8853789549297846163L;
+
+ @Override
+ public void run() {
+ c.clearStates(CardStateName.FaceDown, true);
+ }
+ };
+
+ c.addFaceupCommand(unanimate);
+ }
+ }
movedCard = game.getAction().moveTo(c.getController().getZone(destination), c, sa, null);
if (sa.hasParam("Tapped")) {
movedCard.setTapped(true);
}
- if (sa.hasParam("FaceDown")) {
+
+ // need to do that again?
+ if (sa.hasParam("FaceDown") && !ZoneType.Battlefield.equals(destination)) {
movedCard.setState(CardStateName.FaceDown, true);
}
movedCard.setTimestamp(ts);
@@ -1181,7 +1237,6 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
&& !sa.hasParam("DifferentNames")
&& !sa.hasParam("DifferentCMC")
&& !sa.hasParam("AtRandom")
- && !sa.hasParam("ChangeNum") // TODO: doesn't work with card number limits, e.g. Doomsday
&& (!sa.hasParam("Defined") || sa.hasParam("ChooseFromDefined"))
&& sa.getParam("WithTotalCMC") == null;
}
diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChooseCardNameEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChooseCardNameEffect.java
index 7e35b96c0ca..4938ecb370e 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/ChooseCardNameEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/ChooseCardNameEffect.java
@@ -105,17 +105,8 @@ public class ChooseCardNameEffect extends SpellAbilityEffect {
final String message = validDesc.equals("card") ? "Name a card" : "Name a " + validDesc + " card.";
Predicate cpp = Predicates.alwaysTrue();
- if ( StringUtils.containsIgnoreCase(valid, "nonland") ) {
- cpp = CardFacePredicates.Presets.IS_NON_LAND;
- }
- if ( StringUtils.containsIgnoreCase(valid, "nonbasic") ) {
- cpp = Predicates.not(CardFacePredicates.Presets.IS_BASIC_LAND);
- }
-
- if ( StringUtils.containsIgnoreCase(valid, "noncreature") ) {
- cpp = Predicates.not(CardFacePredicates.Presets.IS_CREATURE);
- } else if ( StringUtils.containsIgnoreCase(valid, "creature") ) {
- cpp = CardFacePredicates.Presets.IS_CREATURE;
+ if (sa.hasParam("ValidCards")) {
+ cpp = CardFacePredicates.valid(valid);
}
chosen = p.getController().chooseCardName(sa, cpp, valid, message);
diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChooseGenericEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChooseGenericEffect.java
index 7cb835542b6..23ab1ad6b46 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/ChooseGenericEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/ChooseGenericEffect.java
@@ -1,5 +1,6 @@
package forge.game.ability.effects;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
@@ -65,7 +66,8 @@ public class ChooseGenericEffect extends SpellAbilityEffect {
int idxChosen = MyRandom.getRandom().nextInt(abilities.size());
chosenSA = abilities.get(idxChosen);
} else {
- chosenSA = p.getController().chooseSingleSpellForEffect(abilities, sa, "Choose one");
+ chosenSA = p.getController().chooseSingleSpellForEffect(abilities, sa, "Choose one",
+ ImmutableMap.of());
}
if (chosenSA != null) {
diff --git a/forge-game/src/main/java/forge/game/ability/effects/CloneEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CloneEffect.java
index 977db72b19d..aa0ab0f4215 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/CloneEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/CloneEffect.java
@@ -64,11 +64,15 @@ public class CloneEffect extends SpellAbilityEffect {
Card cardToCopy = null;
if (sa.hasParam("Choices")) {
- CardCollectionView choices = game.getCardsIn(ZoneType.Battlefield);
+ ZoneType choiceZone = ZoneType.Battlefield;
+ if (sa.hasParam("ChoiceZone")) {
+ choiceZone = ZoneType.smartValueOf(sa.getParam("ChoiceZone"));
+ }
+ CardCollectionView choices = game.getCardsIn(choiceZone);
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), activator, host);
String title = sa.hasParam("ChoiceTitle") ? sa.getParam("ChoiceTitle") : "Choose a card ";
- cardToCopy = activator.getController().chooseSingleEntityForEffect(choices, sa, title, true);
+ cardToCopy = activator.getController().chooseSingleEntityForEffect(choices, sa, title, false);
} else if (sa.hasParam("Defined")) {
List cloneSources = AbilityUtils.getDefinedCards(host, sa.getParam("Defined"), sa);
if (!cloneSources.isEmpty()) {
@@ -213,6 +217,9 @@ public class CloneEffect extends SpellAbilityEffect {
else if (duration.equals("UntilYourNextTurn")) {
game.getCleanup().addUntil(host.getController(), unclone);
}
+ else if (duration.equals("UntilUnattached")) {
+ sa.getHostCard().addUnattachCommand(unclone);
+ }
}
game.fireEvent(new GameEventCardStatsChanged(tgtCard));
} // cloneResolve
diff --git a/forge-game/src/main/java/forge/game/ability/effects/CopyPermanentEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CopyPermanentEffect.java
index 72678bb72a6..2c026736c05 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/CopyPermanentEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/CopyPermanentEffect.java
@@ -5,7 +5,6 @@ import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
import forge.ImageKeys;
import forge.StaticData;
@@ -22,6 +21,7 @@ import forge.game.card.CardCollectionView;
import forge.game.card.CardFactory;
import forge.game.card.CardFactoryUtil;
import forge.game.card.CardLists;
+import forge.game.card.token.TokenInfo;
import forge.game.combat.Combat;
import forge.game.event.GameEventCombatChanged;
import forge.game.player.Player;
@@ -37,10 +37,10 @@ import forge.util.collect.FCollectionView;
import forge.util.PredicateString.StringOp;
import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.tuple.Pair;
import java.util.Arrays;
import java.util.List;
-import java.util.Map;
public class CopyPermanentEffect extends SpellAbilityEffect {
@@ -167,12 +167,18 @@ public class CopyPermanentEffect extends SpellAbilityEffect {
}
}
} else if (sa.hasParam("Choices")) {
+ Player chooser = activator;
+ if (sa.hasParam("Chooser")) {
+ final String choose = sa.getParam("Chooser");
+ chooser = AbilityUtils.getDefinedPlayers(sa.getHostCard(), choose, sa).get(0);
+ }
+
CardCollectionView choices = game.getCardsIn(ZoneType.Battlefield);
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), activator, host);
if (!choices.isEmpty()) {
String title = sa.hasParam("ChoiceTitle") ? sa.getParam("ChoiceTitle") : "Choose a card ";
- Card choosen = activator.getController().chooseSingleEntityForEffect(choices, sa, title, false);
+ Card choosen = chooser.getController().chooseSingleEntityForEffect(choices, sa, title, false);
if (choosen != null) {
tgtCards.add(choosen);
@@ -186,29 +192,17 @@ public class CopyPermanentEffect extends SpellAbilityEffect {
for (final Card c : tgtCards) {
if (!sa.usesTargeting() || c.canBeTargetedBy(sa)) {
- int multiplier = numCopies;
-
- final Map repParams = Maps.newHashMap();
- repParams.put("Event", "CreateToken");
- repParams.put("Affected", controller);
- repParams.put("TokenNum", multiplier);
- repParams.put("EffectOnly", true);
+ Pair result = TokenInfo.calculateMultiplier(
+ game, controller, true, numCopies);
- switch (game.getReplacementHandler().run(repParams)) {
- case NotReplaced:
- break;
- case Updated: {
- multiplier = (int) repParams.get("TokenNum");
- break;
- }
- default:
- return ;
+ if (result.getRight() <= 0) {
+ return;
}
- final List crds = Lists.newArrayListWithCapacity(multiplier);
+ final List crds = Lists.newArrayListWithCapacity(result.getRight());
- for (int i = 0; i < multiplier; i++) {
- final Card copy = CardFactory.copyCopiableCharacteristics(c, activator);
+ for (int i = 0; i < result.getRight(); i++) {
+ final Card copy = CardFactory.copyCopiableCharacteristics(c, result.getLeft());
copy.setToken(true);
copy.setCopiedPermanent(c);
// add keywords from sa
@@ -337,7 +331,7 @@ public class CopyPermanentEffect extends SpellAbilityEffect {
}
}
// set the controller before move to play: Crafty Cutpurse
- copy.setController(controller, 0);
+ copy.setController(result.getLeft(), 0);
copy.updateStateForView();
// Temporarily register triggers of an object created with CopyPermanent
@@ -350,7 +344,7 @@ public class CopyPermanentEffect extends SpellAbilityEffect {
copyInPlay.setCloneOrigin(host);
sa.getHostCard().addClone(copyInPlay);
if (!pumpKeywords.isEmpty()) {
- copyInPlay.addChangedCardKeywords(pumpKeywords, Lists.newArrayList(), false, timestamp);
+ copyInPlay.addChangedCardKeywords(pumpKeywords, Lists.newArrayList(), false, false, timestamp);
}
crds.add(copyInPlay);
if (sa.hasParam("RememberCopied")) {
diff --git a/forge-game/src/main/java/forge/game/ability/effects/CopySpellAbilityEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CopySpellAbilityEffect.java
index aa4d49c6080..116b5aae57b 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/CopySpellAbilityEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/CopySpellAbilityEffect.java
@@ -1,5 +1,6 @@
package forge.game.ability.effects;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
@@ -83,8 +84,9 @@ public class CopySpellAbilityEffect extends SpellAbilityEffect {
for (int multi = 0; multi < spellCount && !tgtSpells.isEmpty(); multi++) {
String prompt = "Select " + Lang.getOrdinal(multi + 1) + " spell to copy to stack";
- SpellAbility chosen = controller.getController().chooseSingleSpellForEffect(tgtSpells, sa, prompt);
- SpellAbility copiedSpell = CardFactory.copySpellAbilityAndSrcCard(card, chosen.getHostCard(), chosen, true);
+ SpellAbility chosen = controller.getController().chooseSingleSpellForEffect(tgtSpells, sa, prompt,
+ ImmutableMap.of());
+ SpellAbility copiedSpell = CardFactory.copySpellAbilityAndPossiblyHost(card, chosen.getHostCard(), chosen, true);
copiedSpell.getHostCard().setController(card.getController(), card.getGame().getNextTimestamp());
copiedSpell.setActivatingPlayer(controller);
copies.add(copiedSpell);
@@ -92,7 +94,8 @@ public class CopySpellAbilityEffect extends SpellAbilityEffect {
}
}
else if (sa.hasParam("CopyForEachCanTarget")) {
- SpellAbility chosenSA = controller.getController().chooseSingleSpellForEffect(tgtSpells, sa, "Select a spell to copy");
+ SpellAbility chosenSA = controller.getController().chooseSingleSpellForEffect(tgtSpells, sa,
+ "Select a spell to copy", ImmutableMap.of());
chosenSA.setActivatingPlayer(controller);
// Find subability or rootability that has targets
SpellAbility targetedSA = chosenSA;
@@ -114,7 +117,7 @@ public class CopySpellAbilityEffect extends SpellAbilityEffect {
mayChooseNewTargets = false;
for (GameEntity o : candidates) {
- SpellAbility copy = CardFactory.copySpellAbilityAndSrcCard(card, chosenSA.getHostCard(), chosenSA, true);
+ SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(card, chosenSA.getHostCard(), chosenSA, true);
resetFirstTargetOnCopy(copy, o, targetedSA);
copies.add(copy);
}
@@ -140,22 +143,23 @@ public class CopySpellAbilityEffect extends SpellAbilityEffect {
valid.remove(originalTarget);
mayChooseNewTargets = false;
for (final Card c : valid) {
- SpellAbility copy = CardFactory.copySpellAbilityAndSrcCard(card, chosenSA.getHostCard(), chosenSA, true);
+ SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(card, chosenSA.getHostCard(), chosenSA, true);
resetFirstTargetOnCopy(copy, c, targetedSA);
copies.add(copy);
}
for (final Player p : players) {
- SpellAbility copy = CardFactory.copySpellAbilityAndSrcCard(card, chosenSA.getHostCard(), chosenSA, true);
+ SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(card, chosenSA.getHostCard(), chosenSA, true);
resetFirstTargetOnCopy(copy, p, targetedSA);
copies.add(copy);
}
}
}
else {
- SpellAbility chosenSA = controller.getController().chooseSingleSpellForEffect(tgtSpells, sa, "Select a spell to copy");
+ SpellAbility chosenSA = controller.getController().chooseSingleSpellForEffect(tgtSpells, sa,
+ "Select a spell to copy", ImmutableMap.of());
chosenSA.setActivatingPlayer(controller);
for (int i = 0; i < amount; i++) {
- copies.add(CardFactory.copySpellAbilityAndSrcCard(card, chosenSA.getHostCard(), chosenSA, true));
+ copies.add(CardFactory.copySpellAbilityAndPossiblyHost(card, chosenSA.getHostCard(), chosenSA, true));
}
}
diff --git a/forge-game/src/main/java/forge/game/ability/effects/CountersMoveEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CountersMoveEffect.java
index 5c876c6720b..2522abf8853 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/CountersMoveEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/CountersMoveEffect.java
@@ -100,6 +100,7 @@ public class CountersMoveEffect extends SpellAbilityEffect {
// Test to see if the card we're trying to add is in the expected state
return;
}
+ dest = cur;
int csum = 0;
@@ -145,7 +146,7 @@ public class CountersMoveEffect extends SpellAbilityEffect {
}
if (csum > 0) {
- dest.addCounter(cType, csum, host, true);
+ dest.addCounter(cType, csum, player, true);
game.updateLastStateForCard(dest);
}
return;
@@ -194,15 +195,15 @@ public class CountersMoveEffect extends SpellAbilityEffect {
Map params = Maps.newHashMap();
params.put("CounterType", cType);
params.put("Source", source);
- params.put("Target", dest);
+ params.put("Target", cur);
StringBuilder sb = new StringBuilder();
- sb.append("Put how many ").append(cType.getName()).append(" counters on ").append(dest).append("?");
+ sb.append("Put how many ").append(cType.getName()).append(" counters on ").append(cur).append("?");
int cnum = player.getController().chooseNumber(sa, sb.toString(), 0, source.getCounters(cType), params);
if (cnum > 0) {
source.subtractCounter(cType, cnum);
- dest.addCounter(cType, cnum, host, true);
- game.updateLastStateForCard(dest);
+ cur.addCounter(cType, cnum, player, true);
+ game.updateLastStateForCard(cur);
updateSource = true;
}
}
@@ -245,7 +246,7 @@ public class CountersMoveEffect extends SpellAbilityEffect {
}
if (!"Any".matches(counterName)) {
- if (!dest.canReceiveCounters(cType)) {
+ if (!cur.canReceiveCounters(cType)) {
continue;
}
@@ -253,7 +254,7 @@ public class CountersMoveEffect extends SpellAbilityEffect {
Map params = Maps.newHashMap();
params.put("CounterType", cType);
params.put("Source", source);
- params.put("Target", dest);
+ params.put("Target", cur);
StringBuilder sb = new StringBuilder();
sb.append("Take how many ").append(cType.getName());
sb.append(" counters from ").append(source).append("?");
@@ -262,8 +263,8 @@ public class CountersMoveEffect extends SpellAbilityEffect {
if (source.getCounters(cType) >= cntToMove) {
source.subtractCounter(cType, cntToMove);
- dest.addCounter(cType, cntToMove, host, true);
- game.updateLastStateForCard(dest);
+ cur.addCounter(cType, cntToMove, player, true);
+ game.updateLastStateForCard(cur);
}
} else {
// any counterType currently only Leech Bonder
@@ -296,7 +297,7 @@ public class CountersMoveEffect extends SpellAbilityEffect {
sa, sb.toString(), 0, Math.min(tgtCounters.get(chosenType), cntToMove), params);
if (chosenAmount > 0) {
- dest.addCounter(chosenType, chosenAmount, host, true);
+ dest.addCounter(chosenType, chosenAmount, player, true);
source.subtractCounter(chosenType, chosenAmount);
game.updateLastStateForCard(dest);
cntToMove -= chosenAmount;
diff --git a/forge-game/src/main/java/forge/game/ability/effects/CountersMultiplyEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CountersMultiplyEffect.java
index 08117188072..8eaced12614 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/CountersMultiplyEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/CountersMultiplyEffect.java
@@ -7,6 +7,7 @@ import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.card.CounterType;
+import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.util.Lang;
@@ -37,6 +38,7 @@ public class CountersMultiplyEffect extends SpellAbilityEffect {
public void resolve(SpellAbility sa) {
final Card host = sa.getHostCard();
final Game game = host.getGame();
+ final Player player = sa.getActivatingPlayer();
final CounterType counterType = getCounterType(sa);
final int n = Integer.valueOf(sa.getParamOrDefault("Multiplier", "2")) - 1;
@@ -50,10 +52,10 @@ public class CountersMultiplyEffect extends SpellAbilityEffect {
continue;
}
if (counterType != null) {
- gameCard.addCounter(counterType, gameCard.getCounters(counterType) * n, host, true);
+ gameCard.addCounter(counterType, gameCard.getCounters(counterType) * n, player, true);
} else {
for (Map.Entry e : gameCard.getCounters().entrySet()) {
- gameCard.addCounter(e.getKey(), e.getValue() * n, host, true);
+ gameCard.addCounter(e.getKey(), e.getValue() * n, player, true);
}
}
game.updateLastStateForCard(gameCard);
diff --git a/forge-game/src/main/java/forge/game/ability/effects/CountersNoteEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CountersNoteEffect.java
index b5190983232..21a7f4c9d2c 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/CountersNoteEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/CountersNoteEffect.java
@@ -6,6 +6,7 @@ import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CounterType;
+import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
public class CountersNoteEffect extends SpellAbilityEffect {
@@ -26,7 +27,7 @@ public class CountersNoteEffect extends SpellAbilityEffect {
if (mode.equals(MODE_STORE)) {
noteCounters(c, source);
} else if (mode.equals(MODE_LOAD)) {
- loadCounters(c, source);
+ loadCounters(c, source, sa.getActivatingPlayer());
}
}
}
@@ -39,11 +40,11 @@ public class CountersNoteEffect extends SpellAbilityEffect {
}
}
- private void loadCounters(Card notee, Card source) {
+ private void loadCounters(Card notee, Card source, final Player p) {
for(Entry svar : source.getSVars().entrySet()) {
String key = svar.getKey();
if (key.startsWith(NOTE_COUNTERS)) {
- notee.addCounter(CounterType.getType(key.substring(NOTE_COUNTERS.length())), Integer.parseInt(svar.getValue()), source, false);
+ notee.addCounter(CounterType.getType(key.substring(NOTE_COUNTERS.length())), Integer.parseInt(svar.getValue()), p, false);
}
// TODO Probably should "remove" the svars that were temporarily used
}
diff --git a/forge-game/src/main/java/forge/game/ability/effects/CountersProliferateEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CountersProliferateEffect.java
index 4b317322118..f5ef5b95ed8 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/CountersProliferateEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/CountersProliferateEffect.java
@@ -24,6 +24,7 @@ public class CountersProliferateEffect extends SpellAbilityEffect {
@Override
public void resolve(SpellAbility sa) {
+ final Player p = sa.getActivatingPlayer();
final Card host = sa.getHostCard();
final Game game = host.getGame();
Player controller = host.getController();
@@ -32,10 +33,10 @@ public class CountersProliferateEffect extends SpellAbilityEffect {
return;
for(Entry ge: proliferateChoice.entrySet()) {
if( ge.getKey() instanceof Player )
- ((Player) ge.getKey()).addCounter(ge.getValue(), 1, host, true);
+ ((Player) ge.getKey()).addCounter(ge.getValue(), 1, p, true);
else if( ge.getKey() instanceof Card) {
Card c = (Card) ge.getKey();
- c.addCounter(ge.getValue(), 1, host, true);
+ c.addCounter(ge.getValue(), 1, p, true);
game.updateLastStateForCard(c);
}
}
diff --git a/forge-game/src/main/java/forge/game/ability/effects/CountersPutAllEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CountersPutAllEffect.java
index 29843622384..cf27b138ff8 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/CountersPutAllEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/CountersPutAllEffect.java
@@ -38,30 +38,37 @@ public class CountersPutAllEffect extends SpellAbilityEffect {
@Override
public void resolve(SpellAbility sa) {
final Card host = sa.getHostCard();
+ final Player activator = sa.getActivatingPlayer();
final String type = sa.getParam("CounterType");
final int counterAmount = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("CounterNum"), sa);
final String valid = sa.getParam("ValidCards");
final ZoneType zone = sa.hasParam("ValidZone") ? ZoneType.smartValueOf(sa.getParam("ValidZone")) : ZoneType.Battlefield;
- final Game game = sa.getActivatingPlayer().getGame();
+ final Game game = activator.getGame();
if (counterAmount <= 0) {
return;
}
CardCollectionView cards = game.getCardsIn(zone);
- cards = CardLists.getValidCards(cards, valid, sa.getHostCard().getController(), sa.getHostCard());
+ cards = CardLists.getValidCards(cards, valid, host.getController(), sa.getHostCard());
if (sa.usesTargeting()) {
final Player pl = sa.getTargets().getFirstTargetedPlayer();
cards = CardLists.filterControlledBy(cards, pl);
}
+ Player placer = activator;
+ if (sa.hasParam("Placer")) {
+ final String pstr = sa.getParam("Placer");
+ placer = AbilityUtils.getDefinedPlayers(host, pstr, sa).get(0);
+ }
+
for (final Card tgtCard : cards) {
if (game.getZoneOf(tgtCard).is(ZoneType.Battlefield)) {
- tgtCard.addCounter(CounterType.valueOf(type), counterAmount, host, true);
+ tgtCard.addCounter(CounterType.valueOf(type), counterAmount, placer, true);
} else {
// adding counters to something like re-suspend cards
- tgtCard.addCounter(CounterType.valueOf(type), counterAmount, host, false);
+ tgtCard.addCounter(CounterType.valueOf(type), counterAmount, placer, false);
}
game.updateLastStateForCard(tgtCard);
}
diff --git a/forge-game/src/main/java/forge/game/ability/effects/CountersPutEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CountersPutEffect.java
index d58b3ab209c..61eb5e73201 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/CountersPutEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/CountersPutEffect.java
@@ -37,7 +37,7 @@ public class CountersPutEffect extends SpellAbilityEffect {
final boolean dividedAsYouChoose = sa.hasParam("DividedAsYouChoose");
- final int amount = AbilityUtils.calculateAmount(card, sa.getParam("CounterNum"), sa);
+ final int amount = AbilityUtils.calculateAmount(card, sa.getParamOrDefault("CounterNum", "1"), sa);
if (sa.hasParam("Bolster")) {
sb.append("Bolster ").append(amount);
return sb.toString();
@@ -110,6 +110,12 @@ public class CountersPutEffect extends SpellAbilityEffect {
}
}
+ Player placer = activator;
+ if (sa.hasParam("Placer")) {
+ final String pstr = sa.getParam("Placer");
+ placer = AbilityUtils.getDefinedPlayers(sa.getHostCard(), pstr, sa).get(0);
+ }
+
final boolean etbcounter = sa.hasParam("ETB");
final boolean remember = sa.hasParam("RememberCounters");
final boolean rememberCards = sa.hasParam("RememberCards");
@@ -129,9 +135,10 @@ public class CountersPutEffect extends SpellAbilityEffect {
for (final GameObject obj : tgtObjects) {
// check if the object is still in game or if it was moved
+ Card gameCard = null;
if (obj instanceof Card) {
Card tgtCard = (Card) obj;
- Card gameCard = game.getCardState(tgtCard, null);
+ gameCard = game.getCardState(tgtCard, null);
// gameCard is LKI in that case, the card is not in game anymore
// or the timestamp did change
// this should check Self too
@@ -155,10 +162,10 @@ public class CountersPutEffect extends SpellAbilityEffect {
if (eachExistingCounter) {
for(CounterType ct : choices) {
if (obj instanceof Player) {
- ((Player) obj).addCounter(ct, counterAmount, card, true);
+ ((Player) obj).addCounter(ct, counterAmount, placer, true);
}
if (obj instanceof Card) {
- ((Card) obj).addCounter(ct, counterAmount, card, true);
+ gameCard.addCounter(ct, counterAmount, placer, true);
}
}
continue;
@@ -179,7 +186,7 @@ public class CountersPutEffect extends SpellAbilityEffect {
}
if (obj instanceof Card) {
- Card tgtCard = (Card) obj;
+ Card tgtCard = gameCard;
counterAmount = sa.usesTargeting() && sa.hasParam("DividedAsYouChoose") ? sa.getTargetRestrictions().getDividedValue(tgtCard) : counterAmount;
if (!sa.usesTargeting() || tgtCard.canBeTargetedBy(sa)) {
if (max != -1) {
@@ -232,9 +239,9 @@ public class CountersPutEffect extends SpellAbilityEffect {
final Zone zone = tgtCard.getGame().getZoneOf(tgtCard);
if (zone == null || zone.is(ZoneType.Battlefield) || zone.is(ZoneType.Stack)) {
if (etbcounter) {
- tgtCard.addEtbCounter(counterType, counterAmount, card);
+ tgtCard.addEtbCounter(counterType, counterAmount, placer);
} else {
- tgtCard.addCounter(counterType, counterAmount, card, true);
+ tgtCard.addCounter(counterType, counterAmount, placer, true);
}
if (remember) {
final int value = tgtCard.getTotalCountersToAdd();
@@ -263,9 +270,9 @@ public class CountersPutEffect extends SpellAbilityEffect {
// adding counters to something like re-suspend cards
// etbcounter should apply multiplier
if (etbcounter) {
- tgtCard.addEtbCounter(counterType, counterAmount, card);
+ tgtCard.addEtbCounter(counterType, counterAmount, placer);
} else {
- tgtCard.addCounter(counterType, counterAmount, card, false);
+ tgtCard.addCounter(counterType, counterAmount, placer, false);
}
}
game.updateLastStateForCard(tgtCard);
@@ -273,7 +280,7 @@ public class CountersPutEffect extends SpellAbilityEffect {
} else if (obj instanceof Player) {
// Add Counters to players!
Player pl = (Player) obj;
- pl.addCounter(counterType, counterAmount, card, true);
+ pl.addCounter(counterType, counterAmount, placer, true);
}
}
}
diff --git a/forge-game/src/main/java/forge/game/ability/effects/CountersPutOrRemoveEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CountersPutOrRemoveEffect.java
index 9273c1ce2c6..8a45a41f78d 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/CountersPutOrRemoveEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/CountersPutOrRemoveEffect.java
@@ -5,6 +5,7 @@ import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.card.CounterType;
+import forge.game.player.Player;
import forge.game.player.PlayerController;
import forge.game.player.PlayerController.BinaryChoiceType;
import forge.game.spellability.SpellAbility;
@@ -64,16 +65,16 @@ public class CountersPutOrRemoveEffect extends SpellAbilityEffect {
if (gameCard == null || !tgtCard.equalsWithTimestamp(gameCard)) {
continue;
}
- if (!sa.usesTargeting() || tgtCard.canBeTargetedBy(sa)) {
- if (tgtCard.hasCounters()) {
+ if (!sa.usesTargeting() || gameCard.canBeTargetedBy(sa)) {
+ if (gameCard.hasCounters()) {
if (sa.hasParam("EachExistingCounter")) {
- for (CounterType listType : Lists.newArrayList(tgtCard.getCounters().keySet())) {
- addOrRemoveCounter(sa, tgtCard, listType, counterAmount);
+ for (CounterType listType : Lists.newArrayList(gameCard.getCounters().keySet())) {
+ addOrRemoveCounter(sa, gameCard, listType, counterAmount);
}
} else {
- addOrRemoveCounter(sa, tgtCard, ctype, counterAmount);
+ addOrRemoveCounter(sa, gameCard, ctype, counterAmount);
}
- game.updateLastStateForCard(tgtCard);
+ game.updateLastStateForCard(gameCard);
}
}
}
@@ -81,8 +82,8 @@ public class CountersPutOrRemoveEffect extends SpellAbilityEffect {
private void addOrRemoveCounter(final SpellAbility sa, final Card tgtCard, CounterType ctype,
final int counterAmount) {
- PlayerController pc = sa.getActivatingPlayer().getController();
- final Card source = sa.getHostCard();
+ final Player pl = sa.getActivatingPlayer();
+ final PlayerController pc = pl.getController();
Map params = Maps.newHashMap();
params.put("Target", tgtCard);
@@ -105,7 +106,7 @@ public class CountersPutOrRemoveEffect extends SpellAbilityEffect {
boolean apply = zone == null || zone.is(ZoneType.Battlefield) || zone.is(ZoneType.Stack);
- tgtCard.addCounter(chosenType, counterAmount, source, apply);
+ tgtCard.addCounter(chosenType, counterAmount, pl, apply);
} else {
tgtCard.subtractCounter(chosenType, counterAmount);
}
diff --git a/forge-game/src/main/java/forge/game/ability/effects/CountersRemoveEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CountersRemoveEffect.java
index 1b6f684af87..4d73671c8ef 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/CountersRemoveEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/CountersRemoveEffect.java
@@ -115,27 +115,27 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
if (gameCard == null || !tgtCard.equalsWithTimestamp(gameCard)) {
continue;
}
- if (!sa.usesTargeting() || tgtCard.canBeTargetedBy(sa)) {
+ if (!sa.usesTargeting() || gameCard.canBeTargetedBy(sa)) {
final Zone zone = game.getZoneOf(gameCard);
if (type.equals("All")) {
- for (Map.Entry e : tgtCard.getCounters().entrySet()) {
- tgtCard.subtractCounter(e.getKey(), e.getValue());
+ for (Map.Entry e : gameCard.getCounters().entrySet()) {
+ gameCard.subtractCounter(e.getKey(), e.getValue());
}
- game.updateLastStateForCard(tgtCard);
+ game.updateLastStateForCard(gameCard);
continue;
} else if (num.equals("All")) {
- cntToRemove = tgtCard.getCounters(counterType);
+ cntToRemove = gameCard.getCounters(counterType);
} else if (sa.getParam("CounterNum").equals("Remembered")) {
- cntToRemove = tgtCard.getCountersAddedBy(card, counterType);
+ cntToRemove = gameCard.getCountersAddedBy(card, counterType);
}
PlayerController pc = sa.getActivatingPlayer().getController();
if (type.equals("Any")) {
- while (cntToRemove > 0 && tgtCard.hasCounters()) {
- final Map tgtCounters = tgtCard.getCounters();
+ while (cntToRemove > 0 && gameCard.hasCounters()) {
+ final Map tgtCounters = gameCard.getCounters();
Map params = Maps.newHashMap();
- params.put("Target", tgtCard);
+ params.put("Target", gameCard);
String prompt = "Select type of counters to remove";
CounterType chosenType = pc.chooseCounterType(
@@ -143,13 +143,13 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
prompt = "Select the number of " + chosenType.getName() + " counters to remove";
int max = Math.min(cntToRemove, tgtCounters.get(chosenType));
params = Maps.newHashMap();
- params.put("Target", tgtCard);
+ params.put("Target", gameCard);
params.put("CounterType", chosenType);
int chosenAmount = pc.chooseNumber(sa, prompt, 1, max, params);
if (chosenAmount > 0) {
- tgtCard.subtractCounter(chosenType, chosenAmount);
- game.updateLastStateForCard(tgtCard);
+ gameCard.subtractCounter(chosenType, chosenAmount);
+ game.updateLastStateForCard(gameCard);
if (rememberRemoved) {
for (int i = 0; i < chosenAmount; i++) {
card.addRemembered(Pair.of(chosenType, i));
@@ -159,12 +159,12 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
}
}
} else {
- cntToRemove = Math.min(cntToRemove, tgtCard.getCounters(counterType));
+ cntToRemove = Math.min(cntToRemove, gameCard.getCounters(counterType));
if (zone.is(ZoneType.Battlefield) || zone.is(ZoneType.Exile)) {
if (sa.hasParam("UpTo")) {
Map params = Maps.newHashMap();
- params.put("Target", tgtCard);
+ params.put("Target", gameCard);
params.put("CounterType", type);
String title = "Select the number of " + type + " counters to remove";
cntToRemove = pc.chooseNumber(sa, title, 0, cntToRemove, params);
@@ -172,13 +172,13 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
}
if (cntToRemove > 0) {
- tgtCard.subtractCounter(counterType, cntToRemove);
+ gameCard.subtractCounter(counterType, cntToRemove);
if (rememberRemoved) {
for (int i = 0; i < cntToRemove; i++) {
card.addRemembered(Pair.of(counterType, i));
}
}
- game.updateLastStateForCard(tgtCard);
+ game.updateLastStateForCard(gameCard);
}
}
}
diff --git a/forge-game/src/main/java/forge/game/ability/effects/DamageBaseEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DamageBaseEffect.java
index 2f17ffbb8b6..95b5092d6cd 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/DamageBaseEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/DamageBaseEffect.java
@@ -1,87 +1,7 @@
package forge.game.ability.effects;
-import forge.GameCommand;
-import forge.game.Game;
-import forge.game.ability.AbilityFactory;
-import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
-import forge.game.card.Card;
-import forge.game.card.CardCollection;
-import forge.game.player.Player;
-import forge.game.replacement.ReplacementEffect;
-import forge.game.replacement.ReplacementHandler;
-import forge.game.replacement.ReplacementLayer;
-import forge.game.spellability.SpellAbility;
-import forge.game.trigger.TriggerType;
-import forge.game.zone.ZoneType;
abstract public class DamageBaseEffect extends SpellAbilityEffect {
- static void replaceDying(final SpellAbility sa) {
- if (sa.hasParam("ReplaceDyingDefined")) {
-
- if (sa.hasParam("ReplaceDyingCondition")) {
- // currently there is only one with Kicker
- final String condition = sa.getParam("ReplaceDyingCondition");
- if ("Kicked".equals(condition)) {
- if (!sa.isKicked()) {
- return;
- }
- }
- }
-
- final Card host = sa.getHostCard();
- final Player controller = sa.getActivatingPlayer();
- final Game game = host.getGame();
- String zone = sa.getParamOrDefault("ReplaceDyingZone", "Exile");
- CardCollection cards = AbilityUtils.getDefinedCards(host, sa.getParam("ReplaceDyingDefined"), sa);
- // no cards, no need for Effect
- if (cards.isEmpty()) {
- return;
- }
-
- // build an Effect with that infomation
- String name = host.getName() + "'s Effect";
-
- final Card eff = createEffect(host, controller, name, host.getImageKey());
- eff.addRemembered(cards);
-
- String repeffstr = "Event$ Moved | ValidCard$ Card.IsRemembered " +
- "| Origin$ Battlefield | Destination$ Graveyard " +
- "| Description$ If the creature would die this turn, exile it instead.";
- String effect = "DB$ ChangeZone | Defined$ ReplacedCard | Origin$ Battlefield | Destination$ " + zone;
-
- ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, eff, true);
- re.setLayer(ReplacementLayer.Other);
-
- re.setOverridingAbility(AbilityFactory.getAbility(effect, eff));
- eff.addReplacementEffect(re);
-
- // Add forgot trigger
- addForgetOnMovedTrigger(eff, "Battlefield");
-
- // Copy text changes
- if (sa.isIntrinsic()) {
- eff.copyChangedTextFrom(host);
- }
-
- final GameCommand endEffect = new GameCommand() {
- private static final long serialVersionUID = -5861759814760561373L;
-
- @Override
- public void run() {
- game.getAction().exile(eff, null);
- }
- };
-
- game.getEndOfTurn().addUntil(endEffect);
-
- eff.updateStateForView();
-
- // TODO: Add targeting to the effect so it knows who it's dealing with
- game.getTriggerHandler().suppressMode(TriggerType.ChangesZone);
- game.getAction().moveTo(ZoneType.Command, eff, sa);
- game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone);
- }
- }
}
diff --git a/forge-game/src/main/java/forge/game/ability/effects/DamageDealEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DamageDealEffect.java
index a7ef697d62c..9f3e2df9ef7 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/DamageDealEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/DamageDealEffect.java
@@ -1,6 +1,8 @@
package forge.game.ability.effects;
import com.google.common.collect.Iterables;
+
+import forge.game.Game;
import forge.game.GameObject;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
@@ -68,6 +70,7 @@ public class DamageDealEffect extends DamageBaseEffect {
@Override
public void resolve(SpellAbility sa) {
final Card hostCard = sa.getHostCard();
+ final Game game = hostCard.getGame();
final String damage = sa.getParam("NumDmg");
int dmg = AbilityUtils.calculateAmount(hostCard, damage, sa);
@@ -176,7 +179,12 @@ public class DamageDealEffect extends DamageBaseEffect {
dmg = (sa.usesTargeting() && sa.hasParam("DividedAsYouChoose")) ? sa.getTargetRestrictions().getDividedValue(o) : dmg;
if (o instanceof Card) {
final Card c = (Card) o;
- if (c.isInPlay() && (!targeted || c.canBeTargetedBy(sa))) {
+ final Card gc = game.getCardState(c, null);
+ if (gc == null || !c.equalsWithTimestamp(gc) || !gc.isInPlay()) {
+ // timestamp different or not in play
+ continue;
+ }
+ if (!targeted || c.canBeTargetedBy(sa)) {
if (removeDamage) {
c.setDamage(0);
c.setHasBeenDealtDeathtouchDamage(false);
diff --git a/forge-game/src/main/java/forge/game/ability/effects/DebuffEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DebuffEffect.java
index 71256eab926..767f43bd9d8 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/DebuffEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/DebuffEffect.java
@@ -137,7 +137,7 @@ public class DebuffEffect extends SpellAbilityEffect {
}
removedKW.addAll(kws);
- tgtC.addChangedCardKeywords(addedKW, removedKW, false, timestamp);
+ tgtC.addChangedCardKeywords(addedKW, removedKW, false, false, timestamp);
}
if (!sa.hasParam("Permanent")) {
game.getEndOfTurn().addUntil(new GameCommand() {
diff --git a/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java
index 19e434bd55f..365adf0855a 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java
@@ -35,7 +35,7 @@ public class DigEffect extends SpellAbilityEffect {
sb.append(Lang.nounWithAmount(numToDig, "card")).append(" of ");
if (tgtPlayers.contains(host.getController())) {
- sb.append("his or her ");
+ sb.append("their ");
}
else {
for (final Player p : tgtPlayers) {
@@ -377,7 +377,7 @@ public class DigEffect extends SpellAbilityEffect {
}
} else if (destZone2 == ZoneType.Exile) {
if (sa.hasParam("ExileWithCounter")) {
- c.addCounter(CounterType.getType(sa.getParam("ExileWithCounter")), 1, effectHost, true);
+ c.addCounter(CounterType.getType(sa.getParam("ExileWithCounter")), 1, player, true);
}
c.setExiledWith(effectHost);
}
diff --git a/forge-game/src/main/java/forge/game/ability/effects/DigUntilEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DigUntilEffect.java
index 73a32885eed..5615f70b2de 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/DigUntilEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/DigUntilEffect.java
@@ -37,7 +37,7 @@ public class DigUntilEffect extends SpellAbilityEffect {
sb.append(pl).append(" ");
}
- sb.append("reveals cards from his or her library until revealing ");
+ sb.append("reveals cards from their library until revealing ");
sb.append(untilAmount).append(" ").append(desc).append(" card");
if (untilAmount != 1) {
sb.append("s");
@@ -56,18 +56,18 @@ public class DigUntilEffect extends SpellAbilityEffect {
sb.append(" ");
if (found.equals(ZoneType.Hand)) {
- sb.append("into his or her hand ");
+ sb.append("into their hand ");
}
if (revealed.equals(ZoneType.Graveyard)) {
- sb.append("and all other cards into his or her graveyard.");
+ sb.append("and all other cards into their graveyard.");
}
if (revealed.equals(ZoneType.Exile)) {
sb.append("and exile all other cards revealed this way.");
}
} else {
if (revealed.equals(ZoneType.Hand)) {
- sb.append("all cards revealed this way into his or her hand");
+ sb.append("all cards revealed this way into their hand");
}
}
return sb.toString();
diff --git a/forge-game/src/main/java/forge/game/ability/effects/DiscardEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DiscardEffect.java
index 942e5e63ab9..b2ebcb304bc 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/DiscardEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/DiscardEffect.java
@@ -35,9 +35,9 @@ public class DiscardEffect extends SpellAbilityEffect {
}
if (mode.equals("RevealYouChoose")) {
- sb.append("reveals his or her hand.").append(" You choose (");
+ sb.append("reveals their hand.").append(" You choose (");
} else if (mode.equals("RevealDiscardAll")) {
- sb.append("reveals his or her hand. Discard (");
+ sb.append("reveals their hand. Discard (");
} else {
sb.append("discards (");
}
@@ -48,7 +48,7 @@ public class DiscardEffect extends SpellAbilityEffect {
}
if (mode.equals("Hand")) {
- sb.append("his or her hand");
+ sb.append("their hand");
} else if (mode.equals("RevealDiscardAll")) {
sb.append("All");
} else if (sa.hasParam("AnyNumber")) {
diff --git a/forge-game/src/main/java/forge/game/ability/effects/ExploreEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ExploreEffect.java
index 942512a5ad7..c06be791574 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/ExploreEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/ExploreEffect.java
@@ -44,7 +44,6 @@ public class ExploreEffect extends SpellAbilityEffect {
@Override
public void resolve(SpellAbility sa) {
// check if only the activating player counts
- final Card card = sa.getHostCard();
final Player pl = sa.getActivatingPlayer();
final PlayerController pc = pl.getController();
final Game game = pl.getGame();
@@ -78,7 +77,7 @@ public class ExploreEffect extends SpellAbilityEffect {
// if the card is not more in the game anymore
// this might still return true but its no problem
if (game.getZoneOf(gamec).is(ZoneType.Battlefield) && gamec.getTimestamp() == c.getTimestamp()) {
- c.addCounter(CounterType.P1P1, 1, card, true);
+ c.addCounter(CounterType.P1P1, 1, pl, true);
}
}
diff --git a/forge-game/src/main/java/forge/game/ability/effects/FightEffect.java b/forge-game/src/main/java/forge/game/ability/effects/FightEffect.java
index 4bbaad2e21d..998cbf27220 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/FightEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/FightEffect.java
@@ -41,10 +41,10 @@ public class FightEffect extends DamageBaseEffect {
public void resolve(SpellAbility sa) {
final Card host = sa.getHostCard();
List fighters = getFighters(sa);
- final Game game = sa.getActivatingPlayer().getGame();
+ final Game game = host.getGame();
- if (fighters.size() < 2 || !fighters.get(0).isInPlay()
- || !fighters.get(1).isInPlay()) {
+ // check is done in getFighters
+ if (fighters.size() < 2) {
return;
}
@@ -55,21 +55,7 @@ public class FightEffect extends DamageBaseEffect {
}
}
- boolean fightToughness = sa.hasParam("FightWithToughness");
- CardDamageMap damageMap = new CardDamageMap();
- CardDamageMap preventMap = new CardDamageMap();
-
- // Damage is dealt simultaneously, so we calculate the damage from source to target before it is applied
- final int dmg1 = fightToughness ? fighters.get(0).getNetToughness() : fighters.get(0).getNetPower();
- final int dmg2 = fightToughness ? fighters.get(1).getNetToughness() : fighters.get(1).getNetPower();
-
- dealDamage(fighters.get(0), fighters.get(1), dmg1, damageMap, preventMap, sa);
- dealDamage(fighters.get(1), fighters.get(0), dmg2, damageMap, preventMap, sa);
-
- preventMap.triggerPreventDamage(false);
- damageMap.triggerDamageDoneOnce(false, sa);
-
- replaceDying(sa);
+ dealDamage(sa, fighters.get(0), fighters.get(1));
for (Card c : fighters) {
final Map runParams = Maps.newHashMap();
@@ -83,6 +69,8 @@ public class FightEffect extends DamageBaseEffect {
Card fighter1 = null;
Card fighter2 = null;
+ final Card host = sa.getHostCard();
+ final Game game = host.getGame();
List tgts = null;
if (sa.usesTargeting()) {
@@ -92,12 +80,27 @@ public class FightEffect extends DamageBaseEffect {
}
}
if (sa.hasParam("Defined")) {
- List defined = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
+ List defined = AbilityUtils.getDefinedCards(host, sa.getParam("Defined"), sa);
// Allow both fighters to come from defined list if first fighter not already found
if (sa.hasParam("ExtraDefined")) {
- defined.addAll(AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("ExtraDefined"), sa));
+ defined.addAll(AbilityUtils.getDefinedCards(host, sa.getParam("ExtraDefined"), sa));
}
+ List newDefined = Lists.newArrayList();
+ for (final Card d : defined) {
+ final Card g = game.getCardState(d, null);
+ // 701.12b If a creature instructed to fight is no longer on the battlefield or is no longer a creature,
+ // no damage is dealt. If a creature is an illegal target
+ // for a resolving spell or ability that instructs it to fight, no damage is dealt.
+ if (g == null || !g.equalsWithTimestamp(d) || !d.isInPlay() || !d.isCreature()) {
+ // Test to see if the card we're trying to add is in the expected state
+ continue;
+ }
+ newDefined.add(g);
+ }
+ // replace with new List using CardState
+ defined = newDefined;
+
if (!defined.isEmpty()) {
if (defined.size() > 1 && fighter1 == null) {
fighter1 = defined.get(0);
@@ -120,9 +123,38 @@ public class FightEffect extends DamageBaseEffect {
return fighterList;
}
-
- private void dealDamage(Card source, Card target, int damage, CardDamageMap damageMap, CardDamageMap preventMap, final SpellAbility sa) {
- target.addDamage(damage, source, damageMap, preventMap, sa);
- }
+ private void dealDamage(final SpellAbility sa, Card fighterA, Card fighterB) {
+ boolean fightToughness = sa.hasParam("FightWithToughness");
+
+ boolean usedDamageMap = true;
+ CardDamageMap damageMap = sa.getDamageMap();
+ CardDamageMap preventMap = sa.getPreventMap();
+
+ if (damageMap == null) {
+ // make a new damage map
+ damageMap = new CardDamageMap();
+ preventMap = new CardDamageMap();
+ usedDamageMap = false;
+ }
+
+ // 701.12c If a creature fights itself, it deals damage to itself equal to twice its power.
+
+ final int dmg1 = fightToughness ? fighterA.getNetToughness() : fighterA.getNetPower();
+ if (fighterA.equals(fighterB)) {
+ fighterA.addDamage(dmg1 * 2, fighterA, damageMap, preventMap, sa);
+ } else {
+ final int dmg2 = fightToughness ? fighterB.getNetToughness() : fighterB.getNetPower();
+
+ fighterB.addDamage(dmg1, fighterA, damageMap, preventMap, sa);
+ fighterA.addDamage(dmg2, fighterB, damageMap, preventMap, sa);
+ }
+
+ if (!usedDamageMap) {
+ preventMap.triggerPreventDamage(false);
+ damageMap.triggerDamageDoneOnce(false, sa);
+ }
+
+ replaceDying(sa);
+ }
}
diff --git a/forge-game/src/main/java/forge/game/ability/effects/FlipCoinEffect.java b/forge-game/src/main/java/forge/game/ability/effects/FlipCoinEffect.java
index e5c609d6c24..9724391b980 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/FlipCoinEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/FlipCoinEffect.java
@@ -118,9 +118,9 @@ public class FlipCoinEffect extends SpellAbilityEffect {
if (sa.getParam("RememberWinner") != null) {
host.addRemembered(host);
}
- AbilitySub sub = sa.getAdditionalAbility("WinSubAbility");
- if (sub != null) {
- AbilityUtils.resolve(sub);
+
+ if (sa.hasAdditionalAbility("WinSubAbility")) {
+ AbilityUtils.resolve(sa.getAdditionalAbility("WinSubAbility"));
}
// runParams.put("Won","True");
} else {
@@ -128,9 +128,8 @@ public class FlipCoinEffect extends SpellAbilityEffect {
host.addRemembered(host);
}
- AbilitySub sub = sa.getAdditionalAbility("LoseSubAbility");
- if (sub != null) {
- AbilityUtils.resolve(sub);
+ if (sa.hasAdditionalAbility("LoseSubAbility")) {
+ AbilityUtils.resolve(sa.getAdditionalAbility("LoseSubAbility"));
}
// runParams.put("Won","False");
}
@@ -167,7 +166,7 @@ public class FlipCoinEffect extends SpellAbilityEffect {
flipper.getGame().getAction().nofityOfValue(sa, flipper, result ? "heads" : "tails", null);
} while (sa.hasParam("FlipUntilYouLose") && result != false);
- if (sa.hasParam("FlipUntilYouLose")) {
+ if (sa.hasParam("FlipUntilYouLose") && sa.hasAdditionalAbility("LoseSubAbility")) {
sa.getAdditionalAbility("LoseSubAbility").setSVar(varName, "Number$" + numSuccesses);
}
@@ -216,7 +215,7 @@ public class FlipCoinEffect extends SpellAbilityEffect {
caller.getGame().getTriggerHandler().runTrigger(TriggerType.FlippedCoin, runParams, false);
} while (sa.hasParam("FlipUntilYouLose") && wonFlip);
- if (sa.hasParam("FlipUntilYouLose")) {
+ if (sa.hasParam("FlipUntilYouLose") && sa.hasAdditionalAbility("LoseSubAbility")) {
sa.getAdditionalAbility("LoseSubAbility").setSVar(varName, "Number$" + numSuccesses);
}
diff --git a/forge-game/src/main/java/forge/game/ability/effects/HauntEffect.java b/forge-game/src/main/java/forge/game/ability/effects/HauntEffect.java
index f429db84781..230d1d12f48 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/HauntEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/HauntEffect.java
@@ -1,5 +1,6 @@
package forge.game.ability.effects;
+import forge.game.Game;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.spellability.SpellAbility;
@@ -8,10 +9,14 @@ public class HauntEffect extends SpellAbilityEffect {
@Override
public void resolve(SpellAbility sa) {
- final Card card = sa.getHostCard();
- if (sa.usesTargeting() && !card.isToken()) {
+ Card card = sa.getHostCard();
+ final Game game = card.getGame();
+ card = game.getCardState(card, null);
+ if (card == null) {
+ return;
+ } else if (sa.usesTargeting() && !card.isToken()) {
// haunt target but only if card is no token
- final Card copy = card.getGame().getAction().exile(card, sa);
+ final Card copy = game.getAction().exile(card, sa);
sa.getTargets().getFirstTargetedCard().addHauntedBy(copy);
} else if (!sa.usesTargeting() && card.getHaunting() != null) {
// unhaunt
diff --git a/forge-game/src/main/java/forge/game/ability/effects/ImmediateTriggerEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ImmediateTriggerEffect.java
new file mode 100644
index 00000000000..fd4ed68e213
--- /dev/null
+++ b/forge-game/src/main/java/forge/game/ability/effects/ImmediateTriggerEffect.java
@@ -0,0 +1,80 @@
+package forge.game.ability.effects;
+
+import com.google.common.collect.Maps;
+import forge.game.ability.AbilityUtils;
+import forge.game.ability.SpellAbilityEffect;
+import forge.game.spellability.SpellAbility;
+import forge.game.trigger.Trigger;
+import forge.game.trigger.TriggerHandler;
+import forge.game.trigger.TriggerType;
+
+import java.util.Map;
+
+public class ImmediateTriggerEffect extends SpellAbilityEffect {
+
+ /* (non-Javadoc)
+ * @see forge.card.abilityfactory.SpellEffect#resolve(java.util.Map, forge.card.spellability.SpellAbility)
+ */
+ @Override
+ protected String getStackDescription(SpellAbility sa) {
+ if (sa.hasParam("TriggerDescription")) {
+ return sa.getParam("TriggerDescription");
+ }
+
+ return "";
+
+ }
+
+ @Override
+ public void resolve(SpellAbility sa) {
+ Map mapParams = Maps.newHashMap(sa.getMapParams());
+
+ if (mapParams.containsKey("Cost")) {
+ mapParams.remove("Cost");
+ }
+
+ if (mapParams.containsKey("SpellDescription")) {
+ mapParams.put("TriggerDescription", mapParams.get("SpellDescription"));
+ mapParams.remove("SpellDescription");
+ }
+
+ String triggerRemembered = null;
+
+ // Set Remembered
+ if (sa.hasParam("RememberObjects")) {
+ triggerRemembered = sa.getParam("RememberObjects");
+ }
+
+ mapParams.put("Mode", TriggerType.Immediate.name());
+
+ final Trigger immediateTrig = TriggerHandler.parseTrigger(mapParams, sa.getHostCard(), true);
+
+ if (sa.hasParam("CopyTriggeringObjects")) {
+ immediateTrig.setStoredTriggeredObjects(sa.getTriggeringObjects());
+ }
+
+ // Need to copy paid costs
+
+ if (triggerRemembered != null) {
+ for (final String rem : triggerRemembered.split(",")) {
+ for (final Object o : AbilityUtils.getDefinedObjects(sa.getHostCard(), rem, sa)) {
+ if (o instanceof SpellAbility) {
+ // "RememberObjects$ Remembered" don't remember spellability
+ continue;
+ }
+ immediateTrig.addRemembered(o);
+ }
+ }
+ }
+
+ if (mapParams.containsKey("Execute") || sa.hasAdditionalAbility("Execute")) {
+ SpellAbility overridingSA = sa.getAdditionalAbility("Execute");
+ overridingSA.setActivatingPlayer(sa.getActivatingPlayer());
+ immediateTrig.setOverridingAbility(overridingSA);
+ }
+ final TriggerHandler trigHandler = sa.getActivatingPlayer().getGame().getTriggerHandler();
+
+ // Instead of registering this, add to the delayed triggers as an immediate trigger type? Which means it'll fire as soon as possible
+ trigHandler.registerDelayedTrigger(immediateTrig);
+ }
+}
diff --git a/forge-game/src/main/java/forge/game/ability/effects/MillEffect.java b/forge-game/src/main/java/forge/game/ability/effects/MillEffect.java
index 8882dfffda6..68676c4fe77 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/MillEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/MillEffect.java
@@ -104,7 +104,7 @@ public class MillEffect extends SpellAbilityEffect {
sb.append("s");
}
final String millPosition = sa.hasParam("FromBottom") ? "bottom" : "top";
- sb.append(" from the " + millPosition + " of his or her library.");
+ sb.append(" from the " + millPosition + " of their library.");
return sb.toString();
diff --git a/forge-game/src/main/java/forge/game/ability/effects/MustAttackEffect.java b/forge-game/src/main/java/forge/game/ability/effects/MustAttackEffect.java
index 960448b9b50..cd8b973ba6b 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/MustAttackEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/MustAttackEffect.java
@@ -36,7 +36,7 @@ public class MustAttackEffect extends SpellAbilityEffect {
for (final Player player : tgtPlayers) {
sb.append("Creatures ").append(player).append(" controls attack ");
- sb.append(defender).append(" during his or her next turn.");
+ sb.append(defender).append(" during their next turn.");
}
for (final Card c : getTargetCards(sa)) {
sb.append(c).append(" must attack ");
diff --git a/forge-game/src/main/java/forge/game/ability/effects/ProtectAllEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ProtectAllEffect.java
index a0a29b16aea..2a09ba0eb5b 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/ProtectAllEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/ProtectAllEffect.java
@@ -90,7 +90,7 @@ public class ProtectAllEffect extends SpellAbilityEffect {
for (final Card tgtC : list) {
if (tgtC.isInPlay()) {
- tgtC.addChangedCardKeywords(gainsKWList, ImmutableList.of(), false, timestamp, true);
+ tgtC.addChangedCardKeywords(gainsKWList, null, false, false, timestamp, true);
if (!sa.hasParam("Permanent")) {
// If not Permanent, remove protection at EOT
diff --git a/forge-game/src/main/java/forge/game/ability/effects/ProtectEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ProtectEffect.java
index 9ad893fd8be..3b2b1fe6445 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/ProtectEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/ProtectEffect.java
@@ -1,6 +1,5 @@
package forge.game.ability.effects;
-import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import forge.GameCommand;
import forge.card.MagicColor;
@@ -153,7 +152,7 @@ public class ProtectEffect extends SpellAbilityEffect {
continue;
}
- tgtC.addChangedCardKeywords(gainsKWList, ImmutableList.of(), false, timestamp, true);
+ tgtC.addChangedCardKeywords(gainsKWList, null, false, false, timestamp, true);
if (!sa.hasParam("Permanent")) {
// If not Permanent, remove protection at EOT
@@ -181,7 +180,7 @@ public class ProtectEffect extends SpellAbilityEffect {
continue;
}
- unTgtC.addChangedCardKeywords(gainsKWList, ImmutableList.of(), false, timestamp, true);
+ unTgtC.addChangedCardKeywords(gainsKWList, null, false, false, timestamp, true);
if (!sa.hasParam("Permanent")) {
// If not Permanent, remove protection at EOT
diff --git a/forge-game/src/main/java/forge/game/ability/effects/PumpAllEffect.java b/forge-game/src/main/java/forge/game/ability/effects/PumpAllEffect.java
index 8a5d0dfebfb..c149b976412 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/PumpAllEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/PumpAllEffect.java
@@ -13,10 +13,11 @@ import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.TextUtil;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import com.google.common.collect.Lists;
+
public class PumpAllEffect extends SpellAbilityEffect {
private static void applyPumpAll(final SpellAbility sa,
final List list, final int a, final int d,
@@ -24,23 +25,18 @@ public class PumpAllEffect extends SpellAbilityEffect {
final Game game = sa.getActivatingPlayer().getGame();
final long timestamp = game.getNextTimestamp();
- final List kws = new ArrayList();
- final List hiddenkws = new ArrayList();
- boolean suspend = false;
+ final List kws = Lists.newArrayList();
+ final List hiddenkws = Lists.newArrayList();
for (String kw : keywords) {
if (kw.startsWith("HIDDEN")) {
hiddenkws.add(kw);
} else {
kws.add(kw);
- if (kw.equals("Suspend")) {
- suspend = true;
- }
}
}
for (final Card tgtC : list) {
-
// only pump things in the affected zones.
boolean found = false;
for (final ZoneType z : affectedZones) {
@@ -55,7 +51,7 @@ public class PumpAllEffect extends SpellAbilityEffect {
tgtC.addTempPowerBoost(a);
tgtC.addTempToughnessBoost(d);
- tgtC.addChangedCardKeywords(kws, new ArrayList(), false, timestamp);
+ tgtC.addChangedCardKeywords(kws, null, false, false, timestamp);
for (String kw : hiddenkws) {
tgtC.addHiddenExtrinsicKeyword(kw);
@@ -118,13 +114,11 @@ public class PumpAllEffect extends SpellAbilityEffect {
@Override
public void resolve(final SpellAbility sa) {
final List tgtPlayers = getTargetPlayers(sa);
- final List affectedZones = new ArrayList();
+ final List affectedZones = Lists.newArrayList();
final Game game = sa.getActivatingPlayer().getGame();
if (sa.hasParam("PumpZone")) {
- for (final String zone : sa.getParam("PumpZone").split(",")) {
- affectedZones.add(ZoneType.valueOf(zone));
- }
+ affectedZones.addAll(ZoneType.listValueOf(sa.getParam("PumpZone")));
} else {
affectedZones.add(ZoneType.Battlefield);
}
@@ -149,7 +143,10 @@ public class PumpAllEffect extends SpellAbilityEffect {
list = (CardCollection)AbilityUtils.filterListByType(list, valid, sa);
- List keywords = sa.hasParam("KW") ? Arrays.asList(sa.getParam("KW").split(" & ")) : new ArrayList();
+ List keywords = Lists.newArrayList();
+ if (sa.hasParam("KW")) {
+ keywords.addAll(Arrays.asList(sa.getParam("KW").split(" & ")));
+ }
final int a = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumAtt"), sa, true);
final int d = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumDef"), sa, true);
@@ -161,6 +158,8 @@ public class PumpAllEffect extends SpellAbilityEffect {
keywords = CardFactoryUtil.sharedKeywords(keywords, restrictions, zones, sa.getHostCard());
}
applyPumpAll(sa, list, a, d, keywords, affectedZones);
+
+ replaceDying(sa);
} // pumpAllResolve()
}
diff --git a/forge-game/src/main/java/forge/game/ability/effects/PumpEffect.java b/forge-game/src/main/java/forge/game/ability/effects/PumpEffect.java
index cbbccffafe2..c7841849b9e 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/PumpEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/PumpEffect.java
@@ -12,7 +12,6 @@ import forge.game.event.GameEventCardStatsChanged;
import forge.game.keyword.KeywordInterface;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
-import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.Aggregates;
import forge.util.Lang;
@@ -31,32 +30,40 @@ public class PumpEffect extends SpellAbilityEffect {
final int a, final int d, final List keywords,
final long timestamp) {
final Card host = sa.getHostCard();
+ final Game game = host.getGame();
//if host is not on the battlefield don't apply
// Suspend should does Affect the Stack
if (sa.hasParam("UntilLoseControlOfHost")
&& !(host.isInPlay() || host.isInZone(ZoneType.Stack))) {
return;
}
- final Game game = sa.getActivatingPlayer().getGame();
+
+ // do Game Check there in case of LKI
+ final Card gameCard = game.getCardState(applyTo, null);
+ if (gameCard == null || !applyTo.equalsWithTimestamp(gameCard)) {
+ return;
+ }
final List kws = Lists.newArrayList();
boolean redrawPT = false;
for (String kw : keywords) {
if (kw.startsWith("HIDDEN")) {
- applyTo.addHiddenExtrinsicKeyword(kw);
+ gameCard.addHiddenExtrinsicKeyword(kw);
redrawPT |= kw.contains("CARDNAME's power and toughness are switched");
} else {
kws.add(kw);
}
}
- applyTo.addTempPowerBoost(a);
- applyTo.addTempToughnessBoost(d);
- applyTo.addChangedCardKeywords(kws, Lists.newArrayList(), false, timestamp);
- if (redrawPT) { applyTo.updatePowerToughnessForView(); }
+ gameCard.addTempPowerBoost(a);
+ gameCard.addTempToughnessBoost(d);
+ gameCard.addChangedCardKeywords(kws, Lists.newArrayList(), false, false, timestamp);
+ if (redrawPT) {
+ gameCard.updatePowerToughnessForView();
+ }
if (sa.hasParam("LeaveBattlefield")) {
- addLeaveBattlefieldReplacement(applyTo, sa, sa.getParam("LeaveBattlefield"));
+ addLeaveBattlefieldReplacement(gameCard, sa, sa.getParam("LeaveBattlefield"));
}
if (!sa.hasParam("Permanent")) {
@@ -66,8 +73,8 @@ public class PumpEffect extends SpellAbilityEffect {
@Override
public void run() {
- applyTo.addTempPowerBoost(-1 * a);
- applyTo.addTempToughnessBoost(-1 * d);
+ gameCard.addTempPowerBoost(-1 * a);
+ gameCard.addTempToughnessBoost(-1 * d);
if (keywords.size() > 0) {
boolean redrawPT = false;
@@ -75,16 +82,16 @@ public class PumpEffect extends SpellAbilityEffect {
for (String kw : keywords) {
redrawPT |= kw.contains("CARDNAME's power and toughness are switched");
if (kw.startsWith("HIDDEN")) {
- applyTo.removeHiddenExtrinsicKeyword(kw);
+ gameCard.removeHiddenExtrinsicKeyword(kw);
if (redrawPT) {
- applyTo.updatePowerToughnessForView();
+ gameCard.updatePowerToughnessForView();
}
}
}
- applyTo.removeChangedCardKeywords(timestamp);
+ gameCard.removeChangedCardKeywords(timestamp);
}
- game.fireEvent(new GameEventCardStatsChanged(applyTo));
+ game.fireEvent(new GameEventCardStatsChanged(gameCard));
}
};
if (sa.hasParam("UntilEndOfCombat")) {
@@ -107,12 +114,19 @@ public class PumpEffect extends SpellAbilityEffect {
game.getEndOfTurn().addUntil(untilEOT);
}
}
- game.fireEvent(new GameEventCardStatsChanged(applyTo));
+ game.fireEvent(new GameEventCardStatsChanged(gameCard));
}
private static void applyPump(final SpellAbility sa, final Player p,
final List keywords, final long timestamp) {
final Game game = p.getGame();
+ final Card host = sa.getHostCard();
+ //if host is not on the battlefield don't apply
+ // Suspend should does Affect the Stack
+ if (sa.hasParam("UntilLoseControlOfHost")
+ && !(host.isInPlay() || host.isInZone(ZoneType.Stack))) {
+ return;
+ }
p.addChangedKeywords(keywords, ImmutableList.of(), timestamp);
if (!sa.hasParam("Permanent")) {
@@ -134,6 +148,9 @@ public class PumpEffect extends SpellAbilityEffect {
game.getEndOfCombat().addUntil(untilEOT);
} else if (sa.hasParam("UntilYourNextUpkeep")) {
game.getUpkeep().addUntil(sa.getActivatingPlayer(), untilEOT);
+ } else if (sa.hasParam("UntilLoseControlOfHost")) {
+ sa.getHostCard().addLeavesPlayCommand(untilEOT);
+ sa.getHostCard().addChangeControllerCommand(untilEOT);
} else {
game.getEndOfTurn().addUntil(untilEOT);
}
@@ -208,7 +225,6 @@ public class PumpEffect extends SpellAbilityEffect {
public void resolve(final SpellAbility sa) {
final List untargetedCards = Lists.newArrayList();
- final TargetRestrictions tgt = sa.getTargetRestrictions();
final Game game = sa.getActivatingPlayer().getGame();
final Card host = sa.getHostCard();
final long timestamp = game.getNextTimestamp();
@@ -251,7 +267,7 @@ public class PumpEffect extends SpellAbilityEffect {
final String landtype = sa.getParam("DefinedLandwalk");
final Card c = AbilityUtils.getDefinedCards(host, landtype, sa).get(0);
for (String type : c.getType()) {
- if (CardType.isALandType(type) || CardType.isABasicLandType(type)) {
+ if (CardType.isALandType(type)) {
keywords.add(type + "walk");
}
}
@@ -340,7 +356,7 @@ public class PumpEffect extends SpellAbilityEffect {
}
// if pump is a target, make sure we can still target now
- if ((tgt != null) && !tgtC.canBeTargetedBy(sa)) {
+ if (sa.usesTargeting() && !tgtC.canBeTargetedBy(sa)) {
continue;
}
@@ -367,5 +383,7 @@ public class PumpEffect extends SpellAbilityEffect {
applyPump(sa, p, keywords, timestamp);
}
+
+ replaceDying(sa);
} // pumpResolve()
}
diff --git a/forge-game/src/main/java/forge/game/ability/effects/RearrangeTopOfLibraryEffect.java b/forge-game/src/main/java/forge/game/ability/effects/RearrangeTopOfLibraryEffect.java
index aa03c0e982d..2794da67bbb 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/RearrangeTopOfLibraryEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/RearrangeTopOfLibraryEffect.java
@@ -51,7 +51,7 @@ public class RearrangeTopOfLibraryEffect extends SpellAbilityEffect {
ret.append("that");
}
- ret.append(" player shuffle his or her library.");
+ ret.append(" player shuffle their library.");
}
return ret.toString();
diff --git a/forge-game/src/main/java/forge/game/ability/effects/ReplaceEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ReplaceEffect.java
index c70ac438935..95f58d5dff9 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/ReplaceEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/ReplaceEffect.java
@@ -48,6 +48,10 @@ public class ReplaceEffect extends SpellAbilityEffect {
params.put(varName, AbilityUtils.calculateAmount(card, varValue, sa));
}
+ if (params.containsKey("EffectOnly")) {
+ params.put("EffectOnly", true);
+ }
+
//try to call replacementHandler with new Params
ReplacementResult result = game.getReplacementHandler().run(params);
switch (result) {
diff --git a/forge-game/src/main/java/forge/game/ability/effects/RevealEffect.java b/forge-game/src/main/java/forge/game/ability/effects/RevealEffect.java
index bedb76f5c38..f718dbf8660 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/RevealEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/RevealEffect.java
@@ -109,7 +109,7 @@ public class RevealEffect extends SpellAbilityEffect {
if (sa.hasParam("Random")) {
sb.append("at random ");
}
- sb.append("from his or her hand.");
+ sb.append("from their hand.");
} else {
sb.append("Error - no target players for RevealHand. ");
}
diff --git a/forge-game/src/main/java/forge/game/ability/effects/SacrificeEffect.java b/forge-game/src/main/java/forge/game/ability/effects/SacrificeEffect.java
index f3809584b62..7b371dc58c4 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/SacrificeEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/SacrificeEffect.java
@@ -43,7 +43,7 @@ public class SacrificeEffect extends SpellAbilityEffect {
return;
}
} else if (sa.hasParam("CumulativeUpkeep")) {
- card.addCounter(CounterType.AGE, 1, card, true);
+ card.addCounter(CounterType.AGE, 1, activator, true);
Cost cumCost = new Cost(sa.getParam("CumulativeUpkeep"), true);
Cost payCost = new Cost(ManaCost.ZERO, true);
int n = card.getCounters(CounterType.AGE);
diff --git a/forge-game/src/main/java/forge/game/ability/effects/ScryEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ScryEffect.java
index 423479c7782..2f5d606fbe3 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/ScryEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/ScryEffect.java
@@ -45,7 +45,7 @@ public class ScryEffect extends SpellAbilityEffect {
continue;
}
- p.scry(num);
+ p.scry(num, sa);
}
}
}
diff --git a/forge-game/src/main/java/forge/game/ability/effects/SetStateEffect.java b/forge-game/src/main/java/forge/game/ability/effects/SetStateEffect.java
index 91695cd6580..c7292e45a60 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/SetStateEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/SetStateEffect.java
@@ -1,9 +1,11 @@
package forge.game.ability.effects;
+import forge.card.CardStateName;
import forge.game.Game;
import forge.game.GameLogEntryType;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
+import forge.game.card.CardCollection;
import forge.game.card.CounterType;
import forge.game.event.GameEventCardStatsChanged;
import forge.game.player.Player;
@@ -66,6 +68,25 @@ public class SetStateEffect extends SpellAbilityEffect {
continue;
}
+ // facedown cards that are not Permanent, can't turn faceup there
+ if ("TurnFace".equals(mode) && tgt.isFaceDown() && tgt.isInZone(ZoneType.Battlefield)
+ && !tgt.getState(CardStateName.Original).getType().isPermanent()) {
+ // need to cache manifest status
+ boolean manifested = tgt.isManifested();
+ // FIXME setState has to many other Consequences, use LKI?
+ tgt.setState(CardStateName.Original, true);
+ game.getAction().reveal(new CardCollection(tgt), tgt.getOwner(), true, "Face-down card can't turn face up");
+ tgt.setState(CardStateName.FaceDown, true);
+ tgt.setManifested(manifested);
+
+ continue;
+ }
+
+ // for reasons it can't transform, skip
+ if ("Transform".equals(mode) && !tgt.canTransform()) {
+ continue;
+ }
+
if ("Transform".equals(mode) && tgt.equals(host) && sa.hasSVar("StoredTransform")) {
// If want to Transform, and host is trying to transform self, skip if not in alignment
boolean skip = tgt.getTransformedTimestamp() != Long.parseLong(sa.getSVar("StoredTransform"));
@@ -104,7 +125,7 @@ public class SetStateEffect extends SpellAbilityEffect {
}
game.fireEvent(new GameEventCardStatsChanged(tgt));
if (sa.hasParam("Mega")) {
- tgt.addCounter(CounterType.P1P1, 1, host, true);
+ tgt.addCounter(CounterType.P1P1, 1, p, true);
}
if (remChanged) {
host.addRemembered(tgt);
diff --git a/forge-game/src/main/java/forge/game/ability/effects/ShuffleEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ShuffleEffect.java
index 3631fbae7fc..a7711c0356b 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/ShuffleEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/ShuffleEffect.java
@@ -48,7 +48,7 @@ public class ShuffleEffect extends SpellAbilityEffect {
if (tgtPlayers.size() > 1) {
sb.append(" their libraries");
} else {
- sb.append("s his or her library");
+ sb.append("s their library");
}
sb.append(".");
diff --git a/forge-game/src/main/java/forge/game/ability/effects/SurveilEffect.java b/forge-game/src/main/java/forge/game/ability/effects/SurveilEffect.java
new file mode 100644
index 00000000000..740ce564b33
--- /dev/null
+++ b/forge-game/src/main/java/forge/game/ability/effects/SurveilEffect.java
@@ -0,0 +1,46 @@
+package forge.game.ability.effects;
+
+import forge.game.ability.AbilityUtils;
+import forge.game.ability.SpellAbilityEffect;
+import forge.game.player.Player;
+import forge.game.spellability.SpellAbility;
+import forge.util.Lang;
+
+public class SurveilEffect extends SpellAbilityEffect {
+ @Override
+ protected String getStackDescription(SpellAbility sa) {
+ final StringBuilder sb = new StringBuilder();
+
+ sb.append(Lang.joinHomogenous(getTargetPlayers(sa)));
+
+ int num = 1;
+ if (sa.hasParam("Amount")) {
+ num = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Amount"), sa);
+ }
+
+ sb.append(" surveil (").append(num).append(").");
+ return sb.toString();
+ }
+
+ @Override
+ public void resolve(SpellAbility sa) {
+ int num = 1;
+ if (sa.hasParam("Amount")) {
+ num = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Amount"), sa);
+ }
+
+ boolean isOptional = sa.hasParam("Optional");
+
+ for (final Player p : getTargetPlayers(sa)) {
+ if (!sa.usesTargeting() || p.canBeTargetedBy(sa)) {
+ if (isOptional && !p.getController().confirmAction(sa, null, "Do you want to surveil?")) {
+ continue;
+ }
+
+ p.surveil(num, sa);
+ }
+ }
+ }
+
+
+}
diff --git a/forge-game/src/main/java/forge/game/ability/effects/TokenEffect.java b/forge-game/src/main/java/forge/game/ability/effects/TokenEffect.java
index 2181ed28ba6..6abd7bdebc1 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/TokenEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/TokenEffect.java
@@ -27,14 +27,18 @@ import org.apache.commons.lang3.StringUtils;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
import forge.card.CardType;
import forge.game.Game;
import forge.game.GameEntity;
+import forge.game.GameObject;
import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
+import forge.game.card.CardCollection;
+import forge.game.card.CardUtil;
import forge.game.combat.Combat;
import forge.game.event.GameEventCombatChanged;
import forge.game.event.GameEventTokenCreated;
@@ -42,6 +46,7 @@ import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerHandler;
+import forge.game.zone.ZoneType;
import forge.item.PaperToken;
import forge.util.collect.FCollectionView;
import forge.util.MyRandom;
@@ -194,17 +199,15 @@ public class TokenEffect extends SpellAbilityEffect {
return sb.toString();
}
- private Card loadTokenPrototype(SpellAbility sa) {
- String script = sa.getParamOrDefault("TokenScript", null);
-
- PaperToken token = null;
- try {
- String edition = sa.getHostCard().getPaperCard().getEdition();
- token = StaticData.instance().getAllTokens().getToken(script, edition);
- } catch(NullPointerException e) {
- // A non-PaperCard creates a new token. We probably want to delegate to the original creator
- System.out.println("Token created by: " + sa.getHostCard() + " has no PaperCard associated to it.");
+ public Card loadTokenPrototype(SpellAbility sa) {
+ if (!sa.hasParam("TokenScript")) {
+ return null;
}
+
+ String script = sa.getParam("TokenScript");
+ String edition = sa.getHostCard().getSetCode();
+ PaperToken token = StaticData.instance().getAllTokens().getToken(script, edition);
+
if (token != null) {
tokenName = token.getName();
return Card.fromPaperCard(token, null, sa.getHostCard().getGame());
@@ -270,7 +273,10 @@ public class TokenEffect extends SpellAbilityEffect {
tokenInfo = new TokenInfo(substitutedName, imageName,
cost, substitutedTypes, this.tokenKeywords, finalPower, finalToughness);
} else {
- tokenInfo = new TokenInfo(prototype);
+ // TODO: Substitute type name for Chosen tokens
+ // TODO: If host has has it's color/type altered make sure that's appropriately applied
+ // TODO: Lock down final power and toughness if it's actually X values
+ tokenInfo = new TokenInfo(prototype, host);
}
for (final Player controller : AbilityUtils.getDefinedPlayers(host, this.tokenOwner, sa)) {
@@ -291,6 +297,11 @@ public class TokenEffect extends SpellAbilityEffect {
if (this.tokenTapped) {
tok.setTapped(true);
}
+
+ if (sa.hasParam("AttachedTo") && !attachTokenTo(tok, sa)) {
+ continue;
+ }
+
// Should this be catching the Card that's returned?
Card c = game.getAction().moveToPlay(tok, sa);
@@ -299,7 +310,7 @@ public class TokenEffect extends SpellAbilityEffect {
}
if (inCombat) {
- combatChanged = addTokenToCombat(game, c, controller, sa, host) || combatChanged;
+ combatChanged = addTokenToCombat(game, c, tok.getController(), sa, host) || combatChanged;
}
c.updateStateForView();
@@ -469,4 +480,79 @@ public class TokenEffect extends SpellAbilityEffect {
}
return combatChanged;
}
+
+ private boolean attachTokenTo(Card tok, SpellAbility sa) {
+ final Card host = sa.getHostCard();
+ final Game game = host.getGame();
+
+ GameObject aTo = Iterables.getFirst(
+ AbilityUtils.getDefinedObjects(host, sa.getParam("AttachedTo"), sa), null);
+
+ if (aTo instanceof GameEntity) {
+ GameEntity ge = (GameEntity)aTo;
+ // check what the token would be on the battlefield
+ Card lki = CardUtil.getLKICopy(tok);
+
+ lki.setLastKnownZone(tok.getController().getZone(ZoneType.Battlefield));
+
+ CardCollection preList = new CardCollection(lki);
+ game.getAction().checkStaticAbilities(false, Sets.newHashSet(lki), preList);
+
+ // TODO update when doing Attach Update
+ boolean canAttach = lki.isAura() || lki.isEquipment() || lki.isFortification();
+
+ if (lki.isAura()) {
+ if (!ge.canBeEnchantedBy(lki)) {
+ canAttach = false;
+ }
+ }
+ if (lki.isEquipment()) {
+ if (ge instanceof Card) {
+ Card gc = (Card) ge;
+ if (!gc.canBeEquippedBy(lki)) {
+ canAttach = false;
+ }
+ } else {
+ canAttach = false;
+ }
+ }
+ if (lki.isFortification()) {
+ if (ge instanceof Card) {
+ Card gc = (Card) ge;
+ if (!gc.isLand()) {
+ canAttach = false;
+ }
+ } else {
+ canAttach = false;
+ }
+ }
+
+ // reset static abilities
+ game.getAction().checkStaticAbilities(false);
+
+ if (!canAttach) {
+ // Token can't attach it
+ return false;
+ }
+
+ // TODO update when doing Attach Update
+ if (lki.isAura()) {
+ tok.enchantEntity(ge);
+ } else if (lki.isEquipment()) {
+ if (ge instanceof Card) {
+ Card gc = (Card) ge;
+ tok.equipCard(gc);
+ }
+ } else if (lki.isFortification()) {
+ if (ge instanceof Card) {
+ Card gc = (Card) ge;
+ tok.fortifyCard(gc);
+ }
+ }
+ return true;
+ } else {
+ // not a GameEntity, cant be attach
+ return false;
+ }
+ }
}
diff --git a/forge-game/src/main/java/forge/game/card/Card.java b/forge-game/src/main/java/forge/game/card/Card.java
index 4bf199ef40b..6fc7ffde41c 100644
--- a/forge-game/src/main/java/forge/game/card/Card.java
+++ b/forge-game/src/main/java/forge/game/card/Card.java
@@ -117,7 +117,7 @@ public class Card extends GameEntity implements Comparable {
// changes by AF animate and continuous static effects - timestamp is the key of maps
private final Map changedCardTypes = Maps.newTreeMap();
private final Map changedCardKeywords = Maps.newTreeMap();
- private final SortedMap changedCardColors = Maps.newTreeMap();
+ private final Map changedCardColors = Maps.newTreeMap();
// changes that say "replace each instance of one [color,type] by another - timestamp is the key of maps
private final CardChangedWords changedTextColors = new CardChangedWords();
@@ -132,7 +132,9 @@ public class Card extends GameEntity implements Comparable {
private final MapOfLists rememberMap = new HashMapOfLists<>(CollectionSuppliers.arrayLists());
private Map flipResult;
- private Map receivedDamageFromThisTurn = Maps.newTreeMap();
+ private Map receivedDamageFromThisTurn = Maps.newHashMap();
+ private Map receivedDamageFromPlayerThisTurn = Maps.newHashMap();
+
private Map dealtDamageToThisTurn = Maps.newTreeMap();
private Map dealtDamageToPlayerThisTurn = Maps.newTreeMap();
private final Map assignedDamageMap = Maps.newTreeMap();
@@ -181,8 +183,8 @@ public class Card extends GameEntity implements Comparable {
private long timestamp = -1; // permanents on the battlefield
// stack of set power/toughness
- private List newPT = Lists.newArrayList();
- private int baseLoyalty = 0;
+ private Map