diff --git a/forge-ai/src/main/java/forge/ai/AiAttackController.java b/forge-ai/src/main/java/forge/ai/AiAttackController.java index 3c5f26bb602..88c05099c95 100644 --- a/forge-ai/src/main/java/forge/ai/AiAttackController.java +++ b/forge-ai/src/main/java/forge/ai/AiAttackController.java @@ -794,6 +794,7 @@ public class AiAttackController { if (bAssault) { return prefDefender; } + // 2. attack planeswalkers List pwDefending = c.getDefendingPlaneswalkers(); if (!pwDefending.isEmpty()) { @@ -801,7 +802,7 @@ public class AiAttackController { return pwNearUlti != null ? pwNearUlti : ComputerUtilCard.getBestPlaneswalkerAI(pwDefending); } - // Get the preferred battle (prefer own battles, then ally battles) + // 3. Get the preferred battle (prefer own battles, then ally battles) final CardCollection defBattles = c.getDefendingBattles(); List ownBattleDefending = CardLists.filter(defBattles, CardPredicates.isController(ai)); List allyBattleDefending = CardLists.filter(defBattles, CardPredicates.isControlledByAnyOf(ai.getAllies())); @@ -1168,10 +1169,8 @@ public class AiAttackController { attritionalAttackers.remove(attritionalAttackers.size() - 1); } } - attackRounds += 1; - if (humanLife <= 0) { - doAttritionalAttack = true; - } + attackRounds++; + doAttritionalAttack = humanLife <= 0; } // ********************* // end attritional attack calculation @@ -1332,6 +1331,112 @@ public class AiAttackController { return aiAggression; } + private class SpellAbilityFactors { + Card attacker = null; + boolean canBeKilled = false; // indicates if the attacker can be killed + boolean canBeKilledByOne = false; // indicates if the attacker can be killed by a single blocker + boolean canKillAll = true; // indicates if the attacker can kill all single blockers + boolean canKillAllDangerous = true; // indicates if the attacker can kill all single blockers with wither or infect + boolean isWorthLessThanAllKillers = true; + boolean hasAttackEffect = false; + boolean hasCombatEffect = false; + boolean dangerousBlockersPresent = false; + boolean canTrampleOverDefenders = false; + int numberOfPossibleBlockers = 0; + int defPower = 0; + + SpellAbilityFactors(Card c) { + attacker = c; + } + + private boolean canBeBlocked() { + return numberOfPossibleBlockers > 2 + || (numberOfPossibleBlockers >= 1 && CombatUtil.canAttackerBeBlockedWithAmount(attacker, 1, defendingOpponent)) + || (numberOfPossibleBlockers == 2 && CombatUtil.canAttackerBeBlockedWithAmount(attacker, 2, defendingOpponent)); + } + + private void calculate(final List defenders, final Combat combat) { + hasAttackEffect = attacker.getSVar("HasAttackEffect").equals("TRUE") || attacker.hasKeyword(Keyword.ANNIHILATOR); + // is there a gain in attacking even when the blocker is not killed (Lifelink, Wither,...) + hasCombatEffect = attacker.getSVar("HasCombatEffect").equals("TRUE") || "Blocked".equals(attacker.getSVar("HasAttackEffect")) + || attacker.isWitherDamage() || attacker.hasKeyword(Keyword.LIFELINK) || attacker.hasKeyword(Keyword.AFFLICT); + + // contains only the defender's blockers that can actually block the attacker + CardCollection validBlockers = CardLists.filter(defenders, defender1 -> CombatUtil.canBlock(attacker, defender1)); + + canTrampleOverDefenders = attacker.hasKeyword(Keyword.TRAMPLE) && attacker.getNetCombatDamage() > Aggregates.sum(validBlockers, Card::getNetToughness); + + // used to check that CanKillAllDangerous check makes sense in context where creatures with dangerous abilities are present + dangerousBlockersPresent = Iterables.any(validBlockers, Predicates.or( + CardPredicates.hasKeyword(Keyword.WITHER), CardPredicates.hasKeyword(Keyword.INFECT), + CardPredicates.hasKeyword(Keyword.LIFELINK))); + + // total power of the defending creatures, used in predicting whether a gang block can kill the attacker + defPower = CardLists.getTotalPower(validBlockers, true, false); + + // look at the attacker in relation to the blockers to establish a + // number of factors about the attacking context that will be relevant + // to the attackers decision according to the selected strategy + for (final Card blocker : validBlockers) { + // if both isWorthLessThanAllKillers and canKillAllDangerous are false there's nothing more to check + if (isWorthLessThanAllKillers || canKillAllDangerous || numberOfPossibleBlockers < 2) { + numberOfPossibleBlockers += 1; + if (isWorthLessThanAllKillers && ComputerUtilCombat.canDestroyAttacker(ai, attacker, blocker, combat, false) + && !(attacker.hasKeyword(Keyword.UNDYING) && attacker.getCounters(CounterEnumType.P1P1) == 0)) { + canBeKilledByOne = true; // there is a single creature on the battlefield that can kill the creature + // see if the defending creature is of higher or lower + // value. We don't want to attack only to lose value + if (isWorthLessThanAllKillers && !attacker.hasSVar("SacMe") + && ComputerUtilCard.evaluateCreature(blocker) <= ComputerUtilCard.evaluateCreature(attacker)) { + isWorthLessThanAllKillers = false; + } + } + // see if this attacking creature can destroy this defender, if + // not record that it can't kill everything + if (canKillAllDangerous && !ComputerUtilCombat.canDestroyBlocker(ai, blocker, attacker, combat, false)) { + canKillAll = false; + + if (blocker.getSVar("HasCombatEffect").equals("TRUE") || blocker.getSVar("HasBlockEffect").equals("TRUE") + || blocker.hasKeyword(Keyword.WITHER) || blocker.hasKeyword(Keyword.INFECT) || blocker.hasKeyword(Keyword.LIFELINK)) { + canKillAllDangerous = false; + // there is a creature that can survive an attack from this creature + // and combat will have negative effects + } + + // Check if maybe we are too reckless in adding this attacker + if (canKillAllDangerous) { + boolean avoidAttackingIntoBlock = ai.getController().isAI() + && ((PlayerControllerAi) ai.getController()).getAi().getBooleanProperty(AiProps.TRY_TO_AVOID_ATTACKING_INTO_CERTAIN_BLOCK); + boolean attackerWillDie = defPower >= attacker.getNetToughness(); + boolean uselessAttack = !hasCombatEffect && !hasAttackEffect; + boolean noContributionToAttack = attackers.size() <= defenders.size() || attacker.getNetPower() <= 0; + + // We are attacking too recklessly if we can't kill a single blocker and: + // - our creature will die for sure (chump attack) + // - our attack will not do anything special (no attack/combat effect to proc) + // - we can't deal damage to our opponent with sheer number of attackers and/or our attacker's power is 0 or less + if (attackerWillDie || (avoidAttackingIntoBlock && uselessAttack && noContributionToAttack)) { + canKillAllDangerous = false; + } + } + } + } + } + + // performance-wise it doesn't seem worth it to check attackVigilance() instead (only includes a single niche card) + if (!attacker.hasKeyword(Keyword.VIGILANCE) && ComputerUtilCard.canBeKilledByRoyalAssassin(ai, attacker)) { + canKillAllDangerous = false; + canBeKilled = true; + canBeKilledByOne = true; + isWorthLessThanAllKillers = false; + hasCombatEffect = false; + } else if ((canKillAllDangerous || !canBeKilled) && ComputerUtilCard.canBeBlockedProfitably(defendingOpponent, attacker, true)) { + canKillAllDangerous = false; + canBeKilled = true; + } + } + } + /** *

* shouldAttack. @@ -1346,14 +1451,6 @@ public class AiAttackController { * @return a boolean. */ public final boolean shouldAttack(final Card attacker, final List defenders, final Combat combat, final GameEntity defender) { - boolean canBeKilled = false; // indicates if the attacker can be killed - boolean canBeKilledByOne = false; // indicates if the attacker can be killed by a single blocker - boolean canKillAll = true; // indicates if the attacker can kill all single blockers - boolean canKillAllDangerous = true; // indicates if the attacker can kill all single blockers with wither or infect - boolean isWorthLessThanAllKillers = true; - boolean canBeBlocked = false; - int numberOfPossibleBlockers = 0; - // Is it a creature that has a more valuable ability with a tap cost than what it can do by attacking? if (attacker.hasSVar("NonCombatPriority") && !attacker.hasKeyword(Keyword.VIGILANCE)) { // For each level of priority, enemy has to have life as much as the creature's power @@ -1364,7 +1461,7 @@ public class AiAttackController { // Check if the card actually has an ability the AI can and wants to play, if not, attacking is fine! for (SpellAbility sa : attacker.getSpellAbilities()) { // Do not attack if we can afford using the ability. - if (sa.isActivatedAbility()) { + if (sa.isActivatedAbility() && sa.getPayCosts().hasTapCost()) { if (ComputerUtilCost.canPayCost(sa, ai, false)) { return false; } @@ -1378,113 +1475,29 @@ public class AiAttackController { if (!isEffectiveAttacker(ai, attacker, combat, defender)) { return false; } - boolean hasAttackEffect = attacker.getSVar("HasAttackEffect").equals("TRUE") || attacker.hasKeyword(Keyword.ANNIHILATOR); - // is there a gain in attacking even when the blocker is not killed (Lifelink, Wither,...) - boolean hasCombatEffect = attacker.getSVar("HasCombatEffect").equals("TRUE") || "Blocked".equals(attacker.getSVar("HasAttackEffect")); - if (!hasCombatEffect) { - if (attacker.isWitherDamage() || attacker.hasKeyword(Keyword.LIFELINK) || attacker.hasKeyword(Keyword.AFFLICT)) { - hasCombatEffect = true; - } - } - - // contains only the defender's blockers that can actually block the attacker - CardCollection validBlockers = CardLists.filter(defenders, defender1 -> CombatUtil.canBlock(attacker, defender1)); - - boolean canTrampleOverDefenders = attacker.hasKeyword(Keyword.TRAMPLE) && attacker.getNetCombatDamage() > Aggregates.sum(validBlockers, Card::getNetToughness); - - // used to check that CanKillAllDangerous check makes sense in context where creatures with dangerous abilities are present - boolean dangerousBlockersPresent = Iterables.any(validBlockers, Predicates.or( - CardPredicates.hasKeyword(Keyword.WITHER), CardPredicates.hasKeyword(Keyword.INFECT), - CardPredicates.hasKeyword(Keyword.LIFELINK))); - - // total power of the defending creatures, used in predicting whether a gang block can kill the attacker - int defPower = CardLists.getTotalPower(validBlockers, true, false); - - // look at the attacker in relation to the blockers to establish a - // number of factors about the attacking context that will be relevant - // to the attackers decision according to the selected strategy - for (final Card blocker : validBlockers) { - // if both isWorthLessThanAllKillers and canKillAllDangerous are false there's nothing more to check - if (isWorthLessThanAllKillers || canKillAllDangerous || numberOfPossibleBlockers < 2) { - numberOfPossibleBlockers += 1; - if (isWorthLessThanAllKillers && ComputerUtilCombat.canDestroyAttacker(ai, attacker, blocker, combat, false) - && !(attacker.hasKeyword(Keyword.UNDYING) && attacker.getCounters(CounterEnumType.P1P1) == 0)) { - canBeKilledByOne = true; // there is a single creature on the battlefield that can kill the creature - // see if the defending creature is of higher or lower - // value. We don't want to attack only to lose value - if (isWorthLessThanAllKillers && !attacker.hasSVar("SacMe") - && ComputerUtilCard.evaluateCreature(blocker) <= ComputerUtilCard.evaluateCreature(attacker)) { - isWorthLessThanAllKillers = false; - } - } - // see if this attacking creature can destroy this defender, if - // not record that it can't kill everything - if (canKillAllDangerous && !ComputerUtilCombat.canDestroyBlocker(ai, blocker, attacker, combat, false)) { - canKillAll = false; - if (blocker.getSVar("HasCombatEffect").equals("TRUE") || blocker.getSVar("HasBlockEffect").equals("TRUE")) { - canKillAllDangerous = false; - } else { - if (blocker.hasKeyword(Keyword.WITHER) || blocker.hasKeyword(Keyword.INFECT) - || blocker.hasKeyword(Keyword.LIFELINK)) { - canKillAllDangerous = false; - // there is a creature that can survive an attack from this creature - // and combat will have negative effects - } - - // Check if maybe we are too reckless in adding this attacker - if (canKillAllDangerous) { - boolean avoidAttackingIntoBlock = ai.getController().isAI() - && ((PlayerControllerAi) ai.getController()).getAi().getBooleanProperty(AiProps.TRY_TO_AVOID_ATTACKING_INTO_CERTAIN_BLOCK); - boolean attackerWillDie = defPower >= attacker.getNetToughness(); - boolean uselessAttack = !hasCombatEffect && !hasAttackEffect; - boolean noContributionToAttack = this.attackers.size() <= defenders.size() || attacker.getNetPower() <= 0; - - // We are attacking too recklessly if we can't kill a single blocker and: - // - our creature will die for sure (chump attack) - // - our attack will not do anything special (no attack/combat effect to proc) - // - we can't deal damage to our opponent with sheer number of attackers and/or our attacker's power is 0 or less - if (attackerWillDie || (avoidAttackingIntoBlock && uselessAttack && noContributionToAttack)) { - canKillAllDangerous = false; - } - } - } - } - } - } - - if (!attacker.hasKeyword(Keyword.VIGILANCE) && ComputerUtilCard.canBeKilledByRoyalAssassin(ai, attacker)) { - canKillAllDangerous = false; - canBeKilled = true; - canBeKilledByOne = true; - isWorthLessThanAllKillers = false; - hasCombatEffect = false; - } else if ((canKillAllDangerous || !canBeKilled) && ComputerUtilCard.canBeBlockedProfitably(defendingOpponent, attacker, true)) { - canKillAllDangerous = false; - canBeKilled = true; + SpellAbilityFactors saf = new SpellAbilityFactors(attacker); + if (aiAggression != 5) { + saf.calculate(defenders, combat); } // if the creature cannot block and can kill all opponents they might as // well attack, they do nothing staying back - if (canKillAll && isWorthLessThanAllKillers && !CombatUtil.canBlock(attacker)) { + if (saf.canKillAll && saf.isWorthLessThanAllKillers && !CombatUtil.canBlock(attacker)) { if (LOG_AI_ATTACKS) System.out.println(attacker.getName() + " = attacking because they can't block, expecting to kill or damage player"); return true; - } else if (!canBeKilled && !dangerousBlockersPresent && canTrampleOverDefenders) { + } + if (!saf.canBeKilled && !saf.dangerousBlockersPresent && saf.canTrampleOverDefenders) { if (LOG_AI_ATTACKS) System.out.println(attacker.getName() + " = expecting to survive and get some Trample damage through"); return true; } - if (numberOfPossibleBlockers > 2 - || (numberOfPossibleBlockers >= 1 && CombatUtil.canAttackerBeBlockedWithAmount(attacker, 1, defendingOpponent)) - || (numberOfPossibleBlockers == 2 && CombatUtil.canAttackerBeBlockedWithAmount(attacker, 2, defendingOpponent))) { - canBeBlocked = true; - } // decide if the creature should attack based on the prevailing strategy choice in aiAggression switch (aiAggression) { case 6: // Exalted: expecting to at least kill a creature of equal value or not be blocked - if ((canKillAll && isWorthLessThanAllKillers) || !canBeBlocked) { + if ((saf.canKillAll && saf.isWorthLessThanAllKillers) || !saf.canBeBlocked()) { if (LOG_AI_ATTACKS) System.out.println(attacker.getName() + " = attacking expecting to kill creature, or is unblockable"); return true; @@ -1495,32 +1508,32 @@ public class AiAttackController { System.out.println(attacker.getName() + " = all out attacking"); return true; case 4: // expecting to at least trade with something, or can attack "for free", expecting no counterattack - if (canKillAll || (dangerousBlockersPresent && canKillAllDangerous && !canBeKilledByOne) || !canBeBlocked - || (defPower == 0 && !ComputerUtilCombat.lifeInDanger(ai, combat))) { + if (saf.canKillAll || (saf.dangerousBlockersPresent && saf.canKillAllDangerous && !saf.canBeKilledByOne) || !saf.canBeBlocked() + || saf.defPower == 0) { if (LOG_AI_ATTACKS) System.out.println(attacker.getName() + " = attacking expecting to at least trade with something"); return true; } break; case 3: // expecting to at least kill a creature of equal value or not be blocked - if ((canKillAll && isWorthLessThanAllKillers) - || (((dangerousBlockersPresent && canKillAllDangerous) || hasAttackEffect || hasCombatEffect) && !canBeKilledByOne) - || !canBeBlocked) { + if ((saf.canKillAll && saf.isWorthLessThanAllKillers) + || (((saf.dangerousBlockersPresent && saf.canKillAllDangerous) || saf.hasAttackEffect || saf.hasCombatEffect) && !saf.canBeKilledByOne) + || !saf.canBeBlocked()) { if (LOG_AI_ATTACKS) System.out.println(attacker.getName() + " = attacking expecting to kill creature or cause damage, or is unblockable"); return true; } break; case 2: // attack expecting to attract a group block or destroying a single blocker and surviving - if (!canBeBlocked || ((canKillAll || hasAttackEffect || hasCombatEffect) && !canBeKilledByOne && - ((dangerousBlockersPresent && canKillAllDangerous) || !canBeKilled))) { + if (!saf.canBeBlocked() || ((saf.canKillAll || saf.hasAttackEffect || saf.hasCombatEffect) && !saf.canBeKilledByOne && + ((saf.dangerousBlockersPresent && saf.canKillAllDangerous) || !saf.canBeKilled))) { if (LOG_AI_ATTACKS) System.out.println(attacker.getName() + " = attacking expecting to survive or attract group block"); return true; } break; case 1: // unblockable creatures only - if (!canBeBlocked || (numberOfPossibleBlockers == 1 && canKillAll && !canBeKilledByOne)) { + if (!saf.canBeBlocked() || (saf.numberOfPossibleBlockers == 1 && saf.canKillAll && !saf.canBeKilledByOne)) { if (LOG_AI_ATTACKS) System.out.println(attacker.getName() + " = attacking expecting not to be blocked"); return true; diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java index ff9583fd7c6..415443aaf1f 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java @@ -724,7 +724,6 @@ public class ComputerUtilCombat { return totalDamageOfBlockers(attacker, blockers) >= getDamageToKill(attacker, false); } - // Will this trigger trigger? /** *

* combatTriggerWillTrigger. 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 4e7bf012122..3b785ce2785 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -4097,7 +4097,6 @@ public class Card extends GameEntity implements Comparable, IHasSVars, ITr return state.getType(); } - // TODO add changed type by card text public Iterable getChangedCardTypes() { // If there are no changed types, just return an empty immutable list, which actually // produces a surprisingly large speedup by avoid lots of temp objects and making iteration @@ -5702,7 +5701,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars, ITr // Just phased in, time to run the phased in trigger getGame().getTriggerHandler().registerActiveTrigger(this, false); - getGame().getTriggerHandler().runTrigger(TriggerType.PhaseIn, runParams, false); + getGame().getTriggerHandler().runTrigger(TriggerType.PhaseIn, runParams, true); } game.updateLastStateForCard(this); diff --git a/forge-game/src/main/java/forge/game/card/CardLists.java b/forge-game/src/main/java/forge/game/card/CardLists.java index 152e349c092..3688b8668a5 100644 --- a/forge-game/src/main/java/forge/game/card/CardLists.java +++ b/forge-game/src/main/java/forge/game/card/CardLists.java @@ -414,7 +414,7 @@ public class CardLists { public static int getTotalPower(Iterable cardList, boolean ignoreNegativePower, boolean crew) { int total = 0; for (final Card crd : cardList) { - if (crew && StaticAbilityCrewValue.hasAnyCrewValue(crd)) { + if (crew) { if (StaticAbilityCrewValue.crewsWithToughness(crd)) { total += ignoreNegativePower ? Math.max(0, crd.getNetToughness()) : crd.getNetToughness(); } else { 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 e6096762792..edf4a71747e 100644 --- a/forge-game/src/main/java/forge/game/card/CardProperty.java +++ b/forge-game/src/main/java/forge/game/card/CardProperty.java @@ -1628,8 +1628,7 @@ public class CardProperty { } } if (property.startsWith("attacking ")) { // generic "attacking [DefinedGameEntity]" - FCollection defined = AbilityUtils.getDefinedEntities(source, property.split(" ")[1], - spellAbility); + FCollection defined = AbilityUtils.getDefinedEntities(source, property.split(" ")[1], spellAbility); final GameEntity defender = combat.getDefenderByAttacker(card); if (!defined.contains(defender)) { return false; diff --git a/forge-game/src/main/java/forge/game/card/CardZoneTable.java b/forge-game/src/main/java/forge/game/card/CardZoneTable.java index 5d78852277d..939341bfb94 100644 --- a/forge-game/src/main/java/forge/game/card/CardZoneTable.java +++ b/forge-game/src/main/java/forge/game/card/CardZoneTable.java @@ -100,15 +100,16 @@ public class CardZoneTable extends ForwardingTable runParams = AbilityKey.newMap(); runParams.put(AbilityKey.Cards, new CardZoneTable(this)); runParams.put(AbilityKey.Cause, cause); diff --git a/forge-game/src/main/java/forge/game/phase/Untap.java b/forge-game/src/main/java/forge/game/phase/Untap.java index 481724cd22d..868a39d0566 100644 --- a/forge-game/src/main/java/forge/game/phase/Untap.java +++ b/forge-game/src/main/java/forge/game/phase/Untap.java @@ -232,7 +232,7 @@ public class Untap extends Phase { final Map runParams = AbilityKey.newMap(); runParams.put(AbilityKey.Map, untapMap); game.getTriggerHandler().runTrigger(TriggerType.UntapAll, runParams, false); - } // end doUntap + } private static boolean optionalUntap(final Card c) { boolean untap = true; @@ -301,6 +301,10 @@ public class Untap extends Phase { runParams.put(AbilityKey.Cards, phasedOut); turn.getGame().getTriggerHandler().runTrigger(TriggerType.PhaseOutAll, runParams, false); } + if (!toPhase.isEmpty()) { + // collect now before some zone change during Untap resets triggers + turn.getGame().getTriggerHandler().collectTriggerForWaiting(); + } } private static void doDayTime(final Player previous) { diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbilityCrewValue.java b/forge-game/src/main/java/forge/game/staticability/StaticAbilityCrewValue.java index b810b8391b5..1ffd68eefa6 100644 --- a/forge-game/src/main/java/forge/game/staticability/StaticAbilityCrewValue.java +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbilityCrewValue.java @@ -8,25 +8,6 @@ public class StaticAbilityCrewValue { static String MODE = "CrewValue"; - public static boolean hasAnyCrewValue(final Card card) { - final Game game = card.getGame(); - for (final Card ca : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) { - for (final StaticAbility stAb : ca.getStaticAbilities()) { - if (!stAb.checkConditions(MODE)) { - continue; - } - if (hasAnyCrewValue(stAb, card)) { - return true; - } - } - } - return false; - } - - public static boolean hasAnyCrewValue(final StaticAbility stAb, final Card card) { - return stAb.matchesValidParam("ValidCard", card); - } - public static boolean crewsWithToughness(final Card card) { final Game game = card.getGame(); for (final Card ca : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) { diff --git a/forge-gui/res/cardsfolder/d/domesticated_mammoth.txt b/forge-gui/res/cardsfolder/d/domesticated_mammoth.txt index b96f6e33acc..68eea20e590 100644 --- a/forge-gui/res/cardsfolder/d/domesticated_mammoth.txt +++ b/forge-gui/res/cardsfolder/d/domesticated_mammoth.txt @@ -3,6 +3,6 @@ ManaCost:1 G Types:Snow Creature Mammoth PT:4/5 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigToken | Static$ True | TriggerDescription$ CARDNAME enters with a token copy of Pacifism attached to it. -SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ pacifism | TokenOwner$ You | AttachedTo$ Self +SVar:TrigToken:DB$ CopyPermanent | DefinedName$ Pacifism | AttachedTo$ Self DeckHas:Ability$Token Oracle:Domesticated Mammoth enters with a token copy of Pacifism attached to it. diff --git a/forge-gui/res/cardsfolder/e/eomer_marshal_of_rohan.txt b/forge-gui/res/cardsfolder/e/eomer_marshal_of_rohan.txt index a1ad40d23de..53137f167cd 100644 --- a/forge-gui/res/cardsfolder/e/eomer_marshal_of_rohan.txt +++ b/forge-gui/res/cardsfolder/e/eomer_marshal_of_rohan.txt @@ -3,7 +3,7 @@ ManaCost:2 R R Types:Legendary Creature Human Knight PT:4/4 K:Haste -T:Mode$ ChangesZoneAll | Origin$ Battlefield | Destination$ Graveyard | ValidCards$ Creature.attackingLKI+Legendary+Other+YouCtrl | TriggerZones$ Battlefield | Execute$ TrigUntapAll | ActivationLimit$ 1 | TriggerDescription$ Whenever one or more other attacking legendary creatures you control die, untap all creatures you control. After this phase, there is an additional combat phase. This ability triggers only once each turn. +T:Mode$ ChangesZoneAll | Origin$ Battlefield | Destination$ Graveyard | ValidCards$ Creature.attacking+Legendary+Other+YouCtrl | TriggerZones$ Battlefield | Execute$ TrigUntapAll | ActivationLimit$ 1 | TriggerDescription$ Whenever one or more other attacking legendary creatures you control die, untap all creatures you control. After this phase, there is an additional combat phase. This ability triggers only once each turn. SVar:TrigUntapAll:DB$ UntapAll | ValidCards$ Creature.YouCtrl | SubAbility$ DBAddCombat SVar:DBAddCombat:DB$ AddPhase | ExtraPhase$ Combat | AfterPhase$ EndCombat DeckHints:Type$Legendary diff --git a/forge-gui/res/cardsfolder/g/garna_bloodfist_of_keld.txt b/forge-gui/res/cardsfolder/g/garna_bloodfist_of_keld.txt index 6ef421398a0..e98d549a33b 100644 --- a/forge-gui/res/cardsfolder/g/garna_bloodfist_of_keld.txt +++ b/forge-gui/res/cardsfolder/g/garna_bloodfist_of_keld.txt @@ -6,6 +6,6 @@ T:Mode$ ChangesZone | ValidCard$ Creature.Other+YouCtrl | Origin$ Battlefield | SVar:TrigBranch:DB$ Branch | BranchConditionSVar$ X | BranchConditionSVarCompare$ GE1 | TrueSubAbility$ DBDraw | FalseSubAbility$ DBDamage SVar:DBDraw:DB$ Draw | Defined$ You | NumCards$ 1 SVar:DBDamage:DB$ DealDamage | Defined$ Opponent | NumDmg$ 1 -SVar:X:TriggeredCard$Valid Creature.attackingLKI +SVar:X:TriggeredCard$Valid Creature.attacking DeckHas:Ability$Sacrifice Oracle:Whenever another creature you control dies, draw a card if it was attacking. Otherwise, Garna, Bloodfist of Keld deals 1 damage to each opponent. diff --git a/forge-gui/res/cardsfolder/g/generated_horizons.txt b/forge-gui/res/cardsfolder/g/generated_horizons.txt index 42585923995..9114acdf112 100644 --- a/forge-gui/res/cardsfolder/g/generated_horizons.txt +++ b/forge-gui/res/cardsfolder/g/generated_horizons.txt @@ -2,5 +2,5 @@ Name:Generated Horizons ManaCost:2 G G Types:Enchantment T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigToken | TriggerDescription$ At the beginning of your upkeep, you create a Forest land token. -SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ c_l_forest | TokenOwner$ You +SVar:TrigToken:DB$ CopyPermanent | DefinedName$ Forest Oracle:At the beginning of your upkeep, create a Forest land token. diff --git a/forge-gui/res/cardsfolder/upcoming/rootcast_apprenticeship.txt b/forge-gui/res/cardsfolder/upcoming/rootcast_apprenticeship.txt index a7dfd3a0ab9..3f86c589681 100644 --- a/forge-gui/res/cardsfolder/upcoming/rootcast_apprenticeship.txt +++ b/forge-gui/res/cardsfolder/upcoming/rootcast_apprenticeship.txt @@ -2,9 +2,9 @@ Name:Rootcast Apprenticeship ManaCost:3 G Types:Sorcery A:SP$ Charm | Choices$ DBPutCounter,DBCopy,DBToken,DBSacrifice | CharmNum$ 3 | CanRepeatModes$ True -SVar:DBPutCounter:DB$ PutCounter | ValidTgts$ Creature | CounterType$ P1P1 | CounterNum$ 3 | SpellDescription$ Put three +1/+1 counters on target creature. +SVar:DBPutCounter:DB$ PutCounter | ValidTgts$ Creature | CounterType$ P1P1 | CounterNum$ 2 | SpellDescription$ Put two +1/+1 counters on target creature. SVar:DBCopy:DB$ CopyPermanent | ValidTgts$ Permanent.token+YouCtrl | TgtPrompt$ Select target token you control | SpellDescription$ Create a token that's a copy of target token you control. SVar:DBToken:DB$ Token | ValidTgts$ Player | TokenAmount$ 1 | TokenScript$ g_1_1_squirrel | StackDescription$ SpellDescription | SpellDescription$ Target player creates a 1/1 green Squirrel creature token. SVar:DBSacrifice:DB$ Sacrifice | ValidTgts$ Opponent | SacValid$ Artifact.nonToken | SpellDescription$ Target opponent sacrifices a nontoken artifact. | SacMessage$ nontoken artifact DeckHas:Ability$Counters|Token -Oracle:Choose three. You may choose the same mode more than once.\n• Put three +1/+1 counters on target creature.\n• Create a token that's a copy of target token you control.\n• Target player creates a 1/1 green Squirrel creature token.\n• Target opponent sacrifices a nontoken artifact. +Oracle:Choose three. You may choose the same mode more than once.\n• Put two +1/+1 counters on target creature.\n• Create a token that's a copy of target token you control.\n• Target player creates a 1/1 green Squirrel creature token.\n• Target opponent sacrifices a nontoken artifact. diff --git a/forge-gui/res/cardsfolder/w/waste_land.txt b/forge-gui/res/cardsfolder/w/waste_land.txt index 70a5ca435c3..d859895702c 100644 --- a/forge-gui/res/cardsfolder/w/waste_land.txt +++ b/forge-gui/res/cardsfolder/w/waste_land.txt @@ -3,6 +3,6 @@ ManaCost:no cost Types:Land A:AB$ Mana | Cost$ T | Produced$ C | SpellDescription$ Add {C}. A:AB$ Destroy | Cost$ T Sac<1/CARDNAME> | ValidTgts$ Land.nonBasic | TgtPrompt$ Select target land. | SubAbility$ DBToken | AILogic$ GhostQuarter | AITgts$ Land.nonBasic | SpellDescription$ Destroy target nonbasic land. That land's controller creates a Wastes land token. (It has {T}: Add {C}.) -SVar:DBToken:DB$ Token | TokenScript$ c_l_wastes | TokenOwner$ TargetedController +SVar:DBToken:DB$ CopyPermanent | DefinedName$ Wastes | Controller$ TargetedController DeckHas:Ability$Mana.Colorless Oracle:{T}: Add {C}.\n{T}, Sacrifice Waste Land: Destroy target nonbasic land. That land's controller creates a Wastes token. (It's a land with {T}: Add {C}.) diff --git a/forge-gui/res/tokenscripts/c_l_forest.txt b/forge-gui/res/tokenscripts/c_l_forest.txt deleted file mode 100644 index deebb5ef659..00000000000 --- a/forge-gui/res/tokenscripts/c_l_forest.txt +++ /dev/null @@ -1,4 +0,0 @@ -Name:Forest Token -ManaCost:no cost -Types:Land Forest -Oracle: diff --git a/forge-gui/res/tokenscripts/c_l_wastes.txt b/forge-gui/res/tokenscripts/c_l_wastes.txt deleted file mode 100644 index e83233f6c4e..00000000000 --- a/forge-gui/res/tokenscripts/c_l_wastes.txt +++ /dev/null @@ -1,5 +0,0 @@ -Name:Wastes Token -ManaCost:no cost -Types:Land -A:AB$ Mana | Cost$ T | Produced$ C | SpellDescription$ Add {C}. -Oracle:{T}: Add {C}. diff --git a/forge-gui/res/tokenscripts/pacifism.txt b/forge-gui/res/tokenscripts/pacifism.txt deleted file mode 100644 index 97956b075d1..00000000000 --- a/forge-gui/res/tokenscripts/pacifism.txt +++ /dev/null @@ -1,7 +0,0 @@ -Name:Pacifism -ManaCost:1 W -Types:Enchantment Aura -K:Enchant creature -A:SP$ Attach | Cost$ 1 W | ValidTgts$ Creature | AILogic$ Curse -S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddHiddenKeyword$ CARDNAME can't attack or block. | Description$ Enchanted creature can't attack or block. -Oracle:Enchant creature\nEnchanted creature can't attack or block. diff --git a/forge-gui/res/tokenscripts/role_cursed.txt b/forge-gui/res/tokenscripts/role_cursed.txt index c44c436b05f..a4f6271f85b 100644 --- a/forge-gui/res/tokenscripts/role_cursed.txt +++ b/forge-gui/res/tokenscripts/role_cursed.txt @@ -3,6 +3,6 @@ ManaCost:no cost Types:Enchantment Aura Role K:Enchant creature A:SP$ Attach | Cost$ 0 | ValidTgts$ Creature | AILogic$ Curse -S:Mode$ Continuous | Affected$ Creature.EnchantedBy | SetPower$ 1 | SetToughness$ 1 | Description$ Enchanted creature is 1/1 +S:Mode$ Continuous | Affected$ Creature.EnchantedBy | SetPower$ 1 | SetToughness$ 1 | Description$ Enchanted creature is 1/1. SVar:SacMe:2 -Oracle:Enchant Creature\nEnchanted creature is 1/1 +Oracle:Enchant Creature\nEnchanted creature is 1/1.