diff --git a/forge-game/src/main/java/forge/game/GameActionUtil.java b/forge-game/src/main/java/forge/game/GameActionUtil.java index 7cc468c6667..5e8b3c013d4 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.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import forge.card.MagicColor; @@ -41,7 +40,6 @@ import forge.game.spellability.*; import forge.game.staticability.StaticAbility; import forge.game.staticability.StaticAbilityAlternativeCost; import forge.game.staticability.StaticAbilityLayer; -import forge.game.trigger.Trigger; import forge.game.zone.Zone; import forge.game.zone.ZoneType; import forge.util.Aggregates; @@ -572,112 +570,87 @@ public final class GameActionUtil { for (KeywordInterface ki : host.getKeywords()) { final String o = ki.getOriginal(); if (o.startsWith("Casualty")) { - Trigger tr = Iterables.getFirst(ki.getTriggers(), null); - if (tr != null) { - String n = o.split(":")[1]; - if (host.wasCast() && n.equals("X")) { - CardCollectionView creatures = activator.getCreaturesInPlay(); - int max = Aggregates.max(creatures, Card::getNetPower); - n = Integer.toString(pc.chooseNumber(sa, "Choose X for Casualty", 0, max)); - } - final String casualtyCost = "Sac<1/Creature.powerGE" + n + "/creature with power " + n + - " or greater>"; - final Cost cost = new Cost(casualtyCost, false); - String str = "Pay for Casualty? " + cost.toSimpleString(); - boolean v = pc.addKeywordCost(sa, cost, ki, str); + String n = o.split(":")[1]; + if (host.wasCast() && n.equals("X")) { + CardCollectionView creatures = activator.getCreaturesInPlay(); + int max = Aggregates.max(creatures, Card::getNetPower); + n = Integer.toString(pc.chooseNumber(sa, "Choose X for Casualty", 0, max)); + } + final String casualtyCost = "Sac<1/Creature.powerGE" + n + "/creature with power " + n + + " or greater>"; + final Cost cost = new Cost(casualtyCost, false); + String str = "Pay for Casualty? " + cost.toSimpleString(); + boolean v = pc.addKeywordCost(sa, cost, ki, str); - tr.setSVar("CasualtyPaid", v ? "1" : "0"); - tr.getOverridingAbility().setSVar("CasualtyPaid", v ? "1" : "0"); - tr.setSVar("Casualty", v ? n : "0"); - tr.getOverridingAbility().setSVar("Casualty", v ? n : "0"); - - if (v) { - if (result == null) { - result = sa.copy(); - } - result.getPayCosts().add(cost); - reset = true; + if (v) { + if (result == null) { + result = sa.copy(); } + result.getPayCosts().add(cost); + reset = true; + result.setOptionalKeywordAmount(ki, Integer.valueOf(n)); } } else if (o.equals("Conspire")) { - Trigger tr = Iterables.getFirst(ki.getTriggers(), null); - if (tr != null) { - final String conspireCost = "tapXType<2/Creature.SharesColorWith/" + - "creature that shares a color with " + host.getName() + ">"; - final Cost cost = new Cost(conspireCost, false); - String str = "Pay for Conspire? " + cost.toSimpleString(); + final String conspireCost = "tapXType<2/Creature.SharesColorWith/" + + "creature that shares a color with " + host.getName() + ">"; + final Cost cost = new Cost(conspireCost, false); + String str = "Pay for Conspire? " + cost.toSimpleString(); - boolean v = pc.addKeywordCost(sa, cost, ki, str); - tr.setSVar("Conspire", v ? "1" : "0"); - - if (v) { - if (result == null) { - result = sa.copy(); - } - result.getPayCosts().add(cost); - reset = true; + if (pc.addKeywordCost(sa, cost, ki, str)) { + if (result == null) { + result = sa.copy(); } + result.getPayCosts().add(cost); + result.setOptionalKeywordAmount(ki, 1); + reset = true; } } else if (o.startsWith("Offspring")) { String[] k = o.split(":"); final Cost cost = new Cost(k[1], false); - Trigger tr = Iterables.getFirst(ki.getTriggers(), null); - if (tr != null) { - String str = "Pay for Offspring? " + cost.toSimpleString(); + String str = "Pay for Offspring? " + cost.toSimpleString(); - boolean v = pc.addKeywordCost(sa, cost, ki, str); - tr.setSVar("Offspring", v ? "1" : "0"); + boolean v = pc.addKeywordCost(sa, cost, ki, str); - if (v) { - if (result == null) { - result = sa.copy(); - } - result.getPayCosts().add(cost); - reset = true; + if (v) { + if (result == null) { + result = sa.copy(); } + result.getPayCosts().add(cost); + reset = true; + result.setOptionalKeywordAmount(ki, 1); } } else if (o.startsWith("Replicate")) { - Trigger tr = Iterables.getFirst(ki.getTriggers(), null); - if (tr != null) { - String costStr = o.split(":")[1]; - final Cost cost = new Cost(costStr, false); + String costStr = o.split(":")[1]; + final Cost cost = new Cost(costStr, false); - String str = "Choose Amount for Replicate: " + cost.toSimpleString(); + String str = "Choose Amount for Replicate: " + cost.toSimpleString(); - int v = pc.chooseNumberForKeywordCost(sa, cost, ki, str, Integer.MAX_VALUE); + int v = pc.chooseNumberForKeywordCost(sa, cost, ki, str, Integer.MAX_VALUE); - tr.setSVar("ReplicateAmount", String.valueOf(v)); - tr.getOverridingAbility().setSVar("ReplicateAmount", String.valueOf(v)); - - for (int i = 0; i < v; i++) { - if (result == null) { - result = sa.copy(); - } - result.getPayCosts().add(cost); - reset = true; + for (int i = 0; i < v; i++) { + if (result == null) { + result = sa.copy(); } + result.getPayCosts().add(cost); + reset = true; } + result.setOptionalKeywordAmount(ki, v); } else if (o.startsWith("Squad")) { - Trigger tr = Iterables.getFirst(ki.getTriggers(), null); - if (tr != null) { - String costStr = o.split(":")[1]; - final Cost cost = new Cost(costStr, false); + String costStr = o.split(":")[1]; + final Cost cost = new Cost(costStr, false); - String str = "Choose amount for Squad: " + cost.toSimpleString(); + String str = "Choose amount for Squad: " + cost.toSimpleString(); - int v = pc.chooseNumberForKeywordCost(sa, cost, ki, str, Integer.MAX_VALUE); + int v = pc.chooseNumberForKeywordCost(sa, cost, ki, str, Integer.MAX_VALUE); - tr.setSVar("SquadAmount", String.valueOf(v)); - tr.getOverridingAbility().setSVar("SquadAmount", String.valueOf(v)); - - for (int i = 0; i < v; i++) { - if (result == null) { - result = sa.copy(); - } - result.getPayCosts().add(cost); - reset = true; + for (int i = 0; i < v; i++) { + if (result == null) { + result = sa.copy(); } + result.getPayCosts().add(cost); + reset = true; } + result.setOptionalKeywordAmount(ki, v); } } 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 92082eb873a..ba70f0b1c20 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java @@ -1893,6 +1893,13 @@ public class AbilityUtils { } return doXMath(v, expr, c, ctb); } + if (sq[0].equals("hasOptionalKeywordAmount")) { + return doXMath(c.getCastSA() != null && c.getCastSA().hasOptionalKeywordAmount(ctb.getKeyword()) ? 1 : 0, expr, c, ctb); + } + + if (sq[0].equals("OptionalKeywordAmount")) { + return doXMath(c.getCastSA() != null ? c.getCastSA().getOptionalKeywordAmount(ctb.getKeyword()) : 0, expr, c, ctb); + } // Count$DevotionDual.. // Count$Devotion. 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 ba9d8171bf2..0c2e0fc3594 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java @@ -907,13 +907,16 @@ public class CardFactoryUtil { String abString = "DB$ CopySpellAbility | Defined$ TriggeredSpellAbility | MayChooseTarget$ True"; String[] k = keyword.split(":"); if (k.length > 2) { - abString = abString + " | " + k[2]; + abString += " | " + k[2]; } final Trigger casualtyTrigger = TriggerHandler.parseTrigger(trigScript, card, intrinsic); - casualtyTrigger.setOverridingAbility(AbilityFactory.getAbility(abString, card)); - casualtyTrigger.setSVar("Casualty", "0"); - casualtyTrigger.setSVar("CasualtyPaid", "0"); + SpellAbility sa = AbilityFactory.getAbility(abString, card); + sa.setSVar("CasualtyPaid", "Count$hasOptionalKeywordAmount"); + sa.setSVar("Casualty", "Count$OptionalKeywordAmount"); + casualtyTrigger.setOverridingAbility(sa); + casualtyTrigger.setSVar("CasualtyPaid", "Count$hasOptionalKeywordAmount"); + casualtyTrigger.setSVar("Casualty", "Count$OptionalKeywordAmount"); inst.addTrigger(casualtyTrigger); } else if (keyword.startsWith("Chapter")) { @@ -960,7 +963,7 @@ public class CardFactoryUtil { final Trigger conspireTrigger = TriggerHandler.parseTrigger(trigScript, card, intrinsic); conspireTrigger.setOverridingAbility(AbilityFactory.getAbility(abString, card)); - conspireTrigger.setSVar("Conspire", "0"); + conspireTrigger.setSVar("Conspire", "Count$OptionalKeywordAmount"); inst.addTrigger(conspireTrigger); } else if (keyword.startsWith("Cumulative upkeep")) { @@ -1585,14 +1588,14 @@ public class CardFactoryUtil { costDesc = "—" + costDesc; } - final String trigStr = "Mode$ ChangesZone | Destination$ Battlefield | ValidCard$ Card.Self+linkedCastTrigger | CheckSVar$ Offspring | Secondary$ True " + + final String trigStr = "Mode$ ChangesZone | Destination$ Battlefield | ValidCard$ Card.Self | CheckSVar$ Offspring | Secondary$ True " + "| TriggerDescription$ Offspring " + costDesc + " (" + inst.getReminderText() + ")"; final String effect = "DB$ CopyPermanent | Defined$ TriggeredCardLKICopy | NumCopies$ 1 | SetPower$ 1 | SetToughness$ 1"; final Trigger trigger = TriggerHandler.parseTrigger(trigStr, card, intrinsic); trigger.setOverridingAbility(AbilityFactory.getAbility(effect, card)); - trigger.setSVar("Offspring", "0"); + trigger.setSVar("Offspring", "Count$OptionalKeywordAmount"); inst.addTrigger(trigger); } else if (keyword.startsWith("Partner:")) { @@ -1743,9 +1746,9 @@ public class CardFactoryUtil { final Trigger replicateTrigger = TriggerHandler.parseTrigger(trigScript, card, intrinsic); final SpellAbility replicateAbility = AbilityFactory.getAbility(abString, card); - replicateAbility.setSVar("ReplicateAmount", "0"); + replicateAbility.setSVar("ReplicateAmount", "Count$OptionalKeywordAmount"); replicateTrigger.setOverridingAbility(replicateAbility); - replicateTrigger.setSVar("ReplicateAmount", "0"); + replicateTrigger.setSVar("ReplicateAmount", "Count$OptionalKeywordAmount"); inst.addTrigger(replicateTrigger); } else if (keyword.startsWith("Ripple")) { final String[] k = keyword.split(":"); @@ -1825,9 +1828,9 @@ public class CardFactoryUtil { final Trigger squadTrigger = TriggerHandler.parseTrigger(trigScript, card, intrinsic); final SpellAbility squadAbility = AbilityFactory.getAbility(abString, card); - squadAbility.setSVar("SquadAmount", "0"); + squadAbility.setSVar("SquadAmount", "Count$OptionalKeywordAmount"); squadTrigger.setOverridingAbility(squadAbility); - squadTrigger.setSVar("SquadAmount", "0"); + squadTrigger.setSVar("SquadAmount", "Count$OptionalKeywordAmount"); inst.addTrigger(squadTrigger); } else if (keyword.equals("Storm")) { final String actualTrigger = "Mode$ SpellCast | ValidCard$ Card.Self | TriggerZones$ Stack | Secondary$ True" 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 4136be56abe..102934cc7da 100644 --- a/forge-game/src/main/java/forge/game/card/CardProperty.java +++ b/forge-game/src/main/java/forge/game/card/CardProperty.java @@ -20,7 +20,6 @@ import forge.game.combat.AttackRequirement; import forge.game.combat.AttackingBand; import forge.game.combat.Combat; import forge.game.combat.CombatUtil; -import forge.game.keyword.KeywordInterface; import forge.game.mana.Mana; import forge.game.player.Player; import forge.game.spellability.OptionalCost; @@ -1801,25 +1800,6 @@ public class CardProperty { if (AbilityUtils.isUnlinkedFromCastSA(spellAbility, card)) { return false; } - } else if (property.equals("linkedCastTrigger")) { - if (card.getCastSA() == null) { - return false; - } - List spellCast = game.getStack().getSpellsCastThisTurn(); - int idx = spellCast.lastIndexOf(source); - if (idx == -1) { - return false; - } - boolean found = false; - for (KeywordInterface kw: spellCast.get(idx).getUnhiddenKeywords()) { - if (!Collections.disjoint(kw.getTriggers(), spellAbility.getKeyword().getTriggers())) { - found = true; - break; - } - } - if (!found) { - return false; - } } else if (property.startsWith("kicked")) { // CR 607.2i check cost is linked if (AbilityUtils.isUnlinkedFromCastSA(spellAbility, card)) { 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 7f9ffb9c904..2833a37d2d9 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java @@ -127,6 +127,8 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit private List triggerRemembered = Lists.newArrayList(); private AlternativeCost altCost = null; + private EnumSet optionalCosts = EnumSet.noneOf(OptionalCost.class); + private Table, Integer> optionalKeywordAmount = HashBasedTable.create(); private boolean aftermath = false; @@ -166,7 +168,6 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit private boolean isCastFromPlayEffect = false; - private EnumSet optionalCosts = EnumSet.noneOf(OptionalCost.class); private TargetRestrictions targetRestrictions; private TargetChoices targetChosen = new TargetChoices(); @@ -1196,6 +1197,8 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit clone.manaPart = new AbilityManaPart(clone, mapParams); } + clone.optionalKeywordAmount = HashBasedTable.create(optionalKeywordAmount); + // need to copy the damage tables if (damageMap != null) { clone.damageMap = new CardDamageMap(damageMap); @@ -2594,4 +2597,21 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit public boolean isCounterableBy(SpellAbility sa) { return true; } + + public boolean hasOptionalKeywordAmount(KeywordInterface kw) { + return this.optionalKeywordAmount.contains(kw.getKeyword(), Pair.of(kw.getIdx(), kw.getStaticId())); + } + public boolean hasOptionalKeywordAmount(Keyword kw) { + return this.optionalKeywordAmount.containsRow(kw); + } + public Set getOptionalKeywords() { + return this.optionalKeywordAmount.rowKeySet(); + } + + public int getOptionalKeywordAmount(KeywordInterface kw) { + return ObjectUtils.firstNonNull(this.optionalKeywordAmount.get(kw.getKeyword(), Pair.of(kw.getIdx(), kw.getStaticId())), 0); + } + public void setOptionalKeywordAmount(KeywordInterface kw, int amount) { + this.optionalKeywordAmount.put(kw.getKeyword(), Pair.of(kw.getIdx(), kw.getStaticId()), amount); + } }