diff --git a/forge-ai/src/main/java/forge/ai/AiAttackController.java b/forge-ai/src/main/java/forge/ai/AiAttackController.java index 63b58c2ff25..0920da8b4d8 100644 --- a/forge-ai/src/main/java/forge/ai/AiAttackController.java +++ b/forge-ai/src/main/java/forge/ai/AiAttackController.java @@ -23,7 +23,6 @@ import com.google.common.collect.Lists; import forge.ai.ability.AnimateAi; import forge.card.CardTypeView; import forge.game.GameEntity; -import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityUtils; import forge.game.ability.ApiType; import forge.game.ability.effects.ProtectEffect; @@ -196,12 +195,12 @@ public class AiAttackController { for (Card c : ai.getOpponents().getCardsIn(ZoneType.Battlefield)) { for (Trigger t : c.getTriggers()) { if (t.getMode() == TriggerType.Attacks) { - SpellAbility sa = t.getOverridingAbility(); - if (sa == null && t.hasParam("Execute")) { - sa = AbilityFactory.getAbility(c, t.getParam("Execute")); + SpellAbility sa = t.ensureAbility(); + if (sa == null) { + continue; } - if (sa != null && sa.getApi() == ApiType.EachDamage && "TriggeredAttacker".equals(sa.getParam("DefinedPlayers"))) { + if (sa.getApi() == ApiType.EachDamage && "TriggeredAttacker".equals(sa.getParam("DefinedPlayers"))) { List valid = CardLists.getValidCards(c.getController().getCreaturesInPlay(), sa.getParam("ValidCards"), c.getController(), c, sa); // TODO: this assumes that 1 damage is dealt per creature. Improve this to check the parameter/X to determine // how much damage is dealt by each of the creatures in the valid list. @@ -1349,9 +1348,9 @@ public class AiAttackController { if (!TriggerType.Exerted.equals(t.getMode())) { continue; } - SpellAbility sa = t.getOverridingAbility(); + SpellAbility sa = t.ensureAbility(); if (sa == null) { - sa = AbilityFactory.getAbility(c, t.getParam("Execute")); + continue; } if (sa.usesTargeting()) { sa.setActivatingPlayer(c.getController()); diff --git a/forge-ai/src/main/java/forge/ai/AiController.java b/forge-ai/src/main/java/forge/ai/AiController.java index fe64fcdebf6..28453966716 100644 --- a/forge-ai/src/main/java/forge/ai/AiController.java +++ b/forge-ai/src/main/java/forge/ai/AiController.java @@ -429,8 +429,7 @@ public class AiController { byte color = MagicColor.fromName(c); for (Card land : landList) { for (final SpellAbility m : ComputerUtilMana.getAIPlayableMana(land)) { - AbilityManaPart mp = m.getManaPart(); - if (mp.canProduce(MagicColor.toShortString(color), m)) { + if (m.canProduce(MagicColor.toShortString(color))) { return land; } } @@ -483,8 +482,7 @@ public class AiController { return land; } for (final SpellAbility m : ComputerUtilMana.getAIPlayableMana(land)) { - AbilityManaPart mp = m.getManaPart(); - if (mp.canProduce(MagicColor.toShortString(color), m)) { + if (m.canProduce(MagicColor.toShortString(color))) { return land; } } diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java index 4424b60a1bb..e51e0d847a4 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java @@ -28,7 +28,6 @@ import forge.card.ColorSet; import forge.card.MagicColor; import forge.card.mana.ManaCostShard; import forge.game.*; -import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityKey; import forge.game.ability.AbilityUtils; import forge.game.ability.ApiType; @@ -1468,15 +1467,13 @@ public class ComputerUtil { // Triggered abilities if (c.isCreature() && c.isInZone(ZoneType.Battlefield) && CombatUtil.canAttack(c)) { for (final Trigger t : c.getTriggers()) { - if ("Attacks".equals(t.getParam("Mode")) && t.hasParam("Execute")) { - String exec = c.getSVar(t.getParam("Execute")); - if (!exec.isEmpty()) { - SpellAbility trigSa = AbilityFactory.getAbility(exec, c); - if (trigSa != null && trigSa.getApi() == ApiType.LoseLife - && trigSa.getParamOrDefault("Defined", "").contains("Opponent")) { - trigSa.setHostCard(c); - damage += AbilityUtils.calculateAmount(trigSa.getHostCard(), trigSa.getParam("LifeAmount"), trigSa); - } + if (TriggerType.Attacks.equals(t.getMode())) { + SpellAbility sa = t.ensureAbility(); + if (sa == null) { + continue; + } + if (sa.getApi() == ApiType.LoseLife && sa.getParamOrDefault("Defined", "").contains("Opponent")) { + damage += AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("LifeAmount"), sa); } } } @@ -2075,9 +2072,8 @@ public class ComputerUtil { for (Card c : lands) { for (SpellAbility sa : c.getManaAbilities()) { - AbilityManaPart abmana = sa.getManaPart(); for (byte col : MagicColor.WUBRG) { - if (abmana.canProduce(MagicColor.toLongString(col))) { + if (sa.canProduce(MagicColor.toLongString(col))) { numProducers.get(col).add(c); } } @@ -2613,7 +2609,6 @@ public class ComputerUtil { theTriggers.addAll(c.getTriggers()); } for (Trigger trigger : theTriggers) { - Map trigParams = trigger.getMapParams(); final Card source = trigger.getHostCard(); @@ -2623,73 +2618,43 @@ public class ComputerUtil { if (!trigger.requirementsCheck(game)) { continue; } - TriggerType mode = trigger.getMode(); - if (mode != TriggerType.SpellCast) { + if (trigger.getMode() != TriggerType.SpellCast) { continue; } - if (trigParams.containsKey("ValidCard")) { - if (!card.isValid(trigParams.get("ValidCard"), source.getController(), source, sa)) { + if (trigger.hasParam("ValidCard")) { + if (!card.isValid(trigger.getParam("ValidCard"), source.getController(), source, sa)) { continue; } } - if (trigParams.containsKey("ValidActivatingPlayer")) { - if (!player.isValid(trigParams.get("ValidActivatingPlayer"), source.getController(), source, sa)) { + if (trigger.hasParam("ValidActivatingPlayer")) { + if (!player.isValid(trigger.getParam("ValidActivatingPlayer"), source.getController(), source, sa)) { continue; } } - if (!trigParams.containsKey("Execute")) { - // fall back for OverridingAbility - SpellAbility trigSa = trigger.getOverridingAbility(); - if (trigSa == null) { + // fall back for OverridingAbility + SpellAbility trigSa = trigger.ensureAbility(); + if (trigSa == null) { + continue; + } + if (trigSa.getApi() == ApiType.DealDamage) { + if (!"TriggeredActivator".equals(trigSa.getParam("Defined"))) { continue; } - if (trigSa.getApi() == ApiType.DealDamage) { - if (!"TriggeredActivator".equals(trigSa.getParam("Defined"))) { - continue; - } - if (!trigSa.hasParam("NumDmg")) { - continue; - } - damage += ComputerUtilCombat.predictDamageTo(player, - AbilityUtils.calculateAmount(source, trigSa.getParam("NumDmg"), trigSa), source, false); - } else if (trigSa.getApi() == ApiType.LoseLife) { - if (!"TriggeredActivator".equals(trigSa.getParam("Defined"))) { - continue; - } - if (!trigSa.hasParam("LifeAmount")) { - continue; - } - damage += AbilityUtils.calculateAmount(source, trigSa.getParam("LifeAmount"), trigSa); - } - } else { - String ability = source.getSVar(trigParams.get("Execute")); - if (ability.isEmpty()) { + if (!trigSa.hasParam("NumDmg")) { continue; } - - final Map abilityParams = AbilityFactory.getMapParams(ability); - if ((abilityParams.containsKey("AB") && abilityParams.get("AB").equals("DealDamage")) - || (abilityParams.containsKey("DB") && abilityParams.get("DB").equals("DealDamage"))) { - if (!"TriggeredActivator".equals(abilityParams.get("Defined"))) { - continue; - } - if (!abilityParams.containsKey("NumDmg")) { - continue; - } - damage += ComputerUtilCombat.predictDamageTo(player, - AbilityUtils.calculateAmount(source, abilityParams.get("NumDmg"), null), source, false); - } else if ((abilityParams.containsKey("AB") && abilityParams.get("AB").equals("LoseLife")) - || (abilityParams.containsKey("DB") && abilityParams.get("DB").equals("LoseLife"))) { - if (!"TriggeredActivator".equals(abilityParams.get("Defined"))) { - continue; - } - if (!abilityParams.containsKey("LifeAmount")) { - continue; - } - damage += AbilityUtils.calculateAmount(source, abilityParams.get("LifeAmount"), null); + damage += ComputerUtilCombat.predictDamageTo(player, + AbilityUtils.calculateAmount(source, trigSa.getParam("NumDmg"), trigSa), source, false); + } else if (trigSa.getApi() == ApiType.LoseLife) { + if (!"TriggeredActivator".equals(trigSa.getParam("Defined"))) { + continue; } + if (!trigSa.hasParam("LifeAmount")) { + continue; + } + damage += AbilityUtils.calculateAmount(source, trigSa.getParam("LifeAmount"), trigSa); } } @@ -2705,7 +2670,6 @@ public class ComputerUtil { theTriggers.addAll(card.getTriggers()); } for (Trigger trigger : theTriggers) { - Map trigParams = trigger.getMapParams(); final Card source = trigger.getHostCard(); @@ -2715,74 +2679,43 @@ public class ComputerUtil { if (!trigger.requirementsCheck(game)) { continue; } - if (trigParams.containsKey("CheckOnTriggeredCard") - && AbilityUtils.getDefinedCards(permanent, source.getSVar(trigParams.get("CheckOnTriggeredCard").split(" ")[0]), null).isEmpty()) { + if (trigger.hasParam("CheckOnTriggeredCard") + && AbilityUtils.getDefinedCards(permanent, source.getSVar(trigger.getParam("CheckOnTriggeredCard").split(" ")[0]), null).isEmpty()) { continue; } - TriggerType mode = trigger.getMode(); - if (mode != TriggerType.ChangesZone) { + if (trigger.getMode() != TriggerType.ChangesZone) { continue; } - if (!"Battlefield".equals(trigParams.get("Destination"))) { + if (!"Battlefield".equals(trigger.getParam("Destination"))) { continue; } - if (trigParams.containsKey("ValidCard")) { - if (!permanent.isValid(trigParams.get("ValidCard"), source.getController(), source, null)) { + if (trigger.hasParam("ValidCard")) { + if (!permanent.isValid(trigger.getParam("ValidCard"), source.getController(), source, null)) { continue; } } - if (!trigParams.containsKey("Execute")) { - // fall back for OverridingAbility - SpellAbility trigSa = trigger.getOverridingAbility(); - if (trigSa == null) { + // fall back for OverridingAbility + SpellAbility trigSa = trigger.ensureAbility(); + if (trigSa == null) { + continue; + } + if (trigSa.getApi() == ApiType.DealDamage) { + if (!"TriggeredCardController".equals(trigSa.getParam("Defined"))) { continue; } - if (trigSa.getApi() == ApiType.DealDamage) { - if (!"TriggeredCardController".equals(trigSa.getParam("Defined"))) { - continue; - } - if (!trigSa.hasParam("NumDmg")) { - continue; - } - damage += ComputerUtilCombat.predictDamageTo(player, - AbilityUtils.calculateAmount(source, trigSa.getParam("NumDmg"), trigSa), source, false); - } else if (trigSa.getApi() == ApiType.LoseLife) { - if (!"TriggeredCardController".equals(trigSa.getParam("Defined"))) { - continue; - } - if (!trigSa.hasParam("LifeAmount")) { - continue; - } - damage += AbilityUtils.calculateAmount(source, trigSa.getParam("LifeAmount"), trigSa); - } - } else { - String ability = source.getSVar(trigParams.get("Execute")); - if (ability.isEmpty()) { + if (!trigSa.hasParam("NumDmg")) { continue; } - - final Map abilityParams = AbilityFactory.getMapParams(ability); - // Destroy triggers - if ((abilityParams.containsKey("AB") && abilityParams.get("AB").equals("DealDamage")) - || (abilityParams.containsKey("DB") && abilityParams.get("DB").equals("DealDamage"))) { - if (!"TriggeredCardController".equals(abilityParams.get("Defined"))) { - continue; - } - if (!abilityParams.containsKey("NumDmg")) { - continue; - } - damage += ComputerUtilCombat.predictDamageTo(player, - AbilityUtils.calculateAmount(source, abilityParams.get("NumDmg"), null), source, false); - } else if ((abilityParams.containsKey("AB") && abilityParams.get("AB").equals("LoseLife")) - || (abilityParams.containsKey("DB") && abilityParams.get("DB").equals("LoseLife"))) { - if (!"TriggeredCardController".equals(abilityParams.get("Defined"))) { - continue; - } - if (!abilityParams.containsKey("LifeAmount")) { - continue; - } - damage += AbilityUtils.calculateAmount(source, abilityParams.get("LifeAmount"), null); + damage += ComputerUtilCombat.predictDamageTo(player, + AbilityUtils.calculateAmount(source, trigSa.getParam("NumDmg"), trigSa), source, false); + } else if (trigSa.getApi() == ApiType.LoseLife) { + if (!"TriggeredCardController".equals(trigSa.getParam("Defined"))) { + continue; } + if (!trigSa.hasParam("LifeAmount")) { + continue; + } + damage += AbilityUtils.calculateAmount(source, trigSa.getParam("LifeAmount"), trigSa); } } return damage; diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java index 69b9191e6b4..109c5dfc423 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java @@ -14,7 +14,6 @@ import forge.deck.Deck; import forge.deck.DeckSection; import forge.game.Game; import forge.game.GameObject; -import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityUtils; import forge.game.ability.ApiType; import forge.game.card.*; @@ -697,39 +696,21 @@ public class ComputerUtilCard { } // same for Trigger that does make Tokens for(Trigger t:c.getTriggers()){ - SpellAbility sa = t.getOverridingAbility(); - String sTokenTypes = null; + SpellAbility sa = t.ensureAbility(); if (sa != null) { if (sa.getApi() != ApiType.Token || !sa.hasParam("TokenTypes")) { continue; } - sTokenTypes = sa.getParam("TokenTypes"); - } else if (t.hasParam("Execute")) { - String name = t.getParam("Execute"); - if (!c.hasSVar(name)) { - continue; + for (String var : sa.getParam("TokenTypes").split(",")) { + if (!CardType.isACreatureType(var)) { + continue; + } + Integer count = typesInDeck.get(var); + if (count == null) { + count = 0; + } + typesInDeck.put(var, count + 1); } - - Map params = AbilityFactory.getMapParams(c.getSVar(name)); - if (!params.containsKey("TokenTypes")) { - continue; - } - sTokenTypes = params.get("TokenTypes"); - } - - if (sTokenTypes == null) { - continue; - } - - for (String var : sTokenTypes.split(",")) { - if (!CardType.isACreatureType(var)) { - continue; - } - Integer count = typesInDeck.get(var); - if (count == null) { - count = 0; - } - typesInDeck.put(var, count + 1); } } // special rule for Fabricate and Servo diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java index 753aaf17989..fe240b141db 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java @@ -28,7 +28,6 @@ import com.google.common.collect.Maps; import forge.game.CardTraitBase; import forge.game.Game; import forge.game.GameEntity; -import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityKey; import forge.game.ability.AbilityUtils; import forge.game.ability.ApiType; @@ -46,7 +45,6 @@ import forge.game.replacement.ReplacementType; import forge.game.spellability.SpellAbility; import forge.game.staticability.StaticAbility; import forge.game.trigger.Trigger; -import forge.game.trigger.TriggerHandler; import forge.game.trigger.TriggerType; import forge.game.zone.ZoneType; import forge.util.MyRandom; @@ -1274,44 +1272,26 @@ public class ComputerUtilCombat { } for (final Trigger trigger : theTriggers) { - final Map trigParams = trigger.getMapParams(); final Card source = trigger.getHostCard(); if (!ComputerUtilCombat.combatTriggerWillTrigger(attacker, blocker, trigger, combat)) { continue; } - Map abilityParams = null; - if (trigger.getOverridingAbility() != null) { - abilityParams = trigger.getOverridingAbility().getMapParams(); - } else if (trigParams.containsKey("Execute")) { - final String ability = source.getSVar(trigParams.get("Execute")); - abilityParams = AbilityFactory.getMapParams(ability); - } else { + SpellAbility sa = trigger.ensureAbility(); + if (sa == null) { continue; } - if (abilityParams.containsKey("ValidTgts") || abilityParams.containsKey("Tgt")) { + if (sa.usesTargeting()) { continue; // targeted pumping not supported } - if (abilityParams.containsKey("AB") && !abilityParams.get("AB").equals("Pump") - && !abilityParams.get("AB").equals("PumpAll")) { - continue; - } - if (abilityParams.containsKey("DB") && !abilityParams.get("DB").equals("Pump") - && !abilityParams.get("DB").equals("PumpAll")) { + + if (!ApiType.Pump.equals(sa.getApi()) && !ApiType.PumpAll.equals(sa.getApi())) { continue; } - if (abilityParams.containsKey("Cost")) { - SpellAbility sa = null; - if (trigger.getOverridingAbility() != null) { - sa = trigger.getOverridingAbility(); - } else { - final String ability = source.getSVar(trigParams.get("Execute")); - sa = AbilityFactory.getAbility(ability, source); - } - + if (sa.hasParam("Cost")) { sa.setActivatingPlayer(source.getController()); if (!CostPayment.canPayAdditionalCosts(sa.getPayCosts(), sa)) { continue; @@ -1319,15 +1299,15 @@ public class ComputerUtilCombat { } List list = Lists.newArrayList(); - if (!abilityParams.containsKey("ValidCards")) { - list = AbilityUtils.getDefinedCards(source, abilityParams.get("Defined"), null); + if (!sa.hasParam("ValidCards")) { + list = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), null); } - if (abilityParams.containsKey("Defined") && abilityParams.get("Defined").equals("TriggeredAttacker")) { + if (sa.hasParam("Defined") && sa.getParam("Defined").equals("TriggeredAttacker")) { list.add(attacker); } - if (abilityParams.containsKey("ValidCards")) { - if (attacker.isValid(abilityParams.get("ValidCards").split(","), source.getController(), source, null) - || attacker.isValid(abilityParams.get("ValidCards").replace("attacking+", "").split(","), + if (sa.hasParam("ValidCards")) { + if (attacker.isValid(sa.getParam("ValidCards").split(","), source.getController(), source, null) + || attacker.isValid(sa.getParam("ValidCards").replace("attacking+", "").split(","), source.getController(), source, null)) { list.add(attacker); } @@ -1338,11 +1318,11 @@ public class ComputerUtilCombat { if (!list.contains(attacker)) { continue; } - if (!abilityParams.containsKey("NumAtt")) { + if (!sa.hasParam("NumAtt")) { continue; } - String att = abilityParams.get("NumAtt"); + String att = sa.getParam("NumAtt"); if (att.startsWith("+")) { att = att.substring(1); } @@ -1657,35 +1637,26 @@ public class ComputerUtilCombat { theTriggers.addAll(card.getTriggers()); } for (Trigger trigger : theTriggers) { - Map trigParams = trigger.getMapParams(); final Card source = trigger.getHostCard(); if (!ComputerUtilCombat.combatTriggerWillTrigger(attacker, blocker, trigger, null)) { continue; } - //consider delayed triggers - if (trigParams.containsKey("DelayedTrigger")) { - String sVarName = trigParams.get("DelayedTrigger"); - trigger = TriggerHandler.parseTrigger(source.getSVar(sVarName), trigger.getHostCard(), true); - trigParams = trigger.getMapParams(); - } - if (!trigParams.containsKey("Execute")) { + SpellAbility sa = trigger.ensureAbility(); + if (sa == null) { continue; } - String ability = source.getSVar(trigParams.get("Execute")); - final Map abilityParams = AbilityFactory.getMapParams(ability); - if ((abilityParams.containsKey("AB") && abilityParams.get("AB").equals("Destroy")) - || (abilityParams.containsKey("DB") && abilityParams.get("DB").equals("Destroy"))) { - if (!abilityParams.containsKey("Defined")) { + if (ApiType.Destroy.equals(sa.getApi())) { + if (!sa.hasParam("Defined")) { continue; } - if (abilityParams.get("Defined").equals("TriggeredAttacker")) { + if (sa.getParam("Defined").equals("TriggeredAttacker")) { return true; } - if (abilityParams.get("Defined").equals("Self") && source.equals(attacker)) { + if (sa.getParam("Defined").equals("Self") && source.equals(attacker)) { return true; } - if (abilityParams.get("Defined").equals("TriggeredTarget") && source.equals(blocker)) { + if (sa.getParam("Defined").equals("TriggeredTarget") && source.equals(blocker)) { return true; } } @@ -1935,36 +1906,27 @@ public class ComputerUtilCombat { theTriggers.addAll(card.getTriggers()); } for (Trigger trigger : theTriggers) { - Map trigParams = trigger.getMapParams(); final Card source = trigger.getHostCard(); if (!ComputerUtilCombat.combatTriggerWillTrigger(attacker, blocker, trigger, null)) { continue; } - //consider delayed triggers - if (trigParams.containsKey("DelayedTrigger")) { - String sVarName = trigParams.get("DelayedTrigger"); - trigger = TriggerHandler.parseTrigger(source.getSVar(sVarName), trigger.getHostCard(), true); - trigParams = trigger.getMapParams(); - } - if (!trigParams.containsKey("Execute")) { + SpellAbility sa = trigger.ensureAbility(); + if (sa == null) { continue; } - String ability = source.getSVar(trigParams.get("Execute")); - final Map abilityParams = AbilityFactory.getMapParams(ability); // Destroy triggers - if ((abilityParams.containsKey("AB") && abilityParams.get("AB").equals("Destroy")) - || (abilityParams.containsKey("DB") && abilityParams.get("DB").equals("Destroy"))) { - if (!abilityParams.containsKey("Defined")) { + if (ApiType.Destroy.equals(sa.getApi())) { + if (!sa.hasParam("Defined")) { continue; } - if (abilityParams.get("Defined").equals("TriggeredBlocker")) { + if (sa.getParam("Defined").equals("TriggeredBlocker")) { return true; } - if (abilityParams.get("Defined").equals("Self") && source.equals(blocker)) { + if (sa.getParam("Defined").equals("Self") && source.equals(blocker)) { return true; } - if (abilityParams.get("Defined").equals("TriggeredTarget") && source.equals(attacker)) { + if (sa.getParam("Defined").equals("TriggeredTarget") && source.equals(attacker)) { return true; } } @@ -2578,26 +2540,24 @@ public class ComputerUtilCombat { // Test for some special triggers that can change the creature in combat for (Trigger t : attacker.getTriggers()) { - if (t.getMode() == TriggerType.Attacks && t.hasParam("Execute")) { - if (!attacker.hasSVar(t.getParam("Execute"))) { + if (t.getMode() == TriggerType.Attacks) { + SpellAbility exec = t.ensureAbility(); + if (exec == null) { continue; } - SpellAbility exec = AbilityFactory.getAbility(attacker, t.getParam("Execute")); - if (exec != null) { - if (exec.getApi() == ApiType.Clone && "Self".equals(exec.getParam("CloneTarget")) - && exec.hasParam("ValidTgts") && exec.getParam("ValidTgts").contains("Creature") - && exec.getParam("ValidTgts").contains("attacking")) { - // Tilonalli's Skinshifter and potentially other similar cards that can clone other stuff - // while attacking - if (exec.getParam("ValidTgts").contains("nonLegendary") && attacker.getType().isLegendary()) { - continue; - } - int maxPwr = 0; - for (Card c : attacker.getController().getCreaturesInPlay()) { - if (c.getNetPower() > maxPwr || (c.getNetPower() == maxPwr && ComputerUtilCard.evaluateCreature(c) > ComputerUtilCard.evaluateCreature(attackerAfterTrigs))) { - maxPwr = c.getNetPower(); - attackerAfterTrigs = c; - } + if (exec.getApi() == ApiType.Clone && "Self".equals(exec.getParam("CloneTarget")) + && exec.hasParam("ValidTgts") && exec.getParam("ValidTgts").contains("Creature") + && exec.getParam("ValidTgts").contains("attacking")) { + // Tilonalli's Skinshifter and potentially other similar cards that can clone other stuff + // while attacking + if (exec.getParam("ValidTgts").contains("nonLegendary") && attacker.getType().isLegendary()) { + continue; + } + int maxPwr = 0; + for (Card c : attacker.getController().getCreaturesInPlay()) { + if (c.getNetPower() > maxPwr || (c.getNetPower() == maxPwr && ComputerUtilCard.evaluateCreature(c) > ComputerUtilCard.evaluateCreature(attackerAfterTrigs))) { + maxPwr = c.getNetPower(); + attackerAfterTrigs = c; } } } diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java b/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java index 1a0985f9acc..ba2cb1bea5d 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java @@ -10,11 +10,10 @@ import forge.card.mana.ManaAtom; import forge.card.mana.ManaCost; import forge.card.mana.ManaCostParser; import forge.card.mana.ManaCostShard; +import forge.game.CardTraitPredicates; import forge.game.Game; import forge.game.GameActionUtil; -import forge.game.ability.AbilityKey; -import forge.game.ability.AbilityUtils; -import forge.game.ability.ApiType; +import forge.game.ability.*; import forge.game.card.*; import forge.game.combat.CombatUtil; import forge.game.cost.*; @@ -25,10 +24,13 @@ import forge.game.phase.PhaseType; import forge.game.player.Player; import forge.game.player.PlayerPredicates; import forge.game.replacement.ReplacementEffect; +import forge.game.replacement.ReplacementLayer; import forge.game.replacement.ReplacementType; import forge.game.spellability.AbilityManaPart; import forge.game.spellability.AbilitySub; import forge.game.spellability.SpellAbility; +import forge.game.trigger.Trigger; +import forge.game.trigger.TriggerType; import forge.game.zone.ZoneType; import forge.util.MyRandom; import forge.util.TextUtil; @@ -52,17 +54,17 @@ public class ComputerUtilMana { public static boolean canPayManaCost(final SpellAbility sa, final Player ai, final int extraMana) { return payManaCost(sa, ai, true, extraMana, true); } - + /** * Return the number of colors used for payment for Converge */ public static int getConvergeCount(final SpellAbility sa, final Player ai) { - ManaCostBeingPaid cost = ComputerUtilMana.calculateManaCost(sa, true, 0); - if (payManaCost(cost, sa, ai, true, true)) { - return cost.getSunburst(); - } else { - return 0; - } + ManaCostBeingPaid cost = ComputerUtilMana.calculateManaCost(sa, true, 0); + if (payManaCost(cost, sa, ai, true, true)) { + return cost.getSunburst(); + } else { + return 0; + } } // Does not check if mana sources can be used right now, just checks for potential chance. @@ -124,7 +126,7 @@ public class ComputerUtilMana { for (final ManaCostShard shard : manaAbilityMap.keySet()) { for (SpellAbility ability : manaAbilityMap.get(shard)) { - final Card hostCard = ability.getHostCard(); + final Card hostCard = ability.getHostCard(); if (!manaCardMap.containsKey(hostCard)) { manaCardMap.put(hostCard, scoreManaProducingCard(hostCard)); orderedCards.add(hostCard); @@ -238,7 +240,7 @@ public class ComputerUtilMana { } } } - + public static SpellAbility chooseManaAbility(ManaCostBeingPaid cost, SpellAbility sa, Player ai, ManaCostShard toPay, Collection saList, boolean checkCosts) { for (final SpellAbility ma : saList) { @@ -316,7 +318,191 @@ public class ComputerUtilMana { } return null; } - + + public static String predictManaReplacement(SpellAbility saPayment, Player ai, ManaCostShard toPay) { + Card hostCard = saPayment.getHostCard(); + Game game = hostCard.getGame(); + String manaProduced = toPay.isSnow() && hostCard.isSnow() ? "S" : GameActionUtil.generatedTotalMana(saPayment); + //String originalProduced = manaProduced; + + final Map repParams = AbilityKey.newMap(); + repParams.put(AbilityKey.Mana, manaProduced); + repParams.put(AbilityKey.Affected, hostCard); + repParams.put(AbilityKey.Player, ai); + repParams.put(AbilityKey.AbilityMana, saPayment); // RootAbility + + // TODO Damping Sphere might replace later? + + // add flags to replacementEffects to filter better? + List reList = game.getReplacementHandler().getReplacementList(ReplacementType.ProduceMana, repParams, ReplacementLayer.Other); + + List replaceMana = Lists.newArrayList(); + List replaceType = Lists.newArrayList(); + List replaceAmount = Lists.newArrayList(); // currently only multi + + // try to guess the color the mana gets replaced to + for (ReplacementEffect re : reList) { + SpellAbility o = re.getOverridingAbility(); + + if (o == null || o.getApi() != ApiType.ReplaceMana) { + continue; + } + + // this one does replace the amount too + if (o.hasParam("ReplaceMana")) { + replaceMana.add(o); + } else if (o.hasParam("ReplaceType") || o.hasParam("ReplaceColor")) { + // this one replaces the color/type + // check if this one can be replaced into wanted mana shard + replaceType.add(o); + } else if (o.hasParam("ReplaceAmount")) { + replaceAmount.add(o); + } + } + + // it is better to apply these ones first + if (!replaceMana.isEmpty()) { + for (SpellAbility saMana : replaceMana) { + // one of then has to Any + // one of then has to C + // one of then has to B + String m = saMana.getParam("ReplaceMana"); + if ("Any".equals(m)) { + byte rs = MagicColor.GREEN; + for (byte c : MagicColor.WUBRGC) { + if (toPay.canBePaidWithManaOfColor(c)) { + rs = c; + break; + } + } + manaProduced = MagicColor.toShortString(rs); + } else { + manaProduced = m; + } + } + } + + // then apply this one + if (!replaceType.isEmpty()) { + for (SpellAbility saMana : replaceAmount) { + Card card = saMana.getHostCard(); + if (saMana.hasParam("ReplaceType")) { + // replace color and colorless + String color = saMana.getParam("ReplaceType"); + if ("Any".equals(color)) { + byte rs = MagicColor.GREEN; + for (byte c : MagicColor.WUBRGC) { + if (toPay.canBePaidWithManaOfColor(c)) { + rs = c; + break; + } + } + color = MagicColor.toShortString(rs); + } + for (byte c : MagicColor.WUBRGC) { + String s = MagicColor.toShortString(c); + manaProduced = manaProduced.replace(s, color); + } + } else if (saMana.hasParam("ReplaceColor")) { + // replace color + String color = saMana.getParam("ReplaceColor"); + if ("Chosen".equals(color)) { + if (card.hasChosenColor()) { + color = MagicColor.toShortString(card.getChosenColor()); + } + } + if (saMana.hasParam("ReplaceOnly")) { + manaProduced = manaProduced.replace(saMana.getParam("ReplaceOnly"), color); + } else { + for (byte c : MagicColor.WUBRG) { + String s = MagicColor.toShortString(c); + manaProduced = manaProduced.replace(s, color); + } + } + } + } + } + + // then multiply if able + if (!replaceAmount.isEmpty()) { + int totalAmount = 1; + for (SpellAbility saMana : replaceAmount) { + totalAmount *= Integer.valueOf(saMana.getParam("ReplaceAmount")); + } + manaProduced = StringUtils.repeat(manaProduced, " ", totalAmount); + } + + return manaProduced; + } + + public static String predictManafromSpellAbility(SpellAbility saPayment, Player ai, ManaCostShard toPay) { + Card hostCard = saPayment.getHostCard(); + + String manaProduced = predictManaReplacement(saPayment, ai, toPay); + String originalProduced = manaProduced; + + if (originalProduced.isEmpty()) { + return manaProduced; + } + + // Run triggers like Nissa + final Map runParams = AbilityKey.mapFromCard(hostCard); + runParams.put(AbilityKey.Player, ai); // assuming AI would only ever gives itself mana + runParams.put(AbilityKey.AbilityMana, saPayment); + runParams.put(AbilityKey.Produced, manaProduced); + runParams.put(AbilityKey.Activator, ai); + for (Trigger tr : ai.getGame().getTriggerHandler().getActiveTrigger(TriggerType.TapsForMana, runParams)) { + SpellAbility trSA = tr.ensureAbility(); + if (trSA == null) { + continue; + } + if (ApiType.Mana.equals(trSA.getApi())) { + int pAmount = trSA.hasParam("Amount") ? Integer.valueOf(trSA.getParam("Amount")) : 1; + String produced = trSA.getParam("Produced"); + if (produced.equals("Chosen")) { + produced = MagicColor.toShortString(trSA.getHostCard().getChosenColor()); + } + manaProduced += " " + StringUtils.repeat(produced, pAmount); + } else if (ApiType.ManaReflected.equals(trSA.getApi())) { + final String colorOrType = trSA.getParamOrDefault("ColorOrType", "Color"); + // currently Color or Type, Type is colors + colorless + final String reflectProperty = trSA.getParam("ReflectProperty"); + + if (reflectProperty.equals("Produced") && !originalProduced.isEmpty()) { + // check if a colorless shard can be paid from the trigger + if (toPay.equals(ManaCostShard.COLORLESS) && colorOrType.equals("Type") && originalProduced.contains("C")) { + manaProduced += " " + "C"; + } else if (originalProduced.length() == 1) { + // if length is only one, and it either is equal C == Type + if (colorOrType.equals("Type") || !originalProduced.equals("C")) { + manaProduced += " " + originalProduced; + } + } else { + // should it look for other shards too? + boolean found = false; + for (String s : originalProduced.split(" ")) { + if (colorOrType.equals("Type") || !s.equals("C") && toPay.canBePaidWithManaOfColor(MagicColor.fromName(s))) { + found = true; + manaProduced += " " + s; + break; + } + } + // no good mana found? just add the first generated color + if (!found) { + for (String s : originalProduced.split(" ")) { + if (colorOrType.equals("Type") || !s.equals("C")) { + manaProduced += " " + s; + break; + } + } + } + } + } + } + } + return manaProduced; + } + public static CardCollection getManaSourcesToPayCost(final ManaCostBeingPaid cost, final SpellAbility sa, final Player ai) { CardCollection manaSources = new CardCollection(); @@ -392,29 +578,19 @@ public class ComputerUtilMana { manaSources.add(saPayment.getHostCard()); setExpressColorChoice(sa, ai, cost, toPay, saPayment); - String manaProduced = toPay.isSnow() ? "S" : GameActionUtil.generatedMana(saPayment); - manaProduced = AbilityManaPart.applyManaReplacement(saPayment, manaProduced); + String manaProduced = predictManafromSpellAbility(saPayment, ai, toPay); + //System.out.println(manaProduced); payMultipleMana(cost, manaProduced, ai); // remove from available lists - /* - * Refactoring this code to sourcesForShards.values().removeIf((SpellAbility srcSa) -> srcSa.getHostCard().equals(saPayment.getHostCard())); - * causes Android build not to compile - * */ - Iterator itSa = sourcesForShards.values().iterator(); - while (itSa.hasNext()) { - SpellAbility srcSa = itSa.next(); - if (srcSa.getHostCard().equals(saPayment.getHostCard())) { - itSa.remove(); - } - } + Iterables.removeIf(sourcesForShards.values(), CardTraitPredicates.isHostCard(saPayment.getHostCard())); } handleOfferingsAI(sa, true, cost.isPaid()); refundMana(manaSpentToPay, ai, sa); - + return manaSources; } // getManaSourcesToPayCost() @@ -427,14 +603,14 @@ public class ComputerUtilMana { List paymentList = Lists.newArrayList(); if (payManaCostFromPool(cost, sa, ai, test, manaSpentToPay)) { - return true; // paid all from floating mana + return true; // paid all from floating mana } - + boolean hasConverge = sa.getHostCard().hasConverge(); ListMultimap sourcesForShards = getSourcesForShards(cost, sa, ai, test, - checkPlayable, manaSpentToPay, hasConverge); + checkPlayable, manaSpentToPay, hasConverge); if (sourcesForShards == null && !purePhyrexian) { - return false; // no mana abilities to use for paying + return false; // no mana abilities to use for paying } final ManaPool manapool = ai.getManaPool(); @@ -443,26 +619,43 @@ public class ComputerUtilMana { // Loop over mana needed while (!cost.isPaid()) { + while (!cost.isPaid() && !manapool.isEmpty()) { + boolean found = false; + for (byte color : MagicColor.WUBRGC) { + if (manapool.tryPayCostWithColor(color, sa, cost)) { + found = true; + break; + } + } + if (!found) { + break; + } + } + if (cost.isPaid()) { + break; + } toPay = getNextShardToPay(cost); boolean lifeInsteadOfBlack = toPay.isBlack() && ai.hasKeyword("PayLifeInsteadOf:B"); Collection saList = null; - if (hasConverge && - (toPay == ManaCostShard.GENERIC || toPay == ManaCostShard.X)) { - final int unpaidColors = cost.getUnpaidColors() + cost.getColorsPaid() ^ ManaCostShard.COLORS_SUPERPOSITION; - for (final byte b : ColorSet.fromMask(unpaidColors)) { // try and pay other colors for converge - final ManaCostShard shard = ManaCostShard.valueOf(b); - saList = sourcesForShards.get(shard); - if (saList != null && !saList.isEmpty()) { - toPay = shard; - break; - } - } - if (saList == null || saList.isEmpty()) { // failed to converge, revert to paying generic - saList = sourcesForShards.get(toPay); - hasConverge = false; - } + if (hasConverge && + (toPay == ManaCostShard.GENERIC || toPay == ManaCostShard.X)) { + final int unpaidColors = cost.getUnpaidColors() + cost.getColorsPaid() ^ ManaCostShard.COLORS_SUPERPOSITION; + for (final byte b : ColorSet.fromMask(unpaidColors)) { + // try and pay other colors for converge + final ManaCostShard shard = ManaCostShard.valueOf(b); + saList = sourcesForShards.get(shard); + if (saList != null && !saList.isEmpty()) { + toPay = shard; + break; + } + } + if (saList == null || saList.isEmpty()) { + // failed to converge, revert to paying generic + saList = sourcesForShards.get(toPay); + hasConverge = false; + } } else { if (!(sourcesForShards == null && purePhyrexian)) { saList = sourcesForShards.get(toPay); @@ -529,33 +722,23 @@ public class ComputerUtilMana { setExpressColorChoice(sa, ai, cost, toPay, saPayment); if (test) { - // Check energy when testing - CostPayEnergy energyCost = saPayment.getPayCosts().getCostEnergy(); - if (energyCost != null) { - testEnergyPool -= Integer.parseInt(energyCost.getAmount()); - if (testEnergyPool < 0) { - // Can't pay energy cost - break; - } - } + // Check energy when testing + CostPayEnergy energyCost = saPayment.getPayCosts().getCostEnergy(); + if (energyCost != null) { + testEnergyPool -= Integer.parseInt(energyCost.getAmount()); + if (testEnergyPool < 0) { + // Can't pay energy cost + break; + } + } - String manaProduced = toPay.isSnow() ? "S" : GameActionUtil.generatedMana(saPayment); - manaProduced = AbilityManaPart.applyManaReplacement(saPayment, manaProduced); - //System.out.println(manaProduced); + String manaProduced = predictManafromSpellAbility(saPayment, ai, toPay); + + // System.out.println(manaProduced); payMultipleMana(cost, manaProduced, ai); // remove from available lists - /* - * Refactoring this code to sourcesForShards.values().removeIf((SpellAbility srcSa) -> srcSa.getHostCard().equals(saPayment.getHostCard())); - * causes Android build not to compile - * */ - Iterator itSa = sourcesForShards.values().iterator(); - while (itSa.hasNext()) { - SpellAbility srcSa = itSa.next(); - if (srcSa.getHostCard().equals(saPayment.getHostCard())) { - itSa.remove(); - } - } + Iterables.removeIf(sourcesForShards.values(), CardTraitPredicates.isHostCard(saPayment.getHostCard())); } else { final CostPayment pay = new CostPayment(saPayment.getPayCosts(), saPayment); @@ -570,20 +753,10 @@ public class ComputerUtilMana { // no need to remove abilities from resource map, // once their costs are paid and consume resources, they can not be used again - - if (hasConverge) { // hack to prevent converge re-using sources - // remove from available lists - /* - * Refactoring this code to sourcesForShards.values().removeIf((SpellAbility srcSa) -> srcSa.getHostCard().equals(saPayment.getHostCard())); - * causes Android build not to compile - * */ - Iterator itSa = sourcesForShards.values().iterator(); - while (itSa.hasNext()) { - SpellAbility srcSa = itSa.next(); - if (srcSa.getHostCard().equals(saPayment.getHostCard())) { - itSa.remove(); - } - } + + if (hasConverge) { + // hack to prevent converge re-using sources + Iterables.removeIf(sourcesForShards.values(), CardTraitPredicates.isHostCard(saPayment.getHostCard())); } } } @@ -596,15 +769,6 @@ public class ComputerUtilMana { // extraMana, sa.getHostCard(), sa.toUnsuppressedString(), StringUtils.join(paymentPlan, "\n\t")); // } - // See if it's possible to pay with something that was left in the mana pool in corner cases, - // e.g. Gemstone Caverns with a Luck counter on it generating colored mana (which fails to be - // processed correctly on a per-ability basis, leaving floating mana in the pool) - if (!cost.isPaid() && !manapool.isEmpty()) { - for (byte color : MagicColor.WUBRGC) { - manapool.tryPayCostWithColor(color, sa, cost); - } - } - // The cost is still unpaid, so refund the mana and report if (!cost.isPaid()) { refundMana(manaSpentToPay, ai, sa); @@ -633,15 +797,16 @@ public class ComputerUtilMana { } - /** - * Creates a mapping between the required mana shards and the available spell abilities to pay for them - */ - private static ListMultimap getSourcesForShards(final ManaCostBeingPaid cost, - final SpellAbility sa, final Player ai, final boolean test, final boolean checkPlayable, - List manaSpentToPay, final boolean hasConverge) { - // arrange all mana abilities by color produced. + /** + * Creates a mapping between the required mana shards and the available spell abilities to pay for them + */ + private static ListMultimap getSourcesForShards(final ManaCostBeingPaid cost, + final SpellAbility sa, final Player ai, final boolean test, final boolean checkPlayable, + List manaSpentToPay, final boolean hasConverge) { + // arrange all mana abilities by color produced. final ListMultimap manaAbilityMap = ComputerUtilMana.groupSourcesByManaColor(ai, checkPlayable); - if (manaAbilityMap.isEmpty()) { // no mana abilities, bailing out + if (manaAbilityMap.isEmpty()) { + // no mana abilities, bailing out refundMana(manaSpentToPay, ai, sa); handleOfferingsAI(sa, test, cost.isPaid()); return null; @@ -652,25 +817,26 @@ public class ComputerUtilMana { // select which abilities may be used for each shard ListMultimap sourcesForShards = ComputerUtilMana.groupAndOrderToPayShards(ai, manaAbilityMap, cost); - if (hasConverge) { // add extra colors for paying converge - final int unpaidColors = cost.getUnpaidColors() + cost.getColorsPaid() ^ ManaCostShard.COLORS_SUPERPOSITION; - for (final byte b : ColorSet.fromMask(unpaidColors)) { - final ManaCostShard shard = ManaCostShard.valueOf(b); - if (!sourcesForShards.containsKey(shard)) { - if (ai.getManaPool().canPayForShardWithColor(shard, b)) { + if (hasConverge) { + // add extra colors for paying converge + final int unpaidColors = cost.getUnpaidColors() + cost.getColorsPaid() ^ ManaCostShard.COLORS_SUPERPOSITION; + for (final byte b : ColorSet.fromMask(unpaidColors)) { + final ManaCostShard shard = ManaCostShard.valueOf(b); + if (!sourcesForShards.containsKey(shard)) { + if (ai.getManaPool().canPayForShardWithColor(shard, b)) { for (SpellAbility saMana : manaAbilityMap.get((int)b)) { - sourcesForShards.get(shard).add(sourcesForShards.get(shard).size(), saMana); + sourcesForShards.get(shard).add(saMana); } } - } - } + } + } } sortManaAbilities(sourcesForShards, sa); if (DEBUG_MANA_PAYMENT) { System.out.println("DEBUG_MANA_PAYMENT: sourcesForShards = " + sourcesForShards); } - return sourcesForShards; - } + return sourcesForShards; + } /** * Checks if the given mana cost can be paid from floating mana. @@ -681,9 +847,9 @@ public class ComputerUtilMana { * @param manaSpentToPay list of mana spent * @return whether the floating mana is sufficient to pay the cost fully */ - private static boolean payManaCostFromPool(final ManaCostBeingPaid cost, final SpellAbility sa, final Player ai, + private static boolean payManaCostFromPool(final ManaCostBeingPaid cost, final SpellAbility sa, final Player ai, final boolean test, List manaSpentToPay) { - final boolean hasConverge = sa.getHostCard().hasConverge(); + final boolean hasConverge = sa.getHostCard().hasConverge(); List unpaidShards = cost.getUnpaidShards(); Collections.sort(unpaidShards); // most difficult shards must come first for (ManaCostShard part : unpaidShards) { @@ -764,15 +930,15 @@ public class ComputerUtilMana { // if we are simulating mana payment for the human controller, use the first mana available (and avoid prompting the human player) if (!(ai.getController() instanceof PlayerControllerAi)) { - return manaChoices.get(0); + return manaChoices.get(0); } // Let them choose then return ai.getController().chooseManaFromPool(manaChoices); } - private static List> selectManaToPayFor(final ManaPool manapool, final ManaCostShard shard, - final SpellAbility saBeingPaidFor, String restriction, final byte colorsPaid) { + private static List> selectManaToPayFor(final ManaPool manapool, final ManaCostShard shard, + final SpellAbility saBeingPaidFor, String restriction, final byte colorsPaid) { final List> weightedOptions = new ArrayList<>(); for (final Mana thisMana : manapool) { if (!manapool.canPayForShardWithColor(shard, thisMana.getColor())) { @@ -794,11 +960,11 @@ public class ComputerUtilMana { int weight = 0; if (colorsPaid == -1) { - // prefer colorless mana to spend - weight += thisMana.isColorless() ? 5 : 0; + // prefer colorless mana to spend + weight += thisMana.isColorless() ? 5 : 0; } else { - // get more colors for converge - weight += (thisMana.getColor() | colorsPaid) != colorsPaid ? 5 : 0; + // get more colors for converge + weight += (thisMana.getColor() | colorsPaid) != colorsPaid ? 5 : 0; } // prefer restricted mana to spend @@ -815,7 +981,7 @@ public class ComputerUtilMana { } return weightedOptions; } - + private static void setExpressColorChoice(final SpellAbility sa, final Player ai, ManaCostBeingPaid cost, ManaCostShard toPay, SpellAbility saPayment) { @@ -856,7 +1022,7 @@ public class ComputerUtilMana { if (isManaSourceReserved(ai, sourceCard, sa)) { return false; } - + if (toPay.isSnow() && !sourceCard.isSnow()) { return false; } @@ -1002,7 +1168,7 @@ public class ComputerUtilMana { *

* getComboManaChoice. *

- * + * * @param manaAb * a {@link forge.game.spellability.SpellAbility} object. * @param saRoot @@ -1026,7 +1192,7 @@ public class ComputerUtilMana { choice = abMana.getExpressChoice(); abMana.clearExpressChoice(); byte colorMask = ManaAtom.fromName(choice); - if (abMana.canProduce(choice, manaAb) && testCost.isAnyPartPayableWith(colorMask, ai.getManaPool())) { + if (manaAb.canProduce(choice) && testCost.isAnyPartPayableWith(colorMask, ai.getManaPool())) { choiceString.append(choice); payMultipleMana(testCost, choice, ai); continue; @@ -1103,7 +1269,7 @@ public class ComputerUtilMana { } return unused.isEmpty() ? null : StringUtils.join(unused, ' '); } - + /** * Find all mana sources. * @param manaAbilityMap The map of SpellAbilities that produce mana. @@ -1135,7 +1301,7 @@ public class ComputerUtilMana { res.putAll(shard, manaAbilityMap.get(ManaAtom.GENERIC)); continue; } - + if (shard == ManaCostShard.GENERIC) { continue; } @@ -1163,7 +1329,7 @@ public class ComputerUtilMana { * @return ManaCost */ public static ManaCostBeingPaid calculateManaCost(final SpellAbility sa, final boolean test, final int extraMana) { - Card card = sa.getHostCard(); + Card card = sa.getHostCard(); ZoneType castFromBackup = null; if (test && sa.isSpell()) { castFromBackup = card.getCastFrom(); @@ -1196,14 +1362,14 @@ public class ComputerUtilMana { sa.setXManaCostPaid(manaToAdd / cost.getXcounter()); } } - + CostAdjustment.adjust(cost, sa, null, test); int timesMultikicked = card.getKickerMagnitude(); if (timesMultikicked > 0 && sa.hasParam("Announce") && sa.getParam("Announce").startsWith("Multikicker")) { ManaCost mkCost = sa.getMultiKickerManaCost(); for (int i = 0; i < timesMultikicked; i++) { - cost.addManaCost(mkCost); + cost.addManaCost(mkCost); } sa.setSVar("Multikicker", String.valueOf(timesMultikicked)); } @@ -1387,20 +1553,6 @@ public class ComputerUtilMana { final ListMultimap manaMap = ArrayListMultimap.create(); final Game game = ai.getGame(); - List replacementEffects = new ArrayList<>(); - for (final Player p : game.getPlayers()) { - for (final Card crd : p.getAllCards()) { - for (final ReplacementEffect replacementEffect : crd.getReplacementEffects()) { - if (replacementEffect.requirementsCheck(game) - && replacementEffect.getMode() == ReplacementType.ProduceMana - && replacementEffect.hasParam("ManaReplacement") - && replacementEffect.zonesCheck(game.getZoneOf(crd))) { - replacementEffects.add(replacementEffect); - } - } - } - } - // Loop over all current available mana sources for (final Card sourceCard : getAvailableManaSources(ai, checkPlayable)) { if (DEBUG_MANA_PAYMENT) { @@ -1430,48 +1582,80 @@ public class ComputerUtilMana { } manaMap.get(ManaAtom.GENERIC).add(m); // add to generic source list - AbilityManaPart mp = m.getManaPart(); - // setup produce mana replacement effects - final Map repParams = AbilityKey.newMap(); - repParams.put(AbilityKey.Mana, mp.getOrigProduced()); - repParams.put(AbilityKey.Affected, sourceCard); - repParams.put(AbilityKey.Player, ai); - repParams.put(AbilityKey.AbilityMana, m); + SpellAbility tail = m; + while (tail != null) { + AbilityManaPart mp = m.getManaPart(); + if (mp != null && tail.metConditions()) { + // TODO Replacement Check currently doesn't work for reflected colors - for (final ReplacementEffect replacementEffect : replacementEffects) { - if (replacementEffect.canReplace(repParams)) { - Card crd = replacementEffect.getHostCard(); - String repType = crd.getSVar(replacementEffect.getParam("ManaReplacement")); - if (repType.contains("Chosen")) { - repType = TextUtil.fastReplace(repType, "Chosen", MagicColor.toShortString(crd.getChosenColor())); + // setup produce mana replacement effects + String origin = mp.getOrigProduced(); + final Map repParams = AbilityKey.newMap(); + repParams.put(AbilityKey.Mana, origin); + repParams.put(AbilityKey.Affected, sourceCard); + repParams.put(AbilityKey.Player, ai); + repParams.put(AbilityKey.AbilityMana, m); // RootAbility + + List reList = game.getReplacementHandler().getReplacementList(ReplacementType.ProduceMana, repParams, ReplacementLayer.Other); + + if (reList.isEmpty()) { + Set reflectedColors = CardUtil.getReflectableManaColors(m); + // find possible colors + for (byte color : MagicColor.WUBRG) { + if (tail.canThisProduce(MagicColor.toShortString(color)) || reflectedColors.contains(MagicColor.toLongString(color))) { + manaMap.put((int)color, m); + } + } + if (m.canThisProduce("C") || reflectedColors.contains(MagicColor.Constant.COLORLESS)) { + manaMap.put(ManaAtom.COLORLESS, m); + } + } else { + // try to guess the color the mana gets replaced to + for (ReplacementEffect re : reList) { + SpellAbility o = re.getOverridingAbility(); + String replaced = origin; + if (o == null || o.getApi() != ApiType.ReplaceMana) { + continue; + } + if (o.hasParam("ReplaceMana")) { + replaced = o.getParam("ReplaceMana"); + } else if (o.hasParam("ReplaceType")) { + String color = o.getParam("ReplaceType"); + for (byte c : MagicColor.WUBRGC) { + String s = MagicColor.toShortString(c); + replaced = replaced.replace(s, color); + } + } else if (o.hasParam("ReplaceColor")) { + String color = o.getParam("ReplaceColor"); + if (o.hasParam("ReplaceOnly")) { + replaced = replaced.replace(o.getParam("ReplaceOnly"), color); + } else { + for (byte c : MagicColor.WUBRG) { + String s = MagicColor.toShortString(c); + replaced = replaced.replace(s, color); + } + } + } + + for (byte color : MagicColor.WUBRG) { + if ("Any".equals(replaced) || replaced.contains(MagicColor.toShortString(color))) { + manaMap.put((int)color, m); + } + } + + if (replaced.contains("C")) { + manaMap.put(ManaAtom.COLORLESS, m); + } + + } } - mp.setManaReplaceType(repType); } + tail = tail.getSubAbility(); } - Set reflectedColors = CardUtil.getReflectableManaColors(m); - // find possible colors - if (mp.canProduce("W", m) || reflectedColors.contains(MagicColor.Constant.WHITE)) { - manaMap.get(ManaAtom.WHITE).add(m); - } - if (mp.canProduce("U", m) || reflectedColors.contains(MagicColor.Constant.BLUE)) { - manaMap.get(ManaAtom.BLUE).add(m); - } - if (mp.canProduce("B", m) || reflectedColors.contains(MagicColor.Constant.BLACK)) { - manaMap.get(ManaAtom.BLACK).add(m); - } - if (mp.canProduce("R", m) || reflectedColors.contains(MagicColor.Constant.RED)) { - manaMap.get(ManaAtom.RED).add(m); - } - if (mp.canProduce("G", m) || reflectedColors.contains(MagicColor.Constant.GREEN)) { - manaMap.get(ManaAtom.GREEN).add(m); - } - if (mp.canProduce("C", m) || reflectedColors.contains(MagicColor.Constant.COLORLESS)) { - manaMap.get(ManaAtom.COLORLESS).add(m); - } - if (mp.isSnow()) { - manaMap.get(ManaAtom.IS_SNOW).add(m); + if (m.getHostCard().isSnow()) { + manaMap.put(ManaAtom.IS_SNOW, m); } if (DEBUG_MANA_PAYMENT) { System.out.println("DEBUG_MANA_PAYMENT: groupSourcesByManaColor manaMap = " + manaMap); @@ -1486,7 +1670,7 @@ public class ComputerUtilMana { *

* determineLeftoverMana. *

- * + * * @param sa * a {@link forge.game.spellability.SpellAbility} object. * @param player @@ -1507,7 +1691,7 @@ public class ComputerUtilMana { *

* determineLeftoverMana. *

- * + * * @param sa * a {@link forge.game.spellability.SpellAbility} object. * @param player @@ -1536,7 +1720,7 @@ public class ComputerUtilMana { *

* getAIPlayableMana. *

- * + * * @return a {@link java.util.List} object. */ public static List getAIPlayableMana(Card c) { @@ -1583,8 +1767,8 @@ public class ComputerUtilMana { sa.resetSacrificedAsEmerge(); } } - - + + /** * Matches list of creatures to shards in mana cost for convoking. * @param cost cost of convoked ability diff --git a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java index a96ee50cd61..ab50c1a806f 100644 --- a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java +++ b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java @@ -494,7 +494,7 @@ public class PlayerControllerAi extends PlayerController { } @Override - public TargetChoices chooseNewTargetsFor(SpellAbility ability) { + public TargetChoices chooseNewTargetsFor(SpellAbility ability, Predicate filter, boolean optional) { // AI currently can't do this. But when it can it will need to be based on Ability API return null; } @@ -833,6 +833,9 @@ public class PlayerControllerAi extends PlayerController { @Override public byte chooseColor(String message, SpellAbility sa, ColorSet colors) { + if (colors.countColors() < 2) { + return Iterables.getFirst(colors, MagicColor.WHITE); + } // You may switch on sa.getApi() here and use sa.getParam("AILogic") CardCollectionView hand = player.getCardsIn(ZoneType.Hand); if (sa.getApi() == ApiType.Mana) { @@ -978,9 +981,13 @@ public class PlayerControllerAi extends PlayerController { if (sa.isSpell()) { player.getGame().getStackZone().add(sa.getHostCard()); } - // TODO check if static abilities needs to be run for things affecting the copy? + + /* FIXME: the new implementation (below) requires implementing setupNewTargets in the AI controller, among other possible changes, otherwise breaks AI + if (sa.isMayChooseNewTargets()) { + sa.setupNewTargets(player); + } + */ if (sa.isMayChooseNewTargets() && !sa.setupTargets()) { - // if targets can't be done, remove copy from existence if (sa.isSpell()) { sa.getHostCard().ceaseToExist(); } diff --git a/forge-ai/src/main/java/forge/ai/SpecialCardAi.java b/forge-ai/src/main/java/forge/ai/SpecialCardAi.java index 2173f53bb8f..ecbd71f2fe4 100644 --- a/forge-ai/src/main/java/forge/ai/SpecialCardAi.java +++ b/forge-ai/src/main/java/forge/ai/SpecialCardAi.java @@ -26,7 +26,6 @@ import forge.card.MagicColor; import forge.card.mana.ManaCost; import forge.game.Game; import forge.game.GameType; -import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityUtils; import forge.game.ability.ApiType; import forge.game.card.*; @@ -636,10 +635,7 @@ public class SpecialCardAi { boolean canRetFromGrave = false; String name = c.getName().replace(',', ';'); for (Trigger t : c.getTriggers()) { - SpellAbility ab = null; - if (t.hasParam("Execute")) { - ab = AbilityFactory.getAbility(c.getSVar(t.getParam("Execute")), c); - } + SpellAbility ab = t.ensureAbility(); if (ab == null) { continue; } if (ab.getApi() == ApiType.ChangeZone diff --git a/forge-ai/src/main/java/forge/ai/SpellAbilityAi.java b/forge-ai/src/main/java/forge/ai/SpellAbilityAi.java index fa33c11087a..d8c185684fd 100644 --- a/forge-ai/src/main/java/forge/ai/SpellAbilityAi.java +++ b/forge-ai/src/main/java/forge/ai/SpellAbilityAi.java @@ -111,7 +111,7 @@ public abstract class SpellAbilityAi { if (aiLogic.equals("CheckCondition")) { SpellAbility saCopy = sa.copy(); saCopy.setActivatingPlayer(ai); - return saCopy.getConditions().areMet(saCopy); + return saCopy.metConditions(); } return !("Never".equals(aiLogic)); 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 7fcc06b0507..d7b12a02bcd 100644 --- a/forge-ai/src/main/java/forge/ai/ability/AnimateAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/AnimateAi.java @@ -122,7 +122,7 @@ public class AnimateAi extends SpellAbilityAi { final Card source = sa.getHostCard(); final Game game = aiPlayer.getGame(); final PhaseHandler ph = game.getPhaseHandler(); - if (sa.getConditions() != null && !sa.getConditions().areMet(sa) && sa.getSubAbility() == null) { + if (!sa.metConditions() && sa.getSubAbility() == null) { return false; // what is this for? } if (!game.getStack().isEmpty() && game.getStack().peekAbility().getApi() == ApiType.Sacrifice) { 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 b3ae3638b6f..ed7d26cc043 100644 --- a/forge-ai/src/main/java/forge/ai/ability/AttachAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/AttachAi.java @@ -1086,12 +1086,7 @@ public class AttachAi extends SpellAbilityAi { final Map params = t.getMapParams(); if ("Card.Self".equals(params.get("ValidCard")) && "Battlefield".equals(params.get("Destination"))) { - SpellAbility trigSa = null; - if (t.hasParam("Execute") && attachSource.hasSVar(t.getParam("Execute"))) { - trigSa = AbilityFactory.getAbility(attachSource.getSVar(params.get("Execute")), attachSource); - } else if (t.getOverridingAbility() != null) { - trigSa = t.getOverridingAbility(); - } + SpellAbility trigSa = t.ensureAbility(); if (trigSa != null && trigSa.getApi() == ApiType.DealDamage && "Enchanted".equals(trigSa.getParam("Defined"))) { for (Card target : list) { if (!target.getController().isOpponentOf(ai)) { 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 74c5c0aa1ab..c364996f774 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java @@ -304,10 +304,10 @@ public class ChangeZoneAi extends SpellAbilityAi { } // don't play if the conditions aren't met, unless it would trigger a beneficial sub-condition - if (!activateForCost && !sa.getConditions().areMet(sa)) { + if (!activateForCost && !sa.metConditions()) { final AbilitySub abSub = sa.getSubAbility(); if (abSub != null && !sa.isWrapper() && "True".equals(source.getSVar("AIPlayForSub"))) { - if (!abSub.getConditions().areMet(abSub)) { + if (!abSub.metConditions()) { return false; } } else { @@ -1811,7 +1811,7 @@ public class ChangeZoneAi extends SpellAbilityAi { } else // This is an intrinsic effect that blinks the card (e.g. Obzedat, Ghost Council), no need to // return the commander to the Command zone. if (subApi == ApiType.DelayedTrigger) { - SpellAbility exec = causeSub.getAdditionalAbility("Execute"); + SpellAbility exec = causeSub.getAdditionalAbility("Execute"); if (exec != null && exec.getApi() == ApiType.ChangeZone) { // A blink effect implemented using a delayed trigger return !"Exile".equals(exec.getParam("Origin")) || !"Battlefield".equals(exec.getParam("Destination")); 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 dee21c7340b..162b90f82f8 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java @@ -126,7 +126,7 @@ public class CountersPutAi extends SpellAbilityAi { Card choice = null; final String type = sa.getParam("CounterType"); final String amountStr = sa.getParamOrDefault("CounterNum", "1"); - final boolean divided = sa.hasParam("DividedAsYouChoose"); + final boolean divided = sa.isDividedAsYouChoose(); final String logic = sa.getParamOrDefault("AILogic", ""); PhaseHandler ph = ai.getGame().getPhaseHandler(); @@ -290,7 +290,7 @@ public class CountersPutAi extends SpellAbilityAi { return doMoveCounterLogic(ai, sa, ph); } - if (sa.getConditions() != null && !sa.getConditions().areMet(sa) && sa.getSubAbility() == null) { + if (!sa.metConditions() && sa.getSubAbility() == null) { return false; } @@ -398,16 +398,15 @@ public class CountersPutAi extends SpellAbilityAi { } if (!ai.getGame().getStack().isEmpty() && !SpellAbilityAi.isSorcerySpeed(sa)) { - final TargetRestrictions abTgt = sa.getTargetRestrictions(); // only evaluates case where all tokens are placed on a single target - if (sa.usesTargeting() && abTgt.getMinTargets(source, sa) < 2) { + if (sa.usesTargeting() && sa.getMinTargets() < 2) { if (ComputerUtilCard.canPumpAgainstRemoval(ai, sa)) { Card c = sa.getTargets().getFirstTargetedCard(); if (sa.getTargets().size() > 1) { sa.resetTargets(); sa.getTargets().add(c); } - abTgt.addDividedAllocation(sa.getTargetCard(), amount); + sa.addDividedAllocation(sa.getTargetCard(), amount); return true; } else { return false; @@ -462,7 +461,6 @@ public class CountersPutAi extends SpellAbilityAi { } if (sourceName.equals("Abzan Charm")) { - final TargetRestrictions abTgt = sa.getTargetRestrictions(); // specific AI for instant with distribute two +1/+1 counters ComputerUtilCard.sortByEvaluateCreature(list); // maximise the number of targets @@ -472,11 +470,11 @@ public class CountersPutAi extends SpellAbilityAi { if (ComputerUtilCard.shouldPumpCard(ai, sa, c, i, i, Lists.newArrayList())) { sa.getTargets().add(c); - abTgt.addDividedAllocation(c, i); + sa.addDividedAllocation(c, i); left -= i; } - if (left < i || sa.getTargets().size() == abTgt.getMaxTargets(source, sa)) { - abTgt.addDividedAllocation(sa.getTargets().getFirstTargetedCard(), left + i); + if (left < i || sa.getTargets().size() == sa.getMaxTargets()) { + sa.addDividedAllocation(sa.getTargets().getFirstTargetedCard(), left + i); left = 0; break; } @@ -542,7 +540,7 @@ public class CountersPutAi extends SpellAbilityAi { list.remove(choice); sa.getTargets().add(choice); if (divided) { - sa.getTargetRestrictions().addDividedAllocation(choice, amount); + sa.addDividedAllocation(choice, amount); break; } choice = null; @@ -609,7 +607,7 @@ public class CountersPutAi extends SpellAbilityAi { final String logic = sa.getParamOrDefault("AILogic", ""); final String amountStr = sa.getParamOrDefault("CounterNum", "1"); - final boolean divided = sa.hasParam("DividedAsYouChoose"); + final boolean divided = sa.isDividedAsYouChoose(); final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa); final boolean isMandatoryTrigger = (sa.isTrigger() && !sa.isOptionalTrigger()) @@ -672,7 +670,7 @@ public class CountersPutAi extends SpellAbilityAi { list.remove(choice); sa.getTargets().add(choice); if (divided) { - sa.getTargetRestrictions().addDividedAllocation(choice, amount); + sa.addDividedAllocation(choice, amount); break; } } @@ -690,7 +688,7 @@ public class CountersPutAi extends SpellAbilityAi { CardCollection list; final String type = sa.getParam("CounterType"); final String amountStr = sa.getParamOrDefault("CounterNum", "1"); - final boolean divided = sa.hasParam("DividedAsYouChoose"); + final boolean divided = sa.isDividedAsYouChoose(); final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa); int left = amount; @@ -818,12 +816,11 @@ public class CountersPutAi extends SpellAbilityAi { } } if (choice != null && divided) { - final TargetRestrictions abTgt = sa.getTargetRestrictions(); int alloc = Math.max(amount / totalTargets, 1); - if (sa.getTargets().size() == Math.min(totalTargets, abTgt.getMaxTargets(sa.getHostCard(), sa)) - 1) { - abTgt.addDividedAllocation(choice, left); + if (sa.getTargets().size() == Math.min(totalTargets, sa.getMaxTargets()) - 1) { + sa.addDividedAllocation(choice, left); } else { - abTgt.addDividedAllocation(choice, alloc); + sa.addDividedAllocation(choice, alloc); left -= alloc; } } 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 35a2ba27f47..471cf90696f 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java @@ -268,7 +268,7 @@ public class DamageDealAi extends DamageAiBase { if ((damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) || sourceName.equals("Crater's Claws")){ // If I can kill my target by paying less mana, do it - if (sa.usesTargeting() && !sa.getTargets().isTargetingAnyPlayer() && !sa.hasParam("DividedAsYouChoose")) { + if (sa.usesTargeting() && !sa.getTargets().isTargetingAnyPlayer() && !sa.isDividedAsYouChoose()) { int actualPay = dmg; final boolean noPrevention = sa.hasParam("NoPrevention"); for (final Card c : sa.getTargets().getTargetCards()) { @@ -547,7 +547,7 @@ public class DamageDealAi extends DamageAiBase { final boolean noPrevention = sa.hasParam("NoPrevention"); final Game game = source.getGame(); final PhaseHandler phase = game.getPhaseHandler(); - final boolean divided = sa.hasParam("DividedAsYouChoose"); + final boolean divided = sa.isDividedAsYouChoose(); final boolean oppTargetsChoice = sa.hasParam("TargetingPlayer"); final String logic = sa.getParamOrDefault("AILogic", ""); @@ -613,7 +613,7 @@ public class DamageDealAi extends DamageAiBase { if (assignedDamage <= dmg && humanCreature.getShieldCount() == 0 && !ComputerUtil.canRegenerate(humanCreature.getController(), humanCreature)) { tcs.add(humanCreature); - tgt.addDividedAllocation(humanCreature, assignedDamage); + sa.addDividedAllocation(humanCreature, assignedDamage); lastTgt = humanCreature; dmg -= assignedDamage; } @@ -625,7 +625,7 @@ public class DamageDealAi extends DamageAiBase { } } if (dmg > 0 && lastTgt != null) { - tgt.addDividedAllocation(lastTgt, tgt.getDividedValue(lastTgt) + dmg); + sa.addDividedAllocation(lastTgt, sa.getDividedValue(lastTgt) + dmg); dmg = 0; return true; } @@ -635,14 +635,14 @@ public class DamageDealAi extends DamageAiBase { continue; } tcs.add(humanCreature); - tgt.addDividedAllocation(humanCreature, dmg); + sa.addDividedAllocation(humanCreature, dmg); dmg = 0; return true; } } int totalTargetedSoFar = -1; - while (tcs.size() < tgt.getMaxTargets(source, sa)) { + while (sa.canAddMoreTarget()) { if (totalTargetedSoFar == tcs.size()) { // Avoid looping endlessly when choosing targets for cards with variable target number and type // like Jaya's Immolating Inferno @@ -664,7 +664,7 @@ public class DamageDealAi extends DamageAiBase { if (divided) { int assignedDamage = ComputerUtilCombat.getEnoughDamageToKill(c, dmg, source, false, noPrevention); assignedDamage = Math.min(dmg, assignedDamage); - tgt.addDividedAllocation(c, assignedDamage); + sa.addDividedAllocation(c, assignedDamage); dmg = dmg - assignedDamage; if (dmg <= 0) { break; @@ -680,7 +680,7 @@ public class DamageDealAi extends DamageAiBase { if (this.shouldTgtP(ai, sa, dmg, noPrevention)) { tcs.add(enemy); if (divided) { - tgt.addDividedAllocation(enemy, dmg); + sa.addDividedAllocation(enemy, dmg); break; } continue; @@ -702,7 +702,7 @@ public class DamageDealAi extends DamageAiBase { if (divided) { final int assignedDamage = ComputerUtilCombat.getEnoughDamageToKill(c, dmg, source, false, noPrevention); if (assignedDamage <= dmg) { - tgt.addDividedAllocation(c, assignedDamage); + sa.addDividedAllocation(c, assignedDamage); } dmg = dmg - assignedDamage; if (dmg <= 0) { @@ -739,7 +739,7 @@ public class DamageDealAi extends DamageAiBase { if (freePing && sa.canTarget(enemy) && (!avoidTargetP(ai, sa))) { tcs.add(enemy); if (divided) { - tgt.addDividedAllocation(enemy, dmg); + sa.addDividedAllocation(enemy, dmg); break; } } @@ -757,9 +757,9 @@ public class DamageDealAi extends DamageAiBase { if (divided) { final int assignedDamage = ComputerUtilCombat.getEnoughDamageToKill(c, dmg, source, false, noPrevention); if (assignedDamage <= dmg) { - tgt.addDividedAllocation(c, assignedDamage); + sa.addDividedAllocation(c, assignedDamage); } else { - tgt.addDividedAllocation(c, dmg); + sa.addDividedAllocation(c, dmg); } dmg = dmg - assignedDamage; if (dmg <= 0) { @@ -785,7 +785,7 @@ public class DamageDealAi extends DamageAiBase { (!avoidTargetP(ai, sa))) { tcs.add(enemy); if (divided) { - tgt.addDividedAllocation(enemy, dmg); + sa.addDividedAllocation(enemy, dmg); break; } continue; @@ -880,16 +880,16 @@ public class DamageDealAi extends DamageAiBase { private boolean damageChooseRequiredTargets(final Player ai, final SpellAbility sa, final TargetRestrictions tgt, final int dmg) { // this is for Triggered targets that are mandatory final boolean noPrevention = sa.hasParam("NoPrevention"); - final boolean divided = sa.hasParam("DividedAsYouChoose"); + final boolean divided = sa.isDividedAsYouChoose(); final Player opp = ai.getWeakestOpponent(); - while (sa.getTargets().size() < tgt.getMinTargets(sa.getHostCard(), sa)) { + while (sa.canAddMoreTarget()) { if (tgt.canTgtPlaneswalker()) { final Card c = this.dealDamageChooseTgtPW(ai, sa, dmg, noPrevention, ai, true); if (c != null) { sa.getTargets().add(c); if (divided) { - tgt.addDividedAllocation(c, dmg); + sa.addDividedAllocation(c, dmg); break; } continue; @@ -902,7 +902,7 @@ public class DamageDealAi extends DamageAiBase { if (c != null) { sa.getTargets().add(c); if (divided) { - tgt.addDividedAllocation(c, dmg); + sa.addDividedAllocation(c, dmg); break; } continue; @@ -912,7 +912,7 @@ public class DamageDealAi extends DamageAiBase { if (sa.canTarget(opp)) { if (sa.getTargets().add(opp)) { if (divided) { - tgt.addDividedAllocation(opp, dmg); + sa.addDividedAllocation(opp, dmg); break; } continue; @@ -927,7 +927,7 @@ public class DamageDealAi extends DamageAiBase { Card c = ComputerUtilCard.getWorstPermanentAI(indestructible, false, false, false, false); sa.getTargets().add(c); if (divided) { - tgt.addDividedAllocation(c, dmg); + sa.addDividedAllocation(c, dmg); break; } continue; @@ -938,7 +938,7 @@ public class DamageDealAi extends DamageAiBase { if (c != null) { sa.getTargets().add(c); if (divided) { - tgt.addDividedAllocation(c, dmg); + sa.addDividedAllocation(c, dmg); break; } continue; @@ -948,7 +948,7 @@ public class DamageDealAi extends DamageAiBase { if (sa.canTarget(ai)) { if (sa.getTargets().add(ai)) { if (divided) { - tgt.addDividedAllocation(ai, dmg); + sa.addDividedAllocation(ai, dmg); break; } continue; @@ -988,7 +988,7 @@ public class DamageDealAi extends DamageAiBase { return false; } - if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid") && !sa.hasParam("DividedAsYouChoose")) { + if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid") && !sa.isDividedAsYouChoose()) { // If I can kill my target by paying less mana, do it int actualPay = 0; final boolean noPrevention = sa.hasParam("NoPrevention"); diff --git a/forge-ai/src/main/java/forge/ai/ability/DamagePreventAi.java b/forge-ai/src/main/java/forge/ai/ability/DamagePreventAi.java index 6e5259f25cb..964c427c649 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DamagePreventAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DamagePreventAi.java @@ -146,8 +146,8 @@ public class DamagePreventAi extends SpellAbilityAi { } } } - if (tgt != null && sa.hasParam("DividedAsYouChoose") && sa.getTargets() != null && !sa.getTargets().isEmpty()) { - tgt.addDividedAllocation(sa.getTargets().get(0), AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Amount"), sa)); + if (sa.usesTargeting() && sa.isDividedAsYouChoose() && !sa.getTargets().isEmpty()) { + sa.addDividedAllocation(sa.getTargets().get(0), AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Amount"), sa)); } return chance; @@ -179,12 +179,11 @@ public class DamagePreventAi extends SpellAbilityAi { * @return a boolean. */ private boolean preventDamageMandatoryTarget(final Player ai, final SpellAbility sa, final boolean mandatory) { - final TargetRestrictions tgt = sa.getTargetRestrictions(); sa.resetTargets(); // filter AIs battlefield by what I can target final Game game = ai.getGame(); CardCollectionView targetables = game.getCardsIn(ZoneType.Battlefield); - targetables = CardLists.getValidCards(targetables, tgt.getValidTgts(), ai, sa.getHostCard(), sa); + targetables = CardLists.getTargetableCards(targetables, sa); final List compTargetables = CardLists.filterControlledBy(targetables, ai); Card target = null; @@ -215,8 +214,8 @@ public class DamagePreventAi extends SpellAbilityAi { target = ComputerUtilCard.getCheapestPermanentAI(targetables, sa, true); } sa.getTargets().add(target); - if (sa.hasParam("DividedAsYouChoose")) { - tgt.addDividedAllocation(target, AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Amount"), sa)); + if (sa.isDividedAsYouChoose()) { + sa.addDividedAllocation(target, AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Amount"), sa)); } return true; } diff --git a/forge-ai/src/main/java/forge/ai/ability/DelayedTriggerAi.java b/forge-ai/src/main/java/forge/ai/ability/DelayedTriggerAi.java index bd22d902785..bede875289d 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DelayedTriggerAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DelayedTriggerAi.java @@ -3,7 +3,6 @@ package forge.ai.ability; import com.google.common.base.Predicate; import forge.ai.*; import forge.card.mana.ManaCost; -import forge.game.ability.AbilityFactory; import forge.game.ability.ApiType; import forge.game.card.Card; import forge.game.card.CardLists; @@ -22,11 +21,9 @@ public class DelayedTriggerAi extends SpellAbilityAi { // TODO: improve ai return true; } - SpellAbility trigsa = null; - if (sa.hasAdditionalAbility("Execute")) { - trigsa = sa.getAdditionalAbility("Execute"); - } else { - trigsa = AbilityFactory.getAbility(sa.getHostCard(), sa.getParam("Execute")); + SpellAbility trigsa = sa.getAdditionalAbility("Execute"); + if (trigsa == null) { + return false; } trigsa.setActivatingPlayer(ai); @@ -39,12 +36,11 @@ public class DelayedTriggerAi extends SpellAbilityAi { @Override protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { - SpellAbility trigsa = null; - if (sa.hasAdditionalAbility("Execute")) { - trigsa = sa.getAdditionalAbility("Execute"); - } else { - trigsa = AbilityFactory.getAbility(sa.getHostCard(), sa.getParam("Execute")); + SpellAbility trigsa = sa.getAdditionalAbility("Execute"); + if (trigsa == null) { + return false; } + AiController aic = ((PlayerControllerAi)ai.getController()).getAi(); trigsa.setActivatingPlayer(ai); @@ -143,11 +139,9 @@ public class DelayedTriggerAi extends SpellAbilityAi { } // Generic logic - SpellAbility trigsa = null; - if (sa.hasAdditionalAbility("Execute")) { - trigsa = sa.getAdditionalAbility("Execute"); - } else { - trigsa = AbilityFactory.getAbility(sa.getHostCard(), sa.getParam("Execute")); + SpellAbility trigsa = sa.getAdditionalAbility("Execute"); + if (trigsa == null) { + return false; } trigsa.setActivatingPlayer(ai); return AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(trigsa); 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 1b95b09c1ca..714376bb3a8 100644 --- a/forge-ai/src/main/java/forge/ai/ability/FightAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/FightAi.java @@ -3,6 +3,7 @@ package forge.ai.ability; import forge.ai.*; import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityUtils; +import forge.game.ability.ApiType; import forge.game.card.Card; import forge.game.card.CardCollection; import forge.game.card.CardCollectionView; @@ -193,7 +194,7 @@ public class FightAi extends SpellAbilityAi { for (Card humanCreature : humCreatures) { for (Card aiCreature : aiCreatures) { if (source.isSpell()) { // heroic triggers adding counters and prowess - final int bonus = getSpellBonus(aiCreature); + final int bonus = getSpellBonus(aiCreature); power += bonus; toughness += bonus; } @@ -247,28 +248,32 @@ public class FightAi extends SpellAbilityAi { return false; } - /** - * Compute the bonus from Heroic +1/+1 counters or Prowess - */ - private static int getSpellBonus(final Card aiCreature) { - for (Trigger t : aiCreature.getTriggers()) { - if (t.getMode() == TriggerType.SpellCast) { - final Map params = t.getMapParams(); - if ("Card.Self".equals(params.get("TargetsValid")) && "You".equals(params.get("ValidActivatingPlayer")) - && params.containsKey("Execute")) { - SpellAbility heroic = AbilityFactory.getAbility(aiCreature.getSVar(params.get("Execute")),aiCreature); - if ("Self".equals(heroic.getParam("Defined")) && "P1P1".equals(heroic.getParam("CounterType"))) { - return AbilityUtils.calculateAmount(aiCreature, heroic.getParam("CounterNum"), heroic); - } - break; - } - if ("ProwessPump".equals(params.get("Execute"))) { - return 1; - } - } - } - return 0; - } + /** + * Compute the bonus from Heroic +1/+1 counters or Prowess + */ + private static int getSpellBonus(final Card aiCreature) { + for (Trigger t : aiCreature.getTriggers()) { + if (t.getMode() == TriggerType.SpellCast) { + SpellAbility sa = t.ensureAbility(); + final Map params = t.getMapParams(); + if (sa == null) { + continue; + } + if (ApiType.PutCounter.equals(sa.getApi())) { + if ("Card.Self".equals(params.get("TargetsValid")) && "You".equals(params.get("ValidActivatingPlayer"))) { + SpellAbility heroic = AbilityFactory.getAbility(aiCreature.getSVar(params.get("Execute")),aiCreature); + if ("Self".equals(heroic.getParam("Defined")) && "P1P1".equals(heroic.getParam("CounterType"))) { + return AbilityUtils.calculateAmount(aiCreature, heroic.getParam("CounterNum"), heroic); + } + break; + } + } else if (ApiType.Pump.equals(sa.getApi())) { + // TODO add prowess boost + } + } + } + return 0; + } private static boolean shouldFight(Card fighter, Card opponent, int pumpAttack, int pumpDefense) { if (canKill(fighter, opponent, pumpAttack)) { diff --git a/forge-ai/src/main/java/forge/ai/ability/ImmediateTriggerAi.java b/forge-ai/src/main/java/forge/ai/ability/ImmediateTriggerAi.java index 2619018d4bc..b241dd8f7bf 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ImmediateTriggerAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ImmediateTriggerAi.java @@ -1,7 +1,6 @@ package forge.ai.ability; import forge.ai.*; -import forge.game.ability.AbilityFactory; import forge.game.player.Player; import forge.game.spellability.AbilitySub; import forge.game.spellability.SpellAbility; @@ -16,12 +15,11 @@ public class ImmediateTriggerAi extends SpellAbilityAi { return true; } - SpellAbility trigsa = null; - if (sa.hasAdditionalAbility("Execute")) { - trigsa = sa.getAdditionalAbility("Execute"); - } else { - trigsa = AbilityFactory.getAbility(sa.getHostCard(), sa.getParam("Execute")); + SpellAbility trigsa = sa.getAdditionalAbility("Execute"); + if (trigsa == null) { + return false; } + trigsa.setActivatingPlayer(ai); if (trigsa instanceof AbilitySub) { @@ -33,12 +31,11 @@ public class ImmediateTriggerAi extends SpellAbilityAi { @Override protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { - SpellAbility trigsa = null; - if (sa.hasAdditionalAbility("Execute")) { - trigsa = sa.getAdditionalAbility("Execute"); - } else { - trigsa = AbilityFactory.getAbility(sa.getHostCard(), sa.getParam("Execute")); + SpellAbility trigsa = sa.getAdditionalAbility("Execute"); + if (trigsa == null) { + return false; } + AiController aic = ((PlayerControllerAi)ai.getController()).getAi(); trigsa.setActivatingPlayer(ai); @@ -56,12 +53,11 @@ public class ImmediateTriggerAi extends SpellAbilityAi { return true; } - SpellAbility trigsa = null; - if (sa.hasAdditionalAbility("Execute")) { - trigsa = sa.getAdditionalAbility("Execute"); - } else { - trigsa = AbilityFactory.getAbility(sa.getHostCard(), sa.getParam("Execute")); + SpellAbility trigsa = sa.getAdditionalAbility("Execute"); + if (trigsa == null) { + return false; } + trigsa.setActivatingPlayer(ai); return AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(trigsa); } diff --git a/forge-ai/src/main/java/forge/ai/ability/LifeGainAi.java b/forge-ai/src/main/java/forge/ai/ability/LifeGainAi.java index 98e351bfdf5..78044dedaec 100644 --- a/forge-ai/src/main/java/forge/ai/ability/LifeGainAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/LifeGainAi.java @@ -146,7 +146,7 @@ public class LifeGainAi extends SpellAbilityAi { } // don't play if the conditions aren't met, unless it would trigger a // beneficial sub-condition - if (!activateForCost && !sa.getConditions().areMet(sa)) { + if (!activateForCost && !sa.metConditions()) { final AbilitySub abSub = sa.getSubAbility(); if (abSub != null && !sa.isWrapper() && "True".equals(source.getSVar("AIPlayForSub"))) { if (!abSub.getConditions().areMet(abSub)) { diff --git a/forge-ai/src/main/java/forge/ai/ability/PermanentAi.java b/forge-ai/src/main/java/forge/ai/ability/PermanentAi.java index bd4ea2d6091..f3ec36f837d 100644 --- a/forge-ai/src/main/java/forge/ai/ability/PermanentAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/PermanentAi.java @@ -279,7 +279,7 @@ public class PermanentAi extends SpellAbilityAi { final Card source = sa.getHostCard(); final Cost cost = sa.getPayCosts(); - if (sa.getConditions() != null && !sa.getConditions().areMet(sa)) { + if (!sa.metConditions()) { return false; } diff --git a/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java b/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java index 9c743f30653..8cf8c3d9d0d 100644 --- a/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java +++ b/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java @@ -6,7 +6,6 @@ import com.google.common.collect.Lists; import com.google.common.collect.Maps; import forge.LobbyPlayer; import forge.ai.LobbyPlayerAi; -import forge.card.CardStateName; import forge.game.*; import forge.game.card.*; import forge.game.card.token.TokenInfo; @@ -289,16 +288,9 @@ public class GameCopier { newCard.setTapped(true); } if (c.isFaceDown()) { - boolean isCreature = newCard.isCreature(); - boolean hasManaCost = !newCard.getManaCost().isNoCost(); newCard.turnFaceDown(true); if (c.isManifested()) { newCard.setManifested(true); - // TODO: Should be able to copy other abilities... - if (isCreature && hasManaCost) { - newCard.getState(CardStateName.Original).addSpellAbility( - CardFactoryUtil.abilityManifestFaceUp(newCard, newCard.getManaCost())); - } } } if (c.isMonstrous()) { diff --git a/forge-ai/src/main/java/forge/ai/simulation/GameSimulator.java b/forge-ai/src/main/java/forge/ai/simulation/GameSimulator.java index b57a51830e4..927b315bf00 100644 --- a/forge-ai/src/main/java/forge/ai/simulation/GameSimulator.java +++ b/forge-ai/src/main/java/forge/ai/simulation/GameSimulator.java @@ -16,8 +16,6 @@ import forge.game.phase.PhaseType; import forge.game.player.Player; import forge.game.spellability.SpellAbility; import forge.game.spellability.TargetChoices; -import forge.game.spellability.TargetRestrictions; -import forge.util.TextUtil; public class GameSimulator { public static boolean COPY_STACK = false; @@ -125,11 +123,13 @@ public class GameSimulator { } private SpellAbility findSaInSimGame(SpellAbility sa) { + // is already an ability from sim game + if (sa.getHostCard().getGame().equals(this.simGame)) { + return sa; + } Card origHostCard = sa.getHostCard(); Card hostCard = (Card) copier.find(origHostCard); String desc = sa.getDescription(); - // FIXME: This is a hack that makes testManifest pass - figure out why it's needed. - desc = TextUtil.fastReplace(desc, "Unmanifest {0}", "Unmanifest no cost"); for (SpellAbility cSa : hostCard.getSpellAbilities()) { if (desc.startsWith(cSa.getDescription())) { return cSa; @@ -163,14 +163,12 @@ public class GameSimulator { SpellAbility saOrSubSa = sa; do { if (origSaOrSubSa.usesTargeting()) { - final boolean divided = origSaOrSubSa.hasParam("DividedAsYouChoose"); - final TargetRestrictions origTgtRes = origSaOrSubSa.getTargetRestrictions(); - final TargetRestrictions tgtRes = saOrSubSa.getTargetRestrictions(); + final boolean divided = origSaOrSubSa.isDividedAsYouChoose(); for (final GameObject o : origSaOrSubSa.getTargets()) { final GameObject target = copier.find(o); saOrSubSa.getTargets().add(target); if (divided) { - tgtRes.addDividedAllocation(target, origTgtRes.getDividedValue(o)); + saOrSubSa.addDividedAllocation(target, origSaOrSubSa.getDividedValue(o)); } } } diff --git a/forge-ai/src/main/java/forge/ai/simulation/PossibleTargetSelector.java b/forge-ai/src/main/java/forge/ai/simulation/PossibleTargetSelector.java index 164e564a15e..18e158e9a23 100644 --- a/forge-ai/src/main/java/forge/ai/simulation/PossibleTargetSelector.java +++ b/forge-ai/src/main/java/forge/ai/simulation/PossibleTargetSelector.java @@ -173,16 +173,15 @@ public class PossibleTargetSelector { // Divide up counters, since AI is expected to do this. For now, // divided evenly with left-overs going to the first target. - if (targetingSa.hasParam("DividedAsYouChoose")) { + if (targetingSa.isDividedAsYouChoose()) { final int targetCount = targetingSa.getTargets().getTargetCards().size(); if (targetCount > 0) { final String amountStr = targetingSa.getParam("CounterNum"); final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, targetingSa); final int amountPerCard = amount / targetCount; int amountLeftOver = amount - (amountPerCard * targetCount); - final TargetRestrictions tgtRes = targetingSa.getTargetRestrictions(); for (GameObject target : targetingSa.getTargets()) { - tgtRes.addDividedAllocation(target, amountPerCard + amountLeftOver); + targetingSa.addDividedAllocation(target, amountPerCard + amountLeftOver); amountLeftOver = 0; } } diff --git a/forge-core/src/main/java/forge/util/FileUtil.java b/forge-core/src/main/java/forge/util/FileUtil.java index 212748744b1..00af3693cb9 100644 --- a/forge-core/src/main/java/forge/util/FileUtil.java +++ b/forge-core/src/main/java/forge/util/FileUtil.java @@ -286,4 +286,11 @@ public final class FileUtil { }, 5000); //abort reading file if it takes longer than 5 seconds return lines; } + + public static String getParent(final String resourcePath) { + File f = new File(resourcePath); + if (f.getParentFile().getName() != null) + return f.getParentFile().getName(); + return ""; + } } diff --git a/forge-game/src/main/java/forge/game/CardTraitPredicates.java b/forge-game/src/main/java/forge/game/CardTraitPredicates.java index 3e9115aee38..c89e0556648 100644 --- a/forge-game/src/main/java/forge/game/CardTraitPredicates.java +++ b/forge-game/src/main/java/forge/game/CardTraitPredicates.java @@ -2,8 +2,19 @@ package forge.game; import com.google.common.base.Predicate; +import forge.game.card.Card; + public class CardTraitPredicates { + public static final Predicate isHostCard(final Card host) { + return new Predicate() { + @Override + public boolean apply(final CardTraitBase sa) { + return host.equals(sa.getHostCard()); + } + }; + } + public static final Predicate hasParam(final String name) { return new Predicate() { @Override diff --git a/forge-game/src/main/java/forge/game/ForgeScript.java b/forge-game/src/main/java/forge/game/ForgeScript.java index 0565c8981e5..26ecb7d6980 100644 --- a/forge-game/src/main/java/forge/game/ForgeScript.java +++ b/forge-game/src/main/java/forge/game/ForgeScript.java @@ -5,6 +5,7 @@ import forge.card.MagicColor; import forge.game.ability.AbilityUtils; import forge.game.card.Card; import forge.game.card.CardState; +import forge.game.cost.Cost; import forge.game.player.Player; import forge.game.spellability.SpellAbility; import forge.game.staticability.StaticAbility; @@ -131,6 +132,9 @@ public class ForgeScript { return !sa.isManaAbility(); } else if (property.equals("withoutXCost")) { return !sa.costHasManaX(); + } else if (property.equals("hasTapCost")) { + Cost cost = sa.getPayCosts(); + return cost != null && cost.hasTapCost(); } else if (property.equals("Buyback")) { return sa.isBuyBackAbility(); } else if (property.equals("Cycling")) { diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java index 403817ebec0..f065694166c 100644 --- a/forge-game/src/main/java/forge/game/GameAction.java +++ b/forge-game/src/main/java/forge/game/GameAction.java @@ -207,7 +207,7 @@ public class GameAction { if (!c.isRealToken()) { copied = CardFactory.copyCard(c, false); - if (fromBattlefield) { + if (!zoneTo.is(ZoneType.Stack)) { // when a card leaves the battlefield, ensure it's in its original state // (we need to do this on the object before copying it, or it won't work correctly e.g. // on Transformed objects) diff --git a/forge-game/src/main/java/forge/game/GameActionUtil.java b/forge-game/src/main/java/forge/game/GameActionUtil.java index e61cd797a85..6c50fa4e18c 100644 --- a/forge-game/src/main/java/forge/game/GameActionUtil.java +++ b/forge-game/src/main/java/forge/game/GameActionUtil.java @@ -17,7 +17,6 @@ */ package forge.game; -import com.google.common.base.Predicates; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Sets; @@ -252,6 +251,18 @@ public final class GameActionUtil { } } + if (sa.isManaAbility() && sa.isActivatedAbility() && activator.hasKeyword("Piracy") && source.isLand() && source.isInPlay() && !activator.equals(source.getController()) && sa.getPayCosts().hasTapCost()) { + SpellAbility newSA = sa.copy(activator); + // to bypass Activator restriction, set Activator to Player + sa.getRestrictions().setActivator("Player"); + + // extra Mana restriction to only Spells + for (AbilityManaPart mp : newSA.getAllManaParts()) { + mp.setExtraManaRestriction("Spell"); + } + alternatives.add(newSA); + } + // below are for some special cases of activated abilities if (sa.isCycling() && activator.hasKeyword("CyclingForZero")) { for (final KeywordInterface inst : source.getKeywords()) { @@ -277,7 +288,6 @@ public final class GameActionUtil { newSA.setDescription(sb.toString()); alternatives.add(newSA); - break; } } @@ -473,62 +483,12 @@ public final class GameActionUtil { int v = pc.chooseNumberForKeywordCost(sa, cost, ki, str, Integer.MAX_VALUE); if (v > 0) { - - final Card eff = new Card(game.nextCardId(), game); - eff.setTimestamp(game.getNextTimestamp()); - eff.setName(c.getName() + "'s Effect"); - eff.addType("Effect"); - eff.setOwner(activator); - - eff.setImageKey(c.getImageKey()); - eff.setColor(MagicColor.COLORLESS); - eff.setImmutable(true); - // try to get the SpellAbility from the mana ability - //eff.setEffectSource((SpellAbility)null); - - eff.addRemembered(host); - - String abStr = "DB$ PutCounter | Defined$ ReplacedCard | CounterType$ P1P1 | ETB$ True | CounterNum$ " + v; - - SpellAbility saAb = AbilityFactory.getAbility(abStr, c); - - CardFactoryUtil.setupETBReplacementAbility(saAb); - - String desc = "It enters the battlefield with "; - desc += Lang.nounWithNumeral(v, CounterEnumType.P1P1.getName() + " counter"); - desc += " on it."; - - String repeffstr = "Event$ Moved | ValidCard$ Card.IsRemembered | Destination$ Battlefield | Description$ " + desc; - - ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, eff, true); - re.setLayer(ReplacementLayer.Other); - re.setOverridingAbility(saAb); - - eff.addReplacementEffect(re); - - // Forgot Trigger - String trig = "Mode$ ChangesZone | ValidCard$ Card.IsRemembered | Origin$ Stack | Destination$ Any | TriggerZones$ Command | Static$ True"; - String forgetEffect = "DB$ Pump | ForgetObjects$ TriggeredCard"; - String exileEffect = "DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile" - + " | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ EQ0"; - - SpellAbility saForget = AbilityFactory.getAbility(forgetEffect, eff); - AbilitySub saExile = (AbilitySub) AbilityFactory.getAbility(exileEffect, eff); - saForget.setSubAbility(saExile); - - final Trigger parsedTrigger = TriggerHandler.parseTrigger(trig, eff, true); - parsedTrigger.setOverridingAbility(saForget); - eff.addTrigger(parsedTrigger); - 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, null); - game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); + Card eff = createETBCountersEffect(c, host, activator, "P1P1", String.valueOf(v)); if (result == null) { result = sa.copy(); } + result.addRollbackEffect(eff); for (int i = 0; i < v; i++) { result.getPayCosts().add(cost); } @@ -546,45 +506,85 @@ public final class GameActionUtil { return result != null ? result : sa; } - private static boolean hasUrzaLands(final Player p) { - final CardCollectionView landsControlled = p.getCardsIn(ZoneType.Battlefield); - return Iterables.any(landsControlled, Predicates.and(CardPredicates.isType("Urza's"), CardPredicates.isType("Mine"))) - && Iterables.any(landsControlled, Predicates.and(CardPredicates.isType("Urza's"), CardPredicates.isType("Power-Plant"))) - && Iterables.any(landsControlled, Predicates.and(CardPredicates.isType("Urza's"), CardPredicates.isType("Tower"))); + public static Card createETBCountersEffect(Card sourceCard, Card c, Player controller, String counter, String amount) { + final Game game = sourceCard.getGame(); + final Card eff = new Card(game.nextCardId(), game); + eff.setTimestamp(game.getNextTimestamp()); + eff.setName(sourceCard.getName() + "'s Effect"); + eff.addType("Effect"); + eff.setOwner(controller); + + eff.setImageKey(sourceCard.getImageKey()); + eff.setColor(MagicColor.COLORLESS); + eff.setImmutable(true); + // try to get the SpellAbility from the mana ability + //eff.setEffectSource((SpellAbility)null); + + eff.addRemembered(c); + + String abStr = "DB$ PutCounter | Defined$ ReplacedCard | CounterType$ " + counter + + " | ETB$ True | CounterNum$ " + amount; + + SpellAbility sa = AbilityFactory.getAbility(abStr, c); + if (!StringUtils.isNumeric(amount)) { + sa.setSVar(amount, sourceCard.getSVar(amount)); + } + CardFactoryUtil.setupETBReplacementAbility(sa); + + String desc = "It enters the battlefield with "; + desc += Lang.nounWithNumeral(amount, CounterType.getType(counter).getName() + " counter"); + desc += " on it."; + + String repeffstr = "Event$ Moved | ValidCard$ Card.IsRemembered | Destination$ Battlefield | Description$ " + desc; + + ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, eff, true); + re.setLayer(ReplacementLayer.Other); + re.setOverridingAbility(sa); + + eff.addReplacementEffect(re); + + // Forgot Trigger + String trig = "Mode$ ChangesZone | ValidCard$ Card.IsRemembered | Origin$ Stack | Destination$ Any | TriggerZones$ Command | Static$ True"; + String forgetEffect = "DB$ Pump | ForgetObjects$ TriggeredCard"; + String exileEffect = "DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile" + + " | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ EQ0"; + + SpellAbility saForget = AbilityFactory.getAbility(forgetEffect, eff); + AbilitySub saExile = (AbilitySub) AbilityFactory.getAbility(exileEffect, eff); + saForget.setSubAbility(saExile); + + final Trigger parsedTrigger = TriggerHandler.parseTrigger(trig, eff, true); + parsedTrigger.setOverridingAbility(saForget); + eff.addTrigger(parsedTrigger); + 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, null); + game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); + + return eff; } - public static int amountOfManaGenerated(final SpellAbility sa, boolean multiply) { - // Calculate generated mana here for stack description and resolving - - int amount = sa.hasParam("Amount") ? AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Amount"), sa) : 1; - AbilityManaPart abMana = sa.getManaPartRecursive(); - - if (sa.hasParam("Bonus")) { - // For mana abilities that get a bonus - // Bonus currently MULTIPLIES the base amount. Base Amounts should - // ALWAYS be Base - int bonus = 0; - if (sa.getParam("Bonus").equals("UrzaLands")) { - if (hasUrzaLands(sa.getActivatingPlayer())) { - bonus = Integer.parseInt(sa.getParam("BonusProduced")); - } + public static String generatedTotalMana(final SpellAbility sa) { + StringBuilder sb = new StringBuilder(); + SpellAbility tail = sa; + while (tail != null) { + String value = generatedMana(tail); + if (!value.isEmpty() && !"0".equals(value)) { + sb.append(value).append(" "); } - - amount += bonus; - } - - if (!multiply || abMana.isAnyMana() || abMana.isComboMana() || abMana.isSpecialMana()) { - return amount; - } else { - // For cards that produce like {C}{R} vs cards that produce {R}{R}. - return abMana.mana().split(" ").length * amount; + tail = tail.getSubAbility(); } + return sb.toString().trim(); } - public static String generatedMana(final SpellAbility sa) { - int amount = amountOfManaGenerated(sa, false); + int amount = sa.amountOfManaGenerated(false); AbilityManaPart abMana = sa.getManaPart(); + if (abMana == null) { + return ""; + } String baseMana; if (abMana.isComboMana()) { diff --git a/forge-game/src/main/java/forge/game/TriggerReplacementBase.java b/forge-game/src/main/java/forge/game/TriggerReplacementBase.java index 1ff5b94e3ae..0764bd7b2fc 100644 --- a/forge-game/src/main/java/forge/game/TriggerReplacementBase.java +++ b/forge-game/src/main/java/forge/game/TriggerReplacementBase.java @@ -77,4 +77,5 @@ public abstract class TriggerReplacementBase extends CardTraitBase implements II this.overridingAbility = overridingAbility0; } + abstract public SpellAbility ensureAbility(); } diff --git a/forge-game/src/main/java/forge/game/ability/AbilityApiBased.java b/forge-game/src/main/java/forge/game/ability/AbilityApiBased.java index a471e78703a..4603cee8d98 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityApiBased.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityApiBased.java @@ -1,9 +1,5 @@ package forge.game.ability; -import forge.game.ability.effects.ChangeZoneAllEffect; -import forge.game.ability.effects.ChangeZoneEffect; -import forge.game.ability.effects.ManaEffect; -import forge.game.ability.effects.ManaReflectedEffect; import forge.game.card.Card; import forge.game.cost.Cost; import forge.game.spellability.AbilityActivated; @@ -15,8 +11,6 @@ import java.util.Map; public class AbilityApiBased extends AbilityActivated { private final SpellAbilityEffect effect; - private static final long serialVersionUID = -4183793555528531978L; - public AbilityApiBased(ApiType api0, Card sourceCard, Cost abCost, TargetRestrictions tgt, Map params0) { super(sourceCard, abCost, tgt); originalMapParams.putAll(params0); @@ -24,13 +18,12 @@ public class AbilityApiBased extends AbilityActivated { api = api0; effect = api.getSpellEffect(); - if (effect instanceof ManaEffect || effect instanceof ManaReflectedEffect) { - + if (api.equals(ApiType.Mana) || api.equals(ApiType.ManaReflected)) { this.setManaPart(new AbilityManaPart(sourceCard, mapParams)); this.setUndoable(true); // will try at least } - if (effect instanceof ChangeZoneEffect || effect instanceof ChangeZoneAllEffect) { + if (api.equals(ApiType.ChangeZone) || api.equals(ApiType.ChangeZoneAll)) { AbilityFactory.adjustChangeZoneTarget(mapParams, this); } } 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 b64e55edf59..9ccdb5315a2 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityFactory.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityFactory.java @@ -249,7 +249,7 @@ public final class AbilityFactory { } } - if (api == ApiType.DelayedTrigger && mapParams.containsKey("Execute")) { + if ((api == ApiType.DelayedTrigger || api == ApiType.ImmediateTrigger) && mapParams.containsKey("Execute")) { spellAbility.setSVar(mapParams.get("Execute"), sVarHolder.getSVar(mapParams.get("Execute"))); } @@ -364,10 +364,6 @@ public final class AbilityFactory { if (mapParams.containsKey("TargetsWithDifferentCMC")) { abTgt.setDifferentCMC(true); } - if (mapParams.containsKey("DividedAsYouChoose")) { - abTgt.calculateStillToDivide(mapParams.get("DividedAsYouChoose"), null, null); - abTgt.setDividedAsYouChoose(true); - } if (mapParams.containsKey("TargetsAtRandom")) { abTgt.setRandomTarget(true); } 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 a4179f8ba29..407fa8dc409 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java @@ -1400,7 +1400,7 @@ public class AbilityUtils { ); // check conditions - if (sa.getConditions().areMet(sa)) { + if (sa.metConditions()) { if (sa.isWrapper() || StringUtils.isBlank(sa.getParam("UnlessCost"))) { sa.resolve(); } @@ -1668,10 +1668,15 @@ public class AbilityUtils { // Count$Kicked.. if (sq[0].startsWith("Kicked")) { - boolean kicked = ((SpellAbility)ctb).isKicked() || c.getKickerMagnitude() > 0; + boolean kicked = sa.isKicked() || c.getKickerMagnitude() > 0; return CardFactoryUtil.doXMath(Integer.parseInt(kicked ? sq[1] : sq[2]), expr, c); } + // Count$UrzaLands.. + if (sq[0].startsWith("UrzaLands")) { + return CardFactoryUtil.doXMath(Integer.parseInt(sa.getActivatingPlayer().hasUrzaLands() ? sq[1] : sq[2]), expr, c); + } + //Count$SearchedLibrary. if (sq[0].contains("SearchedLibrary")) { int sum = 0; 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 8df8f0f2a0c..74a3a612e63 100644 --- a/forge-game/src/main/java/forge/game/ability/ApiType.java +++ b/forge-game/src/main/java/forge/game/ability/ApiType.java @@ -136,6 +136,7 @@ public enum ApiType { Repeat (RepeatEffect.class), RepeatEach (RepeatEachEffect.class), ReplaceEffect (ReplaceEffect.class), + ReplaceMana (ReplaceManaEffect.class), ReplaceDamage (ReplaceDamageEffect.class), ReplaceSplitDamage (ReplaceSplitDamageEffect.class), RestartGame (RestartGameEffect.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 381ef080e5a..4f492f439ff 100644 --- a/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java +++ b/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java @@ -429,7 +429,7 @@ public abstract class SpellAbilityEffect { + " exile it instead of putting it anywhere else."; String effect = "DB$ ChangeZone | Defined$ ReplacedCard | Origin$ Battlefield | Destination$ " + zone; - ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, eff, true); + ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, eff, true, null); re.setLayer(ReplacementLayer.Other); re.setOverridingAbility(AbilityFactory.getAbility(effect, eff)); diff --git a/forge-game/src/main/java/forge/game/ability/SpellApiBased.java b/forge-game/src/main/java/forge/game/ability/SpellApiBased.java index d96401336a4..d7903a2c502 100644 --- a/forge-game/src/main/java/forge/game/ability/SpellApiBased.java +++ b/forge-game/src/main/java/forge/game/ability/SpellApiBased.java @@ -2,10 +2,6 @@ package forge.game.ability; import java.util.Map; -import forge.game.ability.effects.ChangeZoneAllEffect; -import forge.game.ability.effects.ChangeZoneEffect; -import forge.game.ability.effects.ManaEffect; -import forge.game.ability.effects.ManaReflectedEffect; import forge.game.card.Card; import forge.game.cost.Cost; import forge.game.spellability.AbilityManaPart; @@ -28,11 +24,11 @@ public class SpellApiBased extends Spell { // A spell is always intrinsic this.setIntrinsic(true); - if (effect instanceof ManaEffect || effect instanceof ManaReflectedEffect) { + if (api.equals(ApiType.Mana) || api.equals(ApiType.ManaReflected)) { this.setManaPart(new AbilityManaPart(sourceCard, mapParams)); } - if (effect instanceof ChangeZoneEffect || effect instanceof ChangeZoneAllEffect) { + if (api.equals(ApiType.ChangeZone) || api.equals(ApiType.ChangeZoneAll)) { AbilityFactory.adjustChangeZoneTarget(mapParams, this); } } 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 68b50dfae39..86ad24f6249 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 @@ -159,8 +159,7 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect { // Grant triggers final List addedTriggers = Lists.newArrayList(); for (final String s : triggers) { - final Trigger parsedTrigger = TriggerHandler.parseTrigger(AbilityUtils.getSVar(sa, s), c, false); - parsedTrigger.setOverridingAbility(AbilityFactory.getAbility(c, parsedTrigger.getParam("Execute"), sa)); + final Trigger parsedTrigger = TriggerHandler.parseTrigger(AbilityUtils.getSVar(sa, s), c, false, sa); parsedTrigger.setOriginalHost(source); addedTriggers.add(parsedTrigger); } @@ -168,7 +167,7 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect { // give replacement effects final List addedReplacements = Lists.newArrayList(); for (final String s : replacements) { - addedReplacements.add(ReplacementHandler.parseReplacement(AbilityUtils.getSVar(sa, s), c, false)); + addedReplacements.add(ReplacementHandler.parseReplacement(AbilityUtils.getSVar(sa, s), c, false, sa)); } // give static abilities (should only be used by cards to give diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChangeTargetsEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChangeTargetsEffect.java index 56477884311..c52942c35d5 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ChangeTargetsEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ChangeTargetsEffect.java @@ -1,9 +1,11 @@ package forge.game.ability.effects; +import com.google.common.base.Predicate; import com.google.common.collect.Iterables; import forge.game.GameEntity; import forge.game.GameObject; +import forge.game.GameObjectPredicates; import forge.game.ability.SpellAbilityEffect; import forge.game.player.Player; import forge.game.spellability.SpellAbility; @@ -19,7 +21,7 @@ import org.apache.commons.lang3.tuple.Pair; import java.util.ArrayList; import java.util.List; -/** +/** * TODO: Write javadoc for this type. * */ @@ -42,9 +44,6 @@ public class ChangeTargetsEffect extends SpellAbilityEffect { continue; } - boolean changesOneTarget = sa.hasParam("ChangeSingleTarget"); // The only card known to replace targets with self is Spellskite - // There is also Muck Drubb but it replaces ALL occurences of a single target with itself (unlike Spellskite that has to be activated for each). - SpellAbilityStackInstance changingTgtSI = si; Player chooser = sa.getActivatingPlayer(); @@ -54,11 +53,11 @@ public class ChangeTargetsEffect extends SpellAbilityEffect { if (isOptional && !chooser.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantChangeAbilityTargets", tgtSA.getHostCard().toString()))) { continue; } - if (changesOneTarget) { + if (sa.hasParam("ChangeSingleTarget")) { // 1. choose a target of target spell List> allTargets = new ArrayList<>(); while(changingTgtSI != null) { - SpellAbility changedSa = changingTgtSI.getSpellAbility(true); + SpellAbility changedSa = changingTgtSI.getSpellAbility(true); if (changedSa.usesTargeting()) { for(GameObject it : changedSa.getTargets()) allTargets.add(ImmutablePair.of(changingTgtSI, it)); @@ -77,6 +76,8 @@ public class ChangeTargetsEffect extends SpellAbilityEffect { GameObject oldTarget = chosenTarget.getValue(); TargetChoices oldTargetBlock = replaceIn.getTargetChoices(); TargetChoices newTargetBlock = oldTargetBlock.clone(); + // gets the divied value from old target + Integer div = oldTargetBlock.getDividedValue(oldTarget); newTargetBlock.remove(oldTarget); replaceIn.updateTarget(newTargetBlock); // 3. test if updated choices would be correct. @@ -84,7 +85,10 @@ public class ChangeTargetsEffect extends SpellAbilityEffect { if (replaceIn.getSpellAbility(true).canTarget(newTarget)) { newTargetBlock.add(newTarget); - replaceIn.updateTarget(newTargetBlock, oldTarget, newTarget); + if (div != null) { + newTargetBlock.addDividedAllocation(newTarget, div); + } + replaceIn.updateTarget(newTargetBlock); } else { replaceIn.updateTarget(oldTargetBlock); @@ -93,36 +97,38 @@ public class ChangeTargetsEffect extends SpellAbilityEffect { else { while(changingTgtSI != null) { SpellAbility changingTgtSA = changingTgtSI.getSpellAbility(true); - if (sa.hasParam("RandomTarget")){ - if (changingTgtSA.usesTargeting()) { + if (changingTgtSA.usesTargeting()) { + // random target and DefinedMagnet works on single targets + if (sa.hasParam("RandomTarget")){ + int div = changingTgtSA.getTotalDividedValue(); changingTgtSA.resetTargets(); List candidates = changingTgtSA.getTargetRestrictions().getAllCandidates(changingTgtSA, true); GameEntity choice = Aggregates.random(candidates); changingTgtSA.getTargets().add(choice); - changingTgtSI.updateTarget(changingTgtSA.getTargets(), null, choice); + if (changingTgtSA.isDividedAsYouChoose()) { + changingTgtSA.addDividedAllocation(choice, div); + } + + changingTgtSI.updateTarget(changingTgtSA.getTargets()); } - } - else if (sa.hasParam("DefinedMagnet")){ - GameObject newTarget = Iterables.getFirst(getDefinedCardsOrTargeted(sa, "DefinedMagnet"), null); - if (newTarget != null && changingTgtSA.canTarget(newTarget)) { - changingTgtSA.resetTargets(); - changingTgtSA.getTargets().add(newTarget); - changingTgtSI.updateTarget(changingTgtSA.getTargets(), null, newTarget); - } - } - else { - // Update targets, with a potential new target - TargetChoices newTarget = sa.getActivatingPlayer().getController().chooseNewTargetsFor(changingTgtSA); - if (null != newTarget) { - if (sa.hasParam("TargetRestriction")) { - if (newTarget.getFirstTargetedCard() != null && newTarget.getFirstTargetedCard(). - isValid(sa.getParam("TargetRestriction").split(","), activator, sa.getHostCard(), sa)) { - changingTgtSI.updateTarget(newTarget); - } else if (newTarget.getFirstTargetedPlayer() != null && newTarget.getFirstTargetedPlayer(). - isValid(sa.getParam("TargetRestriction").split(","), activator, sa.getHostCard(), sa)) { - changingTgtSI.updateTarget(newTarget); + else if (sa.hasParam("DefinedMagnet")){ + GameObject newTarget = Iterables.getFirst(getDefinedCardsOrTargeted(sa, "DefinedMagnet"), null); + if (newTarget != null && changingTgtSA.canTarget(newTarget)) { + int div = changingTgtSA.getTotalDividedValue(); + changingTgtSA.resetTargets(); + changingTgtSA.getTargets().add(newTarget); + changingTgtSI.updateTarget(changingTgtSA.getTargets()); + if (changingTgtSA.isDividedAsYouChoose()) { + changingTgtSA.addDividedAllocation(newTarget, div); } - } else { + } + } + else { + // Update targets, with a potential new target + Predicate filter = sa.hasParam("TargetRestriction") ? GameObjectPredicates.restriction(sa.getParam("TargetRestriction").split(","), activator, sa.getHostCard(), sa) : null; + // TODO Creature.Other might not work yet as it should + TargetChoices newTarget = sa.getActivatingPlayer().getController().chooseNewTargetsFor(changingTgtSA, filter, false); + if (null != newTarget) { changingTgtSI.updateTarget(newTarget); } } 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 fd33dc41397..719c4f63ad4 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 @@ -108,6 +108,8 @@ public class CloneEffect extends SpellAbilityEffect { if (!cloneTargets.isEmpty()) { tgtCard = cloneTargets.get(0); game.getTriggerHandler().clearActiveTriggers(tgtCard, null); + } else { + return; } } else if (sa.hasParam("Choices") && sa.usesTargeting()) { tgtCard = sa.getTargets().getFirstTargetedCard(); 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 3c1ea43e9f3..50a18e79e60 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 @@ -111,7 +111,7 @@ public class CopySpellAbilityEffect extends SpellAbilityEffect { candidates.remove(p); for (GameEntity o : candidates) { - SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(sa, chosenSA); + SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(sa, chosenSA, controller); resetFirstTargetOnCopy(copy, o, targetedSA); copies.add(copy); } @@ -144,12 +144,12 @@ public class CopySpellAbilityEffect extends SpellAbilityEffect { } for (final Card c : valid) { - SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(sa, chosenSA); + SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(sa, chosenSA, controller); resetFirstTargetOnCopy(copy, c, targetedSA); copies.add(copy); } for (final Player p : players) { - SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(sa, chosenSA); + SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(sa, chosenSA, controller); resetFirstTargetOnCopy(copy, p, targetedSA); copies.add(copy); } @@ -157,12 +157,9 @@ public class CopySpellAbilityEffect extends SpellAbilityEffect { } else { for (int i = 0; i < amount; i++) { - SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(sa, chosenSA); + SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(sa, chosenSA, controller); if (sa.hasParam("MayChooseTarget")) { copy.setMayChooseNewTargets(true); - if (copy.usesTargeting()) { - copy.getTargetRestrictions().setMandatory(true); - } } // extra case for Epic to remove the keyword and the last part of the SpellAbility @@ -206,12 +203,9 @@ public class CopySpellAbilityEffect extends SpellAbilityEffect { } int extraAmount = addAmount - copies.size(); for (int i = 0; i < extraAmount; i++) { - SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(sa, chosenSA); + SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(sa, chosenSA, controller); // extra copies added with CopySpellReplacenment currently always has new choose targets copy.setMayChooseNewTargets(true); - if (copy.usesTargeting()) { - copy.getTargetRestrictions().setMandatory(true); - } copies.add(copy); } 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 9bef78eab96..598fc58446e 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 @@ -27,13 +27,12 @@ public class CountersMoveEffect extends SpellAbilityEffect { @Override protected String getStackDescription(SpellAbility sa) { - final Card host = sa.getHostCard(); final StringBuilder sb = new StringBuilder(); final List tgtCards = getDefinedCardsOrTargeted(sa); Card source = null; - if (sa.usesTargeting() && sa.getTargetRestrictions().getMinTargets(host, sa) == 2) { + if (sa.usesTargeting() && sa.getMinTargets() == 2) { if (tgtCards.size() < 2) { return ""; } @@ -241,7 +240,7 @@ public class CountersMoveEffect extends SpellAbilityEffect { Card source = null; List tgtCards = getDefinedCardsOrTargeted(sa); // special logic for moving from Target to Target - if (sa.usesTargeting() && sa.getTargetRestrictions().getMinTargets(host, sa) == 2) { + if (sa.usesTargeting() && sa.getMinTargets() == 2) { if (tgtCards.size() < 2) { return; } 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 d3ad905eaf2..1dad1191eef 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 @@ -46,7 +46,6 @@ public class CountersPutEffect extends SpellAbilityEffect { protected String getStackDescription(SpellAbility spellAbility) { final StringBuilder stringBuilder = new StringBuilder(); final Card card = spellAbility.getHostCard(); - final boolean dividedAsYouChoose = spellAbility.hasParam("DividedAsYouChoose"); final int amount = AbilityUtils.calculateAmount(card, spellAbility.getParamOrDefault("CounterNum", "1"), spellAbility); //skip the StringBuilder if no targets are chosen ("up to" scenario) @@ -60,7 +59,7 @@ public class CountersPutEffect extends SpellAbilityEffect { stringBuilder.append("Bolster ").append(amount); return stringBuilder.toString(); } - if (dividedAsYouChoose) { + if (spellAbility.isDividedAsYouChoose()) { stringBuilder.append("Distribute "); } else { stringBuilder.append("Put "); @@ -84,7 +83,7 @@ public class CountersPutEffect extends SpellAbilityEffect { stringBuilder.append("s"); } - if (dividedAsYouChoose) { + if (spellAbility.isDividedAsYouChoose()) { stringBuilder.append(" among "); } else { stringBuilder.append(" on "); @@ -96,8 +95,9 @@ public class CountersPutEffect extends SpellAbilityEffect { for(int i = 0; i < targetCards.size(); i++) { Card targetCard = targetCards.get(i); stringBuilder.append(targetCard); - if (spellAbility.getTargetRestrictions().getDividedMap().get(targetCard) != null) // fix null counter stack description - stringBuilder.append(" (").append(spellAbility.getTargetRestrictions().getDividedMap().get(targetCard)).append(" counter)"); + Integer v = spellAbility.getDividedValue(targetCard); + if (v != null) // fix null counter stack description + stringBuilder.append(" (").append(v).append(" counter)"); if(i == targetCards.size() - 2) { stringBuilder.append(" and "); @@ -259,7 +259,7 @@ public class CountersPutEffect extends SpellAbilityEffect { if (obj instanceof Card) { boolean counterAdded = false; - counterAmount = sa.usesTargeting() && sa.hasParam("DividedAsYouChoose") ? sa.getTargetRestrictions().getDividedValue(gameCard) : counterAmount; + counterAmount = sa.usesTargeting() && sa.isDividedAsYouChoose() ? sa.getDividedValue(gameCard) : counterAmount; if (!sa.usesTargeting() || gameCard.canBeTargetedBy(sa)) { if (max != -1) { counterAmount = Math.max(Math.min(max - gameCard.getCounters(counterType), counterAmount), 0); @@ -270,7 +270,7 @@ public class CountersPutEffect extends SpellAbilityEffect { params.put("CounterType", counterType); counterAmount = pc.chooseNumber(sa, Localizer.getInstance().getMessage("lblHowManyCounters"), 0, counterAmount, params); } - if (sa.hasParam("DividedAsYouChoose") && !sa.usesTargeting()) { + if (sa.isDividedAsYouChoose() && !sa.usesTargeting()) { Map params = Maps.newHashMap(); params.put("Target", obj); params.put("CounterType", counterType); @@ -378,7 +378,7 @@ public class CountersPutEffect extends SpellAbilityEffect { card.addRemembered(gameCard); } game.updateLastStateForCard(gameCard); - if (sa.hasParam("DividedAsYouChoose") && !sa.usesTargeting()) { + if (sa.isDividedAsYouChoose() && !sa.usesTargeting()) { counterRemain = counterRemain - counterAmount; } } 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 5f0b4608c55..5b9e0542f5e 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 @@ -61,7 +61,7 @@ public class DamageDealEffect extends DamageBaseEffect { if (spellAbility.usesTargeting()) { if (spellAbility.hasParam("DivideEvenly")) { stringBuilder.append("divided evenly (rounded down) to\n"); - } else if (spellAbility.hasParam("DividedAsYouChoose")) { + } else if (spellAbility.isDividedAsYouChoose()) { stringBuilder.append("divided to\n"); } else stringBuilder.append("to "); @@ -75,8 +75,9 @@ public class DamageDealEffect extends DamageBaseEffect { for (int i = 0; i < targetCards.size(); i++) { Card targetCard = targetCards.get(i); stringBuilder.append(targetCard); - if (spellAbility.getTargetRestrictions().getDividedMap().get(targetCard) != null) //fix null damage stack description - stringBuilder.append(" (").append(spellAbility.getTargetRestrictions().getDividedMap().get(targetCard)).append(" damage)"); + Integer v = spellAbility.getDividedValue(targetCard); + if (v != null) //fix null damage stack description + stringBuilder.append(" (").append(v).append(" damage)"); if (i == targetCount - 2) { stringBuilder.append(" and "); @@ -89,8 +90,9 @@ public class DamageDealEffect extends DamageBaseEffect { for (int i = 0; i < players.size(); i++) { Player targetPlayer = players.get(i); stringBuilder.append(targetPlayer); - if (spellAbility.getTargetRestrictions().getDividedMap().get(targetPlayer) != null) //fix null damage stack description - stringBuilder.append(" (").append(spellAbility.getTargetRestrictions().getDividedMap().get(targetPlayer)).append(" damage)"); + Integer v = spellAbility.getDividedValue(targetPlayer); + if (v != null) //fix null damage stack description + stringBuilder.append(" (").append(v).append(" damage)"); if (i == players.size() - 2) { stringBuilder.append(" and "); @@ -102,7 +104,7 @@ public class DamageDealEffect extends DamageBaseEffect { } else { if (spellAbility.hasParam("DivideEvenly")) { stringBuilder.append("divided evenly (rounded down) "); - } else if (spellAbility.hasParam("DividedAsYouChoose")) { + } else if (spellAbility.isDividedAsYouChoose()) { stringBuilder.append("divided as you choose "); } stringBuilder.append("to ").append(Lang.joinHomogenous(targets)); @@ -229,7 +231,7 @@ public class DamageDealEffect extends DamageBaseEffect { for (final GameObject o : tgts) { if (!removeDamage) { - dmg = (sa.usesTargeting() && sa.hasParam("DividedAsYouChoose")) ? sa.getTargetRestrictions().getDividedValue(o) : dmg; + dmg = (sa.usesTargeting() && sa.isDividedAsYouChoose()) ? sa.getDividedValue(o) : dmg; if (dmg <= 0) { continue; } diff --git a/forge-game/src/main/java/forge/game/ability/effects/DamagePreventEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DamagePreventEffect.java index 710f2f747b6..54e0395a34e 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/DamagePreventEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/DamagePreventEffect.java @@ -25,7 +25,7 @@ public class DamagePreventEffect extends SpellAbilityEffect { sb.append("Prevent the next "); sb.append(sa.getParam("Amount")); sb.append(" damage that would be dealt "); - if (sa.hasParam("DividedAsYouChoose")) { + if (sa.isDividedAsYouChoose()) { sb.append("between "); } else { sb.append("to "); @@ -75,8 +75,8 @@ public class DamagePreventEffect extends SpellAbilityEffect { final boolean targeted = (sa.usesTargeting()); final boolean preventionWithEffect = sa.hasParam("PreventionSubAbility"); - for (final Object o : tgts) { - numDam = (sa.usesTargeting() && sa.hasParam("DividedAsYouChoose")) ? sa.getTargetRestrictions().getDividedValue(o) : numDam; + for (final GameObject o : tgts) { + numDam = (sa.usesTargeting() && sa.isDividedAsYouChoose()) ? sa.getDividedValue(o) : numDam; if (o instanceof Card) { final Card c = (Card) o; if (c.isInPlay() && (!targeted || c.canBeTargetedBy(sa))) { diff --git a/forge-game/src/main/java/forge/game/ability/effects/DelayedTriggerEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DelayedTriggerEffect.java index b8172eaad7f..bb2e7afc4d4 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/DelayedTriggerEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/DelayedTriggerEffect.java @@ -58,7 +58,7 @@ public class DelayedTriggerEffect extends SpellAbilityEffect { Card lki = CardUtil.getLKICopy(gameCard); lki.clearControllers(); lki.setOwner(sa.getActivatingPlayer()); - final Trigger delTrig = TriggerHandler.parseTrigger(mapParams, lki, sa.isIntrinsic()); + final Trigger delTrig = TriggerHandler.parseTrigger(mapParams, lki, sa.isIntrinsic(), null); delTrig.setSpawningAbility(sa.copy(lki, sa.getActivatingPlayer(), true)); if (triggerRemembered != null) { @@ -81,7 +81,7 @@ public class DelayedTriggerEffect extends SpellAbilityEffect { } } - if (mapParams.containsKey("Execute") || sa.hasAdditionalAbility("Execute")) { + if (sa.hasAdditionalAbility("Execute")) { AbilitySub overridingSA = (AbilitySub)sa.getAdditionalAbility("Execute").copy(lki, sa.getActivatingPlayer(), false); // need to reset the parent, additionalAbility does set it to this overridingSA.setParent(null); @@ -96,7 +96,7 @@ public class DelayedTriggerEffect extends SpellAbilityEffect { delTrig.setOverridingAbility(overridingSA); } - final TriggerHandler trigHandler = sa.getActivatingPlayer().getGame().getTriggerHandler(); + final TriggerHandler trigHandler = game.getTriggerHandler(); if (mapParams.containsKey("DelayedTriggerDefinedPlayer")) { // on sb's next turn Player p = Iterables.getFirst(AbilityUtils.getDefinedPlayers(sa.getHostCard(), mapParams.get("DelayedTriggerDefinedPlayer"), sa), null); trigHandler.registerPlayerDefinedDelayedTrigger(p, delTrig); diff --git a/forge-game/src/main/java/forge/game/ability/effects/EffectEffect.java b/forge-game/src/main/java/forge/game/ability/effects/EffectEffect.java index d7e1e7fec74..20723feba63 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/EffectEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/EffectEffect.java @@ -189,7 +189,7 @@ public class EffectEffect extends SpellAbilityEffect { for (final String s : effectReplacementEffects) { final String actualReplacement = AbilityUtils.getSVar(sa, s); - final ReplacementEffect parsedReplacement = ReplacementHandler.parseReplacement(actualReplacement, eff, true); + final ReplacementEffect parsedReplacement = ReplacementHandler.parseReplacement(actualReplacement, eff, true, sa); parsedReplacement.setActiveZone(EnumSet.of(ZoneType.Command)); parsedReplacement.setIntrinsic(true); eff.addReplacementEffect(parsedReplacement); 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 index 959190819d2..b44158828dd 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ImmediateTriggerEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ImmediateTriggerEffect.java @@ -57,7 +57,7 @@ public class ImmediateTriggerEffect extends SpellAbilityEffect { Card lki = CardUtil.getLKICopy(gameCard); lki.clearControllers(); lki.setOwner(sa.getActivatingPlayer()); - final Trigger immediateTrig = TriggerHandler.parseTrigger(mapParams, lki, sa.isIntrinsic()); + final Trigger immediateTrig = TriggerHandler.parseTrigger(mapParams, lki, sa.isIntrinsic(), null); immediateTrig.setSpawningAbility(sa.copy(lki, sa.getActivatingPlayer(), true)); // Need to copy paid costs @@ -74,7 +74,7 @@ public class ImmediateTriggerEffect extends SpellAbilityEffect { } } - if (mapParams.containsKey("Execute") || sa.hasAdditionalAbility("Execute")) { + if (sa.hasAdditionalAbility("Execute")) { AbilitySub overridingSA = (AbilitySub)sa.getAdditionalAbility("Execute").copy(lki, sa.getActivatingPlayer(), false); // need to set Parent to null, otherwise it might have wrong root ability overridingSA.setParent(null); @@ -85,9 +85,8 @@ public class ImmediateTriggerEffect extends SpellAbilityEffect { 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); + game.getTriggerHandler().registerDelayedTrigger(immediateTrig); } } diff --git a/forge-game/src/main/java/forge/game/ability/effects/ManaEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ManaEffect.java index 0a7ab66ac8d..74f6a3bdc3a 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ManaEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ManaEffect.java @@ -15,7 +15,6 @@ import forge.game.mana.Mana; import forge.game.player.Player; import forge.game.spellability.AbilityManaPart; import forge.game.spellability.SpellAbility; -import forge.game.spellability.TargetRestrictions; import forge.game.zone.ZoneType; import forge.util.Localizer; @@ -37,7 +36,6 @@ public class ManaEffect extends SpellAbilityEffect { sa.setUndoable(sa.isAbility() && sa.isUndoable()); final List tgtPlayers = getTargetPlayers(sa); - final TargetRestrictions tgt = sa.getTargetRestrictions(); final boolean optional = sa.hasParam("Optional"); final Game game = sa.getActivatingPlayer().getGame(); @@ -45,46 +43,26 @@ public class ManaEffect extends SpellAbilityEffect { return; } - if (sa.hasParam("DoubleManaInPool")) { - for (final Player player : tgtPlayers) { - for (byte color : ManaAtom.MANATYPES) { - int amountColor = player.getManaPool().getAmountOfColor(color); - for (int i = 0; i < amountColor; i++) { - abMana.produceMana(MagicColor.toShortString(color), player, sa); - } - } + for (Player p : tgtPlayers) { + if (sa.usesTargeting() && !p.canBeTargetedBy(sa)) { + // Illegal target. Skip. + continue; } - } - if (sa.hasParam("ProduceNoOtherMana")) { - return; - } - - if (abMana.isComboMana()) { - for (Player p : tgtPlayers) { + if (abMana.isComboMana()) { int amount = sa.hasParam("Amount") ? AbilityUtils.calculateAmount(card, sa.getParam("Amount"), sa) : 1; - if (tgt != null && !p.canBeTargetedBy(sa)) { - // Illegal target. Skip. - continue; - } - Player activator = sa.getActivatingPlayer(); String express = abMana.getExpressChoice(); String[] colorsProduced = abMana.getComboColors().split(" "); final StringBuilder choiceString = new StringBuilder(); - ColorSet colorOptions = null; + ColorSet colorOptions = ColorSet.fromNames(colorsProduced); String[] colorsNeeded = express.isEmpty() ? null : express.split(" "); - if (!abMana.isAnyMana()) { - colorOptions = ColorSet.fromNames(colorsProduced); - } else { - colorOptions = ColorSet.fromNames(MagicColor.Constant.ONLY_COLORS); - } boolean differentChoice = abMana.getOrigProduced().contains("Different"); ColorSet fullOptions = colorOptions; for (int nMana = 0; nMana < amount; nMana++) { String choice = ""; - if (colorsNeeded != null && colorsNeeded.length > nMana) { // select from express choices if possible + if (colorsNeeded != null && colorsNeeded.length > nMana) { // select from express choices if possible colorOptions = ColorSet .fromMask(fullOptions.getColor() & ManaAtom.fromName(colorsNeeded[nMana])); } @@ -93,10 +71,10 @@ public class ManaEffect extends SpellAbilityEffect { // just use the first possible color. choice = colorsProduced[differentChoice ? nMana : 0]; } else { - byte chosenColor = activator.getController().chooseColor(Localizer.getInstance().getMessage("lblSelectManaProduce"), sa, + byte chosenColor = p.getController().chooseColor(Localizer.getInstance().getMessage("lblSelectManaProduce"), sa, differentChoice ? fullOptions : colorOptions); if (chosenColor == 0) - throw new RuntimeException("ManaEffect::resolve() /*combo mana*/ - " + activator + " color mana choice is empty for " + card.getName()); + throw new RuntimeException("ManaEffect::resolve() /*combo mana*/ - " + p + " color mana choice is empty for " + card.getName()); fullOptions = ColorSet.fromMask(fullOptions.getMyColor() - chosenColor); choice = MagicColor.toShortString(chosenColor); @@ -116,18 +94,10 @@ public class ManaEffect extends SpellAbilityEffect { return; } - game.action.nofityOfValue(sa, card, Localizer.getInstance().getMessage("lblPlayerPickedChosen", activator.getName(), choiceString), activator); + game.getAction().nofityOfValue(sa, card, Localizer.getInstance().getMessage("lblPlayerPickedChosen", p.getName(), choiceString), p); abMana.setExpressChoice(choiceString.toString()); } - } - else if (abMana.isAnyMana()) { - for (Player p : tgtPlayers) { - if (tgt != null && !p.canBeTargetedBy(sa)) { - // Illegal target. Skip. - continue; - } - - Player act = sa.getActivatingPlayer(); + else if (abMana.isAnyMana()) { // AI color choice is set in ComputerUtils so only human players need to make a choice String colorsNeeded = abMana.getExpressChoice(); @@ -142,20 +112,14 @@ public class ManaEffect extends SpellAbilityEffect { colorMenu = mask == 0 ? ColorSet.ALL_COLORS : ColorSet.fromMask(mask); byte val = p.getController().chooseColor(Localizer.getInstance().getMessage("lblSelectManaProduce"), sa, colorMenu); if (0 == val) { - throw new RuntimeException("ManaEffect::resolve() /*any mana*/ - " + act + " color mana choice is empty for " + card.getName()); + throw new RuntimeException("ManaEffect::resolve() /*any mana*/ - " + p + " color mana choice is empty for " + card.getName()); } choice = MagicColor.toShortString(val); - game.action.nofityOfValue(sa, card, Localizer.getInstance().getMessage("lblPlayerPickedChosen", act.getName(), choice), act); + game.getAction().nofityOfValue(sa, card, Localizer.getInstance().getMessage("lblPlayerPickedChosen", p.getName(), choice), p); abMana.setExpressChoice(choice); } - } - else if (abMana.isSpecialMana()) { - for (Player p : tgtPlayers) { - if (tgt != null && !p.canBeTargetedBy(sa)) { - // Illegal target. Skip. - continue; - } + else if (abMana.isSpecialMana()) { String type = abMana.getOrigProduced().split("Special ")[1]; @@ -177,7 +141,7 @@ public class ManaEffect extends SpellAbilityEffect { if (cs.isMonoColor()) sb.append(MagicColor.toShortString(s.getColorMask())); else /* (cs.isMulticolor()) */ { - byte chosenColor = sa.getActivatingPlayer().getController().chooseColor(Localizer.getInstance().getMessage("lblChooseSingleColorFromTarget", s.toString()), sa, cs); + byte chosenColor = p.getController().chooseColor(Localizer.getInstance().getMessage("lblChooseSingleColorFromTarget", s.toString()), sa, cs); sb.append(MagicColor.toShortString(chosenColor)); } } @@ -210,34 +174,36 @@ public class ManaEffect extends SpellAbilityEffect { abMana.setExpressChoice(ColorSet.fromMask(colors)); } else if (type.startsWith("EachColoredManaSymbol")) { final String res = type.split("_")[1]; - final CardCollection list = AbilityUtils.getDefinedCards(card, res, sa); StringBuilder sb = new StringBuilder(); - for (Card c : list) { - String mana = c.getManaCost().toString(); - for (int i = 0; i < mana.length(); i++) { - char symbol = mana.charAt(i); - switch (symbol) { - case 'W': - case 'U': - case 'B': - case 'R': - case 'G': - sb.append(symbol).append(' '); - break; + for (Card c : AbilityUtils.getDefinedCards(card, res, sa)) { + for (ManaCostShard s : c.getManaCost()) { + ColorSet cs = ColorSet.fromMask(s.getColorMask()); + if(cs.isColorless()) + continue; + sb.append(' '); + if (cs.isMonoColor()) + sb.append(MagicColor.toShortString(s.getColorMask())); + else /* (cs.isMulticolor()) */ { + byte chosenColor = p.getController().chooseColor(Localizer.getInstance().getMessage("lblChooseSingleColorFromTarget", s.toString()), sa, cs); + sb.append(MagicColor.toShortString(chosenColor)); } } } abMana.setExpressChoice(sb.toString().trim()); + } else if (type.startsWith("DoubleManaInPool")) { + StringBuilder sb = new StringBuilder(); + for (byte color : ManaAtom.MANATYPES) { + sb.append(StringUtils.repeat(MagicColor.toShortString(color), " ", p.getManaPool().getAmountOfColor(color))).append(" "); + } + abMana.setExpressChoice(sb.toString().trim()); } if (abMana.getExpressChoice().isEmpty()) { System.out.println("AbilityFactoryMana::manaResolve() - special mana effect is empty for " + sa.getHostCard().getName()); } } - } - for (final Player player : tgtPlayers) { - abMana.produceMana(GameActionUtil.generatedMana(sa), player, sa); + abMana.produceMana(GameActionUtil.generatedMana(sa), p, sa); } // Only clear express choice after mana has been produced diff --git a/forge-game/src/main/java/forge/game/ability/effects/ManaReflectedEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ManaReflectedEffect.java index c5867722a2c..bdf69fbd81f 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ManaReflectedEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ManaReflectedEffect.java @@ -11,7 +11,6 @@ import forge.game.spellability.SpellAbility; import forge.util.Localizer; import java.util.Collection; -import java.util.List; import org.apache.commons.lang3.StringUtils; @@ -29,8 +28,7 @@ public class ManaReflectedEffect extends SpellAbilityEffect { final Collection colors = CardUtil.getReflectableManaColors(sa); - final List tgtPlayers = getTargetPlayers(sa); - for (final Player player : tgtPlayers) { + for (final Player player : getTargetPlayers(sa)) { final String generated = generatedReflectedMana(sa, colors, player); ma.produceMana(generated, player, sa); } diff --git a/forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java b/forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java index 17490640e78..09e91dd67e5 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java @@ -1,7 +1,6 @@ package forge.game.ability.effects; import java.util.ArrayList; -import java.util.Iterator; import java.util.List; import org.apache.commons.lang3.StringUtils; @@ -149,14 +148,13 @@ public class PlayEffect extends SpellAbilityEffect { if (sa.hasParam("ValidSA")) { final String valid[] = {sa.getParam("ValidSA")}; - final Iterator itr = tgtCards.iterator(); - while (itr.hasNext()) { - final Card c = itr.next(); - final List validSA = Lists.newArrayList(Iterables.filter(AbilityUtils.getBasicSpellsFromPlayEffect(c, controller), SpellAbilityPredicates.isValid(valid, controller , c, sa))); - if (validSA.size() == 0) { - itr.remove(); + List toRemove = Lists.newArrayList(); + for (Card c : tgtCards) { + if (!Iterables.any(AbilityUtils.getBasicSpellsFromPlayEffect(c, controller), SpellAbilityPredicates.isValid(valid, controller , c, sa))) { + toRemove.add(c); } } + tgtCards.removeAll(toRemove); if (tgtCards.isEmpty()) { return; } @@ -260,13 +258,8 @@ public class PlayEffect extends SpellAbilityEffect { continue; } - final boolean noManaCost = sa.hasParam("WithoutManaCost"); - if (noManaCost) { + if (sa.hasParam("WithoutManaCost")) { tgtSA = tgtSA.copyWithNoManaCost(); - // FIXME: a hack to get cards like Detonate only allow legal targets when cast without paying mana cost (with X=0). - if (tgtSA.hasParam("ValidTgtsWithoutManaCost")) { - tgtSA.getTargetRestrictions().changeValidTargets(tgtSA.getParam("ValidTgtsWithoutManaCost").split(",")); - } } else if (sa.hasParam("PlayCost")) { Cost abCost; if ("ManaCost".equals(sa.getParam("PlayCost"))) { diff --git a/forge-game/src/main/java/forge/game/ability/effects/ReplaceDamageEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ReplaceDamageEffect.java index 028980c02bf..eee8cdfbd0a 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ReplaceDamageEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ReplaceDamageEffect.java @@ -6,12 +6,14 @@ import forge.game.ability.AbilityKey; import org.apache.commons.lang3.StringUtils; import forge.game.Game; +import forge.game.GameLogEntryType; import forge.game.ability.AbilityUtils; import forge.game.ability.SpellAbilityEffect; import forge.game.card.Card; import forge.game.replacement.ReplacementResult; import forge.game.replacement.ReplacementType; import forge.game.spellability.SpellAbility; +import forge.util.TextUtil; public class ReplaceDamageEffect extends SpellAbilityEffect { @@ -58,6 +60,12 @@ public class ReplaceDamageEffect extends SpellAbilityEffect { } params.put(AbilityKey.DamageAmount, dmg); + // need to log Updated events there, or the log is wrong order + String message = sa.getReplacementEffect().toString(); + if ( !StringUtils.isEmpty(message)) { + message = TextUtil.fastReplace(message, "CARDNAME", card.getName()); + game.getGameLog().add(GameLogEntryType.EFFECT_REPLACED, message); + } //try to call replacementHandler with new Params ReplacementResult result = game.getReplacementHandler().run(event, params); 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 af761de13fb..e15dd8731f1 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 @@ -3,9 +3,12 @@ package forge.game.ability.effects; import java.util.List; import java.util.Map; +import org.apache.commons.lang3.StringUtils; + import com.google.common.collect.Maps; import forge.game.Game; +import forge.game.GameLogEntryType; import forge.game.GameObject; import forge.game.ability.AbilityKey; import forge.game.ability.AbilityUtils; @@ -16,6 +19,7 @@ import forge.game.player.Player; import forge.game.replacement.ReplacementResult; import forge.game.replacement.ReplacementType; import forge.game.spellability.SpellAbility; +import forge.util.TextUtil; public class ReplaceEffect extends SpellAbilityEffect { @@ -61,6 +65,13 @@ public class ReplaceEffect extends SpellAbilityEffect { params.put(AbilityKey.EffectOnly, true); } + // need to log Updated events there, or the log is wrong order + String message = sa.getReplacementEffect().toString(); + if ( !StringUtils.isEmpty(message)) { + message = TextUtil.fastReplace(message, "CARDNAME", card.getName()); + game.getGameLog().add(GameLogEntryType.EFFECT_REPLACED, message); + } + //try to call replacementHandler with new Params ReplacementResult result = game.getReplacementHandler().run(retype, params); switch (result) { diff --git a/forge-game/src/main/java/forge/game/ability/effects/ReplaceManaEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ReplaceManaEffect.java new file mode 100644 index 00000000000..246a1857255 --- /dev/null +++ b/forge-game/src/main/java/forge/game/ability/effects/ReplaceManaEffect.java @@ -0,0 +1,109 @@ +package forge.game.ability.effects; + +import java.util.Map; + +import org.apache.commons.lang3.StringUtils; + +import com.google.common.collect.Maps; + +import forge.card.ColorSet; +import forge.card.MagicColor; +import forge.game.Game; +import forge.game.GameLogEntryType; +import forge.game.ability.AbilityKey; +import forge.game.ability.SpellAbilityEffect; +import forge.game.card.Card; +import forge.game.player.Player; +import forge.game.replacement.ReplacementResult; +import forge.game.replacement.ReplacementType; +import forge.game.spellability.SpellAbility; +import forge.util.TextUtil; + +public class ReplaceManaEffect extends SpellAbilityEffect { + + @Override + public void resolve(SpellAbility sa) { + final Card card = sa.getHostCard(); + final Player player = sa.getActivatingPlayer(); + final Game game = card.getGame(); + + // outside of Replacement Effect, unwanted result + if (!sa.isReplacementAbility()) { + return; + } + + final ReplacementType event = sa.getReplacementEffect().getMode(); + @SuppressWarnings("unchecked") + Map originalParams = (Map) sa.getReplacingObject(AbilityKey.OriginalParams); + Map params = Maps.newHashMap(originalParams); + + String replaced = (String)sa.getReplacingObject(AbilityKey.Mana); + if (sa.hasParam("ReplaceMana")) { + // replace type and amount + replaced = sa.getParam("ReplaceMana"); + if ("Any".equals(replaced)) { + byte rs = MagicColor.GREEN; + rs = player.getController().chooseColor("Choose a color", sa, ColorSet.ALL_COLORS); + replaced = MagicColor.toShortString(rs); + } + } else if (sa.hasParam("ReplaceType")) { + // replace color and colorless + String color = sa.getParam("ReplaceType"); + if ("Any".equals(color)) { + byte rs = MagicColor.GREEN; + rs = player.getController().chooseColor("Choose a color", sa, ColorSet.ALL_COLORS); + color = MagicColor.toShortString(rs); + } + for (byte c : MagicColor.WUBRGC) { + String s = MagicColor.toShortString(c); + replaced = replaced.replace(s, color); + } + } else if (sa.hasParam("ReplaceColor")) { + // replace color + String color = sa.getParam("ReplaceColor"); + if ("Chosen".equals(color)) { + if (card.hasChosenColor()) { + color = MagicColor.toShortString(card.getChosenColor()); + } + } + if (sa.hasParam("ReplaceOnly")) { + replaced = replaced.replace(sa.getParam("ReplaceOnly"), color); + } else { + for (byte c : MagicColor.WUBRG) { + String s = MagicColor.toShortString(c); + replaced = replaced.replace(s, color); + } + } + } else if (sa.hasParam("ReplaceAmount")) { + // replace amount = multiples + replaced = StringUtils.repeat(replaced, " ", Integer.valueOf(sa.getParam("ReplaceAmount"))); + } + params.put(AbilityKey.Mana, replaced); + + // need to log Updated events there, or the log is wrong order + String message = sa.getReplacementEffect().toString(); + if ( !StringUtils.isEmpty(message)) { + message = TextUtil.fastReplace(message, "CARDNAME", card.getName()); + game.getGameLog().add(GameLogEntryType.EFFECT_REPLACED, message); + } + + //try to call replacementHandler with new Params + ReplacementResult result = game.getReplacementHandler().run(event, params); + switch (result) { + case NotReplaced: + case Updated: { + for (Map.Entry e : params.entrySet()) { + originalParams.put(e.getKey(), e.getValue()); + } + // effect was updated + originalParams.put(AbilityKey.ReplacementResult, ReplacementResult.Updated); + break; + } + default: + // effect was replaced with something else + originalParams.put(AbilityKey.ReplacementResult, result); + break; + } + } + +} diff --git a/forge-game/src/main/java/forge/game/ability/effects/ReplaceSplitDamageEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ReplaceSplitDamageEffect.java index f06565f1486..ccf1fa909e5 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ReplaceSplitDamageEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ReplaceSplitDamageEffect.java @@ -53,7 +53,7 @@ public class ReplaceSplitDamageEffect extends SpellAbilityEffect { if (card.getType().hasStringType("Effect") && prevent <= 0) { game.getAction().exile(card, null); } else if (!StringUtils.isNumeric(varValue)) { - card.setSVar(varValue, "Number$" + prevent); + sa.setSVar(varValue, "Number$" + prevent); } Card sourceLKI = (Card) sa.getReplacingObject(AbilityKey.Source); 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 c5e695f178e..ca49ca66424 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -625,10 +625,6 @@ public class Card extends GameEntity implements Comparable, IHasSVars { public Card manifest(Player p, SpellAbility sa) { // Turn Face Down (even if it's DFC). - ManaCost cost = getState(CardStateName.Original).getManaCost(); - - boolean isCreature = isCreature(); - // Sometimes cards are manifested while already being face down if (!turnFaceDown(true) && !isFaceDown()) { return null; @@ -643,11 +639,9 @@ public class Card extends GameEntity implements Comparable, IHasSVars { setManifested(true); Card c = game.getAction().moveToPlay(this, p, sa); - - // Add manifest demorph static ability for creatures - if (c.isManifested() && isCreature && !cost.isNoCost()) { - // Add Manifest to original State - c.getState(CardStateName.Original).addSpellAbility(CardFactoryUtil.abilityManifestFaceUp(c, cost)); + if (c.isInPlay()) { + c.setManifested(true); + c.turnFaceDown(true); c.updateStateForView(); } @@ -2491,7 +2485,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars { if (ab.getApi() == ApiType.ManaReflected) { colors.addAll(CardUtil.getReflectableManaColors(ab)); } else { - colors = CardUtil.canProduce(6, ab.getManaPart(), colors); + colors = CardUtil.canProduce(6, ab, colors); } } @@ -2502,7 +2496,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars { return true; } } else { - if (mana.getManaPart().canProduce(MagicColor.toShortString(s))) { + if (mana.canProduce(MagicColor.toShortString(s))) { return true; } } @@ -5450,15 +5444,6 @@ public class Card extends GameEntity implements Comparable, IHasSVars { // Note: This should only be called after state has been set to CardStateName.FaceDown, // so the below call should be valid since the state should have been created already. getState(CardStateName.FaceDown).setImageKey(ImageKeys.getTokenKey(image)); - if (!manifested) { - // remove Manifest Up abilities from Original State - CardState original = getState(CardStateName.Original); - for (SpellAbility sa : original.getNonManaAbilities()) { - if (sa.isManifestUp()) { - original.removeSpellAbility(sa); - } - } - } } public final boolean isForetold() { @@ -6090,21 +6075,20 @@ public class Card extends GameEntity implements Comparable, IHasSVars { public boolean hasETBTrigger(final boolean drawbackOnly) { for (final Trigger tr : getTriggers()) { - final Map params = tr.getMapParams(); if (tr.getMode() != TriggerType.ChangesZone) { continue; } - if (!params.get("Destination").equals(ZoneType.Battlefield.toString())) { + if (!tr.getParam("Destination").equals(ZoneType.Battlefield.toString())) { continue; } - if (params.containsKey("ValidCard") && !params.get("ValidCard").contains("Self")) { + if (tr.hasParam("ValidCard") && !tr.getParam("ValidCard").contains("Self")) { continue; } - if (drawbackOnly && params.containsKey("Execute")){ - String exec = this.getSVar(params.get("Execute")); - if (exec.contains("AB$")) { + if (drawbackOnly) { + SpellAbility sa = tr.ensureAbility(); + if (sa == null || sa.isActivatedAbility()) { continue; } } @@ -6332,6 +6316,14 @@ public class Card extends GameEntity implements Comparable, IHasSVars { } } + if (isInPlay() && isFaceDown() && isManifested()) { + CardState oState = getState(CardStateName.Original); + ManaCost cost = oState.getManaCost(); + if (oState.getType().isCreature()) { + abilities.add(CardFactoryUtil.abilityManifestFaceUp(this, cost)); + } + } + final Collection toRemove = Lists.newArrayListWithCapacity(abilities.size()); for (final SpellAbility sa : abilities) { sa.setActivatingPlayer(player); diff --git a/forge-game/src/main/java/forge/game/card/CardFactory.java b/forge-game/src/main/java/forge/game/card/CardFactory.java index 8a21f092a91..0d08fbf7e8b 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactory.java +++ b/forge-game/src/main/java/forge/game/card/CardFactory.java @@ -117,10 +117,9 @@ public class CardFactory { * which wouldn't ordinarily get set during a simple Card.copy() call. *

* */ - private final static Card copySpellHost(final SpellAbility sourceSA, final SpellAbility targetSA){ + private final static Card copySpellHost(final SpellAbility sourceSA, final SpellAbility targetSA, Player controller) { final Card source = sourceSA.getHostCard(); final Card original = targetSA.getHostCard(); - Player controller = sourceSA.getActivatingPlayer(); final Card c = copyCard(original, true); // change the color of the copy (eg: Fork) @@ -168,17 +167,15 @@ public class CardFactory { * @param bCopyDetails * a boolean. */ - public final static SpellAbility copySpellAbilityAndPossiblyHost(final SpellAbility sourceSA, final SpellAbility targetSA) { - Player controller = sourceSA.getActivatingPlayer(); - + public final static SpellAbility copySpellAbilityAndPossiblyHost(final SpellAbility sourceSA, final SpellAbility targetSA, final Player controller) { //it is only necessary to copy the host card if the SpellAbility is a spell, not an ability - final Card c = targetSA.isSpell() ? copySpellHost(sourceSA, targetSA) : targetSA.getHostCard(); + final Card c = targetSA.isSpell() ? copySpellHost(sourceSA, targetSA, controller) : targetSA.getHostCard(); final SpellAbility copySA; if (targetSA.isTrigger() && targetSA.isWrapper()) { - copySA = getCopiedTriggeredAbility((WrappedAbility)targetSA, c); + copySA = getCopiedTriggeredAbility((WrappedAbility)targetSA, c, controller); } else { - copySA = targetSA.copy(c, false); + copySA = targetSA.copy(c, controller, false); } copySA.setCopied(true); @@ -361,12 +358,12 @@ public class CardFactory { // Name first so Senty has the Card name c.setName(face.getName()); + for (Entry v : face.getVariables()) c.setSVar(v.getKey(), v.getValue()); + for (String r : face.getReplacements()) c.addReplacementEffect(ReplacementHandler.parseReplacement(r, c, true)); for (String s : face.getStaticAbilities()) c.addStaticAbility(s); for (String t : face.getTriggers()) c.addTrigger(TriggerHandler.parseTrigger(t, c, true)); - for (Entry v : face.getVariables()) c.setSVar(v.getKey(), v.getValue()); - // keywords not before variables c.addIntrinsicKeywords(face.getKeywords(), false); @@ -555,12 +552,12 @@ public class CardFactory { * * return a wrapped ability */ - public static SpellAbility getCopiedTriggeredAbility(final WrappedAbility sa, final Card newHost) { + public static SpellAbility getCopiedTriggeredAbility(final WrappedAbility sa, final Card newHost, final Player controller) { if (!sa.isTrigger()) { return null; } - return new WrappedAbility(sa.getTrigger(), sa.getWrappedAbility().copy(newHost, false), sa.getDecider()); + return new WrappedAbility(sa.getTrigger(), sa.getWrappedAbility().copy(newHost, controller, false), controller); } public static CardCloneStates getCloneStates(final Card in, final Card out, final CardTraitBase sa) { @@ -667,6 +664,7 @@ public class CardFactory { if (origSVars.containsKey(s)) { final String actualTrigger = origSVars.get(s); final Trigger parsedTrigger = TriggerHandler.parseTrigger(actualTrigger, out, true); + parsedTrigger.setOriginalHost(host); state.addTrigger(parsedTrigger); } } @@ -690,6 +688,7 @@ public class CardFactory { if (origSVars.containsKey(s)) { final String actualAbility = origSVars.get(s); final SpellAbility grantedAbility = AbilityFactory.getAbility(actualAbility, out); + grantedAbility.setOriginalHost(host); grantedAbility.setIntrinsic(true); state.addSpellAbility(grantedAbility); } @@ -703,6 +702,7 @@ public class CardFactory { if (origSVars.containsKey(s)) { final String actualStatic = origSVars.get(s); final StaticAbility grantedStatic = new StaticAbility(actualStatic, out); + grantedStatic.setOriginalHost(host); grantedStatic.setIntrinsic(true); state.addStaticAbility(grantedStatic); } diff --git a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java index 75d7c69e7e2..0daf6d6d3ac 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java @@ -177,7 +177,7 @@ public class CardFactoryUtil { // Cost need to be set later StringBuilder sb = new StringBuilder(); sb.append("ST$ SetState | Cost$ 0 | CostDesc$ Unmanifest ").append(costDesc); - sb.append(" | ManifestUp$ True | Secondary$ True | IsPresent$ Card.Self+faceDown+manifested"); + sb.append(" | ManifestUp$ True | Secondary$ True | PresentDefined$ Self | IsPresent$ Card.faceDown+manifested"); sb.append(" | Mode$ TurnFace | SpellDescription$ (Turn this face up any time for its mana cost.)"); final SpellAbility manifestUp = AbilityFactory.getAbility(sb.toString(), sourceCard); @@ -1478,7 +1478,7 @@ public class CardFactoryUtil { for (Card card : otb) { if (!card.isTapped() || !untappedOnly) { for (SpellAbility ma : card.getManaAbilities()) { - if (ma.getManaPart().canProduce(MagicColor.toShortString(color))) { + if (ma.canProduce(MagicColor.toShortString(color))) { uniqueColors++; continue outer; } @@ -2230,7 +2230,7 @@ public class CardFactoryUtil { final String abStringAfflict = "DB$ LoseLife | Defined$ TriggeredDefendingPlayer" + " | LifeAmount$ " + n; - final Trigger afflictTrigger = TriggerHandler.parseTrigger(trigStr, card, intrinsic); + final Trigger afflictTrigger = TriggerHandler.parseTrigger(trigStr, card, intrinsic, null); afflictTrigger.setOverridingAbility(AbilityFactory.getAbility(abStringAfflict, card)); inst.addTrigger(afflictTrigger); diff --git a/forge-game/src/main/java/forge/game/card/CardProperty.java b/forge-game/src/main/java/forge/game/card/CardProperty.java index 369eaab2527..445a3bf6956 100644 --- a/forge-game/src/main/java/forge/game/card/CardProperty.java +++ b/forge-game/src/main/java/forge/game/card/CardProperty.java @@ -648,9 +648,10 @@ public class CardProperty { return false; } } else if (property.startsWith("DamagedBy")) { - if ((property.endsWith("Source") || property.equals("DamagedBy")) && - !card.getReceivedDamageFromThisTurn().containsKey(source)) { - return false; + if (property.endsWith("Source") || property.equals("DamagedBy")) { + if (!card.getReceivedDamageFromThisTurn().containsKey(source)) { + return false; + } } else { String prop = property.substring("DamagedBy".length()); @@ -911,7 +912,7 @@ public class CardProperty { } else if (property.startsWith("canProduceManaColor")) { final String color = property.split("canProduceManaColor ")[1]; for (SpellAbility ma : card.getManaAbilities()) { - if (ma.getManaPart().canProduce(MagicColor.toShortString(color))) { + if (ma.canProduce(MagicColor.toShortString(color))) { return true; } } diff --git a/forge-game/src/main/java/forge/game/card/CardUtil.java b/forge-game/src/main/java/forge/game/card/CardUtil.java index e1726689621..975c43a62cc 100644 --- a/forge-game/src/main/java/forge/game/card/CardUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardUtil.java @@ -391,7 +391,6 @@ public final class CardUtil { final String colorOrType = sa.getParam("ColorOrType"); // currently Color or Type, Type is colors + colorless - final String validCard = sa.getParam("Valid"); final String reflectProperty = sa.getParam("ReflectProperty"); // Produce (Reflecting Pool) or Is (Meteor Crater) @@ -400,28 +399,30 @@ public final class CardUtil { maxChoices++; } - CardCollection cards = null; + CardCollection cards; - // Reuse AF_Defined in a slightly different way - if (validCard.startsWith("Defined.")) { - cards = AbilityUtils.getDefinedCards(card, TextUtil.fastReplace(validCard, "Defined.", ""), abMana); - } else { - if (sa.getActivatingPlayer() == null) { - sa.setActivatingPlayer(sa.getHostCard().getController()); + if (sa.hasParam("Valid")) { + final String validCard = sa.getParam("Valid"); + // Reuse AF_Defined in a slightly different way + if (validCard.startsWith("Defined.")) { + cards = AbilityUtils.getDefinedCards(card, TextUtil.fastReplace(validCard, "Defined.", ""), abMana); + } else { + if (sa.getActivatingPlayer() == null) { + sa.setActivatingPlayer(sa.getHostCard().getController()); + } + final Game game = sa.getActivatingPlayer().getGame(); + cards = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), validCard, abMana.getActivatingPlayer(), card); } - final Game game = sa.getActivatingPlayer().getGame(); - cards = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), validCard, abMana.getActivatingPlayer(), card); - } - // remove anything cards that is already in parents - for (final Card p : parents) { - cards.remove(p); - } + // remove anything cards that is already in parents + cards.removeAll(parents); - if ((cards.size() == 0) && !reflectProperty.equals("Produced")) { - return colors; + if (cards.isEmpty()) { + return colors; + } + } else { + cards = new CardCollection(); } - if (reflectProperty.equals("Is")) { // Meteor Crater for (final Card card1 : cards) { // For each card, go through all the colors and if the card is that color, add @@ -436,7 +437,7 @@ public final class CardUtil { } } else if (reflectProperty.equals("Produced")) { // Why is this name so similar to the one below? - final String producedColors = abMana instanceof AbilitySub ? (String) abMana.getRootAbility().getTriggeringObject(AbilityKey.Produced) : (String) abMana.getTriggeringObject(AbilityKey.Produced); + final String producedColors = (String) abMana.getRootAbility().getTriggeringObject(AbilityKey.Produced); for (final String col : MagicColor.Constant.ONLY_COLORS) { final String s = MagicColor.toShortString(col); if (producedColors.contains(s)) { @@ -469,7 +470,7 @@ public final class CardUtil { } continue; } - colors = canProduce(maxChoices, ab.getManaPart(), colors); + colors = canProduce(maxChoices, ab, colors); if (!parents.contains(ab.getHostCard())) { parents.add(ab.getHostCard()); } @@ -486,19 +487,18 @@ public final class CardUtil { return colors; } - public static Set canProduce(final int maxChoices, final AbilityManaPart ab, + public static Set canProduce(final int maxChoices, final SpellAbility sa, final Set colors) { - if (ab == null) { + if (sa == null) { return colors; } for (final String col : MagicColor.Constant.ONLY_COLORS) { - final String s = MagicColor.toShortString(col); - if (ab.canProduce(s)) { + if (sa.canProduce(MagicColor.toShortString(col))) { colors.add(col); } } - if (maxChoices == 6 && ab.canProduce("C")) { + if (maxChoices == 6 && sa.canProduce("C")) { colors.add(MagicColor.Constant.COLORLESS); } diff --git a/forge-game/src/main/java/forge/game/mana/ManaCostBeingPaid.java b/forge-game/src/main/java/forge/game/mana/ManaCostBeingPaid.java index 6ab3e39c5dd..c46309f38bd 100644 --- a/forge-game/src/main/java/forge/game/mana/ManaCostBeingPaid.java +++ b/forge-game/src/main/java/forge/game/mana/ManaCostBeingPaid.java @@ -20,6 +20,8 @@ package forge.game.mana; import com.google.common.base.Predicate; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + import forge.card.ColorSet; import forge.card.MagicColor; import forge.card.mana.IParserManaCost; @@ -105,11 +107,16 @@ public class ManaCostBeingPaid { xCount = copy.xCount; totalCount = copy.totalCount; } + + @Override + public String toString() { + return "{x=" + xCount + " total=" + totalCount + "}"; + } } // holds Mana_Part objects // ManaPartColor is stored before ManaPartGeneric - private final Map unpaidShards = new HashMap<>(); + private final Map unpaidShards = Maps.newHashMap(); private Map xManaCostPaidByColor; private final String sourceRestriction; private byte sunburstMap = 0; @@ -124,7 +131,7 @@ public class ManaCostBeingPaid { unpaidShards.put(m.getKey(), new ShardCount(m.getValue())); } if (manaCostBeingPaid.xManaCostPaidByColor != null) { - xManaCostPaidByColor = new HashMap<>(manaCostBeingPaid.xManaCostPaidByColor); + xManaCostPaidByColor = Maps.newHashMap(manaCostBeingPaid.xManaCostPaidByColor); } sourceRestriction = manaCostBeingPaid.sourceRestriction; sunburstMap = manaCostBeingPaid.sunburstMap; @@ -503,7 +510,7 @@ public class ManaCostBeingPaid { sc.xCount--; String color = MagicColor.toShortString(colorMask); if (xManaCostPaidByColor == null) { - xManaCostPaidByColor = new HashMap<>(); + xManaCostPaidByColor = Maps.newHashMap(); } Integer xColor = xManaCostPaidByColor.get(color); if (xColor == null) { @@ -602,19 +609,7 @@ public class ManaCostBeingPaid { } int nGeneric = getGenericManaAmount(); - List shards = new ArrayList<>(unpaidShards.keySet()); - - // TODO Fix this. Should we really be changing Shards here? - if (false && pool != null) { //replace shards with generic mana if they can be paid with any color mana - for (int i = 0; i < shards.size(); i++) { - ManaCostShard shard = shards.get(i); - if (shard != ManaCostShard.GENERIC && pool.getPossibleColorUses(shard.getColorMask()) == ManaAtom.ALL_MANA_TYPES) { - nGeneric += unpaidShards.get(shard).totalCount; - shards.remove(i); - i--; - } - } - } + List shards = Lists.newArrayList(unpaidShards.keySet()); if (nGeneric > 0) { if (nGeneric <= 20) { diff --git a/forge-game/src/main/java/forge/game/mana/ManaPool.java b/forge-game/src/main/java/forge/game/mana/ManaPool.java index 48cf1e038b4..013ab399c3a 100644 --- a/forge-game/src/main/java/forge/game/mana/ManaPool.java +++ b/forge-game/src/main/java/forge/game/mana/ManaPool.java @@ -166,23 +166,27 @@ public class ManaPool extends ManaConversionMatrix implements Iterable { owner.updateManaForView(); } - private void removeMana(final Mana mana) { - Collection cm = floatingMana.get(mana.getColor()); - if (cm.remove(mana)) { + private boolean removeMana(final Mana mana) { + if (floatingMana.remove(mana.getColor(), mana)) { owner.updateManaForView(); owner.getGame().fireEvent(new GameEventManaPool(owner, EventValueChangeType.Removed, mana)); + return true; } + return false; } public final void payManaFromAbility(final SpellAbility saPaidFor, ManaCostBeingPaid manaCost, final SpellAbility saPayment) { // Mana restriction must be checked before this method is called final List paidAbs = saPaidFor.getPayingManaAbilities(); - AbilityManaPart abManaPart = saPayment.getManaPartRecursive(); paidAbs.add(saPayment); // assumes some part on the mana produced by the ability will get used - for (final Mana mana : abManaPart.getLastManaProduced()) { - if (tryPayCostWithMana(saPaidFor, manaCost, mana, false)) { - saPaidFor.getPayingMana().add(0, mana); + + // need to get all mana from all ManaAbilities of the SpellAbility + for (AbilityManaPart mp : saPayment.getAllManaParts()) { + for (final Mana mana : mp.getLastManaProduced()) { + if (tryPayCostWithMana(saPaidFor, manaCost, mana, false)) { + saPaidFor.getPayingMana().add(0, mana); + } } } } @@ -216,8 +220,12 @@ public class ManaPool extends ManaConversionMatrix implements Iterable { if (!manaCost.isNeeded(mana, this)) { return false; } + // only pay mana into manaCost when the Mana could be removed from the Mana pool + // if the mana wasn't in the mana pool then something is wrong + if (!removeMana(mana)) { + return false; + } manaCost.payMana(mana, this); - removeMana(mana); return true; } diff --git a/forge-game/src/main/java/forge/game/player/Player.java b/forge-game/src/main/java/forge/game/player/Player.java index 6acd4c553a4..0b45d8273f6 100644 --- a/forge-game/src/main/java/forge/game/player/Player.java +++ b/forge-game/src/main/java/forge/game/player/Player.java @@ -3539,4 +3539,11 @@ public class Player extends GameEntity implements Comparable { public void resetCycledThisTurn() { cycledThisTurn = 0; } + + public boolean hasUrzaLands() { + final CardCollectionView landsControlled = getCardsIn(ZoneType.Battlefield); + return Iterables.any(landsControlled, Predicates.and(CardPredicates.isType("Urza's"), CardPredicates.isType("Mine"))) + && Iterables.any(landsControlled, Predicates.and(CardPredicates.isType("Urza's"), CardPredicates.isType("Power-Plant"))) + && Iterables.any(landsControlled, Predicates.and(CardPredicates.isType("Urza's"), CardPredicates.isType("Tower"))); + } } diff --git a/forge-game/src/main/java/forge/game/player/PlayerController.java b/forge-game/src/main/java/forge/game/player/PlayerController.java index bcada42ecd5..b09df0a2b7b 100644 --- a/forge-game/src/main/java/forge/game/player/PlayerController.java +++ b/forge-game/src/main/java/forge/game/player/PlayerController.java @@ -112,7 +112,7 @@ public abstract class PlayerController { public abstract Integer announceRequirements(SpellAbility ability, String announce); public abstract CardCollectionView choosePermanentsToSacrifice(SpellAbility sa, int min, int max, CardCollectionView validTargets, String message); public abstract CardCollectionView choosePermanentsToDestroy(SpellAbility sa, int min, int max, CardCollectionView validTargets, String message); - public abstract TargetChoices chooseNewTargetsFor(SpellAbility ability); + public abstract TargetChoices chooseNewTargetsFor(SpellAbility ability, Predicate filter, boolean optional); public abstract boolean chooseTargetsFor(SpellAbility currentAbility); // this is bad a function for it assigns targets to sa inside its body // Specify a target of a spell (Spellskite) diff --git a/forge-game/src/main/java/forge/game/replacement/ReplaceProduceMana.java b/forge-game/src/main/java/forge/game/replacement/ReplaceProduceMana.java index 5c95623af22..2c31c859b83 100644 --- a/forge-game/src/main/java/forge/game/replacement/ReplaceProduceMana.java +++ b/forge-game/src/main/java/forge/game/replacement/ReplaceProduceMana.java @@ -31,10 +31,10 @@ public class ReplaceProduceMana extends ReplacementEffect { */ @Override public boolean canReplace(Map runParams) { - //Check for tapping - if (!hasParam("NoTapCheck")) { + + if (hasParam("ValidAbility")) { final SpellAbility manaAbility = (SpellAbility) runParams.get(AbilityKey.AbilityMana); - if (manaAbility == null || !manaAbility.getRootAbility().getPayCosts().hasTapCost()) { + if (!matchesValid(manaAbility, getParam("ValidAbility").split(","), getHostCard())) { return false; } } @@ -43,15 +43,23 @@ public class ReplaceProduceMana extends ReplacementEffect { String full = getParam("ManaAmount"); String operator = full.substring(0, 2); String operand = full.substring(2); + int intoperand = AbilityUtils.calculateAmount(getHostCard(), operand, this); + int manaAmount = StringUtils.countMatches((String) runParams.get(AbilityKey.Mana), " ") + 1; if (!Expressions.compare(manaAmount, operator, intoperand)) { return false; } } + if (hasParam("ValidPlayer")) { + if (!matchesValid(runParams.get(AbilityKey.Player), getParam("ValidPlayer").split(","), getHostCard())) { + return false; + } + } + if (hasParam("ValidCard")) { - if (!matchesValid(runParams.get(AbilityKey.Affected), getParam("ValidCard").split(","), this.getHostCard())) { + if (!matchesValid(runParams.get(AbilityKey.Affected), getParam("ValidCard").split(","), getHostCard())) { return false; } } @@ -60,4 +68,7 @@ public class ReplaceProduceMana extends ReplacementEffect { } + public void setReplacingObjects(Map runParams, SpellAbility sa) { + sa.setReplacingObject(AbilityKey.Mana, runParams.get(AbilityKey.Mana)); + } } diff --git a/forge-game/src/main/java/forge/game/replacement/ReplacementEffect.java b/forge-game/src/main/java/forge/game/replacement/ReplacementEffect.java index 06e6bdc95d4..d1aa184e48f 100644 --- a/forge-game/src/main/java/forge/game/replacement/ReplacementEffect.java +++ b/forge-game/src/main/java/forge/game/replacement/ReplacementEffect.java @@ -19,6 +19,7 @@ package forge.game.replacement; import forge.game.Game; import forge.game.TriggerReplacementBase; +import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityKey; import forge.game.ability.AbilityUtils; import forge.game.card.Card; @@ -271,4 +272,13 @@ public abstract class ReplacementEffect extends TriggerReplacementBase { void setMode(ReplacementType mode) { this.mode = mode; } + + public SpellAbility ensureAbility() { + SpellAbility sa = getOverridingAbility(); + if (sa == null && hasParam("ReplaceWith")) { + sa = AbilityFactory.getAbility(getHostCard(), getParam("ReplaceWith")); + setOverridingAbility(sa); + } + return sa; + } } diff --git a/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java b/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java index 9842d8ca356..e042aace629 100644 --- a/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java +++ b/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java @@ -17,9 +17,9 @@ */ package forge.game.replacement; -import forge.card.MagicColor; import forge.game.Game; import forge.game.GameLogEntryType; +import forge.game.IHasSVars; import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityKey; import forge.game.ability.AbilityUtils; @@ -259,12 +259,17 @@ public class ReplacementHandler { chosenRE.setHasRun(false); hasRun.remove(chosenRE); chosenRE.setOtherChoices(null); - String message = chosenRE.getDescription(); - if ( !StringUtils.isEmpty(message)) - if (chosenRE.getHostCard() != null) { - message = TextUtil.fastReplace(message, "CARDNAME", chosenRE.getHostCard().getName()); - } - game.getGameLog().add(GameLogEntryType.EFFECT_REPLACED, message); + + // Updated Replacements need to be logged elsewhere because its otherwise in the wrong order + if (res != ReplacementResult.Updated) { + String message = chosenRE.getDescription(); + if ( !StringUtils.isEmpty(message)) + if (chosenRE.getHostCard() != null) { + message = TextUtil.fastReplace(message, "CARDNAME", chosenRE.getHostCard().getName()); + } + game.getGameLog().add(GameLogEntryType.EFFECT_REPLACED, message); + } + return res; } @@ -344,25 +349,11 @@ public class ReplacementHandler { Player player = host.getController(); - if (mapParams.containsKey("ManaReplacement")) { - final SpellAbility manaAb = (SpellAbility) runParams.get(AbilityKey.AbilityMana); - final Player player1 = (Player) runParams.get(AbilityKey.Player); - final String rep = (String) runParams.get(AbilityKey.Mana); - // Replaced mana type - final Card repHost = host; - String repType = repHost.getSVar(mapParams.get("ManaReplacement")); - if (repType.contains("Chosen") && repHost.hasChosenColor()) { - repType = TextUtil.fastReplace(repType, "Chosen", MagicColor.toShortString(repHost.getChosenColor())); - } - manaAb.getManaPart().setManaReplaceType(repType); - manaAb.getManaPart().produceMana(rep, player1, manaAb); - } else { - player.getController().playSpellAbilityNoStack(effectSA, true); - // if the spellability is a replace effect then its some new logic - // if ReplacementResult is set in run params use that instead - if (runParams.containsKey(AbilityKey.ReplacementResult)) { - return (ReplacementResult) runParams.get(AbilityKey.ReplacementResult); - } + player.getController().playSpellAbilityNoStack(effectSA, true); + // if the spellability is a replace effect then its some new logic + // if ReplacementResult is set in run params use that instead + if (runParams.containsKey(AbilityKey.ReplacementResult)) { + return (ReplacementResult) runParams.get(AbilityKey.ReplacementResult); } return ReplacementResult.Replaced; @@ -380,7 +371,10 @@ public class ReplacementHandler { * @return A finished instance */ public static ReplacementEffect parseReplacement(final String repParse, final Card host, final boolean intrinsic) { - return ReplacementHandler.parseReplacement(parseParams(repParse), host, intrinsic); + return parseReplacement(repParse, host, intrinsic, host); + } + public static ReplacementEffect parseReplacement(final String repParse, final Card host, final boolean intrinsic, final IHasSVars sVarHolder) { + return ReplacementHandler.parseReplacement(parseParams(repParse), host, intrinsic, sVarHolder); } public static Map parseParams(final String repParse) { @@ -398,7 +392,7 @@ public class ReplacementHandler { * The card that hosts the replacement effect * @return The finished instance */ - private static ReplacementEffect parseReplacement(final Map mapParams, final Card host, final boolean intrinsic) { + private static ReplacementEffect parseReplacement(final Map mapParams, final Card host, final boolean intrinsic, final IHasSVars sVarHolder) { final ReplacementType rt = ReplacementType.smartValueOf(mapParams.get("Event")); ReplacementEffect ret = rt.createReplacement(mapParams, host, intrinsic); @@ -407,6 +401,10 @@ public class ReplacementHandler { ret.setActiveZone(EnumSet.copyOf(ZoneType.listValueOf(activeZones))); } + if (mapParams.containsKey("ReplaceWith") && sVarHolder != null) { + ret.setOverridingAbility(AbilityFactory.getAbility(host, mapParams.get("ReplaceWith"), sVarHolder)); + } + return ret; } } diff --git a/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java b/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java index c867926a28b..9bb1ff3dd0e 100644 --- a/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java +++ b/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java @@ -18,18 +18,18 @@ package forge.game.spellability; import com.google.common.collect.Lists; -import com.google.common.collect.Maps; + import forge.card.ColorSet; import forge.card.MagicColor; import forge.card.mana.ManaAtom; import forge.card.mana.ManaCostShard; import forge.game.Game; -import forge.game.ability.AbilityFactory; +import forge.game.GameActionUtil; import forge.game.ability.AbilityKey; +import forge.game.ability.ApiType; import forge.game.card.Card; -import forge.game.card.CardFactoryUtil; -import forge.game.card.CounterType; +import forge.game.card.CardUtil; import forge.game.mana.Mana; import forge.game.mana.ManaPool; import forge.game.player.Player; @@ -37,15 +37,11 @@ import forge.game.replacement.*; import forge.game.trigger.Trigger; import forge.game.trigger.TriggerHandler; import forge.game.trigger.TriggerType; -import forge.game.zone.ZoneType; -import forge.util.Lang; import forge.util.TextUtil; import org.apache.commons.lang3.StringUtils; import java.util.List; import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; /** *

@@ -62,6 +58,7 @@ public class AbilityManaPart implements java.io.Serializable { private final String origProduced; private String lastExpressChoice = ""; private final String manaRestrictions; + private String extraManaRestrictions = ""; private final String cannotCounterSpell; private final String addsKeywords; private final String addsKeywordsType; @@ -69,7 +66,6 @@ public class AbilityManaPart implements java.io.Serializable { private final String addsCounters; private final String triggersWhenSpent; private final boolean persistentMana; - private String manaReplaceType; private transient List lastManaProduced = Lists.newArrayList(); @@ -99,7 +95,6 @@ public class AbilityManaPart implements java.io.Serializable { this.addsCounters = params.get("AddsCounters"); this.triggersWhenSpent = params.get("TriggersWhenSpent"); this.persistentMana = (null != params.get("PersistentMana")) && "True".equalsIgnoreCase(params.get("PersistentMana")); - this.manaReplaceType = params.containsKey("ManaReplaceType") ? params.get("ManaReplaceType") : ""; } /** @@ -126,14 +121,26 @@ public class AbilityManaPart implements java.io.Serializable { public final void produceMana(final String produced, final Player player, SpellAbility sa) { final Card source = this.getSourceCard(); final ManaPool manaPool = player.getManaPool(); - String afterReplace = applyManaReplacement(sa, produced); + String afterReplace = produced; + + SpellAbility root = sa == null ? null : sa.getRootAbility(); + final Map repParams = AbilityKey.mapFromAffected(source); - repParams.put(AbilityKey.Mana, produced); + repParams.put(AbilityKey.Mana, afterReplace); repParams.put(AbilityKey.Player, player); - repParams.put(AbilityKey.AbilityMana, sa); - if (player.getGame().getReplacementHandler().run(ReplacementType.ProduceMana, repParams) != ReplacementResult.NotReplaced) { + repParams.put(AbilityKey.AbilityMana, root); + repParams.put(AbilityKey.Activator, root == null ? null : root.getActivatingPlayer()); + + switch (player.getGame().getReplacementHandler().run(ReplacementType.ProduceMana, repParams)) { + case NotReplaced: + break; + case Updated: + afterReplace = (String) repParams.get(AbilityKey.Mana); + break; + default: return; } + //clear lastProduced this.lastManaProduced.clear(); @@ -159,14 +166,14 @@ public class AbilityManaPart implements java.io.Serializable { // Run triggers final Map runParams = AbilityKey.mapFromCard(source); runParams.put(AbilityKey.Player, player); - runParams.put(AbilityKey.AbilityMana, sa); runParams.put(AbilityKey.Produced, afterReplace); + runParams.put(AbilityKey.AbilityMana, root); + runParams.put(AbilityKey.Activator, root == null ? null : root.getActivatingPlayer()); + player.getGame().getTriggerHandler().runTrigger(TriggerType.TapsForMana, runParams, false); - if (source.isLand()) { - player.setTappedLandForManaThisTurn(true); + if (source.isLand() && sa.getPayCosts() != null && sa.getPayCosts().hasTapCost() ) { + player.setTappedLandForManaThisTurn(true); } - // Clear Mana replacement - this.manaReplaceType = ""; } // end produceMana(String) /** @@ -236,61 +243,7 @@ public class AbilityManaPart implements java.io.Serializable { String[] parse = this.addsCounters.split("_"); // Convert random SVars if there are other cards with this effect if (c.isValid(parse[0], c.getController(), c, null)) { - final Game game = this.sourceCard.getGame(); - final Card eff = new Card(game.nextCardId(), game); - eff.setTimestamp(game.getNextTimestamp()); - eff.setName(sourceCard.getName() + "'s Effect"); - eff.addType("Effect"); - eff.setOwner(controller); - - eff.setImageKey(sourceCard.getImageKey()); - eff.setColor(MagicColor.COLORLESS); - eff.setImmutable(true); - // try to get the SpellAbility from the mana ability - //eff.setEffectSource((SpellAbility)null); - - eff.addRemembered(c); - - String abStr = "DB$ PutCounter | Defined$ ReplacedCard | CounterType$ " + parse[1] - + " | ETB$ True | CounterNum$ " + parse[2]; - - SpellAbility sa = AbilityFactory.getAbility(abStr, c); - if (!StringUtils.isNumeric(parse[2])) { - sa.setSVar(parse[2], sourceCard.getSVar(parse[2])); - } - CardFactoryUtil.setupETBReplacementAbility(sa); - - String desc = "It enters the battlefield with "; - desc += Lang.nounWithNumeral(parse[2], CounterType.getType(parse[1]).getName() + " counter"); - desc += " on it."; - - String repeffstr = "Event$ Moved | ValidCard$ Card.IsRemembered | Destination$ Battlefield | Description$ " + desc; - - ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, eff, true); - re.setLayer(ReplacementLayer.Other); - re.setOverridingAbility(sa); - - eff.addReplacementEffect(re); - - // Forgot Trigger - String trig = "Mode$ ChangesZone | ValidCard$ Card.IsRemembered | Origin$ Stack | Destination$ Any | TriggerZones$ Command | Static$ True"; - String forgetEffect = "DB$ Pump | ForgetObjects$ TriggeredCard"; - String exileEffect = "DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile" - + " | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ EQ0"; - - SpellAbility saForget = AbilityFactory.getAbility(forgetEffect, eff); - AbilitySub saExile = (AbilitySub) AbilityFactory.getAbility(exileEffect, eff); - saForget.setSubAbility(saExile); - - final Trigger parsedTrigger = TriggerHandler.parseTrigger(trig, eff, true); - parsedTrigger.setOverridingAbility(saForget); - eff.addTrigger(parsedTrigger); - 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, null); - game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); + GameActionUtil.createETBCountersEffect(sourceCard, c, controller, parse[1], parse[2]); } } @@ -315,7 +268,15 @@ public class AbilityManaPart implements java.io.Serializable { * @return a {@link java.lang.String} object. */ public String getManaRestrictions() { - return this.manaRestrictions; + return manaRestrictions; + } + + public void setExtraManaRestriction(String str) { + this.extraManaRestrictions = str; + } + + public boolean meetsManaRestrictions(final SpellAbility sa) { + return meetsManaRestrictions(sa, this.manaRestrictions) && meetsManaRestrictions(sa, this.extraManaRestrictions); } /** @@ -327,14 +288,14 @@ public class AbilityManaPart implements java.io.Serializable { * a {@link forge.game.spellability.SpellAbility} object. * @return a boolean. */ - public boolean meetsManaRestrictions(final SpellAbility sa) { + public boolean meetsManaRestrictions(final SpellAbility sa, String restrictions) { // No restrictions - if (this.manaRestrictions.isEmpty()) { + if (restrictions.isEmpty()) { return true; } // Loop over restrictions - for (String restriction : this.manaRestrictions.split(",")) { + for (String restriction : restrictions.split(",")) { if (restriction.equals("nonSpell")) { return !sa.isSpell(); } @@ -365,9 +326,9 @@ public class AbilityManaPart implements java.io.Serializable { if (sa.isValid(restriction, this.getSourceCard().getController(), this.getSourceCard(), null)) { return true; } - + if (restriction.equals("CantPayGenericCosts")) { - return true; + return true; } if (sa.isAbility()) { @@ -390,7 +351,7 @@ public class AbilityManaPart implements java.io.Serializable { return false; } - + /** *

* meetsManaShardRestrictions. @@ -399,44 +360,44 @@ public class AbilityManaPart implements java.io.Serializable { * @param shard * a {@link forge.card.mana.ManaCostShard} object. * @param color - * the color of mana being paid + * the color of mana being paid * @return a boolean. */ public boolean meetsManaShardRestrictions(final ManaCostShard shard, final byte color) { - if (this.manaRestrictions.isEmpty()) { + if (this.manaRestrictions.isEmpty()) { return true; } for (String restriction : this.manaRestrictions.split(",")) { - if (restriction.equals("CantPayGenericCosts")) { - if (shard.isGeneric()) { - if (shard.isOr2Generic() && shard.isColor(color)) { - continue; - } else { - return false; - } - } else { - continue; - } - } + if (restriction.equals("CantPayGenericCosts")) { + if (shard.isGeneric()) { + if (shard.isOr2Generic() && shard.isColor(color)) { + continue; + } else { + return false; + } + } else { + continue; + } + } } return true; } - + /** *

* meetsSpellAndShardRestrictions. *

- * + * * @param sa * a {@link forge.game.spellability.SpellAbility} object. * @param shard * a {@link forge.card.mana.ManaCostShard} object. * @param color - * the color of mana being paid + * the color of mana being paid * @return a boolean. */ public boolean meetsSpellAndShardRestrictions(final SpellAbility sa, final ManaCostShard shard, final byte color) { - return this.meetsManaRestrictions(sa) && this.meetsManaShardRestrictions(shard, color); + return this.meetsManaRestrictions(sa) && this.meetsManaShardRestrictions(shard, color); } /** @@ -524,10 +485,6 @@ public class AbilityManaPart implements java.io.Serializable { return this.getOrigProduced().contains("Special"); } - public final boolean canProduce(final String s) { - return canProduce(s, null); - } - /** *

* canProduce. @@ -552,24 +509,9 @@ public class AbilityManaPart implements java.io.Serializable { if (isComboMana()) { return getComboColors().contains(s); } - if (sa != null) { - return applyManaReplacement(sa, origProduced).contains(s); - } return origProduced.contains(s); } - /** - *

- * isBasic. - *

- * - * @return a boolean. - */ - public final boolean isBasic() { - return this.getOrigProduced().length() == 1 || this.getOrigProduced().contains("Any") - || this.getOrigProduced().contains("Chosen"); - } - /** {@inheritDoc} */ @Override public final boolean equals(final Object o) { @@ -645,81 +587,59 @@ public class AbilityManaPart implements java.io.Serializable { return this.persistentMana; } - /** - * @return the manaReplaceType - */ - public String getManaReplaceType() { - return manaReplaceType; - } + boolean abilityProducesManaColor(final SpellAbility am, final byte neededColor) { + if (0 != (neededColor & ManaAtom.GENERIC)) { + return true; + } - /** - * setManaReplaceType. - */ - public void setManaReplaceType(final String type) { - this.manaReplaceType = type; - } - /** - *

- * applyManaReplacement. - *

- * @return a String - */ - public static String applyManaReplacement(final SpellAbility sa, final String original) { - final Map repMap = Maps.newHashMap(); - final Player act = sa != null ? sa.getActivatingPlayer() : null; - final String manaReplace = sa != null ? sa.getManaPart().getManaReplaceType(): ""; - if (manaReplace.isEmpty()) { - if (act != null && act.getLandsPlayedThisTurn() > 0 && sa.hasParam("ReplaceIfLandPlayed")) { - return sa.getParam("ReplaceIfLandPlayed"); - } - return original; + if (isAnyMana()) { + return true; } - if (manaReplace.startsWith("Any")) { - // Replace any type and amount - String replaced = manaReplace.split("->")[1]; - if (replaced.equals("Any")) { - byte rs = MagicColor.GREEN; - if (act != null) { - rs = act.getController().chooseColor("Choose a color", sa, ColorSet.ALL_COLORS); + + // check for produce mana replacement effects - they mess this up, so just use the mana ability + final Card source = am.getHostCard(); + final Player activator = am.getActivatingPlayer(); + final Game g = source.getGame(); + final Map repParams = AbilityKey.newMap(); + repParams.put(AbilityKey.Mana, getOrigProduced()); + repParams.put(AbilityKey.Affected, source); + repParams.put(AbilityKey.Player, activator); + repParams.put(AbilityKey.AbilityMana, am.getRootAbility()); + + for (final Player p : g.getPlayers()) { + for (final Card crd : p.getAllCards()) { + for (final ReplacementEffect replacementEffect : crd.getReplacementEffects()) { + if (replacementEffect.requirementsCheck(g) + && replacementEffect.getMode() == ReplacementType.ProduceMana + && replacementEffect.canReplace(repParams) + && replacementEffect.zonesCheck(g.getZoneOf(crd))) { + return true; + } } - replaced = MagicColor.toShortString(rs); - } - return replaced; - } - final Pattern splitter = Pattern.compile("->"); - // Replace any type - for (String part : manaReplace.split(" & ")) { - final String[] v = splitter.split(part, 2); - // TODO Colorless mana replacement is probably different now? - if (v[0].equals("Colorless")) { - repMap.put("[0-9][0-9]?", v.length > 1 ? v[1].trim() : ""); - } else { - repMap.put(v[0], v.length > 1 ? v[1].trim() : ""); } } - // Handle different replacement simultaneously - Pattern pattern = Pattern.compile(StringUtils.join(repMap.keySet().iterator(), "|")); - Matcher m = pattern.matcher(original); - StringBuffer sb = new StringBuffer(); - while(m.find()) { - if (m.group().matches("[0-9][0-9]?")) { - final String rep = StringUtils.repeat(repMap.get("[0-9][0-9]?") + " ", - Integer.parseInt(m.group())).trim(); - m.appendReplacement(sb, rep); - } else { - m.appendReplacement(sb, repMap.get(m.group())); + + if (am.getApi() == ApiType.ManaReflected) { + final Iterable reflectableColors = CardUtil.getReflectableManaColors(am); + for (final String color : reflectableColors) { + if (0 != (neededColor & ManaAtom.fromName(color))) { + return true; + } } } - m.appendTail(sb); - String replaced = sb.toString(); - while (replaced.contains("Any")) { - byte rs = MagicColor.GREEN; - if (act != null) { - rs = act.getController().chooseColor("Choose a color", sa, ColorSet.ALL_COLORS); + else { + // treat special mana if it always can be paid + if (isSpecialMana()) { + return true; + } + String colorsProduced = isComboMana() ? getComboColors() : mana(); + for (final String color : colorsProduced.split(" ")) { + if (0 != (neededColor & ManaAtom.fromName(color))) { + return true; + } } - replaced = replaced.replaceFirst("Any", MagicColor.toShortString(rs)); } - return replaced; + return false; } } // end class AbilityMana diff --git a/forge-game/src/main/java/forge/game/spellability/AbilitySub.java b/forge-game/src/main/java/forge/game/spellability/AbilitySub.java index a426e23bf9f..c83e23e714d 100644 --- a/forge-game/src/main/java/forge/game/spellability/AbilitySub.java +++ b/forge-game/src/main/java/forge/game/spellability/AbilitySub.java @@ -20,10 +20,6 @@ package forge.game.spellability; import forge.game.ability.AbilityFactory; import forge.game.ability.ApiType; import forge.game.ability.SpellAbilityEffect; -import forge.game.ability.effects.ChangeZoneAllEffect; -import forge.game.ability.effects.ChangeZoneEffect; -import forge.game.ability.effects.ManaEffect; -import forge.game.ability.effects.ManaReflectedEffect; import forge.game.card.Card; import forge.game.cost.Cost; @@ -92,11 +88,11 @@ public final class AbilitySub extends SpellAbility implements java.io.Serializab effect = api.getSpellEffect(); - if (effect instanceof ManaEffect || effect instanceof ManaReflectedEffect) { + if (api.equals(ApiType.Mana) || api.equals(ApiType.ManaReflected)) { this.setManaPart(new AbilityManaPart(ca, mapParams)); } - if (effect instanceof ChangeZoneEffect || effect instanceof ChangeZoneAllEffect) { + if (api.equals(ApiType.ChangeZone) || api.equals(ApiType.ChangeZoneAll)) { AbilityFactory.adjustChangeZoneTarget(mapParams, this); } } diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java index cd7b9efcc66..6a6f48d53bb 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java @@ -152,6 +152,8 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit private TargetRestrictions targetRestrictions = null; private TargetChoices targetChosen = new TargetChoices(); + private Integer dividedValue = null; + private SpellAbilityView view; private StaticAbility mayPlay = null; @@ -159,6 +161,8 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit private CardCollection lastStateBattlefield = null; private CardCollection lastStateGraveyard = null; + private CardCollection rollbackEffects = new CardCollection(); + private CardDamageMap damageMap = null; private CardDamageMap preventMap = null; private CardZoneTable changeZoneTable = null; @@ -233,19 +237,101 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit view.updateDescription(this); //description can change if host card does } + public boolean canThisProduce(final String s) { + AbilityManaPart mp = getManaPart(); + if (mp != null && metConditions() && mp.canProduce(s, this)) { + return true; + } + return false; + } + + public boolean canProduce(final String s) { + if (canThisProduce(s)) { + return true; + } + + return this.subAbility != null ? this.subAbility.canProduce(s) : false; + } + + public boolean isManaAbilityFor(SpellAbility saPaidFor, byte colorNeeded) { + // is root ability + if (this.getParent() == null) { + if (!canPlay()) { + return false; + } + if (isAbility() && getRestrictions().isInstantSpeed()) { + return false; + } + } + + AbilityManaPart mp = getManaPart(); + if (mp != null && metConditions() && mp.meetsManaRestrictions(saPaidFor) && mp.abilityProducesManaColor(this, colorNeeded)) { + return true; + } + return this.subAbility != null ? this.subAbility.isManaAbilityFor(saPaidFor, colorNeeded) : false; + } + + public boolean isManaCannotCounter(SpellAbility saPaidFor) { + AbilityManaPart mp = getManaPart(); + if (mp != null && metConditions() && mp.meetsManaRestrictions(saPaidFor) && mp.cannotCounterPaidWith(saPaidFor)) { + return true; + } + return this.subAbility != null ? this.subAbility.isManaCannotCounter(saPaidFor) : false; + } + + public int amountOfManaGenerated(boolean multiply) { + int result = 0; + AbilityManaPart mp = getManaPart(); + if (mp != null && metConditions()) { + int amount = hasParam("Amount") ? AbilityUtils.calculateAmount(getHostCard(), getParam("Amount"), this) : 1; + + if (!multiply || mp.isAnyMana() || mp.isComboMana() || mp.isSpecialMana()) { + result += amount; + } else { + // For cards that produce like {C}{R} vs cards that produce {R}{R}. + result += mp.mana().split(" ").length * amount; + } + } + return result; + } + + public int totalAmountOfManaGenerated(SpellAbility saPaidFor, boolean multiply) { + int result = 0; + AbilityManaPart mp = getManaPart(); + if (mp != null && metConditions() && mp.meetsManaRestrictions(saPaidFor)) { + result += amountOfManaGenerated(multiply); + } + result += subAbility != null ? subAbility.totalAmountOfManaGenerated(saPaidFor, multiply) : 0; + return result; + } + + public void setManaExpressChoice(ColorSet cs) { + AbilityManaPart mp = getManaPart(); + if (mp != null) { + mp.setExpressChoice(cs); + } + if (subAbility != null) { + subAbility.setManaExpressChoice(cs); + } + } + public final AbilityManaPart getManaPart() { return manaPart; } - public final AbilityManaPart getManaPartRecursive() { - SpellAbility tail = this; - while (tail != null) { - if (tail.manaPart != null) { - return tail.manaPart; - } - tail = tail.getSubAbility(); + public final List getAllManaParts() { + AbilityManaPart mp = getManaPart(); + if (mp == null && subAbility == null) { + return ImmutableList.of(); } - return null; + List result = Lists.newArrayList(); + if (mp != null) { + result.add(mp); + } + if (subAbility != null) { + result.addAll(subAbility.getAllManaParts()); + } + return result; } public final boolean isManaAbility() { @@ -262,7 +348,14 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit return false; } - return getManaPartRecursive() != null; + SpellAbility tail = this; + while (tail != null) { + if (tail.manaPart != null) { + return true; + } + tail = tail.getSubAbility(); + } + return false; } protected final void setManaPart(AbilityManaPart manaPart0) { @@ -460,6 +553,10 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit conditions = condition; } + public boolean metConditions() { + return getConditions() != null && getConditions().areMet(this); + } + public List getPayingMana() { return payingMana; } @@ -1101,7 +1198,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit if (hasParam("TargetingPlayerControls") && entity instanceof Card) { final Card c = (Card) entity; - if (!c.getController().equals(targetingPlayer)) { + if (!c.getController().equals(getTargetingPlayer())) { return false; } } @@ -1421,6 +1518,35 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit targetChosen = new TargetChoices(); } + /** + * @return a boolean dividedAsYouChoose + */ + public boolean isDividedAsYouChoose() { + return hasParam("DividedAsYouChoose"); + } + + public final void addDividedAllocation(final GameObject tgt, final Integer portionAllocated) { + getTargets().addDividedAllocation(tgt, portionAllocated); + } + public Integer getDividedValue(GameObject c) { + return getTargets().getDividedValue(c); + } + + public int getTotalDividedValue() { + return getTargets().getTotalDividedValue(); + } + + public Integer getDividedValue() { + return this.dividedValue; + } + + public int getStillToDivide() { + if (!isDividedAsYouChoose() || dividedValue == null) { + return 0; + } + return dividedValue - getTotalDividedValue(); + } + /** * Reset the first target. * @@ -1428,16 +1554,15 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit public void resetFirstTarget(GameObject c, SpellAbility originalSA) { SpellAbility sa = this; while (sa != null) { - if (sa.targetRestrictions != null) { - sa.targetChosen = new TargetChoices(); - sa.targetChosen.add(c); - if (!originalSA.targetRestrictions.getDividedMap().isEmpty()) { - sa.targetRestrictions.addDividedAllocation(c, - Iterables.getFirst(originalSA.targetRestrictions.getDividedMap().values(), null)); + if (sa.usesTargeting()) { + sa.resetTargets(); + sa.getTargets().add(c); + if (!originalSA.getTargets().getDividedValues().isEmpty()) { + sa.addDividedAllocation(c, Iterables.getFirst(originalSA.getTargets().getDividedValues(), null)); } break; } - sa = sa.subAbility; + sa = sa.getSubAbility(); } } @@ -1454,7 +1579,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit } public boolean isZeroTargets() { - return getTargetRestrictions().getMinTargets(hostCard, this) == 0 && getTargets().size() == 0; + return getMinTargets() == 0 && getTargets().size() == 0; } public boolean isMinTargetChosen() { @@ -1728,6 +1853,27 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit return p != null && p.isTargeting(o); } + public boolean setupNewTargets(Player forceTargetingPlayer) { + // Skip to paying if parent ability doesn't target and has no subAbilities. + // (or trigger case where its already targeted) + SpellAbility currentAbility = this; + do { + if (currentAbility.usesTargeting()) { + TargetChoices oldTargets = currentAbility.getTargets(); + if (forceTargetingPlayer.getController().chooseNewTargetsFor(currentAbility, null, true) == null) { + currentAbility.setTargets(oldTargets); + } + } + final AbilitySub subAbility = currentAbility.getSubAbility(); + if (subAbility != null) { + // This is necessary for "TargetsWithDefinedController$ ParentTarget" + subAbility.setParent(currentAbility); + } + currentAbility = subAbility; + } while (currentAbility != null); + return true; + } + public boolean setupTargets() { // Skip to paying if parent ability doesn't target and has no subAbilities. // (or trigger case where its already targeted) @@ -1745,6 +1891,8 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit } else { targetingPlayer = getActivatingPlayer(); } + // don't set targeting player when forceful target, + // "targeting player controls" should not be reset when the spell is copied currentAbility.setTargetingPlayer(targetingPlayer); if (!targetingPlayer.getController().chooseTargetsFor(currentAbility)) { return false; @@ -1760,11 +1908,10 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit return true; } public final void clearTargets() { - final TargetRestrictions tg = getTargetRestrictions(); - if (tg != null) { + if (usesTargeting()) { resetTargets(); - if (hasParam("DividedAsYouChoose")) { - tg.calculateStillToDivide(getParam("DividedAsYouChoose"), getHostCard(), this); + if (isDividedAsYouChoose()) { + this.dividedValue = AbilityUtils.calculateAmount(getHostCard(), this.getParam("DividedAsYouChoose"), this); } } } @@ -1830,9 +1977,10 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit public boolean tracksManaSpent() { if (hostCard == null || hostCard.getRules() == null) { return false; } - if (hostCard.hasKeyword(Keyword.SUNBURST)) { + if (isSpell() && hostCard.hasConverge()) { return true; } + String text = hostCard.getRules().getOracleText(); if (isSpell() && text.contains("was spent to cast")) { return true; @@ -1969,7 +2117,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit String mana = manaPart.mana(); if (!mana.equals("Any")) { score += mana.length(); - if (!manaPart.canProduce("C")) { + if (!canProduce("C")) { // Producing colorless should produce a slightly lower score score += 1; } @@ -2134,4 +2282,15 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit public boolean checkRestrictions(Card host, Player activator) { return true; } + + public void addRollbackEffect(Card eff) { + rollbackEffects.add(eff); + } + + public void rollback() { + for (Card c : rollbackEffects) { + c.ceaseToExist(); + } + rollbackEffects.clear(); + } } diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbilityStackInstance.java b/forge-game/src/main/java/forge/game/spellability/SpellAbilityStackInstance.java index ee737cb3b05..14106f5c977 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbilityStackInstance.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbilityStackInstance.java @@ -32,11 +32,10 @@ import forge.util.TextUtil; import org.apache.commons.lang3.StringUtils; import com.google.common.collect.Maps; +import com.google.common.collect.Sets; + import forge.game.GameObject; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -85,7 +84,7 @@ public class SpellAbilityStackInstance implements IIdentifiable, IHasCardView { private Integer xManaPaid = null; // Other Paid things - private final HashMap paidHash; + private final Map paidHash; // Additional info // is Kicked, is Buyback @@ -96,7 +95,6 @@ public class SpellAbilityStackInstance implements IIdentifiable, IHasCardView { private final Map storedSVars = Maps.newHashMap(); - private final List zonesToOpen; private final Map playersWithValidTargets; private final StackItemView view; @@ -109,7 +107,7 @@ public class SpellAbilityStackInstance implements IIdentifiable, IHasCardView { activatingPlayer = sa.getActivatingPlayer(); // Payment info - paidHash = new HashMap<>(ability.getPaidHash()); + paidHash = Maps.newHashMap(ability.getPaidHash()); ability.resetPaidHash(); splicedCards = sa.getSplicedCards(); @@ -149,18 +147,13 @@ public class SpellAbilityStackInstance implements IIdentifiable, IHasCardView { //store zones to open and players to open them for at the time the SpellAbility first goes on the stack based on the selected targets if (tc == null) { - zonesToOpen = null; playersWithValidTargets = null; } else { - zonesToOpen = new ArrayList<>(); - playersWithValidTargets = new HashMap<>(); + playersWithValidTargets = Maps.newHashMap(); for (Card card : tc.getTargetCards()) { ZoneType zoneType = card.getZone() != null ? card.getZone().getZoneType() : null; if (zoneType != ZoneType.Battlefield) { //don't need to worry about targets on battlefield - if (zoneType != null && !zonesToOpen.contains(zoneType)) { - zonesToOpen.add(zoneType); - } playersWithValidTargets.put(card.getController(), null); } } @@ -253,75 +246,32 @@ public class SpellAbilityStackInstance implements IIdentifiable, IHasCardView { return tc; } - public final List getZonesToOpen() { - return zonesToOpen; - } - public final Map getPlayersWithValidTargets() { return playersWithValidTargets; } public void updateTarget(TargetChoices target) { - updateTarget(target, null, null); - } - - public void updateTarget(TargetChoices target, GameObject oldTarget, GameObject newTarget) { if (target != null) { + TargetChoices oldTarget = tc; tc = target; ability.setTargets(tc); stackDescription = ability.getStackDescription(); view.updateTargetCards(this); view.updateTargetPlayers(this); view.updateText(this); - - if (ability.hasParam("DividedAsYouChoose")) { - // try to update DividedAsYouChoose after retargeting - Object toRemove = null; - Object toAdd = null; - HashMap map = ability.getTargetRestrictions().getDividedMap(); - - if (oldTarget != null) { - toRemove = oldTarget; - } else { - // try to deduce which target has been replaced - // (this may be imprecise, updateTarget should specify old target if possible) - for (Object obj : map.keySet()) { - if (!target.contains(obj)) { - toRemove = obj; - break; - } - } - } - - if (newTarget != null) { - toAdd = newTarget; - } else { - // try to deduce which target was added - // (this may be imprecise, updateTarget should specify new target if possible) - for (Object newTgts : target) { - if (!map.containsKey(newTgts)) { - toAdd = newTgts; - break; - } - } - } - - if (toRemove != null && toAdd != null) { - int div = map.get(toRemove); - map.remove(toRemove); - ability.getTargetRestrictions().addDividedAllocation(toAdd, div); - } - } // Run BecomesTargetTrigger - final Map runParams = AbilityKey.newMap(); + Map runParams = AbilityKey.newMap(); runParams.put(AbilityKey.SourceSA, ability); - Set distinctObjects = new HashSet<>(); - for (final Object tgt : target) { - if (distinctObjects.contains(tgt)) { + Set distinctObjects = Sets.newHashSet(); + for (final GameObject tgt : target) { + if (oldTarget != null && oldTarget.contains(tgt)) { + // it was an old target, so don't trigger becomes target + continue; + } + if (!distinctObjects.add(tgt)) { continue; } - distinctObjects.add(tgt); if (tgt instanceof Card && !((Card) tgt).hasBecomeTargetThisTurn()) { runParams.put(AbilityKey.FirstTime, null); @@ -330,7 +280,8 @@ public class SpellAbilityStackInstance implements IIdentifiable, IHasCardView { runParams.put(AbilityKey.Target, tgt); getSourceCard().getGame().getTriggerHandler().runTrigger(TriggerType.BecomesTarget, runParams, false); } - runParams.put(AbilityKey.Targets, target); + runParams = AbilityKey.newMap(); + runParams.put(AbilityKey.Targets, distinctObjects); getSourceCard().getGame().getTriggerHandler().runTrigger(TriggerType.BecomesTargetOnce, runParams, false); } } diff --git a/forge-game/src/main/java/forge/game/spellability/TargetChoices.java b/forge-game/src/main/java/forge/game/spellability/TargetChoices.java index 4d39c7707a6..15cc83cd1c0 100644 --- a/forge-game/src/main/java/forge/game/spellability/TargetChoices.java +++ b/forge-game/src/main/java/forge/game/spellability/TargetChoices.java @@ -21,6 +21,7 @@ import com.google.common.base.Predicates; import com.google.common.collect.ForwardingList; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; +import com.google.common.collect.Maps; import forge.game.GameEntity; import forge.game.GameObject; @@ -30,7 +31,9 @@ import forge.game.card.CardCollectionView; import forge.game.player.Player; import forge.util.collect.FCollection; +import java.util.Collection; import java.util.List; +import java.util.Map; /** *

@@ -44,6 +47,8 @@ public class TargetChoices extends ForwardingList implements Cloneab private final FCollection targets = new FCollection(); + private final Map dividedMap = Maps.newHashMap(); + public final int getTotalTargetedCMC() { int totalCMC = 0; for (Card c : Iterables.filter(targets, Card.class)) { @@ -52,6 +57,7 @@ public class TargetChoices extends ForwardingList implements Cloneab return totalCMC; } + @Override public final boolean add(final GameObject o) { if (o instanceof Player || o instanceof Card || o instanceof SpellAbility) { return super.add(o); @@ -59,6 +65,22 @@ public class TargetChoices extends ForwardingList implements Cloneab return false; } + @Override + public boolean removeAll(Collection collection) { + boolean result = super.removeAll(collection); + for (Object e : collection) { + this.dividedMap.remove(e); + } + return result; + } + + @Override + public boolean remove(Object object) { + boolean result = super.remove(object); + dividedMap.remove(object); + return result; + } + public final CardCollectionView getTargetCards() { return new CardCollection(Iterables.filter(targets, Card.class)); } @@ -108,10 +130,31 @@ public class TargetChoices extends ForwardingList implements Cloneab public TargetChoices clone() { TargetChoices tc = new TargetChoices(); tc.targets.addAll(targets); + tc.dividedMap.putAll(dividedMap); return tc; } @Override protected List delegate() { return targets; } + + public final void addDividedAllocation(final GameObject tgt, final Integer portionAllocated) { + this.dividedMap.put(tgt, portionAllocated); + } + public Integer getDividedValue(GameObject c) { + return dividedMap.get(c); + } + + public Collection getDividedValues() { + return dividedMap.values(); + } + + public int getTotalDividedValue() { + int result = 0; + for (Integer i : getDividedValues()) { + if (i != null) + result += i; + } + return result; + } } diff --git a/forge-game/src/main/java/forge/game/spellability/TargetRestrictions.java b/forge-game/src/main/java/forge/game/spellability/TargetRestrictions.java index eef0d88b541..5eeca0fb5c3 100644 --- a/forge-game/src/main/java/forge/game/spellability/TargetRestrictions.java +++ b/forge-game/src/main/java/forge/game/spellability/TargetRestrictions.java @@ -18,11 +18,9 @@ package forge.game.spellability; import java.util.Arrays; -import java.util.HashMap; import java.util.List; import forge.util.TextUtil; -import org.apache.commons.lang3.StringUtils; import com.google.common.collect.Lists; @@ -75,11 +73,6 @@ public class TargetRestrictions { // What's the max total CMC of targets? private String maxTotalCMC; - - // For "Divided" cards. Is this better in TargetChoices? - private boolean dividedAsYouChoose = false; - private HashMap dividedMap = new HashMap<>(); - private int stillToDivide = 0; // Not sure what's up with Mandatory? Why wouldn't targeting be mandatory? private boolean bMandatory = false; @@ -101,7 +94,6 @@ public class TargetRestrictions { this.maxTotalCMC = target.getMaxTotalCMC(); this.tgtZone = target.getZone(); this.saValidTargeting = target.getSAValidTargeting(); - this.dividedAsYouChoose = target.isDividedAsYouChoose(); this.uniqueTargets = target.isUniqueTargets(); this.singleZone = target.isSingleZone(); this.differentControllers = target.isDifferentControllers(); @@ -728,82 +720,9 @@ public class TargetRestrictions { this.singleTarget = singleTarget; } - /** - * @return a boolean dividedAsYouChoose - */ - public boolean isDividedAsYouChoose() { - return this.dividedAsYouChoose; - } - - /** - * @param divided the boolean to set - */ - public void setDividedAsYouChoose(boolean divided) { - this.dividedAsYouChoose = divided; - } - - /** - * Get the amount remaining to distribute. - * @return int stillToDivide - */ - public int getStillToDivide() { - return this.stillToDivide; - } - - /** - * @param remaining set the amount still to be divided - */ - public void setStillToDivide(final int remaining) { - this.stillToDivide = remaining; - } - - public void calculateStillToDivide(String toDistribute, Card source, SpellAbility sa) { - // Recalculate this value just in case it's variable - if (!this.dividedAsYouChoose) { - return; - } - - if (StringUtils.isNumeric(toDistribute)) { - this.setStillToDivide(Integer.parseInt(toDistribute)); - } else if ( source == null ) { - return; // such calls come from AbilityFactory.readTarget - at this moment we don't yet know X or any other variables - } else if (source.getSVar(toDistribute).equals("xPaid")) { - this.setStillToDivide(source.getXManaCostPaid()); - } else { - this.setStillToDivide(AbilityUtils.calculateAmount(source, toDistribute, sa)); - } - } - - /** - * Store divided amount relative to a specific card/player. - * @param tgt the targeted object - * @param portionAllocated the divided portion allocated - */ - public final void addDividedAllocation(final Object tgt, final Integer portionAllocated) { - this.dividedMap.remove(tgt); - this.dividedMap.put(tgt, portionAllocated); - } - - /** - * Get the divided amount relative to a specific card/player. - * @param tgt the targeted object - * @return an int. - */ - public int getDividedValue(Object tgt) { - return this.dividedMap.get(tgt); - } - - public HashMap getDividedMap() { - return this.dividedMap; - } - public final void applyTargetTextChanges(final SpellAbility sa) { for (int i = 0; i < validTgts.length; i++) { validTgts[i] = AbilityUtils.applyAbilityTextChangeEffects(originalValidTgts[i], sa); } } - - public final void changeValidTargets(final String[] validTgts) { - this.originalValidTgts = validTgts; - } } diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java b/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java index b0ae7dfe94a..c0066417085 100644 --- a/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java @@ -781,8 +781,7 @@ public final class StaticAbilityContinuous { // add Replacement effects if (addReplacements != null) { for (String rep : addReplacements) { - final ReplacementEffect actualRep = ReplacementHandler.parseReplacement(rep, affectedCard, false); - actualRep.setIntrinsic(false); + final ReplacementEffect actualRep = ReplacementHandler.parseReplacement(rep, affectedCard, false, stAb); addedReplacementEffects.add(actualRep); } } @@ -790,17 +789,12 @@ public final class StaticAbilityContinuous { // add triggers if (addTriggers != null) { for (final String trigger : addTriggers) { - final Trigger actualTrigger = TriggerHandler.parseTrigger(trigger, affectedCard, false); + final Trigger actualTrigger = TriggerHandler.parseTrigger(trigger, affectedCard, false, stAb); // if the trigger has Execute param, which most trigger gained by Static Abilties should have // turn them into SpellAbility object before adding to card // with that the TargetedCard does not need the Svars added to them anymore // but only do it if the trigger doesn't already have a overriding ability - if (actualTrigger.hasParam("Execute") && actualTrigger.getOverridingAbility() == null) { - // set overriding ability to the trigger - actualTrigger.setOverridingAbility(AbilityFactory.getAbility(affectedCard, actualTrigger.getParam("Execute"), stAb)); - } actualTrigger.setOriginalHost(hostCard); - actualTrigger.setIntrinsic(false); addedTrigger.add(actualTrigger); } } diff --git a/forge-game/src/main/java/forge/game/trigger/Trigger.java b/forge-game/src/main/java/forge/game/trigger/Trigger.java index 6b27441b38a..811f2aa7371 100644 --- a/forge-game/src/main/java/forge/game/trigger/Trigger.java +++ b/forge-game/src/main/java/forge/game/trigger/Trigger.java @@ -19,6 +19,7 @@ package forge.game.trigger; import forge.game.Game; import forge.game.GameEntity; +import forge.game.IHasSVars; import forge.game.TriggerReplacementBase; import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityKey; @@ -562,12 +563,16 @@ public abstract class Trigger extends TriggerReplacementBase { } } - public SpellAbility ensureAbility() { + public SpellAbility ensureAbility(final IHasSVars sVarHolder) { SpellAbility sa = getOverridingAbility(); if (sa == null && hasParam("Execute")) { - sa = AbilityFactory.getAbility(getHostCard(), getParam("Execute")); + sa = AbilityFactory.getAbility(getHostCard(), getParam("Execute"), sVarHolder); setOverridingAbility(sa); } return sa; } + + public SpellAbility ensureAbility() { + return ensureAbility(this); + } } diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerBecomesTargetOnce.java b/forge-game/src/main/java/forge/game/trigger/TriggerBecomesTargetOnce.java index 821c1866fa6..2b071edb560 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerBecomesTargetOnce.java +++ b/forge-game/src/main/java/forge/game/trigger/TriggerBecomesTargetOnce.java @@ -23,7 +23,6 @@ import forge.game.card.Card; import forge.game.spellability.SpellAbility; import forge.util.Localizer; -import java.util.List; import java.util.Map; /** @@ -64,9 +63,8 @@ public class TriggerBecomesTargetOnce extends Trigger { } } if (hasParam("ValidTarget")) { - List targets = (List) runParams.get(AbilityKey.Targets); boolean valid = false; - for (GameObject b : targets) { + for (GameObject b : (Iterable) runParams.get(AbilityKey.Targets)) { if (matchesValid(b, getParam("ValidTarget").split(","), this.getHostCard())) { valid = true; break; diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerHandler.java b/forge-game/src/main/java/forge/game/trigger/TriggerHandler.java index 3330b279837..65e74b60448 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerHandler.java +++ b/forge-game/src/main/java/forge/game/trigger/TriggerHandler.java @@ -19,6 +19,7 @@ package forge.game.trigger; import forge.game.Game; import forge.game.GlobalRuleChange; +import forge.game.IHasSVars; import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityUtils; import forge.game.ability.ApiType; @@ -124,9 +125,13 @@ public class TriggerHandler { } public static Trigger parseTrigger(final String trigParse, final Card host, final boolean intrinsic) { + return parseTrigger(trigParse, host, intrinsic, host); + } + + public static Trigger parseTrigger(final String trigParse, final Card host, final boolean intrinsic, final IHasSVars sVarHolder) { try { final Map mapParams = TriggerHandler.parseParams(trigParse); - return TriggerHandler.parseTrigger(mapParams, host, intrinsic); + return TriggerHandler.parseTrigger(mapParams, host, intrinsic, sVarHolder); } catch (Exception e) { String msg = "TriggerHandler:parseTrigger failed to parse"; Sentry.getContext().recordBreadcrumb( @@ -138,12 +143,15 @@ public class TriggerHandler { } } - public static Trigger parseTrigger(final Map mapParams, final Card host, final boolean intrinsic) { + public static Trigger parseTrigger(final Map mapParams, final Card host, final boolean intrinsic, final IHasSVars sVarHolder) { Trigger ret = null; try { final TriggerType type = TriggerType.smartValueOf(mapParams.get("Mode")); ret = type.createTrigger(mapParams, host, intrinsic); + if (sVarHolder != null) { + ret.ensureAbility(sVarHolder); + } } catch (Exception e) { String msg = "TriggerHandler:parseTrigger failed to parse"; Sentry.getContext().recordBreadcrumb( @@ -170,13 +178,7 @@ public class TriggerHandler { if (wt.getTriggers() != null) continue; - List trigger = Lists.newArrayList(); - for (final Trigger t : activeTriggers) { - if (canRunTrigger(t,wt.getMode(),wt.getParams())) { - trigger.add(t); - } - } - wt.setTriggers(trigger); + wt.setTriggers(getActiveTrigger(wt.getMode(), wt.getParams())); } } @@ -708,4 +710,14 @@ public class TriggerHandler { return n; } + + public List getActiveTrigger(final TriggerType mode, final Map runParams) { + List trigger = Lists.newArrayList(); + for (final Trigger t : activeTriggers) { + if (canRunTrigger(t, mode, runParams)) { + trigger.add(t); + } + } + return trigger; + } } diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerTapsForMana.java b/forge-game/src/main/java/forge/game/trigger/TriggerTapsForMana.java index 148191feb75..f7895ba385e 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerTapsForMana.java +++ b/forge-game/src/main/java/forge/game/trigger/TriggerTapsForMana.java @@ -20,7 +20,6 @@ package forge.game.trigger; import forge.card.MagicColor; import forge.game.ability.AbilityKey; import forge.game.card.Card; -import forge.game.player.Player; import forge.game.spellability.SpellAbility; import forge.util.Localizer; @@ -68,25 +67,19 @@ public class TriggerTapsForMana extends Trigger { } if (hasParam("ValidCard")) { - final Card tapper = (Card) runParams.get(AbilityKey.Card); - if (!tapper.isValid(getParam("ValidCard").split(","), this.getHostCard().getController(), - this.getHostCard(), null)) { + if (!matchesValid(runParams.get(AbilityKey.Card), getParam("ValidCard").split(","), getHostCard())) { return false; } } if (hasParam("Player")) { - final Player player = (Player) runParams.get(AbilityKey.Player); - if (!player.isValid(getParam("Player").split(","), this.getHostCard().getController(), this.getHostCard(), null)) { + if (!matchesValid(runParams.get(AbilityKey.Player), getParam("Player").split(","), getHostCard())) { return false; } } if (hasParam("Activator")) { - final SpellAbility sa = (SpellAbility) runParams.get(AbilityKey.AbilityMana); - if (sa == null) return false; - final Player activator = sa.getActivatingPlayer(); - if (!activator.isValid(getParam("Activator").split(","), this.getHostCard().getController(), this.getHostCard(), null)) { + if (!matchesValid(runParams.get(AbilityKey.Activator), getParam("Activator").split(","), getHostCard())) { return false; } } @@ -113,7 +106,7 @@ public class TriggerTapsForMana extends Trigger { /** {@inheritDoc} */ @Override public final void setTriggeringObjects(final SpellAbility sa, Map runParams) { - sa.setTriggeringObjectsFrom(runParams, AbilityKey.Card, AbilityKey.Player, AbilityKey.Produced); + sa.setTriggeringObjectsFrom(runParams, AbilityKey.Card, AbilityKey.Player, AbilityKey.Produced, AbilityKey.Activator); } @Override diff --git a/forge-game/src/main/java/forge/game/zone/MagicStack.java b/forge-game/src/main/java/forge/game/zone/MagicStack.java index 4e691cf4739..04e1333bf32 100644 --- a/forge-game/src/main/java/forge/game/zone/MagicStack.java +++ b/forge-game/src/main/java/forge/game/zone/MagicStack.java @@ -247,7 +247,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable { private final FLabel btnImportPictures = _makeButton(localizer.getMessage("btnImportPictures")); private final FLabel btnHowToPlay = _makeButton(localizer.getMessage("btnHowToPlay")); private final FLabel btnDownloadPrices = _makeButton(localizer.getMessage("btnDownloadPrices")); + private final FLabel btnDownloadSkins = _makeButton(localizer.getMessage("btnDownloadSkins")); private final FLabel btnLicensing = _makeButton(localizer.getMessage("btnLicensing")); /** @@ -102,6 +103,9 @@ public enum VSubmenuDownloaders implements IVSubmenu { pnlContent.add(btnDownloadPrices, constraintsBTN); pnlContent.add(_makeLabel(localizer.getMessage("lblDownloadPrices")), constraintsLBL); + pnlContent.add(btnDownloadSkins, constraintsBTN); + pnlContent.add(_makeLabel(localizer.getMessage("lblDownloadSkins")), constraintsLBL); + } else { String text = localizer.getMessage("lblYourVersionOfJavaIsTooOld"); @@ -178,6 +182,7 @@ public enum VSubmenuDownloaders implements IVSubmenu { public void setHowToPlayCommand(UiCommand command) { btnHowToPlay.setCommand(command); } public void setDownloadPricesCommand(UiCommand command) { btnDownloadPrices.setCommand(command); } public void setLicensingCommand(UiCommand command) { btnLicensing.setCommand(command); } + public void setDownloadSkinsCommand(UiCommand command) { btnDownloadSkins.setCommand(command); } public void focusTopButton() { btnDownloadPics.requestFocusInWindow(); diff --git a/forge-gui-desktop/src/main/java/forge/toolbox/FSkin.java b/forge-gui-desktop/src/main/java/forge/toolbox/FSkin.java index d6c34819a06..92d38f61c10 100644 --- a/forge-gui-desktop/src/main/java/forge/toolbox/FSkin.java +++ b/forge-gui-desktop/src/main/java/forge/toolbox/FSkin.java @@ -1089,6 +1089,7 @@ public class FSkin { if (allSkins == null) { //initialize allSkins = new ArrayList<>(); + allSkins.add("Default");//init default final List skinDirectoryNames = getSkinDirectoryNames(); for (String skinDirectoryName : skinDirectoryNames) { allSkins.add(WordUtil.capitalize(skinDirectoryName.replace('_', ' '))); @@ -1101,7 +1102,7 @@ public class FSkin { // Non-default (preferred) skin name and dir. preferredName = skinName.toLowerCase().replace(' ', '_'); - preferredDir = ForgeConstants.SKINS_DIR + preferredName + "/"; + preferredDir = preferredName.equals("default") ? ForgeConstants.DEFAULT_SKINS_DIR : ForgeConstants.CACHE_SKINS_DIR + preferredName + "/"; if (onInit) { final File f = new File(preferredDir + ForgeConstants.SPLASH_BG_FILE); @@ -1371,7 +1372,7 @@ public class FSkin { public static List getSkinDirectoryNames() { final List mySkins = new ArrayList<>(); - final File dir = new File(ForgeConstants.SKINS_DIR); + final File dir = new File(ForgeConstants.CACHE_SKINS_DIR); final String[] children = dir.list(); if (children == null) { System.err.println("FSkin > can't find skins directory!"); diff --git a/forge-gui-desktop/src/test/java/forge/ai/simulation/GameSimulatorTest.java b/forge-gui-desktop/src/test/java/forge/ai/simulation/GameSimulatorTest.java index 4344f303dea..33963b34d54 100644 --- a/forge-gui-desktop/src/test/java/forge/ai/simulation/GameSimulatorTest.java +++ b/forge-gui-desktop/src/test/java/forge/ai/simulation/GameSimulatorTest.java @@ -304,14 +304,18 @@ public class GameSimulatorTest extends SimulationTestCase { Card manifestedCreature = findCardWithName(simGame, ""); assertNotNull(manifestedCreature); - SpellAbility unmanifestSA = findSAWithPrefix(manifestedCreature, "Unmanifest"); + SpellAbility unmanifestSA = findSAWithPrefix(manifestedCreature.getAllPossibleAbilities(p, false), "Unmanifest"); assertNotNull(unmanifestSA); assertEquals(2, manifestedCreature.getNetPower()); assertFalse(manifestedCreature.hasKeyword("Flying")); GameSimulator sim2 = createSimulator(simGame, simGame.getPlayers().get(1)); - sim2.simulateSpellAbility(unmanifestSA); Game simGame2 = sim2.getSimulatedGameState(); + manifestedCreature = findCardWithName(simGame2, ""); + unmanifestSA = findSAWithPrefix(manifestedCreature.getAllPossibleAbilities(simGame2.getPlayers().get(1), false), "Unmanifest"); + + sim2.simulateSpellAbility(unmanifestSA); + Card ornithopter = findCardWithName(simGame2, "Ornithopter"); assertEquals(0, ornithopter.getNetPower()); assertTrue(ornithopter.hasKeyword("Flying")); diff --git a/forge-gui-desktop/src/test/java/forge/gamesimulationtests/util/PlayerControllerForTests.java b/forge-gui-desktop/src/test/java/forge/gamesimulationtests/util/PlayerControllerForTests.java index b7f1a41ba6d..fc43a0a9b97 100644 --- a/forge-gui-desktop/src/test/java/forge/gamesimulationtests/util/PlayerControllerForTests.java +++ b/forge-gui-desktop/src/test/java/forge/gamesimulationtests/util/PlayerControllerForTests.java @@ -142,7 +142,7 @@ public class PlayerControllerForTests extends PlayerController { } @Override - public TargetChoices chooseNewTargetsFor(SpellAbility ability) { + public TargetChoices chooseNewTargetsFor(SpellAbility ability, Predicate filter, boolean optional) { throw new IllegalStateException("Erring on the side of caution here..."); } diff --git a/forge-gui-mobile/src/forge/Forge.java b/forge-gui-mobile/src/forge/Forge.java index 4a7e9321ff7..aebbcc73c25 100644 --- a/forge-gui-mobile/src/forge/Forge.java +++ b/forge-gui-mobile/src/forge/Forge.java @@ -437,24 +437,32 @@ public class Forge implements ApplicationListener { try { endKeyInput(); //end key input before switching screens ForgeAnimation.endAll(); //end all active animations before switching screens - try { - if(dispose) - ImageCache.disposeTexture(); - } - catch (Exception ex) - { - // FIXME: This isn't supposed to be necessary, but disposeTexture crashes e.g. in Quest Tournaments on mobile, needs proper fixing. - System.err.println("Warning: caught an exception while trying to call ImageCache.disposeTexture() in setCurrentScreen."); - } currentScreen = screen0; currentScreen.setSize(screenWidth, screenHeight); currentScreen.onActivate(); + //keep Dscreens growing + if (Dscreens.size() > 3) { + for(int x = Dscreens.size(); x > 3; x--) { + Dscreens.removeLast(); + } + } + /* for checking only + if (!Dscreens.isEmpty()) { + int x = 0; + for(FScreen fScreen : Dscreens) { + System.out.println("Screen ["+x+"]: "+fScreen.toString()); + x++; + } + System.out.println("---------------"); + }*/ } catch (Exception ex) { graphics.end(); BugReporter.reportException(ex); } + if(dispose) + ImageCache.disposeTexture(); } @Override diff --git a/forge-gui-mobile/src/forge/GuiMobile.java b/forge-gui-mobile/src/forge/GuiMobile.java index 3843b3c2cec..c277edc066c 100644 --- a/forge-gui-mobile/src/forge/GuiMobile.java +++ b/forge-gui-mobile/src/forge/GuiMobile.java @@ -267,6 +267,11 @@ public class GuiMobile implements IGuiBase { new GuiDownloader(service, callback).show(); } + @Override + public void refreshSkin() { + //todo refresh skin selector + } + @Override public void copyToClipboard(final String text) { Forge.getClipboard().setContents(text); diff --git a/forge-gui-mobile/src/forge/assets/FSkin.java b/forge-gui-mobile/src/forge/assets/FSkin.java index 6f74fd30d77..553fef18239 100644 --- a/forge-gui-mobile/src/forge/assets/FSkin.java +++ b/forge-gui-mobile/src/forge/assets/FSkin.java @@ -97,7 +97,7 @@ public class FSkin { Forge.hdstart = false; //ensure skins directory exists - final FileHandle dir = Gdx.files.absolute(ForgeConstants.SKINS_DIR); + final FileHandle dir = Gdx.files.absolute(ForgeConstants.CACHE_SKINS_DIR); if (!dir.exists() || !dir.isDirectory()) { //if skins directory doesn't exist, point to internal assets/skin directory instead for the sake of the splash screen preferredDir = Gdx.files.internal("fallback_skin"); @@ -106,6 +106,7 @@ public class FSkin { if (splashScreen != null) { if (allSkins == null) { //initialize allSkins = new Array<>(); + allSkins.add("Default"); //init default final Array skinDirectoryNames = getSkinDirectoryNames(); for (final String skinDirectoryName : skinDirectoryNames) { allSkins.add(WordUtil.capitalize(skinDirectoryName.replace('_', ' '))); @@ -115,7 +116,7 @@ public class FSkin { } // Non-default (preferred) skin name and dir. - preferredDir = Gdx.files.absolute(ForgeConstants.SKINS_DIR + preferredName); + preferredDir = Gdx.files.absolute(preferredName.equals("default") ? ForgeConstants.BASE_SKINS_DIR + preferredName : ForgeConstants.CACHE_SKINS_DIR + preferredName); if (!preferredDir.exists() || !preferredDir.isDirectory()) { preferredDir.mkdirs(); } @@ -219,8 +220,12 @@ public class FSkin { try { textures.put(f1.path(), new Texture(f1)); - textures.put(f2.path(), new Texture(f2)); - Pixmap preferredIcons = new Pixmap(f2); + Pixmap preferredIcons = new Pixmap(f1); + if (f2.exists()) { + textures.put(f2.path(), new Texture(f2)); + preferredIcons = new Pixmap(f2); + } + textures.put(f3.path(), new Texture(f3)); if (f6.exists()) { textures.put(f6.path(), new Texture(f6)); @@ -429,7 +434,7 @@ public class FSkin { public static Array getSkinDirectoryNames() { final Array mySkins = new Array<>(); - final FileHandle dir = Gdx.files.absolute(ForgeConstants.SKINS_DIR); + final FileHandle dir = Gdx.files.absolute(ForgeConstants.CACHE_SKINS_DIR); for (FileHandle skinFile : dir.list()) { String skinName = skinFile.name(); if (skinName.equalsIgnoreCase(".svn")) { continue; } diff --git a/forge-gui-mobile/src/forge/screens/settings/FilesPage.java b/forge-gui-mobile/src/forge/screens/settings/FilesPage.java index bb2ff928276..99f91e5986d 100644 --- a/forge-gui-mobile/src/forge/screens/settings/FilesPage.java +++ b/forge-gui-mobile/src/forge/screens/settings/FilesPage.java @@ -8,6 +8,7 @@ import forge.download.GuiDownloadQuestImages; import forge.download.GuiDownloadSetPicturesLQ; import forge.download.GuiDownloadService; +import forge.download.GuiDownloadSkins; import forge.util.Localizer; import org.apache.commons.lang3.StringUtils; @@ -76,7 +77,13 @@ public class FilesPage extends TabPage { return new GuiDownloadPrices(); } }, 0); - + lstItems.addItem(new ContentDownloader(localizer.getMessage("btnDownloadSkins"), + localizer.getMessage("lblDownloadSkins")) { + @Override + protected GuiDownloadService createService() { + return new GuiDownloadSkins(); + } + }, 0); //storage locations final StorageOption cardPicsOption = new StorageOption(localizer.getMessage("lblCardPicsLocation"), ForgeProfileProperties.getCardPicsDir()) { @Override diff --git a/forge-gui-mobile/src/forge/screens/settings/SettingsPage.java b/forge-gui-mobile/src/forge/screens/settings/SettingsPage.java index 50c56f3e00d..c8a67e0f169 100644 --- a/forge-gui-mobile/src/forge/screens/settings/SettingsPage.java +++ b/forge-gui-mobile/src/forge/screens/settings/SettingsPage.java @@ -510,8 +510,10 @@ public class SettingsPage extends TabPage { public CustomSelectSetting(FPref pref0, String label0, String description0, Iterable options0) { super(pref0, label0 + ":", description0); - for (String option : options0) { - options.add(option); + if (options0 != null) { + for (String option : options0) { + options.add(option); + } } } public > CustomSelectSetting(FPref pref0, String label0, String description0, Class enumData) { diff --git a/forge-gui/res/cardsfolder/a/a_display_of_my_dark_power.txt b/forge-gui/res/cardsfolder/a/a_display_of_my_dark_power.txt index 07e13d171dc..37bb8929def 100644 --- a/forge-gui/res/cardsfolder/a/a_display_of_my_dark_power.txt +++ b/forge-gui/res/cardsfolder/a/a_display_of_my_dark_power.txt @@ -4,6 +4,5 @@ Types:Scheme T:Mode$ SetInMotion | ValidCard$ Card.Self | Execute$ DarkEffect | TriggerZones$ Command | TriggerDescription$ When you set this scheme in motion, until your next turn, whenever a player taps a land for mana, that player adds one mana of any type that land produced. SVar:DarkEffect:DB$ Effect | Name$ Dark Power Scheme | Duration$ UntilYourNextTurn | Triggers$ DarkPower | SVars$ DarkMana SVar:DarkPower:Mode$ TapsForMana | ValidCard$ Land | Execute$ DarkMana | TriggerZones$ Command | Static$ True | TriggerDescription$ Whenever a player taps a land for mana, that player adds one mana of any type that land produced. -SVar:DarkMana:DB$ ManaReflected | ColorOrType$ Type | Valid$ Defined.Triggered | ReflectProperty$ Produced | Defined$ TriggeredPlayer -SVar:Picture:https://downloads.cardforge.org/images/cards/ARC/A Display of My Dark Power.full.jpg +SVar:DarkMana:DB$ ManaReflected | ColorOrType$ Type | ReflectProperty$ Produced | Defined$ TriggeredActivator Oracle:When you set this scheme in motion, until your next turn, whenever a player taps a land for mana, that player adds one mana of any type that land produced. diff --git a/forge-gui/res/cardsfolder/b/bounty_of_skemfar.txt b/forge-gui/res/cardsfolder/b/bounty_of_skemfar.txt new file mode 100644 index 00000000000..65cb3db5cfc --- /dev/null +++ b/forge-gui/res/cardsfolder/b/bounty_of_skemfar.txt @@ -0,0 +1,10 @@ +Name:Bounty of Skemfar +ManaCost:2 G +Types:Sorcery +A:SP$ Dig | DigNum$ 6 | Reveal$ True | ChangeNum$ 1 | Optional$ True | ChangeValid$ Land | DestinationZone$ Battlefield | Tapped$ True | DestinationZone2$ Library | LibraryPosition2$ 0 | SkipReorder$ True | RememberChanged$ True | SubAbility$ DBElf | SpellDescription$ Reveal the top six cards of your library. You may put a land card from among them onto the battlefield tapped and an Elf card from among them into your hand. Put the rest on the bottom of your library in a random order. | StackDescription$ SpellDescription +SVar:DBElf:DB$ Dig | DigNum$ X | References$ X,Y | ChangeNum$ 1 | Optional$ True | ChangeValid$ Elf | RestRandomOrder$ True | SubAbility$ DBCleanup | StackDescription$ None +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +SVar:X:Count$Compare Y GE1.5.6 +SVar:Y:Remembered$Amount +DeckHints:Type$Elf +Oracle:Reveal the top six cards of your library. You may put a land card from among them onto the battlefield tapped and an Elf card from among them into your hand. Put the rest on the bottom of your library in a random order. diff --git a/forge-gui/res/cardsfolder/b/brain_in_a_jar.txt b/forge-gui/res/cardsfolder/b/brain_in_a_jar.txt index 36194f48586..a0b66afb8bd 100644 --- a/forge-gui/res/cardsfolder/b/brain_in_a_jar.txt +++ b/forge-gui/res/cardsfolder/b/brain_in_a_jar.txt @@ -2,7 +2,7 @@ Name:Brain in a Jar ManaCost:2 Types:Artifact A:AB$ PutCounter | Cost$ 1 T | CounterType$ CHARGE | CounterNum$ 1 | SubAbility$ DBCast | SpellDescription$ Put a charge counter on Brain in a Jar, then you may cast an instant or sorcery card with converted mana cost equal to the number of charge counters on Brain in a Jar from your hand without paying its mana cost. -SVar:DBCast:DB$ Play | ValidZone$ Hand | Valid$ Instant.YouOwn+cmcEQY,Sorcery.YouOwn+cmcEQY | Controller$ You | WithoutManaCost$ True | Optional$ True | Amount$ 1 | References$ Y +SVar:DBCast:DB$ Play | ValidZone$ Hand | Valid$ Instant.YouOwn,Sorcery.YouOwn| ValidSA$ Spell.cmcEQY | Controller$ You | WithoutManaCost$ True | Optional$ True | Amount$ 1 | References$ Y A:AB$ Scry | Cost$ 3 T SubCounter | ScryNum$ X | References$ X | AILogic$ BrainJar | SpellDescription$ Scry X. SVar:X:Count$xPaid SVar:Y:Count$CardCounters.CHARGE diff --git a/forge-gui/res/cardsfolder/c/caged_sun.txt b/forge-gui/res/cardsfolder/c/caged_sun.txt index 75e0d4f3152..c4021dbd298 100644 --- a/forge-gui/res/cardsfolder/c/caged_sun.txt +++ b/forge-gui/res/cardsfolder/c/caged_sun.txt @@ -4,7 +4,6 @@ Types:Artifact K:ETBReplacement:Other:ChooseColor SVar:ChooseColor:DB$ ChooseColor | Defined$ You | AILogic$ MostProminentInComputerDeck | SpellDescription$ As CARDNAME enters the battlefield, choose a color. S:Mode$ Continuous | Affected$ Creature.ChosenColor+YouCtrl | AddPower$ 1 | AddToughness$ 1 | Description$ Creatures you control of the chosen color get +1/+1. -T:Mode$ TapsForMana | ValidCard$ Land | Produced$ ChosenColor | NoTapCheck$ True | Player$ You | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever a land's ability adds one or more mana of the chosen color, add one additional mana of that color. -SVar:TrigMana:DB$ Mana | Produced$ Chosen | Amount$ 1 | Defined$ TriggeredPlayer -SVar:Picture:http://www.wizards.com/global/images/magic/general/caged_sun.jpg -Oracle:As Caged Sun enters the battlefield, choose a color.\nCreatures you control of the chosen color get +1/+1.\nWhenever a land's ability adds one or more mana of the chosen color, add one additional mana of that color. +T:Mode$ TapsForMana | ValidCard$ Land | Produced$ ChosenColor | NoTapCheck$ True | Player$ You | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever a land’s ability causes you to add one or more mana of the chosen color, add one additional mana of that color. +SVar:TrigMana:DB$ Mana | Produced$ Chosen | Amount$ 1 | Defined$ You +Oracle:As Caged Sun enters the battlefield, choose a color.\nCreatures you control of the chosen color get +1/+1.\nWhenever a land’s ability causes you to add one or more mana of the chosen color, add one additional mana of that color. diff --git a/forge-gui/res/cardsfolder/c/chaos_moon.txt b/forge-gui/res/cardsfolder/c/chaos_moon.txt index 40d9cca6867..3b116e19969 100644 --- a/forge-gui/res/cardsfolder/c/chaos_moon.txt +++ b/forge-gui/res/cardsfolder/c/chaos_moon.txt @@ -7,8 +7,8 @@ SVar:DBEffect:DB$ Effect | ReplacementEffects$ RepCurse | SVars$ ProduceColorles SVar:TrigRamp:Mode$ TapsForMana | ValidCard$ Mountain | Execute$ TrigMana | Static$ True | TriggerZones$ Command | TriggerDescription$ Whenever a player taps a Mountain for mana, that player adds {R}. SVar:TrigMana:DB$ Mana | Produced$ R | Amount$ 1 | Defined$ TriggeredCardController SVar:STPump:Mode$ Continuous | EffectZone$ Command | AffectedZone$ Battlefield | Affected$ Creature.Red | AddPower$ 1 | AddToughness$ 1 -SVar:RepCurse:Event$ ProduceMana | ActiveZones$ Command | ValidCard$ Mountain | ManaReplacement$ ProduceColorless | Description$ If a player taps a Mountain for mana, that Mountain produces colorless mana instead of any other type. -SVar:ProduceColorless:R->1 & B->1 & U->1 & G->1 & W->1 +SVar:RepCurse:Event$ ProduceMana | ActiveZones$ Command | ValidCard$ Mountain | ValidAbility$ Activated.hasTapCost | ReplaceWith$ ProduceColorless | Description$ If a player taps a Mountain for mana, that Mountain produces colorless mana instead of any other type. +SVar:ProduceColorless:DB$ ReplaceMana | ReplaceType$ C SVar:STCurse:Mode$ Continuous | EffectZone$ Command | AffectedZone$ Battlefield | Affected$ Creature.Red | AddPower$ -1 | AddToughness$ -1 SVar:X:Count$Valid Permanent AI:RemoveDeck:All diff --git a/forge-gui/res/cardsfolder/c/chilling_shade.txt b/forge-gui/res/cardsfolder/c/chilling_shade.txt index 9ca0b82e108..7c3adbd4bfa 100644 --- a/forge-gui/res/cardsfolder/c/chilling_shade.txt +++ b/forge-gui/res/cardsfolder/c/chilling_shade.txt @@ -3,8 +3,6 @@ ManaCost:2 B Types:Snow Creature Shade PT:1/1 K:Flying -A:AB$ Pump | Cost$ S | Defined$ Self | +1 | NumDef$ +1 | SpellDescription$ CARDNAME gets +1/+1 until end of turn. -# AI can now use snow mana to pay for activated abilities. -AI:RemoveDeck:Random -DeckHints:Type$Snow +A:AB$ Pump | Cost$ S | Defined$ Self | NumAtt$ +1 | NumDef$ +1 | SpellDescription$ CARDNAME gets +1/+1 until end of turn. +DeckNeeds:Type$Snow Oracle:Flying\n{S}: Chilling Shade gets +1/+1 until end of turn. ({S} can be paid with one mana from a snow permanent.) diff --git a/forge-gui/res/cardsfolder/c/contamination.txt b/forge-gui/res/cardsfolder/c/contamination.txt index 06d4eadc4b9..96f611b217e 100644 --- a/forge-gui/res/cardsfolder/c/contamination.txt +++ b/forge-gui/res/cardsfolder/c/contamination.txt @@ -2,9 +2,8 @@ Name:Contamination ManaCost:2 B Types:Enchantment K:UpkeepCost:Sac<1/Creature> -R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Land | ManaReplacement$ ProduceB | Description$ If a land is tapped for mana, it produces {B} instead of any other type and amount. -SVar:ProduceB:Any->B +R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Land | ValidAbility$ Activated.hasTapCost | ReplaceWith$ ProduceB | Description$ If a land is tapped for mana, it produces {B} instead of any other type and amount. +SVar:ProduceB:DB$ ReplaceMana | ReplaceMana$ B AI:RemoveDeck:All SVar:NonStackingEffect:True -SVar:Picture:http://www.wizards.com/global/images/magic/general/contamination.jpg Oracle:At the beginning of your upkeep, sacrifice Contamination unless you sacrifice a creature.\nIf a land is tapped for mana, it produces {B} instead of any other type and amount. diff --git a/forge-gui/res/cardsfolder/c/crypt_ghast.txt b/forge-gui/res/cardsfolder/c/crypt_ghast.txt index 2ec4975bfc3..6ea99a1860b 100644 --- a/forge-gui/res/cardsfolder/c/crypt_ghast.txt +++ b/forge-gui/res/cardsfolder/c/crypt_ghast.txt @@ -3,7 +3,6 @@ ManaCost:3 B Types:Creature Spirit PT:2/2 K:Extort -T:Mode$ TapsForMana | ValidCard$ Swamp.YouCtrl | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever you tap a Swamp for mana, add an additional {B}. -SVar:TrigMana:DB$Mana | Produced$ B | Amount$ 1 -SVar:Picture:http://www.wizards.com/global/images/magic/general/crypt_ghast.jpg +T:Mode$ TapsForMana | ValidCard$ Swamp | Activator$ You | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever you tap a Swamp for mana, add an additional {B}. +SVar:TrigMana:DB$ Mana | Produced$ B | Amount$ 1 Oracle:Extort (Whenever you cast a spell, you may pay {W/B}. If you do, each opponent loses 1 life and you gain that much life.)\nWhenever you tap a Swamp for mana, add an additional {B}. diff --git a/forge-gui/res/cardsfolder/d/damping_sphere.txt b/forge-gui/res/cardsfolder/d/damping_sphere.txt index 1674dfb13e2..92bdb8cb095 100644 --- a/forge-gui/res/cardsfolder/d/damping_sphere.txt +++ b/forge-gui/res/cardsfolder/d/damping_sphere.txt @@ -1,10 +1,9 @@ Name:Damping Sphere ManaCost:2 Types:Artifact -R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Land | ManaAmount$ GE2 | ManaReplacement$ ProduceC | Description$ If a land is tapped for two or more mana, it produces {C} instead of any other type and amount. -SVar:ProduceC:Any->C +R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Land | ValidAbility$ Activated.hasTapCost | ManaAmount$ GE2 | ReplaceWith$ ProduceC | Description$ If a land is tapped for two or more mana, it produces {C} instead of any other type and amount. +SVar:ProduceC:DB$ ReplaceMana | ReplaceMana$ C S:Mode$ RaiseCost | Activator$ Player | Type$ Spell | Amount$ X | AffectedAmount$ True | Description$ Each spell a player casts costs {1} more to cast for each other spell that player has cast this turn. SVar:X:Count$ThisTurnCast_Card.YouCtrl SVar:NonStackingEffect:True -SVar:Picture:http://www.wizards.com/global/images/magic/general/damping_sphere.jpg Oracle:If a land is tapped for two or more mana, it produces {C} instead of any other type and amount.\nEach spell a player casts costs {1} more to cast for each other spell that player has cast this turn. diff --git a/forge-gui/res/cardsfolder/d/deep_water.txt b/forge-gui/res/cardsfolder/d/deep_water.txt index 7aeeaa6a777..244889a5aef 100644 --- a/forge-gui/res/cardsfolder/d/deep_water.txt +++ b/forge-gui/res/cardsfolder/d/deep_water.txt @@ -2,9 +2,8 @@ Name:Deep Water ManaCost:U U Types:Enchantment A:AB$ Effect | Cost$ U | ReplacementEffects$ ReplaceU | SVars$ ProduceU | SpellDescription$ Until end of turn, if you tap a land you control for mana, it produces {U} instead of any other type. -SVar:ReplaceU:Event$ ProduceMana | ActiveZones$ Command | ValidCard$ Land.YouCtrl | ManaReplacement$ ProduceU | Description$ If you tap a land you control for mana, it produces U instead of any other type. -SVar:ProduceU:C->U & B->U & R->U & G->U & W->U +SVar:ReplaceU:Event$ ProduceMana | ActiveZones$ Command | ValidPlayer$ You | ValidCard$ Land.YouCtrl | ValidAbility$ Activated.hasTapCost | ReplaceWith$ ProduceU | Description$ If you tap a land you control for mana, it produces U instead of any other type. +SVar:ProduceU:DB$ ReplaceMana | ReplaceType$ U AI:RemoveDeck:All AI:RemoveDeck:Random -SVar:Picture:http://www.wizards.com/global/images/magic/general/deep_water.jpg Oracle:{U}: Until end of turn, if you tap a land you control for mana, it produces {U} instead of any other type. diff --git a/forge-gui/res/cardsfolder/d/detonate.txt b/forge-gui/res/cardsfolder/d/detonate.txt index 0401d47344a..811c8ad7f95 100644 --- a/forge-gui/res/cardsfolder/d/detonate.txt +++ b/forge-gui/res/cardsfolder/d/detonate.txt @@ -1,8 +1,7 @@ Name:Detonate ManaCost:X R Types:Sorcery -A:SP$ Destroy | Cost$ X R | ValidTgts$ Artifact | ValidTgtsWithoutManaCost$ Artifact.cmcEQ0 | TgtPrompt$ Select target artifact | NoRegen$ True | SubAbility$ DBDamage | References$ X | SpellDescription$ Destroy target artifact with converted mana cost X. It can't be regenerated. CARDNAME deals X damage to that artifact's controller. +A:SP$ Destroy | Cost$ X R | ValidTgts$ Artifact | TgtPrompt$ Select target artifact | NoRegen$ True | SubAbility$ DBDamage | References$ X | SpellDescription$ Destroy target artifact with converted mana cost X. It can't be regenerated. CARDNAME deals X damage to that artifact's controller. SVar:DBDamage:DB$DealDamage | Defined$ TargetedController | NumDmg$ X | References$ X -SVar:X:Targeted$CardManaCost -SVar:Picture:http://www.wizards.com/global/images/magic/general/detonate.jpg +SVar:X:Count$xPaid Oracle:Destroy target artifact with converted mana cost X. It can't be regenerated. Detonate deals X damage to that artifact's controller. diff --git a/forge-gui/res/cardsfolder/d/dictate_of_karametra.txt b/forge-gui/res/cardsfolder/d/dictate_of_karametra.txt index 59cbfac0bff..de07863d915 100644 --- a/forge-gui/res/cardsfolder/d/dictate_of_karametra.txt +++ b/forge-gui/res/cardsfolder/d/dictate_of_karametra.txt @@ -3,6 +3,5 @@ ManaCost:3 G G Types:Enchantment K:Flash T:Mode$ TapsForMana | ValidCard$ Land | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever a player taps a land for mana, that player adds one mana of any type that land produced. -SVar:TrigMana:DB$ ManaReflected | ColorOrType$ Type | Valid$ Defined.Triggered | ReflectProperty$ Produced | Defined$ TriggeredPlayer -SVar:Picture:http://www.wizards.com/global/images/magic/general/dictate_of_karametra.jpg +SVar:TrigMana:DB$ ManaReflected | ColorOrType$ Type | ReflectProperty$ Produced | Defined$ TriggeredActivator Oracle:Flash\nWhenever a player taps a land for mana, that player adds one mana of any type that land produced. diff --git a/forge-gui/res/cardsfolder/d/doubling_cube.txt b/forge-gui/res/cardsfolder/d/doubling_cube.txt index 1f505f864dc..f25110460ba 100644 --- a/forge-gui/res/cardsfolder/d/doubling_cube.txt +++ b/forge-gui/res/cardsfolder/d/doubling_cube.txt @@ -1,7 +1,6 @@ Name:Doubling Cube ManaCost:2 Types:Artifact -A:AB$ Mana | Cost$ 3 T | DoubleManaInPool$ True | ProduceNoOtherMana$ True | SpellDescription$ Double the amount of each type of unspent mana you have. +A:AB$ Mana | Cost$ 3 T | Produced$ Special DoubleManaInPool | SpellDescription$ Double the amount of each type of unspent mana you have. AI:RemoveDeck:All -SVar:Picture:http://www.wizards.com/global/images/magic/general/doubling_cube.jpg Oracle:{3}, {T}: Double the amount of each type of unspent mana you have. diff --git a/forge-gui/res/cardsfolder/d/draugr_recruiter.txt b/forge-gui/res/cardsfolder/d/draugr_recruiter.txt index 0fe0d50ba0a..04e05ce2edd 100644 --- a/forge-gui/res/cardsfolder/d/draugr_recruiter.txt +++ b/forge-gui/res/cardsfolder/d/draugr_recruiter.txt @@ -1,6 +1,6 @@ Name:Draugr Recruiter ManaCost:3 B Types:Creature Zombie Cleric -PT:2/2 -A:AB$ ChangeZone | Cost$ 3 B | Origin$ Graveyard | Destination$ Hand | TgtPrompt$ Select target creature card in your graveyard | ValidTgts$ Creature.YouCtrl | Boast$ True |SpellDescription$ Return target creature card from your graveyard to your hand. -Oracle:Boast — {3}{B}: Return target creature card from your graveyard to your hand. (Activate this ability only if this creature attacked this turn and only once each turn.) \ No newline at end of file +PT:3/3 +A:AB$ ChangeZone | Cost$ 3 B | Origin$ Graveyard | Destination$ Hand | TgtPrompt$ Select target creature card in your graveyard | ValidTgts$ Creature.YouCtrl | Boast$ True | SpellDescription$ Return target creature card from your graveyard to your hand. +Oracle:Boast — {3}{B}: Return target creature card from your graveyard to your hand. (Activate this ability only if this creature attacked this turn and only once each turn.) diff --git a/forge-gui/res/cardsfolder/e/eloren_wilds.txt b/forge-gui/res/cardsfolder/e/eloren_wilds.txt index 5493db87546..e0d31fe71b5 100644 --- a/forge-gui/res/cardsfolder/e/eloren_wilds.txt +++ b/forge-gui/res/cardsfolder/e/eloren_wilds.txt @@ -2,7 +2,7 @@ Name:Eloren Wilds ManaCost:no cost Types:Plane Shandalar T:Mode$ TapsForMana | ValidCard$ Permanent | Execute$ TrigMana | TriggerZones$ Command | Static$ True | TriggerDescription$ Whenever a player taps a permanent for mana, that player adds one mana of any type that permanent produced. -SVar:TrigMana:DB$ ManaReflected | ColorOrType$ Type | Valid$ Defined.Triggered | ReflectProperty$ Produced | Defined$ TriggeredPlayer +SVar:TrigMana:DB$ ManaReflected | ColorOrType$ Type | ReflectProperty$ Produced | Defined$ TriggeredActivator T:Mode$ PlanarDice | Result$ Chaos | TriggerZones$ Command | Execute$ RolledChaos | TriggerDescription$ Whenever you roll {CHAOS}, target player can't cast spells until a player planeswalks. SVar:RolledChaos:DB$ Effect | ValidTgts$ Player | IsCurse$ True | Name$ Eloren Wilds Effect | StaticAbilities$ STCantCast | Triggers$ TrigPlaneswalk | SVars$ ExileSelf | RememberObjects$ Targeted | Duration$ Permanent SVar:STCantCast:Mode$ CantBeCast | EffectZone$ Command | ValidCard$ Card | Caster$ Player.IsRemembered | Description$ Target player can't cast spells until a player planeswalks. diff --git a/forge-gui/res/cardsfolder/e/extraplanar_lens.txt b/forge-gui/res/cardsfolder/e/extraplanar_lens.txt index cd725a4329f..c50b4ec94de 100644 --- a/forge-gui/res/cardsfolder/e/extraplanar_lens.txt +++ b/forge-gui/res/cardsfolder/e/extraplanar_lens.txt @@ -4,12 +4,11 @@ Types:Artifact T:Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Any | Destination$ Battlefield | Execute$ TrigExile | OptionalDecider$ You | TriggerDescription$ Imprint — When CARDNAME enters the battlefield, you may exile target land you control. SVar:TrigExile:DB$ ChangeZone | Imprint$ True | Origin$ Battlefield | Destination$ Exile | ValidTgts$ Land.YouCtrl | TgtPrompt$ Select a target land you control | AILogic$ ExtraplanarLens T:Mode$ TapsForMana | ValidCard$ Land.sharesNameWith Imprinted | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever a land with the same name as the exiled card is tapped for mana, its controller adds one mana of any type that land produced. -SVar:TrigMana:DB$ ManaReflected | Valid$ Defined.Triggered | ColorOrType$ Type | ReflectProperty$ Produced | Defined$ TriggeredPlayer +SVar:TrigMana:DB$ ManaReflected | ColorOrType$ Type | ReflectProperty$ Produced | Defined$ TriggeredCardController T:Mode$ ChangesZone | Origin$ Battlefield | ValidCard$ Card.Self | Destination$ Any | Execute$ DBCleanup | Static$ True SVar:DBCleanup:DB$ Cleanup | ClearImprinted$ True T:Mode$ ChangesZone | ValidCard$ Card.IsImprinted+ExiledWithSource | Origin$ Exile | Execute$ DBForget | Static$ True SVar:DBForget:DB$ Pump | ForgetImprinted$ TriggeredCard SVar:NeedsToPlay:Land.Basic+YouCtrl AI:RemoveDeck:Random -SVar:Picture:http://www.wizards.com/global/images/magic/general/extraplanar_lens.jpg Oracle:Imprint — When Extraplanar Lens enters the battlefield, you may exile target land you control.\nWhenever a land with the same name as the exiled card is tapped for mana, its controller adds one mana of any type that land produced. diff --git a/forge-gui/res/cardsfolder/f/false_dawn.txt b/forge-gui/res/cardsfolder/f/false_dawn.txt index f9edade399e..0b182ef7274 100644 --- a/forge-gui/res/cardsfolder/f/false_dawn.txt +++ b/forge-gui/res/cardsfolder/f/false_dawn.txt @@ -3,9 +3,8 @@ ManaCost:1 W Types:Sorcery A:SP$ Effect | Cost$ 1 W | ReplacementEffects$ FDRep | StaticAbilities$ FDManaConvertion | SVars$ ProduceW | SubAbility$ DBDraw | SpellDescription$ Until end of turn, spells and abilities you control that would add colored mana add that much white mana instead. Until end of turn, you may spend white mana as though it were mana of any color. Draw a card. SVar:DBDraw:DB$ Draw | NumCards$ 1 -SVar:FDRep:Event$ ProduceMana | ActiveZones$ Command | ValidCard$ Card.YouCtrl | NoTapCheck$ True | ManaReplacement$ ProduceW | Description$ Spells and abilities you control that would add colored mana add that much white mana instead. -SVar:ProduceW:R->W & B->W & U->W & G->W +SVar:FDRep:Event$ ProduceMana | ActiveZones$ Command | ValidCard$ Card.YouCtrl | ReplaceWith$ ProduceW | Description$ Spells and abilities you control that would add colored mana add that much white mana instead. +SVar:ProduceW:DB$ ReplaceMana | ReplaceColor$ W SVar:FDManaConvertion:Mode$ Continuous | EffectZone$ Command | Affected$ You | ManaColorConversion$ Additive | WhiteConversion$ Color | Description$ You may spend white mana as though it were mana of any color. AI:RemoveDeck:All -SVar:Picture:http://www.wizards.com/global/images/magic/general/false_dawn.jpg Oracle:Until end of turn, spells and abilities you control that would add colored mana add that much white mana instead. Until end of turn, you may spend white mana as though it were mana of any color.\nDraw a card. diff --git a/forge-gui/res/cardsfolder/f/forsaken_monument.txt b/forge-gui/res/cardsfolder/f/forsaken_monument.txt index 5282f5a0ce1..3f32b9111dd 100644 --- a/forge-gui/res/cardsfolder/f/forsaken_monument.txt +++ b/forge-gui/res/cardsfolder/f/forsaken_monument.txt @@ -2,7 +2,7 @@ Name:Forsaken Monument ManaCost:5 Types:Legendary Artifact S:Mode$ Continuous | Affected$ Creature.Colorless+YouCtrl | AddPower$ 2 | AddToughness$ 2 | Description$ Colorless creatures you control get +2/+2. -T:Mode$ TapsForMana | ValidCard$ Permanent.YouCtrl | Produced$ C | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever you tap a permanent for {C}, add an additional {C}. +T:Mode$ TapsForMana | ValidCard$ Permanent | Activator$ You | Produced$ C | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever you tap a permanent for {C}, add an additional {C}. SVar:TrigMana:DB$ Mana | Produced$ C T:Mode$ SpellCast | ValidCard$ Card.Colorless | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigGainLife | TriggerDescription$ Whenever you cast a colorless spell, you gain 2 life. SVar:TrigGainLife:DB$ GainLife | Defined$ You | LifeAmount$ 2 diff --git a/forge-gui/res/cardsfolder/h/hall_of_gemstone.txt b/forge-gui/res/cardsfolder/h/hall_of_gemstone.txt index 3b1ecfe9733..f759a16f6ce 100644 --- a/forge-gui/res/cardsfolder/h/hall_of_gemstone.txt +++ b/forge-gui/res/cardsfolder/h/hall_of_gemstone.txt @@ -4,8 +4,7 @@ Types:World Enchantment T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ Player | Execute$ TrigChoose | TriggerZones$ Battlefield | TriggerDescription$ At the beginning of each player's upkeep, that player chooses a color. Until end of turn, lands tapped for mana produce mana of the chosen color instead of any other color. SVar:TrigChoose:DB$ ChooseColor | Defined$ TriggeredPlayer | AILogic$ MostProminentInActivePlayerHand | SubAbility$ DBEffect SVar:DBEffect:DB$ Effect | ReplacementEffects$ ReplaceChosen | SVars$ ProduceChosen -SVar:ReplaceChosen:Event$ ProduceMana | ActiveZones$ Command | ValidCard$ Land | ManaReplacement$ ProduceChosen | Description$ Lands tapped for mana produce mana of the chosen color instead of any other color. -SVar:ProduceChosen:C->Chosen & U->Chosen & B->Chosen & R->Chosen & G->Chosen & W->Chosen +SVar:ReplaceChosen:Event$ ProduceMana | ActiveZones$ Command | ValidCard$ Land | ValidAbility$ Activated.hasTapCost | ReplaceWith$ ProduceChosen | Description$ Lands tapped for mana produce mana of the chosen color instead of any other color. +SVar:ProduceChosen:DB$ ReplaceMana | ReplaceColor$ Chosen AI:RemoveDeck:Random -SVar:Picture:http://www.wizards.com/global/images/magic/general/hall_of_gemstone.jpg Oracle:At the beginning of each player's upkeep, that player chooses a color. Until end of turn, lands tapped for mana produce mana of the chosen color instead of any other color. diff --git a/forge-gui/res/cardsfolder/h/harvest_mage.txt b/forge-gui/res/cardsfolder/h/harvest_mage.txt index 4210c4d3a46..883dd8ebf4f 100644 --- a/forge-gui/res/cardsfolder/h/harvest_mage.txt +++ b/forge-gui/res/cardsfolder/h/harvest_mage.txt @@ -3,8 +3,8 @@ ManaCost:G Types:Creature Human Spellshaper PT:1/1 A:AB$ Effect | Cost$ G T Discard<1/Card> | ReplacementEffects$ HarvestReplacement | SVars$ HarvestProduce | References$ HarvestReplacement,HarvestProduce | AILogic$ Never | Stackable$ False | SpellDescription$ Until end of turn, if you tap a land for mana, it produces one mana of a color of your choice instead of any other type and amount. -SVar:HarvestReplacement:Event$ ProduceMana | ActiveZones$ Command | ValidCard$ Land.YouCtrl | ManaReplacement$ HarvestProduce | Description$ If you tap a land for mana, it produces one mana of a color of your choice instead of any other type and amount. -SVar:HarvestProduce:Any->Any +SVar:HarvestReplacement:Event$ ProduceMana | ActiveZones$ Command | ValidPlayer$ You | ValidCard$ Land | ValidAbility$ Activated.hasTapCost | ReplaceWith$ HarvestProduce | Description$ If you tap a land for mana, it produces one mana of a color of your choice instead of any other type and amount. +SVar:HarvestProduce:DB$ ReplaceMana | ReplaceMana$ Any AI:RemoveDeck:All SVar:NonStackingEffect:True SVar:Picture:http://www.wizards.com/global/images/magic/general/harvest_mage.jpg diff --git a/forge-gui/res/cardsfolder/h/heartbeat_of_spring.txt b/forge-gui/res/cardsfolder/h/heartbeat_of_spring.txt index c30b56eb47f..2c9033feaea 100644 --- a/forge-gui/res/cardsfolder/h/heartbeat_of_spring.txt +++ b/forge-gui/res/cardsfolder/h/heartbeat_of_spring.txt @@ -2,7 +2,5 @@ Name:Heartbeat of Spring ManaCost:2 G Types:Enchantment T:Mode$ TapsForMana | ValidCard$ Land | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever a player taps a land for mana, that player adds one mana of any type that land produced. -SVar:TrigMana:DB$ ManaReflected | ColorOrType$ Type | Valid$ Defined.Triggered | ReflectProperty$ Produced | Defined$ TriggeredPlayer -AI:RemoveDeck:All -SVar:Picture:http://www.wizards.com/global/images/magic/general/heartbeat_of_spring.jpg +SVar:TrigMana:DB$ ManaReflected | ColorOrType$ Type | ReflectProperty$ Produced | Defined$ TriggeredActivator Oracle:Whenever a player taps a land for mana, that player adds one mana of any type that land produced. diff --git a/forge-gui/res/cardsfolder/i/infernal_darkness.txt b/forge-gui/res/cardsfolder/i/infernal_darkness.txt index 59c9333f082..547ea7192c2 100644 --- a/forge-gui/res/cardsfolder/i/infernal_darkness.txt +++ b/forge-gui/res/cardsfolder/i/infernal_darkness.txt @@ -2,9 +2,8 @@ Name:Infernal Darkness ManaCost:2 B B Types:Enchantment K:Cumulative upkeep:B PayLife<1>:Pay {B} and 1 life. -R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Land | ManaReplacement$ ProduceB | Description$ If a land is tapped for mana, it produces {B} instead of any other type. -SVar:ProduceB:C->B & U->B & R->B & G->B & W->B +R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Land | ValidAbility$ Activated.hasTapCost | ReplaceWith$ ProduceB | Description$ If a land is tapped for mana, it produces {B} instead of any other type. +SVar:ProduceB:DB$ ReplaceMana | ReplaceType$ B AI:RemoveDeck:All AI:RemoveDeck:Random -SVar:Picture:http://www.wizards.com/global/images/magic/general/infernal_darkness.jpg Oracle:Cumulative upkeep—Pay {B} and 1 life. (At the beginning of your upkeep, put an age counter on this permanent, then sacrifice it unless you pay its upkeep cost for each age counter on it.)\nIf a land is tapped for mana, it produces {B} instead of any other type. diff --git a/forge-gui/res/cardsfolder/k/kaho_minamo_historian.txt b/forge-gui/res/cardsfolder/k/kaho_minamo_historian.txt index f266e634d2f..6eefb21c9ee 100644 --- a/forge-gui/res/cardsfolder/k/kaho_minamo_historian.txt +++ b/forge-gui/res/cardsfolder/k/kaho_minamo_historian.txt @@ -4,7 +4,7 @@ Types:Legendary Creature Human Wizard PT:2/2 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Creature.Self | Execute$ Catalogue | TriggerDescription$ When CARDNAME enters the battlefield, search your library for up to three instant cards and exile them. Then shuffle your library. SVar:Catalogue:DB$ ChangeZone | Origin$ Library | Destination$ Exile | ChangeType$ Instant | ChangeNum$ 3 | RememberChanged$ True | ForgetOtherRemembered$ True -A:AB$ Play | Cost$ X T | Valid$ Card.IsRemembered+ExiledWithSource+cmcEQX | ValidZone$ Exile | WithoutManaCost$ True | Amount$ 1 | Controller$ You | Optional$ True | References$ X | ForgetTargetRemembered$ True | SpellDescription$ You may cast a card with converted mana cost X exiled with Kaho without paying its mana cost. +A:AB$ Play | Cost$ X T | Valid$ Card.IsRemembered+ExiledWithSource | ValidSA$ Spell.cmcEQX | ValidZone$ Exile | WithoutManaCost$ True | Amount$ 1 | Controller$ You | Optional$ True | References$ X | ForgetTargetRemembered$ True | SpellDescription$ You may cast a spell with converted mana cost X exiled with Kaho without paying its mana cost. T:Mode$ ChangesZone | ValidCard$ Card.IsRemembered+ExiledWithSource | Origin$ Exile | Destination$ Any | Execute$ ForgetCard | Static$ True T:Mode$ SpellCast | ValidCard$ Card.IsRemembered+ExiledWithSource | Execute$ ForgetCard | Static$ True SVar:ForgetCard:DB$ Cleanup | ForgetDefined$ TriggeredCard @@ -13,4 +13,4 @@ SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:X:Count$xPaid AI:RemoveDeck:All SVar:Picture:http://www.wizards.com/global/images/magic/general/kaho_minamo_historian.jpg -Oracle:When Kaho, Minamo Historian enters the battlefield, search your library for up to three instant cards and exile them. Then shuffle your library.\n{X}, {T}: You may cast a card with converted mana cost X exiled with Kaho without paying its mana cost. +Oracle:When Kaho, Minamo Historian enters the battlefield, search your library for up to three instant cards and exile them. Then shuffle your library.\n{X}, {T}: You may cast a spell with converted mana cost X exiled with Kaho without paying its mana cost. diff --git a/forge-gui/res/cardsfolder/k/keeper_of_progenitus.txt b/forge-gui/res/cardsfolder/k/keeper_of_progenitus.txt index d9d3a9bc7aa..4525f98f52d 100644 --- a/forge-gui/res/cardsfolder/k/keeper_of_progenitus.txt +++ b/forge-gui/res/cardsfolder/k/keeper_of_progenitus.txt @@ -3,7 +3,6 @@ ManaCost:3 G Types:Creature Elf Druid PT:1/3 T:Mode$ TapsForMana | ValidCard$ Mountain,Forest,Plains | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever a player taps a Mountain, Forest, or Plains for mana, that player adds one mana of any type that land produced. -SVar:TrigMana:DB$ ManaReflected | ColorOrType$ Type | Valid$ Defined.Triggered | ReflectProperty$ Produced | Defined$ TriggeredPlayer +SVar:TrigMana:DB$ ManaReflected | ColorOrType$ Type | ReflectProperty$ Produced | Defined$ TriggeredActivator AI:RemoveDeck:All -SVar:Picture:http://www.wizards.com/global/images/magic/general/keeper_of_progenitus.jpg Oracle:Whenever a player taps a Mountain, Forest, or Plains for mana, that player adds one mana of any type that land produced. diff --git a/forge-gui/res/cardsfolder/k/kinnan_bonder_prodigy.txt b/forge-gui/res/cardsfolder/k/kinnan_bonder_prodigy.txt index e73a35fff33..5b1ba46361e 100755 --- a/forge-gui/res/cardsfolder/k/kinnan_bonder_prodigy.txt +++ b/forge-gui/res/cardsfolder/k/kinnan_bonder_prodigy.txt @@ -2,7 +2,7 @@ Name:Kinnan, Bonder Prodigy ManaCost:G U Types:Legendary Creature Human Druid PT:2/2 -T:Mode$ TapsForMana | ValidCard$ Permanent.nonLand+YouCtrl | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever you tap a nonland permanent for mana, add one mana of any type that permanent produced. -SVar:TrigMana:DB$ ManaReflected | ColorOrType$ Type | Valid$ Defined.Triggered | ReflectProperty$ Produced | Defined$ TriggeredPlayer +T:Mode$ TapsForMana | ValidCard$ Permanent.nonLand | Activator$ You | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever you tap a nonland permanent for mana, add one mana of any type that permanent produced. +SVar:TrigMana:DB$ ManaReflected | ColorOrType$ Type | ReflectProperty$ Produced | Defined$ You A:AB$ Dig | Cost$ 5 G U | ForceRevealToController$ True | DigNum$ 5 | ChangeNum$ 1 | Optional$ True | ChangeValid$ Creature.nonHuman | DestinationZone$ Battlefield | RestRandomOrder$ True | SpellDescription$ Look at the top five cards of your library. You may put a non-Human creature card from among them onto the battlefield. Put the rest on the bottom of your library in a random order. Oracle:Whenever you tap a nonland permanent for mana, add one mana of any type that permanent produced.\n{5}{G}{U}: Look at the top five cards of your library. You may put a non-Human creature card from among them onto the battlefield. Put the rest on the bottom of your library in a random order. diff --git a/forge-gui/res/tokenscripts/lathril_blade_of_the_elves.txt b/forge-gui/res/cardsfolder/l/lathril_blade_of_the_elves.txt similarity index 100% rename from forge-gui/res/tokenscripts/lathril_blade_of_the_elves.txt rename to forge-gui/res/cardsfolder/l/lathril_blade_of_the_elves.txt diff --git a/forge-gui/res/cardsfolder/m/magmatic_core.txt b/forge-gui/res/cardsfolder/m/magmatic_core.txt index 9e5709e750b..2a701170984 100644 --- a/forge-gui/res/cardsfolder/m/magmatic_core.txt +++ b/forge-gui/res/cardsfolder/m/magmatic_core.txt @@ -2,12 +2,9 @@ Name:Magmatic Core ManaCost:2 R R Types:Enchantment K:Cumulative upkeep:1 -T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ CountMagma | Static$ True | Secondary$ True | TriggerDescription$ At the beginning of your end step, CARDNAME deals X damage divided as you choose among any number of target creatures, where X is the number of age counters on it. -SVar:CountMagma:DB$ StoreSVar | SVar$ CoreStrength | Type$ Count | Expression$ CardCounters.AGE T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ MagmaticDamage | TriggerDescription$ At the beginning of your end step, CARDNAME deals X damage divided as you choose among any number of target creatures, where X is the number of age counters on it. SVar:MagmaticDamage:DB$ DealDamage | ValidTgts$ Creature | TgtPrompt$ Select target creature to distribute damage to | NumDmg$ CoreStrength | TargetMin$ Min | TargetMax$ CoreStrength | References$ Min,CoreStrength | DividedAsYouChoose$ CoreStrength SVar:CoreStrength:Count$CardCounters.AGE SVar:Min:SVar$CoreStrength/LimitMax.1 AI:RemoveDeck:Random -SVar:Picture:http://www.wizards.com/global/images/magic/general/magmatic_core.jpg Oracle:Cumulative upkeep {1} (At the beginning of your upkeep, put an age counter on this permanent, then sacrifice it unless you pay its upkeep cost for each age counter on it.)\nAt the beginning of your end step, Magmatic Core deals X damage divided as you choose among any number of target creatures, where X is the number of age counters on it. diff --git a/forge-gui/res/cardsfolder/m/mana_flare.txt b/forge-gui/res/cardsfolder/m/mana_flare.txt index 5410caf9d2a..d9c858848b1 100644 --- a/forge-gui/res/cardsfolder/m/mana_flare.txt +++ b/forge-gui/res/cardsfolder/m/mana_flare.txt @@ -2,6 +2,5 @@ Name:Mana Flare ManaCost:2 R Types:Enchantment T:Mode$ TapsForMana | ValidCard$ Land | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever a player taps a land for mana, that player adds one mana of any type that land produced. -SVar:TrigMana:DB$ ManaReflected | ColorOrType$ Type | Valid$ Defined.Triggered | ReflectProperty$ Produced | Defined$ TriggeredPlayer -SVar:Picture:http://www.wizards.com/global/images/magic/general/mana_flare.jpg +SVar:TrigMana:DB$ ManaReflected | ColorOrType$ Type | ReflectProperty$ Produced | Defined$ TriggeredActivator Oracle:Whenever a player taps a land for mana, that player adds one mana of any type that land produced. diff --git a/forge-gui/res/cardsfolder/m/mana_reflection.txt b/forge-gui/res/cardsfolder/m/mana_reflection.txt index c233484ccb7..04ebe0d17df 100644 --- a/forge-gui/res/cardsfolder/m/mana_reflection.txt +++ b/forge-gui/res/cardsfolder/m/mana_reflection.txt @@ -1,7 +1,6 @@ Name:Mana Reflection ManaCost:4 G G Types:Enchantment -R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Permanent.YouCtrl | ManaReplacement$ ProduceTwice | Description$ If you tap a permanent for mana, it produces twice as much of that mana instead. -SVar:ProduceTwice:C->C C & R->R R & B->B B & U->U U & G->G G & W->W W -SVar:Picture:http://www.wizards.com/global/images/magic/general/mana_reflection.jpg +R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidPlayer$ You | ValidCard$ Permanent | ValidAbility$ Activated.hasTapCost | ReplaceWith$ ProduceTwice | Description$ If you tap a permanent for mana, it produces twice as much of that mana instead. +SVar:ProduceTwice:DB$ ReplaceMana | ReplaceAmount$ 2 Oracle:If you tap a permanent for mana, it produces twice as much of that mana instead. diff --git a/forge-gui/res/cardsfolder/m/masked_vandal.txt b/forge-gui/res/cardsfolder/m/masked_vandal.txt index 93fe4e8ae18..b92e609c2a5 100644 --- a/forge-gui/res/cardsfolder/m/masked_vandal.txt +++ b/forge-gui/res/cardsfolder/m/masked_vandal.txt @@ -3,7 +3,6 @@ ManaCost:1 G Types:Creature Shapeshifter PT:1/3 K:Changeling -T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | OptionalDecider$ You | Execute$ TrigExile | TriggerDescription$ When Masked Vandal enters the battlefield, you may exile a creature card from your graveyard. If you do, exile target artifact or enchantment an opponent controls. -SVar:TrigExile:DB$ChangeZone | Hidden$ True | Origin$ Graveyard | Destination$ Exile | ChangeType$ Creature.YouCtrl | ChangeNum$ 1 | SubAbility$ DBChangeZone -SVar:DBChangeZone:DB$ ChangeZone | ValidTgts$ Artifact.OppCtrl,Enchantment.OppCtrl | TgtPrompt$ Select target artifact or enchantment an opponent controls | Origin$ Battlefield | Destination$ Exile | SpellDescription$ Exile target artifact or enchantment an opponent controls. +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigExile | TriggerDescription$ When CARDNAME enters the battlefield, you may exile a creature card from your graveyard. If you do, exile target artifact or enchantment an opponent controls. +SVar:TrigExile:AB$ ChangeZone | Cost$ ExileFromGrave<1/Creature> | Origin$ Battlefield | Destination$ Exile | ValidTgts$ Artifact.OppCtrl,Enchantment.OppCtrl | TgtPrompt$ Choose target artifact or enchantment an opponent controls. Oracle:Changeling (This card is every creature type.)\nWhen Masked Vandal enters the battlefield, you may exile a creature card from your graveyard. If you do, exile target artifact or enchantment an opponent controls. diff --git a/forge-gui/res/cardsfolder/m/miraris_wake.txt b/forge-gui/res/cardsfolder/m/miraris_wake.txt index b0824cc90a1..3fcdbd0f81c 100644 --- a/forge-gui/res/cardsfolder/m/miraris_wake.txt +++ b/forge-gui/res/cardsfolder/m/miraris_wake.txt @@ -2,7 +2,6 @@ Name:Mirari's Wake ManaCost:3 G W Types:Enchantment S:Mode$ Continuous | Affected$ Creature.YouCtrl | AddPower$ 1 | AddToughness$ 1 | Description$ Creatures you control get +1/+1. -T:Mode$ TapsForMana | ValidCard$ Land.YouCtrl | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever you tap a land for mana, add one mana of any type that land produced. -SVar:TrigMana:DB$ManaReflected | ColorOrType$ Type | Valid$ Defined.Triggered | ReflectProperty$ Produced | Defined$ TriggeredPlayer -SVar:Picture:http://www.wizards.com/global/images/magic/general/miraris_wake.jpg +T:Mode$ TapsForMana | ValidCard$ Land | Activator$ You | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever you tap a land for mana, add one mana of any type that land produced. +SVar:TrigMana:DB$ ManaReflected | ColorOrType$ Type | ReflectProperty$ Produced | Defined$ You Oracle:Creatures you control get +1/+1.\nWhenever you tap a land for mana, add one mana of any type that land produced. diff --git a/forge-gui/res/cardsfolder/m/mirri.txt b/forge-gui/res/cardsfolder/m/mirri.txt index ddbf7096e00..163d728ebe3 100644 --- a/forge-gui/res/cardsfolder/m/mirri.txt +++ b/forge-gui/res/cardsfolder/m/mirri.txt @@ -2,8 +2,8 @@ Name:Mirri ManaCost:no cost Types:Vanguard HandLifeModifier:+0/+5 -R:Event$ ProduceMana | ActiveZones$ Command | ValidCard$ Land.Basic+YouCtrl | ManaReplacement$ ProduceAny | Description$ If a basic land you control is tapped for mana, it produces mana of a color of your choice instead of any other type. -SVar:ProduceAny:C->Any & B->Any & R->Any & G->Any & W->Any & U->Any +R:Event$ ProduceMana | ActiveZones$ Command | ValidCard$ Land.Basic+YouCtrl | ValidAbility$ Activated.hasTapCost | ReplaceWith$ ProduceAny | Description$ If a basic land you control is tapped for mana, it produces mana of a color of your choice instead of any other type. +SVar:ProduceAny:DB$ ReplaceMana | ReplaceType$ Any AI:RemoveDeck:All SVar:Picture:https://downloads.cardforge.org/images/cards/VAN/Mirri.full.jpg Oracle:Hand +0, life +5\nIf a basic land you control is tapped for mana, it produces mana of a color of your choice instead of any other type. diff --git a/forge-gui/res/cardsfolder/n/naked_singularity.txt b/forge-gui/res/cardsfolder/n/naked_singularity.txt index 9da248d9f55..ce0bfaac124 100644 --- a/forge-gui/res/cardsfolder/n/naked_singularity.txt +++ b/forge-gui/res/cardsfolder/n/naked_singularity.txt @@ -3,17 +3,16 @@ ManaCost:5 Types:Artifact Text:If tapped for mana, Plains produce {R}, Islands produce {G}, Swamps produce {W}, Mountains produce {U}, and Forests produce {B} instead of any other type. K:Cumulative upkeep:3 -R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Plains | ManaReplacement$ ProduceR | Secondary$ True | Description$ If tapped for mana, Plains produce R. -SVar:ProduceR:C->R & B->R & U->R & G->R & W->R -R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Island | ManaReplacement$ ProduceG | Secondary$ True | Description$ If tapped for mana, Islands produce G. -SVar:ProduceG:C->G & B->G & U->G & R->G & W->G -R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Swamp | ManaReplacement$ ProduceW | Secondary$ True | Description$ If tapped for mana, Swamps produce W. -SVar:ProduceW:C->W & B->W & U->W & R->W & G->W -R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Mountain | ManaReplacement$ ProduceU | Secondary$ True | Description$ If tapped for mana, Mountains produce U. -SVar:ProduceU:C->U & B->U & G->U & R->U & W->U -R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Forest | ManaReplacement$ ProduceB | Secondary$ True | Description$ If tapped for mana, Forests produce B. -SVar:ProduceB:C->B & G->B & U->B & R->B & W->B +R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Plains | ValidAbility$ Activated.hasTapCost | ReplaceWith$ ProduceR | Secondary$ True | Description$ If tapped for mana, Plains produce R. +SVar:ProduceR:DB$ ReplaceMana | ReplaceType$ R +R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Island | ValidAbility$ Activated.hasTapCost | ReplaceWith$ ProduceG | Secondary$ True | Description$ If tapped for mana, Islands produce G. +SVar:ProduceG:DB$ ReplaceMana | ReplaceType$ G +R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Swamp | ValidAbility$ Activated.hasTapCost | ReplaceWith$ ProduceW | Secondary$ True | Description$ If tapped for mana, Swamps produce W. +SVar:ProduceW:DB$ ReplaceMana | ReplaceType$ W +R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Mountain | ValidAbility$ Activated.hasTapCost | ReplaceWith$ ProduceU | Secondary$ True | Description$ If tapped for mana, Mountains produce U. +SVar:ProduceU:DB$ ReplaceMana | ReplaceType$ U +R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Forest | ValidAbility$ Activated.hasTapCost | ReplaceWith$ ProduceB | Secondary$ True | Description$ If tapped for mana, Forests produce B. +SVar:ProduceB:DB$ ReplaceMana | ReplaceType$ B AI:RemoveDeck:All AI:RemoveDeck:Random -SVar:Picture:http://www.wizards.com/global/images/magic/general/naked_singularity.jpg Oracle:Cumulative upkeep {3} (At the beginning of your upkeep, put an age counter on this permanent, then sacrifice it unless you pay its upkeep cost for each age counter on it.)\nIf tapped for mana, Plains produce {R}, Islands produce {G}, Swamps produce {W}, Mountains produce {U}, and Forests produce {B} instead of any other type. diff --git a/forge-gui/res/cardsfolder/n/narfi_betrayer_king.txt b/forge-gui/res/cardsfolder/n/narfi_betrayer_king.txt index a81be801068..03414882ed7 100644 --- a/forge-gui/res/cardsfolder/n/narfi_betrayer_king.txt +++ b/forge-gui/res/cardsfolder/n/narfi_betrayer_king.txt @@ -2,7 +2,7 @@ Name:Narfi, Betrayer King ManaCost:3 U B Types:Legendary Snow Creature Zombie Wizard PT:4/3 -S:Mode$ Continuous | Affected$ Creature.Zombie+Other+YouCtrl,Creature.Snow+YouCtrl | AddPower$ 1 | AddToughness$ 1 | Description$ Other Snow and Zombie creatures you control get +1/+1. +S:Mode$ Continuous | Affected$ Creature.Zombie+Other+YouCtrl,Creature.Snow+Other+YouCtrl | AddPower$ 1 | AddToughness$ 1 | Description$ Other Snow and Zombie creatures you control get +1/+1. SVar:PlayMain1:TRUE A:AB$ ChangeZone | Cost$ S S S | Origin$ Graveyard | Destination$ Battlefield | ActivationZone$ Graveyard | Tapped$ True | SpellDescription$ Return CARDNAME from your graveyard to the battlefield tapped DeckHints:Type$Snow|Zombie diff --git a/forge-gui/res/cardsfolder/n/niko_aris.txt b/forge-gui/res/cardsfolder/n/niko_aris.txt index a267f66d1ae..a2fd54ce075 100644 --- a/forge-gui/res/cardsfolder/n/niko_aris.txt +++ b/forge-gui/res/cardsfolder/n/niko_aris.txt @@ -1,11 +1,12 @@ Name:Niko Aris -ManaCost:X W W U +ManaCost:X W U U Types:Legendary Planeswalker Niko Loyalty:3 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigToken | TriggerDescription$ When CARDNAME enters the battlefield, create X Shard tokens. (They're enchantments with "{2}, Sacrifice this enchantment: Scry 1, then draw a card.") SVar:TrigToken:DB$ Token | TokenAmount$ X | TokenScript$ shard | TokenOwner$ You SVar:X:Count$xPaid -A:AB$ Effect | Cost$ AddCounter<1/LOYALTY> | Name$ Niko Aris Effect | Planeswalker$ True | Triggers$ Trig | SVars$ Eff | KW$ HIDDEN Unblockable | TargetMin$ 0 | TargetMax$ 1 | ValidTgts$ Creature | TgtPrompt$ Select target creature | RememberObjects$ Targeted | SpellDescription$ Up to one target creature you control can't be blocked this turn. Whenever that creature deals damage this turn, return it to its owner's hand. +A:AB$ Effect | Cost$ AddCounter<1/LOYALTY> | Name$ Niko Aris Effect | Planeswalker$ True | Triggers$ Trig | SVars$ Eff | TargetMin$ 0 | TargetMax$ 1 | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | RememberObjects$ Targeted | SubAbility$ DBPump | SpellDescription$ Up to one target creature you control can't be blocked this turn. Whenever that creature deals damage this turn, return it to its owner's hand. +SVar:DBPump:DB$ Pump | KW$ HIDDEN Unblockable | Defined$ Targeted SVar:Trig:Mode$ DamageDealtOnce | ValidSource$ Creature.IsRemembered | Execute$ Eff | TriggerDescription$ Whenever this creature deals damage this turn, return it to its owner's hand. SVar:Eff:DB$ ChangeZone | ValidTgts$ Creature.IsRemembered | Origin$ Battlefield | Destination$ Hand A:AB$ DealDamage | Cost$ SubCounter<1/LOYALTY> | Planeswalker$ True | ValidTgts$ Creature.tapped | NumDmg$ Y | References$ Y | TgtPrompt$ Select target tapped creature | SpellDescription$ CARDNAME deals 2 damage to target tapped creature for each card you've drawn this turn. diff --git a/forge-gui/res/cardsfolder/n/nikya_of_the_old_ways.txt b/forge-gui/res/cardsfolder/n/nikya_of_the_old_ways.txt index 99e2fe3d7bd..f2ef730ff3c 100644 --- a/forge-gui/res/cardsfolder/n/nikya_of_the_old_ways.txt +++ b/forge-gui/res/cardsfolder/n/nikya_of_the_old_ways.txt @@ -3,6 +3,6 @@ ManaCost:3 R G Types:Legendary Creature Centaur Druid PT:5/5 S:Mode$ CantBeCast | ValidCard$ Card.nonCreature | Caster$ You | Description$ You can't cast noncreature spells. -T:Mode$ TapsForMana | ValidCard$ Land.YouCtrl | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever you tap a land for mana, add one mana of any type that land produced. -SVar:TrigMana:DB$ManaReflected | ColorOrType$ Type | Valid$ Defined.Triggered | ReflectProperty$ Produced | Defined$ TriggeredPlayer +T:Mode$ TapsForMana | ValidCard$ Land | Activator$ You | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever you tap a land for mana, add one mana of any type that land produced. +SVar:TrigMana:DB$ ManaReflected | ColorOrType$ Type | ReflectProperty$ Produced | Defined$ You Oracle:You can't cast noncreature spells.\nWhenever you tap a land for mana, add one mana of any type that land produced. diff --git a/forge-gui/res/cardsfolder/n/nirkana_revenant.txt b/forge-gui/res/cardsfolder/n/nirkana_revenant.txt index 25d69b82369..6385d51421f 100644 --- a/forge-gui/res/cardsfolder/n/nirkana_revenant.txt +++ b/forge-gui/res/cardsfolder/n/nirkana_revenant.txt @@ -3,7 +3,6 @@ ManaCost:4 B B Types:Creature Vampire Shade PT:4/4 A:AB$ Pump | Cost$ B | NumAtt$ +1 | NumDef$ +1 | SpellDescription$ CARDNAME gets +1/+1 until end of turn. -T:Mode$ TapsForMana | ValidCard$ Swamp.YouCtrl | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever you tap a Swamp for mana, add an additional {B}. -SVar:TrigMana:DB$Mana | Produced$ B | Amount$ 1 -SVar:Picture:http://www.wizards.com/global/images/magic/general/nirkana_revenant.jpg +T:Mode$ TapsForMana | ValidCard$ Swamp | Activator$ You | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever you tap a Swamp for mana, add an additional {B}. +SVar:TrigMana:DB$ Mana | Produced$ B | Amount$ 1 Oracle:Whenever you tap a Swamp for mana, add an additional {B}.\n{B}: Nirkana Revenant gets +1/+1 until end of turn. diff --git a/forge-gui/res/cardsfolder/n/nissa_who_shakes_the_world.txt b/forge-gui/res/cardsfolder/n/nissa_who_shakes_the_world.txt index 6db26539168..d0d28f708dc 100644 --- a/forge-gui/res/cardsfolder/n/nissa_who_shakes_the_world.txt +++ b/forge-gui/res/cardsfolder/n/nissa_who_shakes_the_world.txt @@ -2,8 +2,8 @@ Name:Nissa, Who Shakes the World ManaCost:3 G G Types:Legendary Planeswalker Nissa Loyalty:5 -T:Mode$ TapsForMana | ValidCard$ Forest.YouCtrl | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever you tap a Forest for mana, add an additional {G}. -SVar:TrigMana:DB$Mana | Produced$ G | Amount$ 1 +T:Mode$ TapsForMana | ValidCard$ Forest | Activator$ You | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever you tap a Forest for mana, add an additional {G}. +SVar:TrigMana:DB$ Mana | Produced$ G | Amount$ 1 A:AB$ PutCounter | Cost$ AddCounter<1/LOYALTY> | Planeswalker$ True | CounterType$ P1P1 | CounterNum$ 3 | ValidTgts$ Land.nonCreature+YouCtrl | TargetMin$ 0 | TargetMax$ 1 | TgtPrompt$ Select target noncreature land you control | SubAbility$ DBUntap | SpellDescription$ Put three +1/+1 counters on up to one target noncreature land you control. Untap it. It becomes a 0/0 Elemental creature with vigilance and haste that's still a land. SVar:DBUntap:DB$ Untap | Defined$ Targeted | SubAbility$ DBAnimate SVar:DBAnimate:DB$ Animate | Defined$ Targeted | Power$ 0 | Toughness$ 0 | Types$ Creature,Elemental | Keywords$ Vigilance & Haste | Permanent$ True diff --git a/forge-gui/res/cardsfolder/n/nyxbloom_ancient.txt b/forge-gui/res/cardsfolder/n/nyxbloom_ancient.txt index 97cab14f3f4..6186b559ecf 100755 --- a/forge-gui/res/cardsfolder/n/nyxbloom_ancient.txt +++ b/forge-gui/res/cardsfolder/n/nyxbloom_ancient.txt @@ -3,6 +3,6 @@ ManaCost:4 G G G Types:Enchantment Creature Elemental PT:5/5 K:Trample -R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Permanent.YouCtrl | ManaReplacement$ ProduceThrice | Description$ If you tap a permanent for mana, it produces three times as much of that mana instead. -SVar:ProduceThrice:C->C C C & R->R R R & B->B B B & U->U U U & G->G G G & W->W W W +R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Permanent.YouCtrl | ValidAbility$ Activated.hasTapCost | ReplaceWith$ ProduceThrice | Description$ If you tap a permanent for mana, it produces three times as much of that mana instead. +SVar:ProduceThrice:DB$ ReplaceMana | ReplaceAmount$ 3 Oracle:Trample\nIf you tap a permanent for mana, it produces three times as much of that mana instead. diff --git a/forge-gui/res/cardsfolder/o/overabundance.txt b/forge-gui/res/cardsfolder/o/overabundance.txt index fb5958be812..cf0c413dfa5 100644 --- a/forge-gui/res/cardsfolder/o/overabundance.txt +++ b/forge-gui/res/cardsfolder/o/overabundance.txt @@ -3,7 +3,6 @@ ManaCost:1 R G Types:Enchantment T:Mode$ TapsForMana | ValidCard$ Land | Execute$ TrigDmg | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever a player taps a land for mana, that player adds one mana of any type that land produced, and CARDNAME deals 1 damage to them. SVar:TrigDmg:DB$ DealDamage | Defined$ TriggeredCardController | NumDmg$ 1 | SubAbility$ DBMana -SVar:DBMana:DB$ ManaReflected | ColorOrType$ Type | Valid$ Defined.Triggered | ReflectProperty$ Produced | Defined$ TriggeredCardController +SVar:DBMana:DB$ ManaReflected | ColorOrType$ Type | ReflectProperty$ Produced | Defined$ TriggeredActivator AI:RemoveDeck:Random -SVar:Picture:http://www.wizards.com/global/images/magic/general/overabundance.jpg Oracle:Whenever a player taps a land for mana, that player adds one mana of any type that land produced, and Overabundance deals 1 damage to them. diff --git a/forge-gui/res/cardsfolder/p/pact_of_the_serpent.txt b/forge-gui/res/cardsfolder/p/pact_of_the_serpent.txt new file mode 100644 index 00000000000..5807eb000b0 --- /dev/null +++ b/forge-gui/res/cardsfolder/p/pact_of_the_serpent.txt @@ -0,0 +1,9 @@ +Name:Pact of the Serpent +ManaCost:1 B B +Types:Sorcery +A:SP$ ChooseType | Defined$ You | Type$ Creature | SubAbility$ DBDraw | StackDescription$ SpellDescription | SpellDescription$ Choose a creature type. Target player draws X cards and loses X life, where X is the number of creatures they control of the chosen type. +SVar:DBDraw:DB$ Draw | Defined$ Targeted | ValidTgts$ Player | TgtPrompt$ Choose target player | NumCards$ X | References$ X | SubAbility$ DBLoseLife | StackDescription$ None +SVar:DBLoseLife:DB$ LoseLife | Defined$ Targeted | LifeAmount$ X | References$ X | StackDescription$ None +SVar:X:TargetedPlayer$Valid Creature.YouCtrl+ChosenType +AI:RemoveDeck:All +Oracle:Choose a creature type. Target player draws X cards and loses X life, where X is the number of creatures they control of the chosen type. diff --git a/forge-gui/res/cardsfolder/p/pale_moon.txt b/forge-gui/res/cardsfolder/p/pale_moon.txt index c88cf4adfcf..5a618b3116c 100644 --- a/forge-gui/res/cardsfolder/p/pale_moon.txt +++ b/forge-gui/res/cardsfolder/p/pale_moon.txt @@ -2,9 +2,8 @@ Name:Pale Moon ManaCost:1 U Types:Instant A:SP$ Effect | Cost$ 1 U | ReplacementEffects$ ReplaceColorless | SVars$ ProduceColorless | SpellDescription$ Until end of turn, if a player taps a nonbasic land for mana, it produces colorless mana instead of any other type. -SVar:ReplaceColorless:Event$ ProduceMana | ActiveZones$ Command | ValidCard$ Land.nonBasic | ManaReplacement$ ProduceColorless | Description$ If a player taps a nonbasic land for mana, it produces colorless mana instead of any other type. -SVar:ProduceColorless:U->1 & B->1 & R->1 & G->1 & W->1 +SVar:ReplaceColorless:Event$ ProduceMana | ActiveZones$ Command | ValidCard$ Land.nonBasic | ValidAbility$ Activated.hasTapCost | ReplaceWith$ ProduceColorless | Description$ If a player taps a nonbasic land for mana, it produces colorless mana instead of any other type. +SVar:ProduceColorless:DB$ ReplaceMana | ReplaceType$ C AI:RemoveDeck:All AI:RemoveDeck:Random -SVar:Picture:http://www.wizards.com/global/images/magic/general/pale_moon.jpg Oracle:Until end of turn, if a player taps a nonbasic land for mana, it produces colorless mana instead of any other type. diff --git a/forge-gui/res/cardsfolder/p/pulse_of_llanowar.txt b/forge-gui/res/cardsfolder/p/pulse_of_llanowar.txt index 473470e27af..c669015798a 100644 --- a/forge-gui/res/cardsfolder/p/pulse_of_llanowar.txt +++ b/forge-gui/res/cardsfolder/p/pulse_of_llanowar.txt @@ -1,8 +1,8 @@ Name:Pulse of Llanowar ManaCost:3 G Types:Enchantment -R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Land.Basic+YouCtrl | ManaReplacement$ ProduceAny | Description$ If a basic land you control is tapped for mana, it produces mana of a color of your choice instead of any other type. -SVar:ProduceAny:C->Any & R->Any & B->Any & U->Any & G->Any & W->Any +R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Land.Basic+YouCtrl | ValidAbility$ Activated.hasTapCost | ReplaceWith$ ProduceAny | Description$ If a basic land you control is tapped for mana, it produces mana of a color of your choice instead of any other type. +SVar:ProduceAny:DB$ ReplaceMana | ReplaceType$ Any AI:RemoveDeck:All SVar:NonStackingEffect:True SVar:Picture:http://www.wizards.com/global/images/magic/general/pulse_of_llanowar.jpg diff --git a/forge-gui/res/cardsfolder/q/quarum_trench_gnomes.txt b/forge-gui/res/cardsfolder/q/quarum_trench_gnomes.txt index f7d1e9915fb..d2b951ce90d 100644 --- a/forge-gui/res/cardsfolder/q/quarum_trench_gnomes.txt +++ b/forge-gui/res/cardsfolder/q/quarum_trench_gnomes.txt @@ -2,9 +2,9 @@ Name:Quarum Trench Gnomes ManaCost:3 R Types:Creature Gnome PT:1/1 -A:AB$ Animate | Cost$ T | ValidTgts$ Plains | IsCurse$ True | TgtPrompt$ Choose target plains | Replacements$ QuarumReplacement | sVars$ QuarumProduce | Permanent$ True | StackDescription$ If target {c:Targeted} is tapped for mana, it produces colorless mana instead of white mana. (This effect lasts indefinitely.) | SpellDescription$ If target Plains is tapped for mana, it produces colorless mana instead of white mana. (This effect lasts indefinitely.) -SVar:QuarumReplacement:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Card.Plains+Self | ManaReplacement$ QuarumProduce | Description$ If CARDNAME is tapped for mana, it produces colorless mana instead of white mana. -SVar:QuarumProduce:W->1 +A:AB$ Effect | Cost$ T | ValidTgts$ Plains | IsCurse$ True | TgtPrompt$ Choose target plains | RememberObjects$ Targeted | ForgetOnMoved$ Battlefield | Duration$ Permanent | ReplacementEffects$ QuarumReplacement | SVars$ QuarumProduce | StackDescription$ If target {c:Targeted} is tapped for mana, it produces colorless mana instead of white mana. (This effect lasts indefinitely.) | SpellDescription$ If target Plains is tapped for mana, it produces colorless mana instead of white mana. (This effect lasts indefinitely.) +SVar:QuarumReplacement:Event$ ProduceMana | ValidCard$ Card.IsRemembered | ReplaceWith$ QuarumProduce | Description$ If this Land is tapped for mana, it produces colorless mana instead of white mana. +SVar:QuarumProduce:DB$ ReplaceMana | ReplaceColor$ C | ReplaceOnly$ W AI:RemoveDeck:All AI:RemoveDeck:Random SVar:Picture:http://www.wizards.com/global/images/magic/general/quarum_trench_gnomes.jpg diff --git a/forge-gui/res/cardsfolder/r/reality_twist.txt b/forge-gui/res/cardsfolder/r/reality_twist.txt index 66c104972ca..ff1cb58d421 100644 --- a/forge-gui/res/cardsfolder/r/reality_twist.txt +++ b/forge-gui/res/cardsfolder/r/reality_twist.txt @@ -3,16 +3,15 @@ ManaCost:U U U Types:Enchantment Text:If tapped for mana, Plains produce {R}, Swamps produce {G}, Mountains produce {W}, and Forests produce {B} instead of any other type. K:Cumulative upkeep:1 U U -R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Plains | ManaReplacement$ ProduceR | Secondary$ True | Description$ If tapped for mana, Plains produce R. -SVar:ProduceR:C->R & B->R & U->R & G->R & W->R -R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Swamp | ManaReplacement$ ProduceG | Secondary$ True | Description$ If tapped for mana, Swamps produce G. -SVar:ProduceG:C->G & B->G & U->G & R->G & W->G -R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Mountain | ManaReplacement$ ProduceW | Secondary$ True | Description$ If tapped for mana, Mountains produce U. -SVar:ProduceW:C->W & B->W & G->W & R->W & U->W -R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Forest | ManaReplacement$ ProduceB | Secondary$ True | Description$ If tapped for mana, Forests produce B. -SVar:ProduceB:C->B & G->B & U->B & R->B & W->B +R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Plains | ValidAbility$ Activated.hasTapCost | ReplaceWith$ ProduceR | Secondary$ True | Description$ If tapped for mana, Plains produce R. +SVar:ProduceR:DB$ ReplaceMana | ReplaceType$ R +R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Swamp | ValidAbility$ Activated.hasTapCost | ReplaceWith$ ProduceG | Secondary$ True | Description$ If tapped for mana, Swamps produce G. +SVar:ProduceG:DB$ ReplaceMana | ReplaceType$ G +R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Mountain | ValidAbility$ Activated.hasTapCost | ReplaceWith$ ProduceW | Secondary$ True | Description$ If tapped for mana, Mountains produce U. +SVar:ProduceW:DB$ ReplaceMana | ReplaceType$ W +R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Forest | ValidAbility$ Activated.hasTapCost | ReplaceWith$ ProduceB | Secondary$ True | Description$ If tapped for mana, Forests produce B. +SVar:ProduceB:DB$ ReplaceMana | ReplaceType$ B AI:RemoveDeck:All AI:RemoveDeck:Random SVar:NonStackingEffect:True -SVar:Picture:http://www.wizards.com/global/images/magic/general/reality_twist.jpg Oracle:Cumulative upkeep {1}{U}{U} (At the beginning of your upkeep, put an age counter on this permanent, then sacrifice it unless you pay its upkeep cost for each age counter on it.)\nIf tapped for mana, Plains produce {R}, Swamps produce {G}, Mountains produce {W}, and Forests produce {B} instead of any other type. diff --git a/forge-gui/res/cardsfolder/r/regal_behemoth.txt b/forge-gui/res/cardsfolder/r/regal_behemoth.txt index 17622a1a20b..0364bf56dda 100644 --- a/forge-gui/res/cardsfolder/r/regal_behemoth.txt +++ b/forge-gui/res/cardsfolder/r/regal_behemoth.txt @@ -5,7 +5,6 @@ PT:5/5 K:Trample T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigMonarch | TriggerDescription$ When CARDNAME enters the battlefield, you become the monarch. SVar:TrigMonarch:DB$ BecomeMonarch | Defined$ You -T:Mode$ TapsForMana | ValidCard$ Land.YouCtrl | CheckDefinedPlayer$ You.isMonarch | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever you tap a land for mana while you're the monarch, add an additional one mana of any color. +T:Mode$ TapsForMana | ValidCard$ Land | Activator$ You | CheckDefinedPlayer$ You.isMonarch | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever you tap a land for mana while you're the monarch, add an additional one mana of any color. SVar:TrigMana:DB$ Mana | Produced$ Combo Any | Amount$ 1 | AILogic$ MostProminentInComputerHand -SVar:Picture:http://www.wizards.com/global/images/magic/general/regal_behemoth.jpg -Oracle:Trample\nWhen Regal Behemoth enters the battlefield, you become the monarch.\nWhenever you tap a land for mana while you're the monarch, add an additional one mana of any color. \ No newline at end of file +Oracle:Trample\nWhen Regal Behemoth enters the battlefield, you become the monarch.\nWhenever you tap a land for mana while you're the monarch, add an additional one mana of any color. diff --git a/forge-gui/res/cardsfolder/r/reidane_god_of_the_worthy_valkmira_protectors_shield.txt b/forge-gui/res/cardsfolder/r/reidane_god_of_the_worthy_valkmira_protectors_shield.txt index 8f06d5959c5..cd9c97dbf3b 100644 --- a/forge-gui/res/cardsfolder/r/reidane_god_of_the_worthy_valkmira_protectors_shield.txt +++ b/forge-gui/res/cardsfolder/r/reidane_god_of_the_worthy_valkmira_protectors_shield.txt @@ -16,6 +16,6 @@ Name:Valkmira, Protector's Shield ManaCost:3 W Types:Legendary Artifact S:Mode$ PreventDamage | Target$ You,Permanent.YouCtrl | ValidSource$ Card.OppCtrl | Amount$ 1 | Description$ If a source an opponent controls would deal damage to you or a permanent you control, prevent 1 of that damage. -T:Mode$ BecomesTarget | ValidSource$ Card.OppCtrl | ValidTarget$ You | TriggerZones$ Battlefield | Execute$ TrigCounter | TriggerDescription$ Whenever you become the target of a spell or ability an opponent controls, counter that spell or ability unless its controller pays {1}. +T:Mode$ BecomesTarget | ValidSource$ Card.OppCtrl | ValidTarget$ You,Permanent.YouCtrl+Other | TriggerZones$ Battlefield | Execute$ TrigCounter | TriggerDescription$ Whenever you or another permanent you control becomes the target of a spell or ability an opponent controls, counter that spell or ability unless its controller pays {1}. SVar:TrigCounter:DB$ Counter | Defined$ TriggeredSourceSA | UnlessCost$ 1 | UnlessPayer$ TriggeredSourceSAController -Oracle:If a source an opponent controls would deal damage to you or a permanent you control, prevent 1 of that damage.\nWhenever you or a permanent you control becomes the target of a spell or ability an opponent controls, counter that spell unless its controller pays {1}. \ No newline at end of file +Oracle:If a source an opponent controls would deal damage to you or a permanent you control, prevent 1 of that damage.\nWhenever you or another permanent you control becomes the target of a spell or ability an opponent controls, counter that spell unless its controller pays {1}. diff --git a/forge-gui/res/cardsfolder/r/ritual_of_subdual.txt b/forge-gui/res/cardsfolder/r/ritual_of_subdual.txt index 87972a95860..6b2e6d3bb38 100644 --- a/forge-gui/res/cardsfolder/r/ritual_of_subdual.txt +++ b/forge-gui/res/cardsfolder/r/ritual_of_subdual.txt @@ -2,10 +2,9 @@ Name:Ritual of Subdual ManaCost:4 G G Types:Enchantment K:Cumulative upkeep:2 -R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Land | ManaReplacement$ ProduceColorless | Description$ If a land is tapped for mana, it produces colorless mana instead of any other type. -SVar:ProduceColorless:B->1 & U->1 & R->1 & G->1 & W->1 +R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Land | ValidAbility$ Activated.hasTapCost | ReplaceWith$ ProduceColorless | Description$ If a land is tapped for mana, it produces colorless mana instead of any other type. +SVar:ProduceColorless:DB$ ReplaceMana | ReplaceType$ C AI:RemoveDeck:All AI:RemoveDeck:Random SVar:NonStackingEffect:True -SVar:Picture:http://www.wizards.com/global/images/magic/general/ritual_of_subdual.jpg Oracle:Cumulative upkeep {2} (At the beginning of your upkeep, put an age counter on this permanent, then sacrifice it unless you pay its upkeep cost for each age counter on it.)\nIf a land is tapped for mana, it produces colorless mana instead of any other type. diff --git a/forge-gui/res/cardsfolder/r/river_of_tears.txt b/forge-gui/res/cardsfolder/r/river_of_tears.txt index 867be205b00..afa80fc920b 100644 --- a/forge-gui/res/cardsfolder/r/river_of_tears.txt +++ b/forge-gui/res/cardsfolder/r/river_of_tears.txt @@ -1,8 +1,9 @@ Name:River of Tears ManaCost:no cost Types:Land -A:AB$ Mana | Cost$ T | Produced$ U | ReplaceIfLandPlayed$ B | SpellDescription$ Add {U}. If you played a land this turn, add {B} instead. +A:AB$ Mana | Cost$ T | Produced$ U | ConditionCheckSVar$ X | ConditionSVarCompare$ EQ0 | References$ X | SubAbility$ ManaB | SpellDescription$ Add {U}. If you played a land this turn, add {B} instead. +SVar:ManaB:DB$ Mana | Produced$ B | ConditionCheckSVar$ X | ConditionSVarCompare$ GE1 | References$ X AI:RemoveDeck:Random DeckHints:Color$Blue|Black -SVar:Picture:http://www.wizards.com/global/images/magic/general/river_of_tears.jpg +SVar:X:Count$YourLandsPlayed Oracle:{T}: Add {U}. If you played a land this turn, add {B} instead. diff --git a/forge-gui/res/cardsfolder/s/sasaya_orochi_ascendant_sasayas_essence.txt b/forge-gui/res/cardsfolder/s/sasaya_orochi_ascendant_sasayas_essence.txt index 7a5bec76bc6..e40e8095955 100644 --- a/forge-gui/res/cardsfolder/s/sasaya_orochi_ascendant_sasayas_essence.txt +++ b/forge-gui/res/cardsfolder/s/sasaya_orochi_ascendant_sasayas_essence.txt @@ -5,7 +5,6 @@ PT:2/3 A:AB$ SetState | Cost$ Reveal<1/Hand> | Defined$ Self | Mode$ Flip | ConditionCheckSVar$ CheckHandLand | ConditionSVarCompare$ GE7 | AILogic$ CheckCondition | References$ CheckHandLand | SpellDescription$ If you have seven or more land cards in your hand, flip CARDNAME. SVar:CheckHandLand:Count$ValidHand Land.YouCtrl AlternateMode:Flip -SVar:Picture:http://www.wizards.com/global/images/magic/general/sasaya_orochi_ascendant.jpg Oracle:Reveal your hand: If you have seven or more land cards in your hand, flip Sasaya, Orochi Ascendant. ALTERNATE @@ -17,7 +16,6 @@ Types:Legendary Enchantment T:Mode$ TapsForMana | ValidCard$ Land.YouCtrl | TriggerZones$ Battlefield | Execute$ TrigMana | Static$ True | TriggerDescription$ Whenever a land you control is tapped for mana, for each other land you control with the same name, add one mana of any type that land produced. SVar:TrigMana:DB$ Pump | RememberObjects$ TriggeredCard | SubAbility$ DBRepeat SVar:DBRepeat:DB$ RepeatEach | UseImprinted$ True | RepeatCards$ Land.YouCtrl+IsNotRemembered+sharesNameWith Remembered | RepeatSubAbility$ DBManaReflect | SubAbility$ DBCleanup -SVar:DBManaReflect:DB$ ManaReflected | ColorOrType$ Type | Valid$ Defined.Imprinted | ReflectProperty$ Produced | Defined$ You +SVar:DBManaReflect:DB$ ManaReflected | ColorOrType$ Type | ReflectProperty$ Produced | Defined$ You SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True -SVar:Picture:http://www.wizards.com/global/images/magic/general/sasayas_essence.jpg Oracle:Whenever a land you control is tapped for mana, for each other land you control with the same name, add one mana of any type that land produced. diff --git a/forge-gui/res/cardsfolder/s/savage_firecat.txt b/forge-gui/res/cardsfolder/s/savage_firecat.txt index 627ae9a3575..55bf0687eba 100644 --- a/forge-gui/res/cardsfolder/s/savage_firecat.txt +++ b/forge-gui/res/cardsfolder/s/savage_firecat.txt @@ -4,8 +4,7 @@ Types:Creature Elemental Cat PT:0/0 K:etbCounter:P1P1:7 K:Trample -T:Mode$ TapsForMana | ValidCard$ Land.YouCtrl | Execute$ TrigRemoveCounter | TriggerZones$ Battlefield | TriggerDescription$ Whenever you tap a land for mana, remove a +1/+1 counter from CARDNAME. -SVar:TrigRemoveCounter:DB$RemoveCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1 +T:Mode$ TapsForMana | ValidCard$ Land | Activator$ You | Execute$ TrigRemoveCounter | TriggerZones$ Battlefield | TriggerDescription$ Whenever you tap a land for mana, remove a +1/+1 counter from CARDNAME. +SVar:TrigRemoveCounter:DB$ RemoveCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1 AI:RemoveDeck:All -SVar:Picture:http://www.wizards.com/global/images/magic/general/savage_firecat.jpg Oracle:Trample\nSavage Firecat enters the battlefield with seven +1/+1 counters on it.\nWhenever you tap a land for mana, remove a +1/+1 counter from Savage Firecat. diff --git a/forge-gui/res/cardsfolder/s/selesnya_loft_gardens.txt b/forge-gui/res/cardsfolder/s/selesnya_loft_gardens.txt index e3d6c9c4808..48829905e25 100644 --- a/forge-gui/res/cardsfolder/s/selesnya_loft_gardens.txt +++ b/forge-gui/res/cardsfolder/s/selesnya_loft_gardens.txt @@ -9,8 +9,7 @@ SVar:Y:ReplaceCount$TokenNum/Twice SVar:Z:ReplaceCount$CounterNum/Twice T:Mode$ PlanarDice | Result$ Chaos | TriggerZones$ Command | Execute$ RolledChaos | TriggerDescription$ Whenever you roll {CHAOS}, until end of turn, whenever you tap a land for mana, add one mana of any type that land produced. SVar:RolledChaos:DB$ Effect | AILogic$ Always | Triggers$ TrigTapForMana | SVars$ TrigMana -SVar:TrigTapForMana:Mode$ TapsForMana | TriggerZones$ Command | ValidCard$ Land.YouCtrl | Execute$ TrigMana | Static$ True | TriggerDescription$ Whenever you tap a land for mana, add one mana of any type that land produced. -SVar:TrigMana:DB$ ManaReflected | ColorOrType$ Type | Valid$ Defined.Triggered | ReflectProperty$ Produced | Defined$ TriggeredPlayer -SVar:Picture:http://www.wizards.com/global/images/magic/general/selesnya_loft_gardens.jpg +SVar:TrigTapForMana:Mode$ TapsForMana | TriggerZones$ Command | ValidCard$ Land | Activator$ You | Execute$ TrigMana | Static$ True | TriggerDescription$ Whenever you tap a land for mana, add one mana of any type that land produced. +SVar:TrigMana:DB$ ManaReflected | ColorOrType$ Type | ReflectProperty$ Produced | Defined$ You SVar:AIRollPlanarDieParams:Mode$ Always | MinTurn$ 1 | RollInMain1$ True Oracle:If an effect would create one or more tokens, it creates twice that many of those tokens instead.\nIf an effect would put one or more counters on a permanent, it puts twice that many of those counters on that permanent instead.\nWhenever you roll {CHAOS}, until end of turn, whenever you tap a land for mana, add one mana of any type that land produced. diff --git a/forge-gui/res/cardsfolder/s/serpents_soul_jar.txt b/forge-gui/res/cardsfolder/s/serpents_soul_jar.txt new file mode 100644 index 00000000000..81a19369fa4 --- /dev/null +++ b/forge-gui/res/cardsfolder/s/serpents_soul_jar.txt @@ -0,0 +1,9 @@ +Name:Serpent's Soul-Jar +ManaCost:2 B +Types:Artifact +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Elf.YouCtrl | TriggerZones$ Battlefield | Execute$ TrigChange | TriggerDescription$ Whenever an Elf you control dies, exile it. +SVar:TrigChange:DB$ ChangeZone | Defined$ TriggeredCard | Origin$ Graveyard | Destination$ Exile +A:AB$ Effect | Cost$ T PayLife<2> | StaticAbilities$ MayCast | SpellDescription$ Until end of turn, you may cast a creature spell from among cards exiled with Serpent’s Soul-Jar. +SVar:MayCast:Mode$ Continuous | Affected$ Creature.ExiledWithEffectSource | MayPlay$ True | AffectedZone$ Exile | MayPlayLimit$ 1 | Description$ Until end of turn, you may cast a creature spell from among cards exiled with Serpent’s Soul-Jar. +DeckNeeds:Type$Elf +Oracle:Whenever an Elf you control dies, exile it.\n{T}, Pay 2 life: Until end of turn, you may cast a creature spell from among cards exiled with Serpent’s Soul-Jar. diff --git a/forge-gui/res/cardsfolder/s/sisay.txt b/forge-gui/res/cardsfolder/s/sisay.txt index cb53c690a27..0c33d5fc7e5 100644 --- a/forge-gui/res/cardsfolder/s/sisay.txt +++ b/forge-gui/res/cardsfolder/s/sisay.txt @@ -2,8 +2,7 @@ Name:Sisay ManaCost:no cost Types:Vanguard HandLifeModifier:-2/-3 -T:Mode$ TapsForMana | TriggerZones$ Command | ValidCard$ Land.YouCtrl | Execute$ TrigMana | Static$ True | TriggerDescription$ Whenever you tap a land for mana, add one mana of any type that land produced. -SVar:TrigMana:DB$ ManaReflected | ColorOrType$ Type | Valid$ Defined.Triggered | ReflectProperty$ Produced | Defined$ TriggeredPlayer +T:Mode$ TapsForMana | TriggerZones$ Command | ValidCard$ Land | Activator$ You | Execute$ TrigMana | Static$ True | TriggerDescription$ Whenever you tap a land for mana, add one mana of any type that land produced. +SVar:TrigMana:DB$ ManaReflected | ColorOrType$ Type | ReflectProperty$ Produced | Defined$ You AI:RemoveDeck:All -SVar:Picture:https://downloads.cardforge.org/images/cards/VAN/Sisay.full.jpg Oracle:Hand -2, life -3\nWhenever you tap a land for mana, add one mana of any type that land produced. diff --git a/forge-gui/res/cardsfolder/s/sunbirds_invocation.txt b/forge-gui/res/cardsfolder/s/sunbirds_invocation.txt index 654fcea8d13..cb6432794af 100644 --- a/forge-gui/res/cardsfolder/s/sunbirds_invocation.txt +++ b/forge-gui/res/cardsfolder/s/sunbirds_invocation.txt @@ -1,11 +1,11 @@ Name:Sunbird's Invocation ManaCost:5 R Types:Enchantment -T:Mode$ SpellCast | ValidCard$ Card.wasCastFromHand | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigDig | TriggerDescription$ Whenever you cast a spell from your hand, reveal the top X cards of your library, where X is that spell's converted mana cost. You may cast a card revealed this way with converted mana cost X or less without paying its mana cost. Put the rest on the bottom of your library in a random order. +T:Mode$ SpellCast | ValidCard$ Card.wasCastFromHand | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigDig | TriggerDescription$ Whenever you cast a spell from your hand, reveal the top X cards of your library, where X is that spell's converted mana cost. You may cast a spell revealed this way with converted mana cost X or less without paying its mana cost. Put the rest on the bottom of your library in a random order. SVar:TrigDig:DB$ PeekAndReveal | Defined$ You | PeekAmount$ X | RememberRevealed$ True | References$ X | SubAbility$ DBPlay -SVar:DBPlay:DB$ Play | ValidZone$ Library | Valid$ Card.nonLand+IsRemembered+cmcLEX | WithoutManaCost$ True | Optional$ True | Amount$ 1 | ShowCards$ Card.IsRemembered | ForgetTargetRemembered$ True | References$ X | SubAbility$ DBRestRandomOrder +SVar:DBPlay:DB$ Play | ValidZone$ Library | Valid$ Card.nonLand+IsRemembered | ValidSA$ Spell.cmcLEX | WithoutManaCost$ True | Optional$ True | Amount$ 1 | ShowCards$ Card.IsRemembered | ForgetTargetRemembered$ True | References$ X | SubAbility$ DBRestRandomOrder SVar:DBRestRandomOrder:DB$ ChangeZone | Defined$ Remembered | AtRandom$ True | Origin$ Library | Destination$ Library | LibraryPosition$ -1 | Shuffle$ False | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:X:TriggerCount$CastSACMC SVar:Picture:http://media.wizards.com/2017/xln/en_wZkZ5jvNGp.png -Oracle:Whenever you cast a spell from your hand, reveal the top X cards of your library, where X is that spell's converted mana cost. You may cast a card revealed this way with converted mana cost X or less without paying its mana cost. Put the rest on the bottom of your library in a random order. +Oracle:Whenever you cast a spell from your hand, reveal the top X cards of your library, where X is that spell's converted mana cost. You may cast a spell revealed this way with converted mana cost X or less without paying its mana cost. Put the rest on the bottom of your library in a random order. diff --git a/forge-gui/res/cardsfolder/u/urzas_mine.txt b/forge-gui/res/cardsfolder/u/urzas_mine.txt index dac61afc558..4450f4c8173 100644 --- a/forge-gui/res/cardsfolder/u/urzas_mine.txt +++ b/forge-gui/res/cardsfolder/u/urzas_mine.txt @@ -1,7 +1,7 @@ Name:Urza's Mine ManaCost:no cost Types:Land Urza's Mine -A:AB$ Mana | Cost$ T | Produced$ C | Bonus$ UrzaLands | BonusProduced$ 1 | SpellDescription$ Add {C}. If you control an Urza's Power-Plant and an Urza's Tower, add {C}{C} instead. +A:AB$ Mana | Cost$ T | Produced$ C | Amount$ UrzaAmount | References$ UrzaAmount | SpellDescription$ Add {C}. If you control an Urza's Power-Plant and an Urza's Tower, add {C}{C} instead. +SVar:UrzaAmount:Count$UrzaLands.2.1 AI:RemoveDeck:Random -SVar:Picture:http://www.wizards.com/global/images/magic/general/urzas_mine.jpg Oracle:{T}: Add {C}. If you control an Urza's Power-Plant and an Urza's Tower, add {C}{C} instead. diff --git a/forge-gui/res/cardsfolder/u/urzas_power_plant.txt b/forge-gui/res/cardsfolder/u/urzas_power_plant.txt index 0cf4980b9a1..78bd2068f0c 100644 --- a/forge-gui/res/cardsfolder/u/urzas_power_plant.txt +++ b/forge-gui/res/cardsfolder/u/urzas_power_plant.txt @@ -1,7 +1,7 @@ Name:Urza's Power Plant ManaCost:no cost Types:Land Urza's Power-Plant -A:AB$ Mana | Cost$ T | Produced$ C | Bonus$ UrzaLands | BonusProduced$ 1 | SpellDescription$ Add {C}. If you control an Urza's Mine and an Urza's Tower, add {C}{C} instead. +A:AB$ Mana | Cost$ T | Produced$ C | Amount$ UrzaAmount | References$ UrzaAmount | SpellDescription$ Add {C}. If you control an Urza's Mine and an Urza's Tower, add {C}{C} instead. +SVar:UrzaAmount:Count$UrzaLands.2.1 AI:RemoveDeck:Random -SVar:Picture:http://www.wizards.com/global/images/magic/general/urzas_power_plant.jpg Oracle:{T}: Add {C}. If you control an Urza's Mine and an Urza's Tower, add {C}{C} instead. diff --git a/forge-gui/res/cardsfolder/u/urzas_tower.txt b/forge-gui/res/cardsfolder/u/urzas_tower.txt index c6beb6130e6..52257b4be12 100644 --- a/forge-gui/res/cardsfolder/u/urzas_tower.txt +++ b/forge-gui/res/cardsfolder/u/urzas_tower.txt @@ -1,7 +1,7 @@ Name:Urza's Tower ManaCost:no cost Types:Land Urza's Tower -A:AB$ Mana | Cost$ T | Produced$ C | Bonus$ UrzaLands | BonusProduced$ 2 | SpellDescription$ Add {C}. If you control an Urza's Mine and an Urza's Power-Plant, add {C}{C}{C} instead. +A:AB$ Mana | Cost$ T | Produced$ C | Amount$ UrzaAmount | References$ UrzaAmount | SpellDescription$ Add {C}. If you control an Urza's Mine and an Urza's Power-Plant, add {C}{C}{C} instead. +SVar:UrzaAmount:Count$UrzaLands.3.1 AI:RemoveDeck:Random -SVar:Picture:http://www.wizards.com/global/images/magic/general/urzas_tower.jpg Oracle:{T}: Add {C}. If you control an Urza's Mine and an Urza's Power-Plant, add {C}{C}{C} instead. diff --git a/forge-gui/res/cardsfolder/upcoming/piracy.txt b/forge-gui/res/cardsfolder/upcoming/piracy.txt new file mode 100644 index 00000000000..96aeb5ea258 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/piracy.txt @@ -0,0 +1,6 @@ +Name:Piracy +ManaCost:U U +Types:Sorcery +A:SP$ Effect | Cost$ U U | StaticAbilities$ STPiracy | AINoRecursiveCheck$ True | SpellDescription$ Until end of turn, you may tap lands you don’t control for mana. Spend this mana only to cast spells. +SVar:STPiracy:Mode$ Continuous | Affected$ You | AddKeyword$ Piracy | Description$ Until end of turn, you may tap lands you don’t control for mana. Spend this mana only to cast spells. +Oracle:Until end of turn, you may tap lands you don’t control for mana. Spend this mana only to cast spells. diff --git a/forge-gui/res/cardsfolder/v/vorinclex_voice_of_hunger.txt b/forge-gui/res/cardsfolder/v/vorinclex_voice_of_hunger.txt index 68b627655f7..2e24c03e087 100644 --- a/forge-gui/res/cardsfolder/v/vorinclex_voice_of_hunger.txt +++ b/forge-gui/res/cardsfolder/v/vorinclex_voice_of_hunger.txt @@ -3,8 +3,8 @@ ManaCost:6 G G Types:Legendary Creature Praetor PT:7/6 K:Trample -T:Mode$ TapsForMana | ValidCard$ Land.YouCtrl | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever you tap a land for mana, add one mana of any type that land produced. -SVar:TrigMana:DB$ ManaReflected | ColorOrType$ Type | Valid$ Defined.Triggered | ReflectProperty$ Produced | Defined$ You +T:Mode$ TapsForMana | ValidCard$ Land | Activator$ You | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever you tap a land for mana, add one mana of any type that land produced. +SVar:TrigMana:DB$ ManaReflected | ColorOrType$ Type | ReflectProperty$ Produced | Defined$ You T:Mode$ TapsForMana | ValidCard$ Land.OppCtrl | Execute$ TrigPump | TriggerZones$ Battlefield | TriggerDescription$ Whenever an opponent taps a land for mana, that land doesn't untap during its controller's next untap step. SVar:TrigPump:DB$ Pump | Defined$ TriggeredCard | Permanent$ True | KW$ HIDDEN This card doesn't untap during your next untap step. Oracle:Trample\nWhenever you tap a land for mana, add one mana of any type that land produced.\nWhenever an opponent taps a land for mana, that land doesn't untap during its controller's next untap step. diff --git a/forge-gui/res/cardsfolder/w/winters_night.txt b/forge-gui/res/cardsfolder/w/winters_night.txt index f319bd8a6d7..71885262d3b 100644 --- a/forge-gui/res/cardsfolder/w/winters_night.txt +++ b/forge-gui/res/cardsfolder/w/winters_night.txt @@ -2,7 +2,7 @@ Name:Winter's Night ManaCost:R G W Types:World Enchantment T:Mode$ TapsForMana | ValidCard$ Land.Snow | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever a player taps a snow land for mana, that player adds one mana of any type that land produced. That land doesn't untap during its controller's next untap step. -SVar:TrigMana:DB$ ManaReflected | ColorOrType$ Type | Valid$ Defined.Triggered | ReflectProperty$ Produced | Defined$ TriggeredPlayer | SubAbility$ DBPump +SVar:TrigMana:DB$ ManaReflected | ColorOrType$ Type | ReflectProperty$ Produced | Defined$ TriggeredActivator | SubAbility$ DBPump SVar:DBPump:DB$ Pump | Defined$ TriggeredCard | Permanent$ True | KW$ HIDDEN This card doesn't untap during your next untap step. AI:RemoveDeck:All AI:RemoveDeck:Random diff --git a/forge-gui/res/cardsfolder/z/zendikar_resurgent.txt b/forge-gui/res/cardsfolder/z/zendikar_resurgent.txt index 40b8705d514..504ef0569b5 100644 --- a/forge-gui/res/cardsfolder/z/zendikar_resurgent.txt +++ b/forge-gui/res/cardsfolder/z/zendikar_resurgent.txt @@ -1,9 +1,8 @@ Name:Zendikar Resurgent ManaCost:5 G G Types:Enchantment -T:Mode$ TapsForMana | ValidCard$ Land.YouCtrl | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever you tap a land for mana, add one mana of any type that land produced. (The types of mana are white, blue, black, red, green, and colorless.) -SVar:TrigMana:DB$ManaReflected | ColorOrType$ Type | Valid$ Defined.Triggered | ReflectProperty$ Produced | Defined$ TriggeredPlayer +T:Mode$ TapsForMana | ValidCard$ Land | Activator$ You | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever you tap a land for mana, add one mana of any type that land produced. (The types of mana are white, blue, black, red, green, and colorless.) +SVar:TrigMana:DB$ ManaReflected | ColorOrType$ Type | ReflectProperty$ Produced | Defined$ You T:Mode$ SpellCast | ValidCard$ Creature | ValidActivatingPlayer$ You | Execute$ TrigDraw | TriggerZones$ Battlefield | TriggerDescription$ Whenever you cast a creature spell, draw a card. -SVar:TrigDraw:DB$Draw | Defined$ You | NumCards$ 1 -SVar:Picture:http://www.wizards.com/global/images/magic/general/zendikar_resurgent.jpg +SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ 1 Oracle:Whenever you tap a land for mana, add one mana of any type that land produced. (The types of mana are white, blue, black, red, green, and colorless.)\nWhenever you cast a creature spell, draw a card. diff --git a/forge-gui/res/cardsfolder/z/zhur_taa_ancient.txt b/forge-gui/res/cardsfolder/z/zhur_taa_ancient.txt index 9f5cad19ae7..ae4375d973e 100644 --- a/forge-gui/res/cardsfolder/z/zhur_taa_ancient.txt +++ b/forge-gui/res/cardsfolder/z/zhur_taa_ancient.txt @@ -3,6 +3,5 @@ ManaCost:3 R G Types:Creature Beast PT:7/5 T:Mode$ TapsForMana | ValidCard$ Land | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever a player taps a land for mana, that player adds one mana of any type that land produced. -SVar:TrigMana:DB$ ManaReflected | ColorOrType$ Type | Valid$ Defined.Triggered | ReflectProperty$ Produced | Defined$ TriggeredPlayer -SVar:Picture:http://www.wizards.com/global/images/magic/general/zhur_taa_ancient.jpg +SVar:TrigMana:DB$ ManaReflected | ColorOrType$ Type | ReflectProperty$ Produced | Defined$ TriggeredActivator Oracle:Whenever a player taps a land for mana, that player adds one mana of any type that land produced. diff --git a/forge-gui/res/editions/Kaldheim Commander.txt b/forge-gui/res/editions/Kaldheim Commander.txt index 84eade4e0a9..8816ddb1c34 100644 --- a/forge-gui/res/editions/Kaldheim Commander.txt +++ b/forge-gui/res/editions/Kaldheim Commander.txt @@ -61,7 +61,7 @@ Type=Other 54 R Beast Whisperer 55 R Cultivator of Blades 56 R Dwynen, Gilt-Leaf Daen -57 R Elvish Arcdruid +57 R Elvish Archdruid 58 C Elvish Mystic 59 U Elvish Promenade 60 C Elvish Rejuvenator @@ -75,7 +75,7 @@ Type=Other 68 R Marwyn, the Nurturer 69 R Masked Admirers 70 U Nullmage Shepherd -71 U Numa, Joraga Chieftrain +71 U Numa, Joraga Chieftain 72 U Reclamation Sage 73 R Rhys the Exiled 74 C Springbloom Druid diff --git a/forge-gui/res/editions/Secret Lair Drop Series.txt b/forge-gui/res/editions/Secret Lair Drop Series.txt index af64ab0ee50..cb105639f07 100644 --- a/forge-gui/res/editions/Secret Lair Drop Series.txt +++ b/forge-gui/res/editions/Secret Lair Drop Series.txt @@ -109,6 +109,10 @@ Type=Other 112 R Fatal Push 113 R Anger of the Gods 114 R Explore +115 R Glen Elendra Archmage +116 R Mistbind Clique +117 R Spellstutter Sprite +118 M Vendilion Clique 119 R Swamp 120 R Sower of Temptation 121 R Damnation @@ -147,6 +151,27 @@ Type=Other 203 R Commander's Sphere 204 R Darksteel Ingot 205 R Gilded Lotus +214 M Heliod, Sun-Crowned +215 R Goblin Rabblemaster +216 R Monastery Swiftspear +217 R Boros Charm +218 M Gisela, Blade of Goldnight +220 M Frost Titan +221 M Primeval Titan +222 M Uro, Titan of Nature's Wrath +223 M Grave Titan +224 M Inferno Titan +225 M Kroxa, Titan of Death's Hunger +239 R Plains +240 R Island +241 R Swamp +242 R Mountain +243 R Forest +244 R Shalai, Voice of Plenty +245 R Ponder +246 R Cultivate +247 M Kaya, Ghost Assassin +248 M Teferi, Hero of Dominaria 501 R Karn, the Great Creator 502 R Ugin, the Ineffable 503 M Gideon Blackblade @@ -193,6 +218,7 @@ b_1_1_faerie_rogue_flying b_1_1_faerie_rogue_flying b_1_1_faerie_rogue_flying g_1_1_squirrel +r_1_1_goblin w_1_1_cat_lifelink w_1_1_cat_lifelink walker diff --git a/forge-gui/res/languages/de-DE.properties b/forge-gui/res/languages/de-DE.properties index f1a256b9a09..9f0f4486aac 100644 --- a/forge-gui/res/languages/de-DE.properties +++ b/forge-gui/res/languages/de-DE.properties @@ -206,6 +206,7 @@ lblListImageData=Prüfe auf von Forge nicht unterstütze Karten und fehlende Kar btnImportPictures=Daten importieren btnHowToPlay=Wie man spielt btnDownloadPrices=Kartenpreise herunterladen +btnDownloadSkins=Skins herunterladen btnLicensing=Lizenzhinweis lblCheckForUpdates=Prüfe Server auf eine aktuellere Version lblDownloadPics=Lädt ein Standardbild pro Karte. @@ -214,6 +215,7 @@ lblDownloadSetPics=Lädt alle Bilder pro Karte. Eines für jedes Set, in welchem lblDownloadQuestImages=Lädt die Bilder für den Quest-Modus. lblDownloadAchievementImages=Lädt die Bilder zu den möglichen Erfolgen. Verschönert die Trophäensammlung. lblDownloadPrices=Lädt aktuelle Kartenpreise für den Kartenladen im Spiel. +lblDownloadSkins=Lade verfügbare Skins herunter, um die Erscheinung von Forge anzupassen. lblYourVersionOfJavaIsTooOld=Deine Java-Version ist leider zu alt. lblPleaseUpdateToTheLatestVersionOfJava=Bitte aktualisiere auf die neueste Java-Version. lblYoureRunning=Du nutzt diff --git a/forge-gui/res/languages/en-US.properties b/forge-gui/res/languages/en-US.properties index 1b4b7103f69..a265bb4bfc1 100644 --- a/forge-gui/res/languages/en-US.properties +++ b/forge-gui/res/languages/en-US.properties @@ -206,6 +206,7 @@ lblListImageData=Audit cards not implemented by Forge and missing card images btnImportPictures=Import Data btnHowToPlay=How To Play btnDownloadPrices=Download Card Prices +btnDownloadSkins=Download Skins btnLicensing=License Details lblCheckForUpdates=Check Forge server to see if there''s a more recent release lblDownloadPics=Download default card picture for each card. @@ -214,6 +215,7 @@ lblDownloadSetPics=Download all pictures of each card (one for each set the card lblDownloadQuestImages=Download tokens and icons used in Quest mode. lblDownloadAchievementImages=Download achievement images to really make your trophies stand out. lblDownloadPrices=Download up-to-date price list for in-game card shops. +lblDownloadSkins=Download available skins used in customizing forge appearance. lblYourVersionOfJavaIsTooOld=Your version of Java is too old to use the content downloaders. lblPleaseUpdateToTheLatestVersionOfJava=Please update to the latest version of Java lblYoureRunning=You''re running diff --git a/forge-gui/res/languages/es-ES.properties b/forge-gui/res/languages/es-ES.properties index a93c31c04bc..73e4fa6cf10 100644 --- a/forge-gui/res/languages/es-ES.properties +++ b/forge-gui/res/languages/es-ES.properties @@ -206,6 +206,7 @@ lblListImageData=Audita cartas no implementadas por Forge e imágenes de cartas btnImportPictures=Importar Datos btnHowToPlay=Cómo jugar (Inglés) btnDownloadPrices=Descargar precios de las cartas +btnDownloadSkins=Download Skins btnLicensing=Detalles de la licencia lblCheckForUpdates=Comprueba si en el servidor de Forge existe alguna versión más reciente lblDownloadPics=Descarga una única carta de la última edición donde apareció. @@ -214,6 +215,7 @@ lblDownloadSetPics=Descarga las cartas de todas las ediciones (una por cada edic lblDownloadQuestImages=Descarga fichas e íconos utilizados en el modo Aventura. lblDownloadAchievementImages=Descarga las imágenes de los trofeos para que destaquen. lblDownloadPrices=Descarga la lista de precios actualizada para la tienda de cartas del juego. +lblDownloadSkins=Download available skins used in customizing forge appearance. lblYourVersionOfJavaIsTooOld=Tu versión de Java es demasiado antigua para usar los descargadores de contenido. lblPleaseUpdateToTheLatestVersionOfJava=Por favor, actualiza a la última versión de Java lblYoureRunning=Estás ejecutando diff --git a/forge-gui/res/languages/it-IT.properties b/forge-gui/res/languages/it-IT.properties index f9e8aed6cd0..024d0a829b3 100644 --- a/forge-gui/res/languages/it-IT.properties +++ b/forge-gui/res/languages/it-IT.properties @@ -206,6 +206,7 @@ lblListImageData=Carte di controllo non implementate da Forge e immagini di cart btnImportPictures=Importa dati btnHowToPlay=Come giocare btnDownloadPrices=Scarica i prezzi delle carte +btnDownloadSkins=Download Skins btnLicensing=Dettagli della licenza lblCheckForUpdates=Check Forge server to see if there''s a more recent release lblDownloadPics=Scarica l''immagine della carta predefinita per ogni carta. @@ -214,6 +215,7 @@ lblDownloadSetPics=Scarica tutte le immagini di ogni carta (una per ogni set in lblDownloadQuestImages=Scarica i token e le icone utilizzati nella modalità Quest. lblDownloadAchievementImages=Scarica le immagini degli obiettivi per far risaltare davvero i tuoi trofei. lblDownloadPrices=Scarica il listino aggiornato per i negozi di carte di gioco. +lblDownloadSkins=Download available skins used in customizing forge appearance. lblYourVersionOfJavaIsTooOld=La tua versione di Java è troppo vecchia per utilizzare i downloader di contenuti. lblPleaseUpdateToTheLatestVersionOfJava=Si prega di aggiornare all''ultima versione di Java lblYoureRunning=Stai correndo diff --git a/forge-gui/res/languages/zh-CN.properties b/forge-gui/res/languages/zh-CN.properties index 8c4a73a9d57..745726573f2 100644 --- a/forge-gui/res/languages/zh-CN.properties +++ b/forge-gui/res/languages/zh-CN.properties @@ -206,6 +206,7 @@ lblListImageData=统计Forge实现且缺少的图片的牌 btnImportPictures=导入数据 btnHowToPlay=如何玩 btnDownloadPrices=下载卡牌价格 +btnDownloadSkins=Download Skins btnLicensing=许可证详情 lblCheckForUpdates=查看是否有新的版本 lblDownloadPics=下载缺省牌的图片 @@ -214,6 +215,7 @@ lblDownloadSetPics=下载每张牌的图片(每张牌出现一次) lblDownloadQuestImages=下载冒险之旅里使用的衍生物与图标 lblDownloadAchievementImages=下载成就图片,让你的奖杯更引人注目。 lblDownloadPrices=下载卡牌商店最新的价格表 +lblDownloadSkins=Download available skins used in customizing forge appearance. lblYourVersionOfJavaIsTooOld=你的Java版本太旧无法开始下载内容 lblPleaseUpdateToTheLatestVersionOfJava=请更新到最新版本的JRE lblYoureRunning=你在运行 diff --git a/forge-gui/res/lists/skinsList.txt b/forge-gui/res/lists/skinsList.txt new file mode 100644 index 00000000000..6b7cefbb947 --- /dev/null +++ b/forge-gui/res/lists/skinsList.txt @@ -0,0 +1,119 @@ +https://downloads.cardforge.org/skins/arabian_nights/bg_match.jpg +https://downloads.cardforge.org/skins/arabian_nights/bg_splash.png +https://downloads.cardforge.org/skins/arabian_nights/bg_texture.jpg +https://downloads.cardforge.org/skins/arabian_nights/font1.ttf +https://downloads.cardforge.org/skins/arabian_nights/sprite_icons.png +https://downloads.cardforge.org/skins/comic/bg_match.jpg +https://downloads.cardforge.org/skins/comic/bg_splash.png +https://downloads.cardforge.org/skins/comic/bg_texture.jpg +https://downloads.cardforge.org/skins/comic/font1.ttf +https://downloads.cardforge.org/skins/comic/sprite_icons.png +https://downloads.cardforge.org/skins/darkforge/bg_chaos_wheel.png +https://downloads.cardforge.org/skins/darkforge/bg_match.jpg +https://downloads.cardforge.org/skins/darkforge/bg_splash.png +https://downloads.cardforge.org/skins/darkforge/bg_splash_hd.png +https://downloads.cardforge.org/skins/darkforge/bg_texture.jpg +https://downloads.cardforge.org/skins/darkforge/font1.ttf +https://downloads.cardforge.org/skins/darkforge/hd_logo.png +https://downloads.cardforge.org/skins/darkforge/sprite_avatars.png +https://downloads.cardforge.org/skins/darkforge/sprite_buttons.png +https://downloads.cardforge.org/skins/darkforge/sprite_icons.png +https://downloads.cardforge.org/skins/darkforge/sprite_start.png +https://downloads.cardforge.org/skins/darkforge/sprite_trophies.png +https://downloads.cardforge.org/skins/darkred/bg_match.jpg +https://downloads.cardforge.org/skins/darkred/bg_splash.png +https://downloads.cardforge.org/skins/darkred/bg_texture.jpg +https://downloads.cardforge.org/skins/darkred/font1.ttf +https://downloads.cardforge.org/skins/darkred/sprite_icons.png +https://downloads.cardforge.org/skins/darky/bg_match.jpg +https://downloads.cardforge.org/skins/darky/bg_splash.png +https://downloads.cardforge.org/skins/darky/bg_texture.jpg +https://downloads.cardforge.org/skins/darky/font1.ttf +https://downloads.cardforge.org/skins/darky/sprite_icons.png +https://downloads.cardforge.org/skins/dark_ascension/bg_match.jpg +https://downloads.cardforge.org/skins/dark_ascension/bg_splash.png +https://downloads.cardforge.org/skins/dark_ascension/bg_texture.jpg +https://downloads.cardforge.org/skins/dark_ascension/font1.ttf +https://downloads.cardforge.org/skins/dark_ascension/sprite_icons.png +https://downloads.cardforge.org/skins/firebloom/bg_match.jpg +https://downloads.cardforge.org/skins/firebloom/bg_splash.png +https://downloads.cardforge.org/skins/firebloom/bg_texture.jpg +https://downloads.cardforge.org/skins/firebloom/font1.ttf +https://downloads.cardforge.org/skins/firebloom/sprite_icons.png +https://downloads.cardforge.org/skins/inferno/bg_match.jpg +https://downloads.cardforge.org/skins/inferno/bg_splash.png +https://downloads.cardforge.org/skins/inferno/bg_texture.jpg +https://downloads.cardforge.org/skins/inferno/font1.ttf +https://downloads.cardforge.org/skins/inferno/sprite_icons.png +https://downloads.cardforge.org/skins/innistrad/bg_match.jpg +https://downloads.cardforge.org/skins/innistrad/bg_splash.png +https://downloads.cardforge.org/skins/innistrad/bg_texture.jpg +https://downloads.cardforge.org/skins/innistrad/font1.ttf +https://downloads.cardforge.org/skins/innistrad/sprite_icons.png +https://downloads.cardforge.org/skins/journeyman/bg_match.jpg +https://downloads.cardforge.org/skins/journeyman/bg_splash.png +https://downloads.cardforge.org/skins/journeyman/bg_texture.jpg +https://downloads.cardforge.org/skins/journeyman/font1.ttf +https://downloads.cardforge.org/skins/journeyman/sprite_icons.png +https://downloads.cardforge.org/skins/kamigawa/bg_match.jpg +https://downloads.cardforge.org/skins/kamigawa/bg_splash.png +https://downloads.cardforge.org/skins/kamigawa/bg_texture.jpg +https://downloads.cardforge.org/skins/kamigawa/font1.ttf +https://downloads.cardforge.org/skins/kamigawa/sprite_icons.png +https://downloads.cardforge.org/skins/marble_blue/bg_match.jpg +https://downloads.cardforge.org/skins/marble_blue/bg_splash.png +https://downloads.cardforge.org/skins/marble_blue/bg_texture.jpg +https://downloads.cardforge.org/skins/marble_blue/font1.ttf +https://downloads.cardforge.org/skins/marble_blue/notes.txt +https://downloads.cardforge.org/skins/marble_blue/sprite_icons.png +https://downloads.cardforge.org/skins/metalcraft/bg_match.jpg +https://downloads.cardforge.org/skins/metalcraft/bg_splash.png +https://downloads.cardforge.org/skins/metalcraft/bg_texture.jpg +https://downloads.cardforge.org/skins/metalcraft/font1.ttf +https://downloads.cardforge.org/skins/metalcraft/sprite_icons.png +https://downloads.cardforge.org/skins/mythic_rare/bg_match.jpg +https://downloads.cardforge.org/skins/mythic_rare/bg_splash.png +https://downloads.cardforge.org/skins/mythic_rare/bg_texture.jpg +https://downloads.cardforge.org/skins/mythic_rare/font1.ttf +https://downloads.cardforge.org/skins/mythic_rare/sprite_icons.png +https://downloads.cardforge.org/skins/phyrexia/bg_match.jpg +https://downloads.cardforge.org/skins/phyrexia/bg_splash.png +https://downloads.cardforge.org/skins/phyrexia/bg_texture.jpg +https://downloads.cardforge.org/skins/phyrexia/font1.ttf +https://downloads.cardforge.org/skins/phyrexia/sprite_icons.png +https://downloads.cardforge.org/skins/ravnica/bg_match.jpg +https://downloads.cardforge.org/skins/ravnica/bg_splash.png +https://downloads.cardforge.org/skins/ravnica/bg_texture.jpg +https://downloads.cardforge.org/skins/ravnica/font1.ttf +https://downloads.cardforge.org/skins/ravnica/sprite_icons.png +https://downloads.cardforge.org/skins/rebel/bg_match.jpg +https://downloads.cardforge.org/skins/rebel/bg_splash.png +https://downloads.cardforge.org/skins/rebel/bg_texture.jpg +https://downloads.cardforge.org/skins/rebel/font1.ttf +https://downloads.cardforge.org/skins/rebel/sprite_icons.png +https://downloads.cardforge.org/skins/sleeping_forest/bg_match.jpg +https://downloads.cardforge.org/skins/sleeping_forest/bg_splash.png +https://downloads.cardforge.org/skins/sleeping_forest/bg_texture.jpg +https://downloads.cardforge.org/skins/sleeping_forest/font1.ttf +https://downloads.cardforge.org/skins/sleeping_forest/sprite_icons.png +https://downloads.cardforge.org/skins/smith/bg_match.jpg +https://downloads.cardforge.org/skins/smith/bg_splash.png +https://downloads.cardforge.org/skins/smith/bg_texture.jpg +https://downloads.cardforge.org/skins/smith/font1.ttf +https://downloads.cardforge.org/skins/smith/sprite_icons.png +https://downloads.cardforge.org/skins/the_dale/bg_match.jpg +https://downloads.cardforge.org/skins/the_dale/bg_splash.png +https://downloads.cardforge.org/skins/the_dale/bg_texture.jpg +https://downloads.cardforge.org/skins/the_dale/font1.ttf +https://downloads.cardforge.org/skins/the_dale/sprite_icons.png +https://downloads.cardforge.org/skins/the_simpsons/bg_match.jpg +https://downloads.cardforge.org/skins/the_simpsons/bg_splash.png +https://downloads.cardforge.org/skins/the_simpsons/bg_texture.jpg +https://downloads.cardforge.org/skins/the_simpsons/font1.ttf +https://downloads.cardforge.org/skins/the_simpsons/sprite_avatars.png +https://downloads.cardforge.org/skins/the_simpsons/sprite_icons.png +https://downloads.cardforge.org/skins/zendikar/bg_match.jpg +https://downloads.cardforge.org/skins/zendikar/bg_splash.png +https://downloads.cardforge.org/skins/zendikar/bg_texture.jpg +https://downloads.cardforge.org/skins/zendikar/font1.ttf +https://downloads.cardforge.org/skins/zendikar/sprite_icons.png diff --git a/forge-gui/res/skins/arabian_nights/bg_match.jpg b/forge-gui/res/skins/arabian_nights/bg_match.jpg deleted file mode 100644 index 1a85b274024..00000000000 Binary files a/forge-gui/res/skins/arabian_nights/bg_match.jpg and /dev/null differ diff --git a/forge-gui/res/skins/arabian_nights/bg_splash.png b/forge-gui/res/skins/arabian_nights/bg_splash.png deleted file mode 100644 index 8b4e3041588..00000000000 Binary files a/forge-gui/res/skins/arabian_nights/bg_splash.png and /dev/null differ diff --git a/forge-gui/res/skins/arabian_nights/bg_texture.jpg b/forge-gui/res/skins/arabian_nights/bg_texture.jpg deleted file mode 100644 index da62521ec05..00000000000 Binary files a/forge-gui/res/skins/arabian_nights/bg_texture.jpg and /dev/null differ diff --git a/forge-gui/res/skins/arabian_nights/font1.ttf b/forge-gui/res/skins/arabian_nights/font1.ttf deleted file mode 100644 index 8b7a6c92e9f..00000000000 Binary files a/forge-gui/res/skins/arabian_nights/font1.ttf and /dev/null differ diff --git a/forge-gui/res/skins/arabian_nights/sprite_icons.png b/forge-gui/res/skins/arabian_nights/sprite_icons.png deleted file mode 100644 index d50775e8f63..00000000000 Binary files a/forge-gui/res/skins/arabian_nights/sprite_icons.png and /dev/null differ diff --git a/forge-gui/res/skins/comic/bg_match.jpg b/forge-gui/res/skins/comic/bg_match.jpg deleted file mode 100644 index 0e17ab40aa4..00000000000 Binary files a/forge-gui/res/skins/comic/bg_match.jpg and /dev/null differ diff --git a/forge-gui/res/skins/comic/bg_splash.png b/forge-gui/res/skins/comic/bg_splash.png deleted file mode 100644 index 7b350627cd3..00000000000 Binary files a/forge-gui/res/skins/comic/bg_splash.png and /dev/null differ diff --git a/forge-gui/res/skins/comic/bg_texture.jpg b/forge-gui/res/skins/comic/bg_texture.jpg deleted file mode 100644 index 370f9b87cce..00000000000 Binary files a/forge-gui/res/skins/comic/bg_texture.jpg and /dev/null differ diff --git a/forge-gui/res/skins/comic/font1.ttf b/forge-gui/res/skins/comic/font1.ttf deleted file mode 100644 index dd68def3b49..00000000000 Binary files a/forge-gui/res/skins/comic/font1.ttf and /dev/null differ diff --git a/forge-gui/res/skins/comic/sprite_icons.png b/forge-gui/res/skins/comic/sprite_icons.png deleted file mode 100644 index 322cc064b75..00000000000 Binary files a/forge-gui/res/skins/comic/sprite_icons.png and /dev/null differ diff --git a/forge-gui/res/skins/dark_ascension/bg_match.jpg b/forge-gui/res/skins/dark_ascension/bg_match.jpg deleted file mode 100644 index bf1b0446826..00000000000 Binary files a/forge-gui/res/skins/dark_ascension/bg_match.jpg and /dev/null differ diff --git a/forge-gui/res/skins/dark_ascension/bg_splash.png b/forge-gui/res/skins/dark_ascension/bg_splash.png deleted file mode 100644 index 262681917c1..00000000000 Binary files a/forge-gui/res/skins/dark_ascension/bg_splash.png and /dev/null differ diff --git a/forge-gui/res/skins/dark_ascension/bg_texture.jpg b/forge-gui/res/skins/dark_ascension/bg_texture.jpg deleted file mode 100644 index 3c43f341e68..00000000000 Binary files a/forge-gui/res/skins/dark_ascension/bg_texture.jpg and /dev/null differ diff --git a/forge-gui/res/skins/dark_ascension/font1.ttf b/forge-gui/res/skins/dark_ascension/font1.ttf deleted file mode 100644 index 8cd78415278..00000000000 Binary files a/forge-gui/res/skins/dark_ascension/font1.ttf and /dev/null differ diff --git a/forge-gui/res/skins/dark_ascension/sprite_icons.png b/forge-gui/res/skins/dark_ascension/sprite_icons.png deleted file mode 100644 index 83ffee25ce5..00000000000 Binary files a/forge-gui/res/skins/dark_ascension/sprite_icons.png and /dev/null differ diff --git a/forge-gui/res/skins/darkforge/bg_chaos_wheel.png b/forge-gui/res/skins/darkforge/bg_chaos_wheel.png deleted file mode 100644 index 9800f5aff45..00000000000 Binary files a/forge-gui/res/skins/darkforge/bg_chaos_wheel.png and /dev/null differ diff --git a/forge-gui/res/skins/darkforge/bg_match.jpg b/forge-gui/res/skins/darkforge/bg_match.jpg deleted file mode 100644 index 5fe8905fbb2..00000000000 Binary files a/forge-gui/res/skins/darkforge/bg_match.jpg and /dev/null differ diff --git a/forge-gui/res/skins/darkforge/bg_splash.png b/forge-gui/res/skins/darkforge/bg_splash.png deleted file mode 100644 index 9f686363ca5..00000000000 Binary files a/forge-gui/res/skins/darkforge/bg_splash.png and /dev/null differ diff --git a/forge-gui/res/skins/darkforge/bg_splash_hd.png b/forge-gui/res/skins/darkforge/bg_splash_hd.png deleted file mode 100644 index ceb26f07d83..00000000000 Binary files a/forge-gui/res/skins/darkforge/bg_splash_hd.png and /dev/null differ diff --git a/forge-gui/res/skins/darkforge/bg_texture.jpg b/forge-gui/res/skins/darkforge/bg_texture.jpg deleted file mode 100644 index ab54820d1df..00000000000 Binary files a/forge-gui/res/skins/darkforge/bg_texture.jpg and /dev/null differ diff --git a/forge-gui/res/skins/darkforge/font1.ttf b/forge-gui/res/skins/darkforge/font1.ttf deleted file mode 100644 index 81aa8defb4b..00000000000 Binary files a/forge-gui/res/skins/darkforge/font1.ttf and /dev/null differ diff --git a/forge-gui/res/skins/darkforge/hd_logo.png b/forge-gui/res/skins/darkforge/hd_logo.png deleted file mode 100644 index c4dcb5036cc..00000000000 Binary files a/forge-gui/res/skins/darkforge/hd_logo.png and /dev/null differ diff --git a/forge-gui/res/skins/darkforge/sprite_avatars.png b/forge-gui/res/skins/darkforge/sprite_avatars.png deleted file mode 100644 index 12dbcbb61ed..00000000000 Binary files a/forge-gui/res/skins/darkforge/sprite_avatars.png and /dev/null differ diff --git a/forge-gui/res/skins/darkforge/sprite_buttons.png b/forge-gui/res/skins/darkforge/sprite_buttons.png deleted file mode 100644 index 35deb772ef9..00000000000 Binary files a/forge-gui/res/skins/darkforge/sprite_buttons.png and /dev/null differ diff --git a/forge-gui/res/skins/darkforge/sprite_icons.png b/forge-gui/res/skins/darkforge/sprite_icons.png deleted file mode 100644 index 3f83d37da80..00000000000 Binary files a/forge-gui/res/skins/darkforge/sprite_icons.png and /dev/null differ diff --git a/forge-gui/res/skins/darkforge/sprite_start.png b/forge-gui/res/skins/darkforge/sprite_start.png deleted file mode 100644 index 310eb073465..00000000000 Binary files a/forge-gui/res/skins/darkforge/sprite_start.png and /dev/null differ diff --git a/forge-gui/res/skins/darkforge/sprite_trophies.png b/forge-gui/res/skins/darkforge/sprite_trophies.png deleted file mode 100644 index a9bc240154f..00000000000 Binary files a/forge-gui/res/skins/darkforge/sprite_trophies.png and /dev/null differ diff --git a/forge-gui/res/skins/darkred/bg_match.jpg b/forge-gui/res/skins/darkred/bg_match.jpg deleted file mode 100644 index fb7c0298072..00000000000 Binary files a/forge-gui/res/skins/darkred/bg_match.jpg and /dev/null differ diff --git a/forge-gui/res/skins/darkred/bg_splash.png b/forge-gui/res/skins/darkred/bg_splash.png deleted file mode 100644 index 880e3c2068d..00000000000 Binary files a/forge-gui/res/skins/darkred/bg_splash.png and /dev/null differ diff --git a/forge-gui/res/skins/darkred/bg_texture.jpg b/forge-gui/res/skins/darkred/bg_texture.jpg deleted file mode 100644 index 3c221294890..00000000000 Binary files a/forge-gui/res/skins/darkred/bg_texture.jpg and /dev/null differ diff --git a/forge-gui/res/skins/darkred/font1.ttf b/forge-gui/res/skins/darkred/font1.ttf deleted file mode 100644 index 1f5704ed579..00000000000 Binary files a/forge-gui/res/skins/darkred/font1.ttf and /dev/null differ diff --git a/forge-gui/res/skins/darkred/sprite_icons.png b/forge-gui/res/skins/darkred/sprite_icons.png deleted file mode 100644 index b1e19b256e3..00000000000 Binary files a/forge-gui/res/skins/darkred/sprite_icons.png and /dev/null differ diff --git a/forge-gui/res/skins/darky/bg_match.jpg b/forge-gui/res/skins/darky/bg_match.jpg deleted file mode 100644 index ace2bf45d10..00000000000 Binary files a/forge-gui/res/skins/darky/bg_match.jpg and /dev/null differ diff --git a/forge-gui/res/skins/darky/bg_splash.png b/forge-gui/res/skins/darky/bg_splash.png deleted file mode 100644 index 549afedbb77..00000000000 Binary files a/forge-gui/res/skins/darky/bg_splash.png and /dev/null differ diff --git a/forge-gui/res/skins/darky/bg_texture.jpg b/forge-gui/res/skins/darky/bg_texture.jpg deleted file mode 100644 index 9b0da179073..00000000000 Binary files a/forge-gui/res/skins/darky/bg_texture.jpg and /dev/null differ diff --git a/forge-gui/res/skins/darky/font1.ttf b/forge-gui/res/skins/darky/font1.ttf deleted file mode 100644 index 4b4ecc66671..00000000000 Binary files a/forge-gui/res/skins/darky/font1.ttf and /dev/null differ diff --git a/forge-gui/res/skins/darky/sprite_icons.png b/forge-gui/res/skins/darky/sprite_icons.png deleted file mode 100644 index 8ae0e68c4e9..00000000000 Binary files a/forge-gui/res/skins/darky/sprite_icons.png and /dev/null differ diff --git a/forge-gui/res/skins/firebloom/bg_match.jpg b/forge-gui/res/skins/firebloom/bg_match.jpg deleted file mode 100644 index fa664d0c43e..00000000000 Binary files a/forge-gui/res/skins/firebloom/bg_match.jpg and /dev/null differ diff --git a/forge-gui/res/skins/firebloom/bg_splash.png b/forge-gui/res/skins/firebloom/bg_splash.png deleted file mode 100644 index fd63b8ee9d2..00000000000 Binary files a/forge-gui/res/skins/firebloom/bg_splash.png and /dev/null differ diff --git a/forge-gui/res/skins/firebloom/bg_texture.jpg b/forge-gui/res/skins/firebloom/bg_texture.jpg deleted file mode 100644 index 479dea8c51b..00000000000 Binary files a/forge-gui/res/skins/firebloom/bg_texture.jpg and /dev/null differ diff --git a/forge-gui/res/skins/firebloom/font1.ttf b/forge-gui/res/skins/firebloom/font1.ttf deleted file mode 100644 index 59b14a2d2d4..00000000000 Binary files a/forge-gui/res/skins/firebloom/font1.ttf and /dev/null differ diff --git a/forge-gui/res/skins/firebloom/sprite_icons.png b/forge-gui/res/skins/firebloom/sprite_icons.png deleted file mode 100644 index 346e8e59541..00000000000 Binary files a/forge-gui/res/skins/firebloom/sprite_icons.png and /dev/null differ diff --git a/forge-gui/res/skins/inferno/bg_match.jpg b/forge-gui/res/skins/inferno/bg_match.jpg deleted file mode 100644 index 8d04ecf0d89..00000000000 Binary files a/forge-gui/res/skins/inferno/bg_match.jpg and /dev/null differ diff --git a/forge-gui/res/skins/inferno/bg_splash.png b/forge-gui/res/skins/inferno/bg_splash.png deleted file mode 100644 index 2bfb99b6298..00000000000 Binary files a/forge-gui/res/skins/inferno/bg_splash.png and /dev/null differ diff --git a/forge-gui/res/skins/inferno/bg_texture.jpg b/forge-gui/res/skins/inferno/bg_texture.jpg deleted file mode 100644 index 6d3203a8317..00000000000 Binary files a/forge-gui/res/skins/inferno/bg_texture.jpg and /dev/null differ diff --git a/forge-gui/res/skins/inferno/font1.ttf b/forge-gui/res/skins/inferno/font1.ttf deleted file mode 100644 index 146ad3c0b5c..00000000000 Binary files a/forge-gui/res/skins/inferno/font1.ttf and /dev/null differ diff --git a/forge-gui/res/skins/inferno/sprite_icons.png b/forge-gui/res/skins/inferno/sprite_icons.png deleted file mode 100644 index b6b0fb537ce..00000000000 Binary files a/forge-gui/res/skins/inferno/sprite_icons.png and /dev/null differ diff --git a/forge-gui/res/skins/innistrad/bg_match.jpg b/forge-gui/res/skins/innistrad/bg_match.jpg deleted file mode 100644 index 862452f2d64..00000000000 Binary files a/forge-gui/res/skins/innistrad/bg_match.jpg and /dev/null differ diff --git a/forge-gui/res/skins/innistrad/bg_splash.png b/forge-gui/res/skins/innistrad/bg_splash.png deleted file mode 100644 index dcd906879a9..00000000000 Binary files a/forge-gui/res/skins/innistrad/bg_splash.png and /dev/null differ diff --git a/forge-gui/res/skins/innistrad/bg_texture.jpg b/forge-gui/res/skins/innistrad/bg_texture.jpg deleted file mode 100644 index fd8c5332a8f..00000000000 Binary files a/forge-gui/res/skins/innistrad/bg_texture.jpg and /dev/null differ diff --git a/forge-gui/res/skins/innistrad/font1.ttf b/forge-gui/res/skins/innistrad/font1.ttf deleted file mode 100644 index d7154a17b10..00000000000 Binary files a/forge-gui/res/skins/innistrad/font1.ttf and /dev/null differ diff --git a/forge-gui/res/skins/innistrad/sprite_icons.png b/forge-gui/res/skins/innistrad/sprite_icons.png deleted file mode 100644 index b89b9d3daac..00000000000 Binary files a/forge-gui/res/skins/innistrad/sprite_icons.png and /dev/null differ diff --git a/forge-gui/res/skins/journeyman/bg_match.jpg b/forge-gui/res/skins/journeyman/bg_match.jpg deleted file mode 100644 index dbf6095de89..00000000000 Binary files a/forge-gui/res/skins/journeyman/bg_match.jpg and /dev/null differ diff --git a/forge-gui/res/skins/journeyman/bg_splash.png b/forge-gui/res/skins/journeyman/bg_splash.png deleted file mode 100644 index 49691861150..00000000000 Binary files a/forge-gui/res/skins/journeyman/bg_splash.png and /dev/null differ diff --git a/forge-gui/res/skins/journeyman/bg_texture.jpg b/forge-gui/res/skins/journeyman/bg_texture.jpg deleted file mode 100644 index ba4fb24f997..00000000000 Binary files a/forge-gui/res/skins/journeyman/bg_texture.jpg and /dev/null differ diff --git a/forge-gui/res/skins/journeyman/font1.ttf b/forge-gui/res/skins/journeyman/font1.ttf deleted file mode 100644 index 4b4ecc66671..00000000000 Binary files a/forge-gui/res/skins/journeyman/font1.ttf and /dev/null differ diff --git a/forge-gui/res/skins/journeyman/sprite_icons.png b/forge-gui/res/skins/journeyman/sprite_icons.png deleted file mode 100644 index 25376009531..00000000000 Binary files a/forge-gui/res/skins/journeyman/sprite_icons.png and /dev/null differ diff --git a/forge-gui/res/skins/kamigawa/bg_match.jpg b/forge-gui/res/skins/kamigawa/bg_match.jpg deleted file mode 100644 index 6e5d3cef206..00000000000 Binary files a/forge-gui/res/skins/kamigawa/bg_match.jpg and /dev/null differ diff --git a/forge-gui/res/skins/kamigawa/bg_splash.png b/forge-gui/res/skins/kamigawa/bg_splash.png deleted file mode 100644 index ea32f4c9e60..00000000000 Binary files a/forge-gui/res/skins/kamigawa/bg_splash.png and /dev/null differ diff --git a/forge-gui/res/skins/kamigawa/bg_texture.jpg b/forge-gui/res/skins/kamigawa/bg_texture.jpg deleted file mode 100644 index f4d397df962..00000000000 Binary files a/forge-gui/res/skins/kamigawa/bg_texture.jpg and /dev/null differ diff --git a/forge-gui/res/skins/kamigawa/font1.ttf b/forge-gui/res/skins/kamigawa/font1.ttf deleted file mode 100644 index ac0cedf41e2..00000000000 Binary files a/forge-gui/res/skins/kamigawa/font1.ttf and /dev/null differ diff --git a/forge-gui/res/skins/kamigawa/sprite_icons.png b/forge-gui/res/skins/kamigawa/sprite_icons.png deleted file mode 100644 index 9672d171dd2..00000000000 Binary files a/forge-gui/res/skins/kamigawa/sprite_icons.png and /dev/null differ diff --git a/forge-gui/res/skins/marble_blue/bg_match.jpg b/forge-gui/res/skins/marble_blue/bg_match.jpg deleted file mode 100644 index be7d73da0f8..00000000000 Binary files a/forge-gui/res/skins/marble_blue/bg_match.jpg and /dev/null differ diff --git a/forge-gui/res/skins/marble_blue/bg_splash.png b/forge-gui/res/skins/marble_blue/bg_splash.png deleted file mode 100644 index 8f47dec7581..00000000000 Binary files a/forge-gui/res/skins/marble_blue/bg_splash.png and /dev/null differ diff --git a/forge-gui/res/skins/marble_blue/bg_texture.jpg b/forge-gui/res/skins/marble_blue/bg_texture.jpg deleted file mode 100644 index be7d73da0f8..00000000000 Binary files a/forge-gui/res/skins/marble_blue/bg_texture.jpg and /dev/null differ diff --git a/forge-gui/res/skins/marble_blue/font1.ttf b/forge-gui/res/skins/marble_blue/font1.ttf deleted file mode 100644 index 40e9c69dcc9..00000000000 Binary files a/forge-gui/res/skins/marble_blue/font1.ttf and /dev/null differ diff --git a/forge-gui/res/skins/marble_blue/notes.txt b/forge-gui/res/skins/marble_blue/notes.txt deleted file mode 100644 index e702719c531..00000000000 --- a/forge-gui/res/skins/marble_blue/notes.txt +++ /dev/null @@ -1,3 +0,0 @@ -Abbadon font used for splash. -Aller font used as font1. -Marbles rendered using VRay. \ No newline at end of file diff --git a/forge-gui/res/skins/marble_blue/sprite_icons.png b/forge-gui/res/skins/marble_blue/sprite_icons.png deleted file mode 100644 index 9b9c0d85367..00000000000 Binary files a/forge-gui/res/skins/marble_blue/sprite_icons.png and /dev/null differ diff --git a/forge-gui/res/skins/metalcraft/bg_match.jpg b/forge-gui/res/skins/metalcraft/bg_match.jpg deleted file mode 100644 index cac561fe8e5..00000000000 Binary files a/forge-gui/res/skins/metalcraft/bg_match.jpg and /dev/null differ diff --git a/forge-gui/res/skins/metalcraft/bg_splash.png b/forge-gui/res/skins/metalcraft/bg_splash.png deleted file mode 100644 index f187bacb7c6..00000000000 Binary files a/forge-gui/res/skins/metalcraft/bg_splash.png and /dev/null differ diff --git a/forge-gui/res/skins/metalcraft/bg_texture.jpg b/forge-gui/res/skins/metalcraft/bg_texture.jpg deleted file mode 100644 index 4b950221381..00000000000 Binary files a/forge-gui/res/skins/metalcraft/bg_texture.jpg and /dev/null differ diff --git a/forge-gui/res/skins/metalcraft/font1.ttf b/forge-gui/res/skins/metalcraft/font1.ttf deleted file mode 100644 index e3ed150c0bd..00000000000 Binary files a/forge-gui/res/skins/metalcraft/font1.ttf and /dev/null differ diff --git a/forge-gui/res/skins/metalcraft/sprite_icons.png b/forge-gui/res/skins/metalcraft/sprite_icons.png deleted file mode 100644 index 12ba864d475..00000000000 Binary files a/forge-gui/res/skins/metalcraft/sprite_icons.png and /dev/null differ diff --git a/forge-gui/res/skins/mythic_rare/bg_match.jpg b/forge-gui/res/skins/mythic_rare/bg_match.jpg deleted file mode 100644 index d78fd20e2ae..00000000000 Binary files a/forge-gui/res/skins/mythic_rare/bg_match.jpg and /dev/null differ diff --git a/forge-gui/res/skins/mythic_rare/bg_splash.png b/forge-gui/res/skins/mythic_rare/bg_splash.png deleted file mode 100644 index e9f28372aaf..00000000000 Binary files a/forge-gui/res/skins/mythic_rare/bg_splash.png and /dev/null differ diff --git a/forge-gui/res/skins/mythic_rare/bg_texture.jpg b/forge-gui/res/skins/mythic_rare/bg_texture.jpg deleted file mode 100644 index d61a406bb64..00000000000 Binary files a/forge-gui/res/skins/mythic_rare/bg_texture.jpg and /dev/null differ diff --git a/forge-gui/res/skins/mythic_rare/font1.ttf b/forge-gui/res/skins/mythic_rare/font1.ttf deleted file mode 100644 index 4b4ecc66671..00000000000 Binary files a/forge-gui/res/skins/mythic_rare/font1.ttf and /dev/null differ diff --git a/forge-gui/res/skins/mythic_rare/sprite_icons.png b/forge-gui/res/skins/mythic_rare/sprite_icons.png deleted file mode 100644 index da6e209cbc7..00000000000 Binary files a/forge-gui/res/skins/mythic_rare/sprite_icons.png and /dev/null differ diff --git a/forge-gui/res/skins/phyrexia/bg_match.jpg b/forge-gui/res/skins/phyrexia/bg_match.jpg deleted file mode 100644 index 5748e97388c..00000000000 Binary files a/forge-gui/res/skins/phyrexia/bg_match.jpg and /dev/null differ diff --git a/forge-gui/res/skins/phyrexia/bg_splash.png b/forge-gui/res/skins/phyrexia/bg_splash.png deleted file mode 100644 index 21ba79de523..00000000000 Binary files a/forge-gui/res/skins/phyrexia/bg_splash.png and /dev/null differ diff --git a/forge-gui/res/skins/phyrexia/bg_texture.jpg b/forge-gui/res/skins/phyrexia/bg_texture.jpg deleted file mode 100644 index cf2181f30c4..00000000000 Binary files a/forge-gui/res/skins/phyrexia/bg_texture.jpg and /dev/null differ diff --git a/forge-gui/res/skins/phyrexia/font1.ttf b/forge-gui/res/skins/phyrexia/font1.ttf deleted file mode 100644 index b8d1cf1c926..00000000000 Binary files a/forge-gui/res/skins/phyrexia/font1.ttf and /dev/null differ diff --git a/forge-gui/res/skins/phyrexia/sprite_icons.png b/forge-gui/res/skins/phyrexia/sprite_icons.png deleted file mode 100644 index 585928e6b04..00000000000 Binary files a/forge-gui/res/skins/phyrexia/sprite_icons.png and /dev/null differ diff --git a/forge-gui/res/skins/ravnica/bg_match.jpg b/forge-gui/res/skins/ravnica/bg_match.jpg deleted file mode 100644 index ddc9940bb43..00000000000 Binary files a/forge-gui/res/skins/ravnica/bg_match.jpg and /dev/null differ diff --git a/forge-gui/res/skins/ravnica/bg_splash.png b/forge-gui/res/skins/ravnica/bg_splash.png deleted file mode 100644 index 9b31e13f6f7..00000000000 Binary files a/forge-gui/res/skins/ravnica/bg_splash.png and /dev/null differ diff --git a/forge-gui/res/skins/ravnica/bg_texture.jpg b/forge-gui/res/skins/ravnica/bg_texture.jpg deleted file mode 100644 index 3ce7287d89a..00000000000 Binary files a/forge-gui/res/skins/ravnica/bg_texture.jpg and /dev/null differ diff --git a/forge-gui/res/skins/ravnica/font1.ttf b/forge-gui/res/skins/ravnica/font1.ttf deleted file mode 100644 index 9850a062906..00000000000 Binary files a/forge-gui/res/skins/ravnica/font1.ttf and /dev/null differ diff --git a/forge-gui/res/skins/ravnica/sprite_icons.png b/forge-gui/res/skins/ravnica/sprite_icons.png deleted file mode 100644 index c5e73f97068..00000000000 Binary files a/forge-gui/res/skins/ravnica/sprite_icons.png and /dev/null differ diff --git a/forge-gui/res/skins/rebel/bg_match.jpg b/forge-gui/res/skins/rebel/bg_match.jpg deleted file mode 100644 index f84722e6b0b..00000000000 Binary files a/forge-gui/res/skins/rebel/bg_match.jpg and /dev/null differ diff --git a/forge-gui/res/skins/rebel/bg_splash.png b/forge-gui/res/skins/rebel/bg_splash.png deleted file mode 100644 index d89b7c4fd39..00000000000 Binary files a/forge-gui/res/skins/rebel/bg_splash.png and /dev/null differ diff --git a/forge-gui/res/skins/rebel/bg_texture.jpg b/forge-gui/res/skins/rebel/bg_texture.jpg deleted file mode 100644 index 43fa60ce9ef..00000000000 Binary files a/forge-gui/res/skins/rebel/bg_texture.jpg and /dev/null differ diff --git a/forge-gui/res/skins/rebel/font1.ttf b/forge-gui/res/skins/rebel/font1.ttf deleted file mode 100644 index 026d5331ea8..00000000000 Binary files a/forge-gui/res/skins/rebel/font1.ttf and /dev/null differ diff --git a/forge-gui/res/skins/rebel/sprite_icons.png b/forge-gui/res/skins/rebel/sprite_icons.png deleted file mode 100644 index e3aa89c2e4e..00000000000 Binary files a/forge-gui/res/skins/rebel/sprite_icons.png and /dev/null differ diff --git a/forge-gui/res/skins/sleeping_forest/bg_match.jpg b/forge-gui/res/skins/sleeping_forest/bg_match.jpg deleted file mode 100644 index c5b7e633940..00000000000 Binary files a/forge-gui/res/skins/sleeping_forest/bg_match.jpg and /dev/null differ diff --git a/forge-gui/res/skins/sleeping_forest/bg_splash.png b/forge-gui/res/skins/sleeping_forest/bg_splash.png deleted file mode 100644 index 58433d0d0c3..00000000000 Binary files a/forge-gui/res/skins/sleeping_forest/bg_splash.png and /dev/null differ diff --git a/forge-gui/res/skins/sleeping_forest/bg_texture.jpg b/forge-gui/res/skins/sleeping_forest/bg_texture.jpg deleted file mode 100644 index 0f59326d633..00000000000 Binary files a/forge-gui/res/skins/sleeping_forest/bg_texture.jpg and /dev/null differ diff --git a/forge-gui/res/skins/sleeping_forest/font1.ttf b/forge-gui/res/skins/sleeping_forest/font1.ttf deleted file mode 100644 index 2047d41bc92..00000000000 Binary files a/forge-gui/res/skins/sleeping_forest/font1.ttf and /dev/null differ diff --git a/forge-gui/res/skins/sleeping_forest/sprite_icons.png b/forge-gui/res/skins/sleeping_forest/sprite_icons.png deleted file mode 100644 index 2cb5abc5584..00000000000 Binary files a/forge-gui/res/skins/sleeping_forest/sprite_icons.png and /dev/null differ diff --git a/forge-gui/res/skins/smith/bg_match.jpg b/forge-gui/res/skins/smith/bg_match.jpg deleted file mode 100644 index 3089f2a9350..00000000000 Binary files a/forge-gui/res/skins/smith/bg_match.jpg and /dev/null differ diff --git a/forge-gui/res/skins/smith/bg_splash.png b/forge-gui/res/skins/smith/bg_splash.png deleted file mode 100644 index c31186e0c85..00000000000 Binary files a/forge-gui/res/skins/smith/bg_splash.png and /dev/null differ diff --git a/forge-gui/res/skins/smith/bg_texture.jpg b/forge-gui/res/skins/smith/bg_texture.jpg deleted file mode 100644 index 9bf4763c26c..00000000000 Binary files a/forge-gui/res/skins/smith/bg_texture.jpg and /dev/null differ diff --git a/forge-gui/res/skins/smith/font1.ttf b/forge-gui/res/skins/smith/font1.ttf deleted file mode 100644 index b07b620fcac..00000000000 Binary files a/forge-gui/res/skins/smith/font1.ttf and /dev/null differ diff --git a/forge-gui/res/skins/smith/sprite_icons.png b/forge-gui/res/skins/smith/sprite_icons.png deleted file mode 100644 index b2dd5c2d111..00000000000 Binary files a/forge-gui/res/skins/smith/sprite_icons.png and /dev/null differ diff --git a/forge-gui/res/skins/the_dale/bg_match.jpg b/forge-gui/res/skins/the_dale/bg_match.jpg deleted file mode 100644 index 594b6c8e9d0..00000000000 Binary files a/forge-gui/res/skins/the_dale/bg_match.jpg and /dev/null differ diff --git a/forge-gui/res/skins/the_dale/bg_splash.png b/forge-gui/res/skins/the_dale/bg_splash.png deleted file mode 100644 index 812d1db8b62..00000000000 Binary files a/forge-gui/res/skins/the_dale/bg_splash.png and /dev/null differ diff --git a/forge-gui/res/skins/the_dale/bg_texture.jpg b/forge-gui/res/skins/the_dale/bg_texture.jpg deleted file mode 100644 index 97d18e4517d..00000000000 Binary files a/forge-gui/res/skins/the_dale/bg_texture.jpg and /dev/null differ diff --git a/forge-gui/res/skins/the_dale/font1.ttf b/forge-gui/res/skins/the_dale/font1.ttf deleted file mode 100644 index 4b4ecc66671..00000000000 Binary files a/forge-gui/res/skins/the_dale/font1.ttf and /dev/null differ diff --git a/forge-gui/res/skins/the_dale/sprite_icons.png b/forge-gui/res/skins/the_dale/sprite_icons.png deleted file mode 100644 index c5c22872a8c..00000000000 Binary files a/forge-gui/res/skins/the_dale/sprite_icons.png and /dev/null differ diff --git a/forge-gui/res/skins/the_simpsons/bg_match.jpg b/forge-gui/res/skins/the_simpsons/bg_match.jpg deleted file mode 100644 index 8153f912086..00000000000 Binary files a/forge-gui/res/skins/the_simpsons/bg_match.jpg and /dev/null differ diff --git a/forge-gui/res/skins/the_simpsons/bg_splash.png b/forge-gui/res/skins/the_simpsons/bg_splash.png deleted file mode 100644 index 809526af311..00000000000 Binary files a/forge-gui/res/skins/the_simpsons/bg_splash.png and /dev/null differ diff --git a/forge-gui/res/skins/the_simpsons/bg_texture.jpg b/forge-gui/res/skins/the_simpsons/bg_texture.jpg deleted file mode 100644 index 4076d52bce5..00000000000 Binary files a/forge-gui/res/skins/the_simpsons/bg_texture.jpg and /dev/null differ diff --git a/forge-gui/res/skins/the_simpsons/font1.ttf b/forge-gui/res/skins/the_simpsons/font1.ttf deleted file mode 100644 index b6f9e5db4c1..00000000000 Binary files a/forge-gui/res/skins/the_simpsons/font1.ttf and /dev/null differ diff --git a/forge-gui/res/skins/the_simpsons/sprite_avatars.png b/forge-gui/res/skins/the_simpsons/sprite_avatars.png deleted file mode 100644 index 0010663edb0..00000000000 Binary files a/forge-gui/res/skins/the_simpsons/sprite_avatars.png and /dev/null differ diff --git a/forge-gui/res/skins/the_simpsons/sprite_icons.png b/forge-gui/res/skins/the_simpsons/sprite_icons.png deleted file mode 100644 index 9ebfc15955a..00000000000 Binary files a/forge-gui/res/skins/the_simpsons/sprite_icons.png and /dev/null differ diff --git a/forge-gui/res/skins/zendikar/bg_match.jpg b/forge-gui/res/skins/zendikar/bg_match.jpg deleted file mode 100644 index 42d3873853c..00000000000 Binary files a/forge-gui/res/skins/zendikar/bg_match.jpg and /dev/null differ diff --git a/forge-gui/res/skins/zendikar/bg_splash.png b/forge-gui/res/skins/zendikar/bg_splash.png deleted file mode 100644 index 72135afeb2d..00000000000 Binary files a/forge-gui/res/skins/zendikar/bg_splash.png and /dev/null differ diff --git a/forge-gui/res/skins/zendikar/bg_texture.jpg b/forge-gui/res/skins/zendikar/bg_texture.jpg deleted file mode 100644 index 7081f51c4eb..00000000000 Binary files a/forge-gui/res/skins/zendikar/bg_texture.jpg and /dev/null differ diff --git a/forge-gui/res/skins/zendikar/font1.ttf b/forge-gui/res/skins/zendikar/font1.ttf deleted file mode 100644 index 9868ce70e23..00000000000 Binary files a/forge-gui/res/skins/zendikar/font1.ttf and /dev/null differ diff --git a/forge-gui/res/skins/zendikar/sprite_icons.png b/forge-gui/res/skins/zendikar/sprite_icons.png deleted file mode 100644 index eb3e21379d8..00000000000 Binary files a/forge-gui/res/skins/zendikar/sprite_icons.png and /dev/null differ diff --git a/forge-gui/src/main/java/forge/deck/CardRelationMatrixGenerator.java b/forge-gui/src/main/java/forge/deck/CardRelationMatrixGenerator.java index 6c9c7e74687..0898e16d672 100644 --- a/forge-gui/src/main/java/forge/deck/CardRelationMatrixGenerator.java +++ b/forge-gui/src/main/java/forge/deck/CardRelationMatrixGenerator.java @@ -26,7 +26,7 @@ public final class CardRelationMatrixGenerator { public static HashMap>>> cardPools = new HashMap<>(); - public static Map>>> ldaPools = new HashMap(); + public static Map>>> ldaPools = new HashMap<>(); /** To ensure that only cards with at least 14 connections (as 14*4+4=60) are included in the card based deck generation pools diff --git a/forge-gui/src/main/java/forge/download/GuiDownloadService.java b/forge-gui/src/main/java/forge/download/GuiDownloadService.java index ecc29596c4f..07cdf8afa79 100644 --- a/forge-gui/src/main/java/forge/download/GuiDownloadService.java +++ b/forge-gui/src/main/java/forge/download/GuiDownloadService.java @@ -270,7 +270,7 @@ public abstract class GuiDownloadService implements Runnable { String decodedKey = decodeURL(kv.getKey()); File fileDest = new File(decodedKey); final String filePath = fileDest.getPath(); - final String subLastIndex = filePath.contains("pics") ? "\\pics\\" : "\\db\\"; + final String subLastIndex = filePath.contains("pics") ? "\\pics\\" : filePath.contains("skins") ? "\\"+FileUtil.getParent(filePath)+"\\" : "\\db\\"; System.out.println(count + "/" + totalCount + " - .." + filePath.substring(filePath.lastIndexOf(subLastIndex)+1)); @@ -409,8 +409,11 @@ public abstract class GuiDownloadService implements Runnable { protected abstract Map getNeededFiles(); protected static void addMissingItems(Map list, String nameUrlFile, String dir) { + addMissingItems(list, nameUrlFile, dir, false); + } + protected static void addMissingItems(Map list, String nameUrlFile, String dir, boolean includeParent) { for (Pair nameUrlPair : FileUtil.readNameUrlFile(nameUrlFile)) { - File f = new File(dir, decodeURL(nameUrlPair.getLeft())); + File f = new File(includeParent? dir+FileUtil.getParent(nameUrlPair.getRight()) : dir , decodeURL(nameUrlPair.getLeft())); //System.out.println(f.getAbsolutePath()); if (!f.exists()) { list.put(f.getAbsolutePath(), nameUrlPair.getRight()); diff --git a/forge-gui/src/main/java/forge/download/GuiDownloadSkins.java b/forge-gui/src/main/java/forge/download/GuiDownloadSkins.java new file mode 100644 index 00000000000..502c057f710 --- /dev/null +++ b/forge-gui/src/main/java/forge/download/GuiDownloadSkins.java @@ -0,0 +1,23 @@ +package forge.download; + +import forge.properties.ForgeConstants; + +import java.util.Map; +import java.util.TreeMap; + +public class GuiDownloadSkins extends GuiDownloadService { + @Override + public String getTitle() { + return "Download Skins"; + } + + @Override + protected final Map getNeededFiles() { + // read all card names and urls + final Map urls = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + + addMissingItems(urls, ForgeConstants.SKINS_LIST_FILE, ForgeConstants.CACHE_SKINS_DIR, true); + + return urls; + } +} diff --git a/forge-gui/src/main/java/forge/interfaces/IGuiBase.java b/forge-gui/src/main/java/forge/interfaces/IGuiBase.java index 4b137d323ef..78605c2ba63 100644 --- a/forge-gui/src/main/java/forge/interfaces/IGuiBase.java +++ b/forge-gui/src/main/java/forge/interfaces/IGuiBase.java @@ -42,6 +42,7 @@ public interface IGuiBase { String showFileDialog(String title, String defaultDir); File getSaveFile(File defaultFile); void download(GuiDownloadService service, Callback callback); + void refreshSkin(); void showCardList(String title, String message, List list); boolean showBoxedProduct(String title, String message, List list); PaperCard chooseCard(String title, String message, List list); diff --git a/forge-gui/src/main/java/forge/match/AbstractGuiGame.java b/forge-gui/src/main/java/forge/match/AbstractGuiGame.java index 4d90d993e5c..dd08bdaab61 100644 --- a/forge-gui/src/main/java/forge/match/AbstractGuiGame.java +++ b/forge-gui/src/main/java/forge/match/AbstractGuiGame.java @@ -230,6 +230,11 @@ public abstract class AbstractGuiGame implements IGuiGame, IMayViewCards { case Meld: case Modal: return true; + case Adventure: + if (cv.isFaceDown()) { + return getCurrentPlayer() == null || cv.canFaceDownBeShownToAny(getLocalPlayers()); + } + return false; default: return false; } diff --git a/forge-gui/src/main/java/forge/match/input/InputPayMana.java b/forge-gui/src/main/java/forge/match/input/InputPayMana.java index 56276bb5b6d..964f89f34f3 100644 --- a/forge-gui/src/main/java/forge/match/input/InputPayMana.java +++ b/forge-gui/src/main/java/forge/match/input/InputPayMana.java @@ -3,8 +3,6 @@ package forge.match.input; import java.util.*; import forge.GuiBase; -import forge.game.GameActionUtil; -import forge.game.ability.AbilityKey; import forge.game.spellability.SpellAbilityView; import forge.util.TextUtil; import org.apache.commons.lang3.StringUtils; @@ -18,15 +16,11 @@ import forge.card.ColorSet; import forge.card.MagicColor; import forge.card.mana.ManaAtom; import forge.game.Game; -import forge.game.ability.ApiType; +import forge.game.GameActionUtil; import forge.game.card.Card; -import forge.game.card.CardUtil; import forge.game.mana.ManaCostBeingPaid; import forge.game.player.Player; import forge.game.player.PlayerView; -import forge.game.replacement.ReplacementEffect; -import forge.game.replacement.ReplacementType; -import forge.game.spellability.AbilityManaPart; import forge.game.spellability.SpellAbility; import forge.player.HumanPlay; import forge.player.PlayerControllerHuman; @@ -77,7 +71,7 @@ public abstract class InputPayMana extends InputSyncronizedBase { // Mobile Forge allows to tap cards underneath the current card even if the current one is tapped if (otherCardsToSelect != null) { for (Card c : otherCardsToSelect) { - for (SpellAbility sa : c.getManaAbilities()) { + for (SpellAbility sa : getAllManaAbilities(c)) { if (sa.canPlay()) { delaySelectCards.add(c); break; @@ -85,16 +79,17 @@ public abstract class InputPayMana extends InputSyncronizedBase { } } } - if (!card.getManaAbilities().isEmpty() && activateManaAbility(card)) { + if (!getAllManaAbilities(card).isEmpty() && activateManaAbility(card)) { return true; } return activateDelayedCard(); } else { + List manaAbilities = getAllManaAbilities(card); // Desktop Forge floating menu functionality - if (card.getManaAbilities().size() == 1) { - activateManaAbility(card, card.getManaAbilities().get(0)); + if (manaAbilities.size() == 1) { + activateManaAbility(card, manaAbilities.get(0)); } else { - SpellAbility spellAbility = getController().getAbilityToPlay(card, Lists.newArrayList(card.getManaAbilities()), triggerEvent); + SpellAbility spellAbility = getController().getAbilityToPlay(card, manaAbilities, triggerEvent); if (spellAbility != null) { activateManaAbility(card, spellAbility); } @@ -103,9 +98,29 @@ public abstract class InputPayMana extends InputSyncronizedBase { } } + protected List getAllManaAbilities(Card card) { + List result = Lists.newArrayList(); + for (SpellAbility sa : card.getManaAbilities()) { + result.add(sa); + result.addAll(GameActionUtil.getAlternativeCosts(sa, player)); + } + final Collection toRemove = Lists.newArrayListWithCapacity(result.size()); + for (final SpellAbility sa : result) { + sa.setActivatingPlayer(player); + // fix things like retrace + // check only if SA can't be cast normally + if (sa.canPlay(true)) { + continue; + } + toRemove.add(sa); + } + result.removeAll(toRemove); + return result; + } + @Override public String getActivateAction(Card card) { - for (SpellAbility sa : card.getManaAbilities()) { + for (SpellAbility sa : getAllManaAbilities(card)) { if (sa.canPlay()) { return "pay mana with card"; } @@ -135,6 +150,7 @@ public abstract class InputPayMana extends InputSyncronizedBase { return false; } + @Deprecated public List getUsefulManaAbilities(Card card) { List abilities = new ArrayList<>(); @@ -151,7 +167,7 @@ public abstract class InputPayMana extends InputSyncronizedBase { if (manaCost.isAnyPartPayableWith((byte) ManaAtom.GENERIC, player.getManaPool())) { colorCanUse |= ManaAtom.GENERIC; } - if (colorCanUse == 0) { // no mana cost or something + if (colorCanUse == 0) { // no mana cost or something return abilities; } @@ -160,15 +176,10 @@ public abstract class InputPayMana extends InputSyncronizedBase { return abilities; } - for (SpellAbility ma : card.getManaAbilities()) { + for (SpellAbility ma : getAllManaAbilities(card)) { ma.setActivatingPlayer(player); - AbilityManaPart m = ma.getManaPartRecursive(); - if (m == null || !ma.canPlay()) { continue; } - if (!abilityProducesManaColor(ma, m, colorCanUse)) { continue; } - if (ma.isAbility() && ma.getRestrictions().isInstantSpeed()) { continue; } - if (!m.meetsManaRestrictions(saPaidFor)) { continue; } - - abilities.add(ma); + if (ma.isManaAbilityFor(saPaidFor, colorCanUse)) + abilities.add(ma); } return abilities; } @@ -189,11 +200,8 @@ public abstract class InputPayMana extends InputSyncronizedBase { System.err.print("Should wait till previous call to playAbility finishes."); return false; } - + // make sure computer's lands aren't selected - if (card.getController() != player) { - return false; - } byte colorCanUse = 0; byte colorNeeded = 0; @@ -206,106 +214,96 @@ public abstract class InputPayMana extends InputSyncronizedBase { colorCanUse |= ManaAtom.GENERIC; } - if (colorCanUse == 0) { // no mana cost or something + if (colorCanUse == 0) { // no mana cost or something return false; } - HashMap abilitiesMap = new HashMap<>(); - // you can't remove unneeded abilities inside a for (am:abilities) loop :( - - final String typeRes = manaCost.getSourceRestriction(); - if (StringUtils.isNotBlank(typeRes) && !card.getType().hasStringType(typeRes)) { - return false; - } - - boolean guessAbilityWithRequiredColors = true; - int amountOfMana = -1; - for (SpellAbility ma : card.getManaAbilities()) { - ma.setActivatingPlayer(player); - - AbilityManaPart m = ma.getManaPartRecursive(); - if (m == null || !ma.canPlay()) { continue; } - if (!abilityProducesManaColor(ma, m, colorCanUse)) { continue; } - if (ma.isAbility() && ma.getRestrictions().isInstantSpeed()) { continue; } - if (!m.meetsManaRestrictions(saPaidFor)) { continue; } - - // If Mana Abilities produce differing amounts of mana, let the player choose - int maAmount = GameActionUtil.amountOfManaGenerated(ma, true); - if (amountOfMana == -1) { - amountOfMana = maAmount; - } else { - if (amountOfMana != maAmount) { - guessAbilityWithRequiredColors = false; - } - } - - abilitiesMap.put(ma.getView(), ma); - - // skip express mana if the ability is not undoable or reusable - if (!ma.isUndoable() || !ma.getPayCosts().isRenewableResource() || ma.getSubAbility() != null) { - guessAbilityWithRequiredColors = false; - } - } - - if (abilitiesMap.isEmpty() || (chosenAbility != null && !abilitiesMap.containsKey(chosenAbility.getView()))) { - return false; - } - - // Store some information about color costs to help with any mana choices - if (colorNeeded == 0) { // only colorless left - if (saPaidFor.getHostCard() != null && saPaidFor.getHostCard().hasSVar("ManaNeededToAvoidNegativeEffect")) { - String[] negEffects = saPaidFor.getHostCard().getSVar("ManaNeededToAvoidNegativeEffect").split(","); - for (String negColor : negEffects) { - byte col = ManaAtom.fromName(negColor); - colorCanUse |= col; - } - } - } - - // If the card has any ability that tracks mana spent, skip express Mana choice - if (saPaidFor.tracksManaSpent()) { - colorCanUse = ColorSet.ALL_COLORS.getColor(); - guessAbilityWithRequiredColors = false; - } - - boolean choice = true; - boolean isPayingGeneric = false; - if (guessAbilityWithRequiredColors) { - // express Mana Choice - if (colorNeeded == 0) { - choice = false; - //avoid unnecessary prompt by pretending we need White - //for the sake of "Add one mana of any color" effects - colorNeeded = MagicColor.WHITE; - isPayingGeneric = true; // for further processing - } - else { - final HashMap colorMatches = new HashMap<>(); - for (SpellAbility sa : abilitiesMap.values()) { - if (abilityProducesManaColor(sa, sa.getManaPartRecursive(), colorNeeded)) { - colorMatches.put(sa.getView(), sa); - } - } - - if (colorMatches.isEmpty()) { - // can only match colorless just grab the first and move on. - // This is wrong. Sometimes all abilities aren't created equal - choice = false; - } - else if (colorMatches.size() < abilitiesMap.size()) { - // leave behind only color matches - abilitiesMap = colorMatches; - } - } - } - - // Exceptions for cards that have conditional abilities which are better handled manually - if (card.getName().equals("Cavern of Souls") && isPayingGeneric) { - choice = true; - } - final SpellAbility chosen; if (chosenAbility == null) { + HashMap abilitiesMap = new HashMap<>(); + // you can't remove unneeded abilities inside a for (am:abilities) loop :( + + final String typeRes = manaCost.getSourceRestriction(); + if (StringUtils.isNotBlank(typeRes) && !card.getType().hasStringType(typeRes)) { + return false; + } + + boolean guessAbilityWithRequiredColors = true; + int amountOfMana = -1; + for (SpellAbility ma : getAllManaAbilities(card)) { + ma.setActivatingPlayer(player); + + if (!ma.isManaAbilityFor(saPaidFor, colorCanUse)) { continue; } + + // If Mana Abilities produce differing amounts of mana, let the player choose + int maAmount = ma.totalAmountOfManaGenerated(saPaidFor, true); + if (amountOfMana == -1) { + amountOfMana = maAmount; + } else { + if (amountOfMana != maAmount) { + guessAbilityWithRequiredColors = false; + } + } + + abilitiesMap.put(ma.getView(), ma); + + // skip express mana if the ability is not undoable or reusable + if (!ma.isUndoable() || !ma.getPayCosts().isRenewableResource() || ma.getSubAbility() != null + || ma.isManaCannotCounter(saPaidFor)) { + guessAbilityWithRequiredColors = false; + } + } + + if (abilitiesMap.isEmpty()) { + return false; + } + + // Store some information about color costs to help with any mana choices + if (colorNeeded == 0) { // only colorless left + if (saPaidFor.getHostCard() != null && saPaidFor.getHostCard().hasSVar("ManaNeededToAvoidNegativeEffect")) { + String[] negEffects = saPaidFor.getHostCard().getSVar("ManaNeededToAvoidNegativeEffect").split(","); + for (String negColor : negEffects) { + byte col = ManaAtom.fromName(negColor); + colorCanUse |= col; + } + } + } + + // If the card has any ability that tracks mana spent, skip express Mana choice + if (saPaidFor.tracksManaSpent()) { + colorCanUse = ColorSet.ALL_COLORS.getColor(); + guessAbilityWithRequiredColors = false; + } + + boolean choice = true; + if (guessAbilityWithRequiredColors) { + // express Mana Choice + if (colorNeeded == 0) { + choice = false; + //avoid unnecessary prompt by pretending we need White + //for the sake of "Add one mana of any color" effects + colorNeeded = MagicColor.WHITE; + } + else { + final HashMap colorMatches = new HashMap<>(); + for (SpellAbility sa : abilitiesMap.values()) { + if (sa.isManaAbilityFor(saPaidFor, colorNeeded)) { + colorMatches.put(sa.getView(), sa); + } + } + + if (colorMatches.isEmpty()) { + // can only match colorless just grab the first and move on. + // This is wrong. Sometimes all abilities aren't created equal + choice = false; + } + else if (colorMatches.size() < abilitiesMap.size()) { + // leave behind only color matches + abilitiesMap = colorMatches; + } + } + } + ArrayList choices = new ArrayList<>(abilitiesMap.keySet()); chosen = abilitiesMap.size() > 1 && choice ? abilitiesMap.get(getController().getGui().one(Localizer.getInstance().getMessage("lblChooseManaAbility"), choices)) : abilitiesMap.get(choices.get(0)); } else { @@ -317,14 +315,13 @@ public abstract class InputPayMana extends InputSyncronizedBase { // Filter the colors for the express choice so that only actually producible colors can be chosen int producedColorMask = 0; for (final byte color : ManaAtom.MANATYPES) { - if (chosen.getManaPartRecursive().getOrigProduced().contains(MagicColor.toShortString(color)) - && colors.hasAnyColor(color)) { + if (chosen.canProduce(MagicColor.toShortString(color)) && colors.hasAnyColor(color)) { producedColorMask |= color; } } ColorSet producedAndNeededColors = ColorSet.fromMask(producedColorMask); - chosen.getManaPartRecursive().setExpressChoice(producedAndNeededColors); + chosen.setManaExpressChoice(producedAndNeededColors); // System.out.println("Chosen sa=" + chosen + " of " + chosen.getHostCard() + " to pay mana"); @@ -344,62 +341,6 @@ public abstract class InputPayMana extends InputSyncronizedBase { return true; } - private static boolean abilityProducesManaColor(final SpellAbility am, AbilityManaPart m, final byte neededColor) { - if (0 != (neededColor & ManaAtom.GENERIC)) { - return true; - } - - if (m.isAnyMana()) { - return true; - } - - // check for produce mana replacement effects - they mess this up, so just use the mana ability - final Card source = am.getHostCard(); - final Player activator = am.getActivatingPlayer(); - final Game g = source.getGame(); - final Map repParams = AbilityKey.newMap(); - repParams.put(AbilityKey.Mana, m.getOrigProduced()); - repParams.put(AbilityKey.Affected, source); - repParams.put(AbilityKey.Player, activator); - repParams.put(AbilityKey.AbilityMana, am); - - for (final Player p : g.getPlayers()) { - for (final Card crd : p.getAllCards()) { - for (final ReplacementEffect replacementEffect : crd.getReplacementEffects()) { - if (replacementEffect.requirementsCheck(g) - && replacementEffect.getMode() == ReplacementType.ProduceMana - && replacementEffect.canReplace(repParams) - && replacementEffect.hasParam("ManaReplacement") - && replacementEffect.zonesCheck(g.getZoneOf(crd))) { - return true; - } - } - } - } - - if (am.getApi() == ApiType.ManaReflected) { - final Iterable reflectableColors = CardUtil.getReflectableManaColors(am); - for (final String color : reflectableColors) { - if (0 != (neededColor & ManaAtom.fromName(color))) { - return true; - } - } - } - else { - // treat special mana if it always can be paid - if (m.isSpecialMana()) { - return true; - } - String colorsProduced = m.isComboMana() ? m.getComboColors() : m.mana(); - for (final String color : colorsProduced.split(" ")) { - if (0 != (neededColor & ManaAtom.fromName(color))) { - return true; - } - } - } - return false; - } - protected boolean isAlreadyPaid() { if (manaCost.isPaid()) { bPaid = true; diff --git a/forge-gui/src/main/java/forge/match/input/InputSelectTargets.java b/forge-gui/src/main/java/forge/match/input/InputSelectTargets.java index 83c3dc9ae3e..a8b1622baed 100644 --- a/forge-gui/src/main/java/forge/match/input/InputSelectTargets.java +++ b/forge-gui/src/main/java/forge/match/input/InputSelectTargets.java @@ -1,6 +1,9 @@ package forge.match.input; +import com.google.common.base.Predicate; import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; + import forge.FThreads; import forge.game.GameEntity; import forge.game.GameObject; @@ -22,6 +25,7 @@ import forge.util.ITriggerEvent; import forge.util.TextUtil; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -33,21 +37,25 @@ public final class InputSelectTargets extends InputSyncronizedBase { private final Map targetDepth = new HashMap<>(); private final TargetRestrictions tgt; private final SpellAbility sa; + private final Collection divisionValues; private Card lastTarget = null; private boolean bCancel = false; private boolean bOk = false; private final boolean mandatory; + private Predicate filter; private static final long serialVersionUID = -1091595663541356356L; public final boolean hasCancelled() { return bCancel; } public final boolean hasPressedOk() { return bOk; } - public InputSelectTargets(final PlayerControllerHuman controller, final List choices, final SpellAbility sa, final boolean mandatory) { + public InputSelectTargets(final PlayerControllerHuman controller, final List choices, final SpellAbility sa, final boolean mandatory, Collection divisionValues, Predicate filter) { super(controller); this.choices = choices; this.tgt = sa.getTargetRestrictions(); this.sa = sa; this.mandatory = mandatory; + this.divisionValues = divisionValues; + this.filter = filter; controller.getGui().setSelectables(CardView.getCollection(choices)); final PlayerZoneUpdates zonesToUpdate = new PlayerZoneUpdates(); for (final Card c : choices) { @@ -97,7 +105,7 @@ public final class InputSelectTargets extends InputSyncronizedBase { sb.append(sa.getUniqueTargets()); } - final int maxTargets = tgt.getMaxTargets(sa.getHostCard(), sa); + final int maxTargets = sa.getMaxTargets(); final int targeted = sa.getTargets().size(); if(maxTargets > 1) { sb.append(TextUtil.concatNoSpace("\n(", String.valueOf(maxTargets - targeted), " more can be targeted)")); @@ -108,10 +116,10 @@ public final class InputSelectTargets extends InputSyncronizedBase { "(Targeting ERROR)", ""); showMessage(message, sa.getView()); - if (tgt.isDividedAsYouChoose() && tgt.getMinTargets(sa.getHostCard(), sa) == 0 && sa.getTargets().size() == 0) { + if (sa.isDividedAsYouChoose() && sa.getMinTargets() == 0 && sa.getTargets().size() == 0) { // extra logic for Divided with min targets = 0, should only work if num targets are 0 too getController().getGui().updateButtons(getOwner(), true, true, false); - } else if (!tgt.isMinTargetsChosen(sa.getHostCard(), sa) || tgt.isDividedAsYouChoose()) { + } else if (!sa.isMinTargetChosen() || sa.isDividedAsYouChoose()) { // If reached Minimum targets, enable OK button if (mandatory && tgt.hasCandidates(sa, true)) { // Player has to click on a target @@ -239,40 +247,11 @@ public final class InputSelectTargets extends InputSyncronizedBase { return false; } - if (tgt.isDividedAsYouChoose()) { - final int stillToDivide = tgt.getStillToDivide(); - int allocatedPortion = 0; - // allow allocation only if the max targets isn't reached and there are more candidates - if ((sa.getTargets().size() + 1 < tgt.getMaxTargets(sa.getHostCard(), sa)) - && (tgt.getNumCandidates(sa, true) - 1 > 0) && stillToDivide > 1) { - final ImmutableList.Builder choices = ImmutableList.builder(); - for (int i = 1; i <= stillToDivide; i++) { - choices.add(Integer.valueOf(i)); - } - String apiBasedMessage = "Distribute how much to "; - if (sa.getApi() == ApiType.DealDamage) { - apiBasedMessage = "Select how much damage to deal to "; - } - else if (sa.getApi() == ApiType.PreventDamage) { - apiBasedMessage = "Select how much damage to prevent to "; - } - else if (sa.getApi() == ApiType.PutCounter) { - apiBasedMessage = "Select how many counters to distribute to "; - } - final StringBuilder sb = new StringBuilder(); - sb.append(apiBasedMessage); - sb.append(card.toString()); - final Integer chosen = getController().getGui().oneOrNone(sb.toString(), choices.build()); - if (chosen == null) { - return true; //still return true since there was a valid choice - } - allocatedPortion = chosen; + if (sa.isDividedAsYouChoose()) { + Boolean val = onDividedAsYouChoose(card); + if (val != null) { + return val; } - else { // otherwise assign the rest of the damage/protection - allocatedPortion = stillToDivide; - } - tgt.setStillToDivide(stillToDivide - allocatedPortion); - tgt.addDividedAllocation(card, allocatedPortion); } addTarget(card); return true; @@ -305,12 +284,49 @@ public final class InputSelectTargets extends InputSyncronizedBase { showMessage(sa.getHostCard() + " - Cannot target this player (Hexproof? Protection? Restrictions?)."); return; } + if (filter != null && !filter.apply(player)) { + showMessage(sa.getHostCard() + " - Cannot target this player (Hexproof? Protection? Restrictions?)."); + return; + } - if (tgt.isDividedAsYouChoose()) { - final int stillToDivide = tgt.getStillToDivide(); + if (sa.isDividedAsYouChoose()) { + Boolean val = onDividedAsYouChoose(player); + if (val != null) { + return; + } + } + addTarget(player); + } + + protected Boolean onDividedAsYouChoose(GameObject go) { + if (divisionValues != null) { + if (divisionValues.isEmpty()) { + return false; + } + String apiBasedMessage = "Distribute how much to "; + if (sa.getApi() == ApiType.DealDamage) { + apiBasedMessage = "Select how much damage to deal to "; + } + else if (sa.getApi() == ApiType.PreventDamage) { + apiBasedMessage = "Select how much damage to prevent to "; + } + else if (sa.getApi() == ApiType.PutCounter) { + apiBasedMessage = "Select how many counters to distribute to "; + } + final StringBuilder sb = new StringBuilder(); + sb.append(apiBasedMessage); + sb.append(go.toString()); + final Integer chosen = getController().getGui().oneOrNone(sb.toString(), Lists.newArrayList(divisionValues)); + if (chosen == null) { + return true; //still return true since there was a valid choice + } + divisionValues.remove(chosen); + sa.addDividedAllocation(go, chosen); + } else { + final int stillToDivide = sa.getStillToDivide(); int allocatedPortion = 0; // allow allocation only if the max targets isn't reached and there are more candidates - if ((sa.getTargets().size() + 1 < tgt.getMaxTargets(sa.getHostCard(), sa)) && (tgt.getNumCandidates(sa, true) - 1 > 0) && stillToDivide > 1) { + if ((sa.getTargets().size() + 1 < sa.getMaxTargets()) && (tgt.getNumCandidates(sa, true) - 1 > 0) && stillToDivide > 1) { final ImmutableList.Builder choices = ImmutableList.builder(); for (int i = 1; i <= stillToDivide; i++) { choices.add(Integer.valueOf(i)); @@ -323,19 +339,18 @@ public final class InputSelectTargets extends InputSyncronizedBase { } final StringBuilder sb = new StringBuilder(); sb.append(apiBasedMessage); - sb.append(player.getName()); + sb.append(go.toString()); final Integer chosen = getController().getGui().oneOrNone(sb.toString(), choices.build()); if (null == chosen) { - return; + return true; } allocatedPortion = chosen; } else { // otherwise assign the rest of the damage/protection allocatedPortion = stillToDivide; } - tgt.setStillToDivide(stillToDivide - allocatedPortion); - tgt.addDividedAllocation(player, allocatedPortion); + sa.addDividedAllocation(go, allocatedPortion); } - addTarget(player); + return null; } private void addTarget(final GameEntity ge) { @@ -366,7 +381,7 @@ public final class InputSelectTargets extends InputSyncronizedBase { } private boolean hasAllTargets() { - return tgt.isMaxTargetsChosen(sa.getHostCard(), sa) || ( tgt.getStillToDivide() == 0 && tgt.isDividedAsYouChoose()); + return sa.isMaxTargetChosen() || (sa.isDividedAsYouChoose() && sa.getStillToDivide() == 0); } @Override diff --git a/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java b/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java index f773d7c57ab..41c0292d479 100644 --- a/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java +++ b/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java @@ -35,6 +35,7 @@ import forge.game.player.Player; import forge.game.player.PlayerController; import forge.game.spellability.*; import forge.game.zone.Zone; + import org.apache.commons.lang3.StringUtils; import java.util.Collections; @@ -207,6 +208,12 @@ public class HumanPlaySpellAbility { oldCard.getZone().remove(oldCard); fromZone.add(oldCard, zonePosition >= 0 ? Integer.valueOf(zonePosition) : null); ability.setHostCard(oldCard); + // better safe than sorry approach in case rolled back ability was copy (from addExtraKeywordCost) + for (SpellAbility sa : oldCard.getSpells()) { + sa.setHostCard(oldCard); + } + //for Chorus of the Conclave + ability.rollback(); oldCard.setBackSide(false); oldCard.setState(oldCard.getFaceupCardStateName(), true); diff --git a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java index 7b0ee78cf5c..5b02178ff66 100644 --- a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java +++ b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java @@ -1064,19 +1064,20 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont * SpellAbility, forge.card.spellability.SpellAbilityStackInstance) */ @Override - public TargetChoices chooseNewTargetsFor(final SpellAbility ability) { + public TargetChoices chooseNewTargetsFor(final SpellAbility ability, Predicate filter, boolean optional) { final SpellAbility sa = ability.isWrapper() ? ((WrappedAbility) ability).getWrappedAbility() : ability; - if (sa.getTargetRestrictions() == null) { + if (!sa.usesTargeting()) { return null; } final TargetChoices oldTarget = sa.getTargets(); final TargetSelection select = new TargetSelection(this, sa); sa.resetTargets(); - if (select.chooseTargets(oldTarget.size())) { + if (select.chooseTargets(oldTarget.size(), Lists.newArrayList(oldTarget.getDividedValues()), filter, optional)) { return sa.getTargets(); } else { + sa.setTargets(oldTarget); // Return old target, since we had to reset them above - return oldTarget; + return null; } } @@ -1798,11 +1799,8 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont player.getGame().getStackZone().add(next.getHostCard()); } // TODO check if static abilities needs to be run for things affecting the copy? - if (next.isMayChooseNewTargets() && !next.setupTargets()) { - // if targets can't be done, remove copy from existence - if (next.isSpell()) { - next.getHostCard().ceaseToExist(); - } + if (next.isMayChooseNewTargets()) { + next.setupNewTargets(player); } } player.getGame().getStack().add(next); @@ -1823,7 +1821,7 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont @Override public boolean chooseTargetsFor(final SpellAbility currentAbility) { final TargetSelection select = new TargetSelection(this, currentAbility); - return select.chooseTargets(null); + return select.chooseTargets(null, null, null, false); } @Override diff --git a/forge-gui/src/main/java/forge/player/TargetSelection.java b/forge-gui/src/main/java/forge/player/TargetSelection.java index f62b1a41e29..24cf167ed87 100644 --- a/forge-gui/src/main/java/forge/player/TargetSelection.java +++ b/forge-gui/src/main/java/forge/player/TargetSelection.java @@ -17,6 +17,8 @@ */ package forge.player; +import com.google.common.base.Predicate; +import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import forge.game.Game; @@ -25,6 +27,7 @@ import forge.game.GameEntityView; import forge.game.GameEntityViewMap; import forge.game.GameObject; import forge.game.card.Card; +import forge.game.card.CardCollection; import forge.game.card.CardUtil; import forge.game.card.CardView; import forge.game.player.PlayerView; @@ -38,6 +41,7 @@ import forge.match.input.InputSelectTargets; import forge.util.Aggregates; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -70,15 +74,15 @@ public class TargetSelection { return ability.isTrigger() || getTgt().getMandatory(); } - public final boolean chooseTargets(Integer numTargets) { + public final boolean chooseTargets(Integer numTargets, Collection divisionValues, Predicate filter, boolean optional) { if (!ability.usesTargeting()) { throw new RuntimeException("TargetSelection.chooseTargets called for ability that does not target - " + ability); } final TargetRestrictions tgt = getTgt(); // Number of targets is explicitly set only if spell is being redirected (ex. Swerve or Redirect) - final int minTargets = numTargets != null ? numTargets.intValue() : tgt.getMinTargets(ability.getHostCard(), ability); - final int maxTargets = numTargets != null ? numTargets.intValue() : tgt.getMaxTargets(ability.getHostCard(), ability); + final int minTargets = numTargets != null ? numTargets.intValue() : ability.getMinTargets(); + final int maxTargets = numTargets != null ? numTargets.intValue() : ability.getMaxTargets(); //final int maxTotalCMC = tgt.getMaxTotalCMC(ability.getHostCard(), ability); final int numTargeted = ability.getTargets().size(); final boolean isSingleZone = ability.getTargetRestrictions().isSingleZone(); @@ -92,7 +96,7 @@ public class TargetSelection { return false; } - if (this.bTargetingDone && hasEnoughTargets || hasAllTargets || tgt.isDividedAsYouChoose() && tgt.getStillToDivide() == 0) { + if (this.bTargetingDone && hasEnoughTargets || hasAllTargets || ability.isDividedAsYouChoose() && divisionValues == null && ability.getStillToDivide() == 0) { return true; } @@ -107,11 +111,10 @@ public class TargetSelection { } final List zones = tgt.getZone(); - final boolean mandatory = isMandatory() && hasCandidates; + final boolean mandatory = isMandatory() && hasCandidates && !optional; final boolean choiceResult; - final boolean random = tgt.isRandomTarget(); - if (random) { + if (tgt.isRandomTarget() && numTargets == null) { final List candidates = tgt.getAllCandidates(this.ability, true); final GameObject choice = Aggregates.random(candidates); return ability.getTargets().add(choice); @@ -122,7 +125,11 @@ public class TargetSelection { return this.chooseCardFromStack(mandatory); } else { - final List validTargets = CardUtil.getValidCardsToTarget(tgt, ability); + List validTargets = CardUtil.getValidCardsToTarget(tgt, ability); + if (filter != null) { + validTargets = new CardCollection(Iterables.filter(validTargets, filter)); + } + // single zone if (isSingleZone) { final List removeCandidates = new ArrayList<>(); @@ -149,8 +156,8 @@ public class TargetSelection { //if only one valid target card for triggered ability, auto-target that card //only do this for triggered abilities to prevent auto-targeting when user chooses //to play a spell or activat an ability - if (tgt.isDividedAsYouChoose()) { - tgt.addDividedAllocation(validTargets.get(0), tgt.getStillToDivide()); + if (ability.isDividedAsYouChoose()) { + ability.addDividedAllocation(validTargets.get(0), ability.getStillToDivide()); } return ability.getTargets().add(validTargets.get(0)); } @@ -162,7 +169,7 @@ public class TargetSelection { PlayerView playerView = controller.getLocalPlayerView(); PlayerZoneUpdates playerZoneUpdates = controller.getGui().openZones(playerView, zones, playersWithValidTargets); if (!zones.contains(ZoneType.Stack)) { - InputSelectTargets inp = new InputSelectTargets(controller, validTargets, ability, mandatory); + InputSelectTargets inp = new InputSelectTargets(controller, validTargets, ability, mandatory, divisionValues, filter); inp.showAndWait(); choiceResult = !inp.hasCancelled(); bTargetingDone = inp.hasPressedOk(); @@ -174,7 +181,7 @@ public class TargetSelection { } } // some inputs choose cards one-by-one and need to be called again - return choiceResult && chooseTargets(numTargets); + return choiceResult && chooseTargets(numTargets, divisionValues, filter, optional); } private final boolean chooseCardFromList(final List choices, final boolean targeted, final boolean mandatory) { @@ -232,7 +239,7 @@ public class TargetSelection { } final String msgDone = "[FINISH TARGETING]"; - if (this.getTgt().isMinTargetsChosen(this.ability.getHostCard(), this.ability)) { + if (ability.isMinTargetChosen()) { // is there a more elegant way of doing this? choicesFiltered.add(msgDone); } @@ -282,12 +289,12 @@ public class TargetSelection { } while(!bTargetingDone) { - if (tgt.isMaxTargetsChosen(this.ability.getHostCard(), this.ability)) { + if (ability.isMaxTargetChosen()) { bTargetingDone = true; return true; } - if (!selectOptions.contains("[FINISH TARGETING]") && tgt.isMinTargetsChosen(this.ability.getHostCard(), this.ability)) { + if (!selectOptions.contains("[FINISH TARGETING]") && ability.isMinTargetChosen()) { selectOptions.add("[FINISH TARGETING]"); } diff --git a/forge-gui/src/main/java/forge/properties/ForgeConstants.java b/forge-gui/src/main/java/forge/properties/ForgeConstants.java index 7834cdb58cf..db0919b535e 100644 --- a/forge-gui/src/main/java/forge/properties/ForgeConstants.java +++ b/forge-gui/src/main/java/forge/properties/ForgeConstants.java @@ -49,6 +49,7 @@ public final class ForgeConstants { public static final String NET_DECKS_COMMANDER_LIST_FILE = LISTS_DIR + "net-decks-commander.txt"; public static final String NET_DECKS_BRAWL_LIST_FILE = LISTS_DIR + "net-decks-brawl.txt"; public static final String BORDERLESS_CARD_LIST_FILE = LISTS_DIR + "borderlessCardList.txt"; + public static final String SKINS_LIST_FILE = LISTS_DIR + "skinsList.txt"; public static final String CHANGES_FILE = ASSETS_DIR + "README.txt"; @@ -89,9 +90,9 @@ public final class ForgeConstants { private static final String CONQUEST_DIR = RES_DIR + "conquest" + PATH_SEPARATOR; public static final String CONQUEST_PLANES_DIR = CONQUEST_DIR + "planes" + PATH_SEPARATOR; - public static final String SKINS_DIR = RES_DIR + "skins" + PATH_SEPARATOR; + public static final String BASE_SKINS_DIR = RES_DIR + "skins" + PATH_SEPARATOR; public static final String COMMON_FONTS_DIR = RES_DIR + "fonts" + PATH_SEPARATOR; - public static final String DEFAULT_SKINS_DIR = SKINS_DIR + "default" + PATH_SEPARATOR; + public static final String DEFAULT_SKINS_DIR = BASE_SKINS_DIR + "default" + PATH_SEPARATOR; //don't associate these skin files with a directory since skin directory will be determined later public static final String SPRITE_ICONS_FILE = "sprite_icons.png"; public static final String SPRITE_FOILS_FILE = "sprite_foils.png"; @@ -258,6 +259,7 @@ public final class ForgeConstants { private static final String PICS_DIR = CACHE_DIR + "pics" + PATH_SEPARATOR; public static final String DB_DIR = CACHE_DIR + "db" + PATH_SEPARATOR; public static final String FONTS_DIR = CACHE_DIR + "fonts" + PATH_SEPARATOR; + public static final String CACHE_SKINS_DIR = CACHE_DIR + "skins" + PATH_SEPARATOR; public static final String CACHE_TOKEN_PICS_DIR = PICS_DIR + "tokens" + PATH_SEPARATOR; public static final String CACHE_ICON_PICS_DIR = PICS_DIR + "icons" + PATH_SEPARATOR; public static final String CACHE_SYMBOLS_DIR = PICS_DIR + "symbols" + PATH_SEPARATOR; diff --git a/forge-gui/src/main/java/forge/sound/EventVisualizer.java b/forge-gui/src/main/java/forge/sound/EventVisualizer.java index dc4e848b4f4..1269c80fe6a 100644 --- a/forge-gui/src/main/java/forge/sound/EventVisualizer.java +++ b/forge-gui/src/main/java/forge/sound/EventVisualizer.java @@ -33,6 +33,7 @@ import forge.game.event.GameEventTokenCreated; import forge.game.event.GameEventTurnEnded; import forge.game.event.GameEventZone; import forge.game.event.IGameEventVisitor; +import forge.game.spellability.AbilityManaPart; import forge.game.spellability.SpellAbility; import forge.game.zone.ZoneType; import forge.util.TextUtil; @@ -179,10 +180,10 @@ public class EventVisualizer extends IGameEventVisitor.Base imp @Override public SoundEffectType visit(final GameEventLandPlayed event) { SoundEffectType resultSound = null; - return resultSound; + return resultSound; } - + @Override public SoundEffectType visit(GameEventZone event) { Card card = event.card; @@ -208,10 +209,13 @@ public class EventVisualizer extends IGameEventVisitor.Base imp // I want to get all real colors this land can produce - no interest in colorless or devoid StringBuilder fullManaColors = new StringBuilder(); for (final SpellAbility sa : land.getManaAbilities()) { - String currManaColor = sa.getManaPartRecursive().getOrigProduced(); - if(!"C".equals(currManaColor)) { - fullManaColors.append(currManaColor); + for (AbilityManaPart mp : sa.getAllManaParts()) { + String currManaColor = mp.getOrigProduced(); + if(!"C".equals(currManaColor)) { + fullManaColors.append(currManaColor); + } } + } // No interest if "colors together" or "alternative colors" - only interested in colors themselves fullManaColors = new StringBuilder(TextUtil.fastReplace(fullManaColors.toString()," ", ""));