diff --git a/forge-ai/src/main/java/forge/ai/AIDeckStatistics.java b/forge-ai/src/main/java/forge/ai/AIDeckStatistics.java index 4d02476deb0..caf6a65e1f3 100644 --- a/forge-ai/src/main/java/forge/ai/AIDeckStatistics.java +++ b/forge-ai/src/main/java/forge/ai/AIDeckStatistics.java @@ -35,14 +35,19 @@ public class AIDeckStatistics { this.numLands = numLands; } - public static AIDeckStatistics fromCardList(List cards) { + public static AIDeckStatistics fromCards(List cards) { int totalCMC = 0; int totalCount = 0; int numLands = 0; int maxCost = 0; int[] maxPips = new int[6]; int maxColoredCost = 0; - for (CardRules rules : cards) { + for (Card c : cards) { + CardRules rules = c.getRules(); + if (rules == null) { + System.err.println(c + " CardRules is null" + (c.isToken() ? "/token" : ".")); + continue; + } CardType type = rules.getType(); if (type.isLand()) { numLands += 1; @@ -80,15 +85,15 @@ public class AIDeckStatistics { } - public static AIDeckStatistics fromDeck(Deck deck) { - List rules_list = new ArrayList<>(); + public static AIDeckStatistics fromDeck(Deck deck, Player player) { + List cardlist = new ArrayList<>(); for (final Map.Entry deckEntry : deck) { switch (deckEntry.getKey()) { case Main: case Commander: for (final Map.Entry poolEntry : deckEntry.getValue()) { - CardRules rules = poolEntry.getKey().getRules(); - rules_list.add(rules); + Card card = Card.fromPaperCard(poolEntry.getKey(), player); + cardlist.add(card); } break; default: @@ -96,25 +101,25 @@ public class AIDeckStatistics { } } - return fromCardList(rules_list); + return fromCards(cardlist); } public static AIDeckStatistics fromPlayer(Player player) { Deck deck = player.getRegisteredPlayer().getDeck(); if (deck.isEmpty()) { // we're in a test or some weird match, search through the hand and library and build the decklist - List rules_list = new ArrayList<>(); + List cardlist = new ArrayList<>(); for (Card c : player.getAllCards()) { if (c.getPaperCard() == null) { continue; } - rules_list.add(c.getRules()); + cardlist.add(c); } - return fromCardList(rules_list); + return fromCards(cardlist); } - return fromDeck(deck); + return fromDeck(deck, player); } diff --git a/forge-ai/src/main/java/forge/ai/AiBlockController.java b/forge-ai/src/main/java/forge/ai/AiBlockController.java index 59f16a3a75d..aabccfc1c04 100644 --- a/forge-ai/src/main/java/forge/ai/AiBlockController.java +++ b/forge-ai/src/main/java/forge/ai/AiBlockController.java @@ -277,7 +277,7 @@ public class AiBlockController { if (mode == TriggerType.DamageDone) { if (trigger.matchesValidParam("ValidSource", attacker) - && attacker.getNetCombatDamage() > 0 + && !"False".equals(trigger.getParam("CombatDamage")) && attacker.getNetCombatDamage() > 0 && trigger.matchesValidParam("ValidTarget", combat.getDefenderByAttacker(attacker))) { value += 50; } diff --git a/forge-ai/src/main/java/forge/ai/AiController.java b/forge-ai/src/main/java/forge/ai/AiController.java index d36f180b66d..3645edf1e1d 100644 --- a/forge-ai/src/main/java/forge/ai/AiController.java +++ b/forge-ai/src/main/java/forge/ai/AiController.java @@ -807,29 +807,26 @@ public class AiController { // Account for possible Ward after the spell is fully targeted // TODO: ideally, this should be done while targeting, so that a different target can be preferred if the best // one is warded and can't be paid for. (currently it will be stuck with the target until it could pay) - if (sa.usesTargeting() && (!sa.isSpell() || CardFactoryUtil.isCounterable(host))) { - for (Card tgt : sa.getTargets().getTargetCards()) { - // TODO some older cards don't use the keyword, so check for trigger instead - if (tgt.hasKeyword(Keyword.WARD) && tgt.isInPlay() && tgt.getController().isOpponentOf(host.getController())) { - int amount = 0; - Cost wardCost = ComputerUtilCard.getTotalWardCost(tgt); - if (wardCost.hasManaCost()) { - amount = wardCost.getTotalMana().getCMC(); - if (amount > 0 && !ComputerUtilCost.canPayCost(sa, player, true)) { - return AiPlayDecision.CantAfford; + if (!sa.isSpell() || CardFactoryUtil.isCounterable(host)) { + for (TargetChoices tc : sa.getAllTargetChoices()) { + for (Card tgt : tc.getTargetCards()) { + // TODO some older cards don't use the keyword, so check for trigger instead + if (tgt.hasKeyword(Keyword.WARD) && tgt.isInPlay() && tgt.getController().isOpponentOf(host.getController())) { + Cost wardCost = ComputerUtilCard.getTotalWardCost(tgt); + if (wardCost.hasManaCost()) { + xCost = wardCost.getTotalMana().getCMC() > 0; + } + SpellAbilityAi topAI = new SpellAbilityAi() {}; + if (!topAI.willPayCosts(player, sa, wardCost, host)) { + return AiPlayDecision.CostNotAcceptable; } - } - SpellAbilityAi topAI = new SpellAbilityAi() { - }; - if (!topAI.willPayCosts(player, sa, wardCost, host)) { - return AiPlayDecision.CostNotAcceptable; } } } } // check if some target raised cost - if (oldCMC > -1) { + if (!xCost && oldCMC > -1) { int finalCMC = CostAdjustment.adjust(sa.getPayCosts(), sa).getTotalMana().getCMC(); if (finalCMC > oldCMC) { xCost = true; @@ -1332,10 +1329,7 @@ public class AiController { if (sa == null) { return null; } - - final List abilities = Lists.newArrayList(); - abilities.add(sa); - return abilities; + return Lists.newArrayList(sa); } public List chooseSpellAbilityToPlay() { diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java index 439dbc05cdc..c408f45f413 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java @@ -1720,14 +1720,10 @@ public class ComputerUtil { return threatened; } } else { - objects = topStack.getTargets(); final List canBeTargeted = new ArrayList<>(); - for (Object o : objects) { - if (o instanceof GameEntity) { - final GameEntity ge = (GameEntity) o; - if (ge.canBeTargetedBy(topStack)) { - canBeTargeted.add(ge); - } + for (GameEntity ge : topStack.getTargets().getTargetEntities()) { + if (ge.canBeTargetedBy(topStack)) { + canBeTargeted.add(ge); } } if (canBeTargeted.isEmpty()) { diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java index f2ca7b1972f..850c8f4f5cf 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java @@ -1865,7 +1865,6 @@ public class ComputerUtilCard { */ public static boolean canPumpAgainstRemoval(Player ai, SpellAbility sa) { final List objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa, true); - final CardCollection threatenedTargets = new CardCollection(); if (!sa.usesTargeting()) { final List cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa); @@ -1877,14 +1876,11 @@ public class ComputerUtilCard { // For pumps without targeting restrictions, just return immediately until this is fleshed out. return false; } - CardCollection targetables = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa); - targetables = ComputerUtil.getSafeTargets(ai, sa, targetables); - for (final Card c : targetables) { - if (objects.contains(c)) { - threatenedTargets.add(c); - } - } + CardCollection threatenedTargets = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa); + threatenedTargets = ComputerUtil.getSafeTargets(ai, sa, threatenedTargets); + threatenedTargets.retainAll(objects); + if (!threatenedTargets.isEmpty()) { sortByEvaluateCreature(threatenedTargets); for (Card c : threatenedTargets) { diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java index db2f505d62f..22a80476315 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java @@ -673,7 +673,6 @@ public class ComputerUtilCombat { && !blocker.hasKeyword(Keyword.INDESTRUCTIBLE)) { return 0; } - } // flanking final int defBushidoMagnitude = blocker.getKeywordMagnitude(Keyword.BUSHIDO); @@ -859,7 +858,7 @@ public class ComputerUtilCombat { } } else if (mode == TriggerType.DamageDone) { willTrigger = true; - if (trigger.hasParam("ValidSource")) { + if (trigger.hasParam("ValidSource") && !"False".equals(trigger.getParam("CombatDamage"))) { if (!(trigger.matchesValidParam("ValidSource", defender) && defender.getNetCombatDamage() > 0 && trigger.matchesValidParam("ValidTarget", attacker))) { @@ -2461,7 +2460,7 @@ public class ComputerUtilCombat { CardCollectionView trigCards = attacker.getController().getCardsIn(ZoneType.Battlefield); for (Card c : trigCards) { for (Trigger t : c.getTriggers()) { - if (t.getMode() == TriggerType.DamageDone && "True".equals(t.getParam("CombatDamage")) && t.matchesValidParam("ValidSource", attacker)) { + if (t.getMode() == TriggerType.DamageDone && !"False".equals(t.getParam("CombatDamage")) && t.matchesValidParam("ValidSource", attacker)) { SpellAbility ab = t.getOverridingAbility(); if (ab.getApi() == ApiType.Poison && "TriggeredTarget".equals(ab.getParam("Defined"))) { pd += AbilityUtils.calculateAmount(attacker, ab.getParam("Num"), ab); diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java index f9ca43b15a5..ab5f6ee1406 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java @@ -22,6 +22,7 @@ import forge.game.phase.PhaseType; import forge.game.player.Player; import forge.game.spellability.Spell; import forge.game.spellability.SpellAbility; +import forge.game.spellability.TargetChoices; import forge.game.zone.ZoneType; import forge.util.MyRandom; import forge.util.TextUtil; @@ -556,12 +557,14 @@ public class ComputerUtilCost { } // Ward - will be accounted for when rechecking a targeted ability - if (!sa.isTrigger() && sa.usesTargeting() && (!sa.isSpell() || !cannotBeCountered)) { - for (Card tgt : sa.getTargets().getTargetCards()) { - if (tgt.hasKeyword(Keyword.WARD) && tgt.isInPlay() && tgt.getController().isOpponentOf(sa.getHostCard().getController())) { - Cost wardCost = ComputerUtilCard.getTotalWardCost(tgt); - if (wardCost.hasManaCost()) { - extraManaNeeded += wardCost.getTotalMana().getCMC(); + if (!sa.isTrigger() && (!sa.isSpell() || !cannotBeCountered)) { + for (TargetChoices tc : sa.getAllTargetChoices()) { + for (Card tgt : tc.getTargetCards()) { + if (tgt.hasKeyword(Keyword.WARD) && tgt.isInPlay() && tgt.getController().isOpponentOf(sa.getHostCard().getController())) { + Cost wardCost = ComputerUtilCard.getTotalWardCost(tgt); + if (wardCost.hasManaCost()) { + extraManaNeeded += wardCost.getTotalMana().getCMC(); + } } } } diff --git a/forge-ai/src/main/java/forge/ai/SpecialCardAi.java b/forge-ai/src/main/java/forge/ai/SpecialCardAi.java index 92385a56eee..04eae1847b4 100644 --- a/forge-ai/src/main/java/forge/ai/SpecialCardAi.java +++ b/forge-ai/src/main/java/forge/ai/SpecialCardAi.java @@ -22,6 +22,7 @@ import com.google.common.base.Predicates; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import forge.ai.ability.AnimateAi; +import forge.ai.ability.FightAi; import forge.card.ColorSet; import forge.card.MagicColor; import forge.card.mana.ManaCost; @@ -79,6 +80,49 @@ import java.util.List; */ public class SpecialCardAi { + // Arena and Magus of the Arena + public static class Arena { + public static boolean consider(final Player ai, final SpellAbility sa) { + final Game game = ai.getGame(); + + if (!game.getPhaseHandler().is(PhaseType.END_OF_TURN) || game.getPhaseHandler().getNextTurn() != ai) { + return false; // at opponent's EOT only, to conserve mana + } + + CardCollection aiCreatures = ai.getCreaturesInPlay(); + if (aiCreatures.isEmpty()) { + return false; + } + + for (Player opp : ai.getOpponents()) { + CardCollection oppCreatures = opp.getCreaturesInPlay(); + if (oppCreatures.isEmpty()) { + continue; + } + + for (Card aiCreature : aiCreatures) { + boolean canKillAll = true; + for (Card oppCreature : oppCreatures) { + if (FightAi.canKill(oppCreature, aiCreature, 0)) { + canKillAll = false; + break; + } + if (!FightAi.canKill(aiCreature, oppCreature, 0)) { + canKillAll = false; + break; + } + } + if (canKillAll) { + sa.getTargets().clear(); + sa.getTargets().add(aiCreature); + return true; + } + } + } + return sa.isTargetNumberValid(); + } + } + // Black Lotus and Lotus Bloom public static class BlackLotus { public static boolean consider(final Player ai, final SpellAbility sa, final ManaCostBeingPaid cost) { 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 0f28bdd4894..931baf986f7 100644 --- a/forge-ai/src/main/java/forge/ai/ability/AnimateAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/AnimateAi.java @@ -86,7 +86,7 @@ public class AnimateAi extends SpellAbilityAi { } } // Don't use instant speed animate abilities before AI's COMBAT_BEGIN - if (!ph.is(PhaseType.COMBAT_BEGIN) && ph.isPlayerTurn(ai) && !SpellAbilityAi.isSorcerySpeed(sa, ai) + if (!ph.is(PhaseType.COMBAT_BEGIN) && ph.isPlayerTurn(ai) && !isSorcerySpeed(sa, ai) && !sa.hasParam("ActivationPhases") && !"Permanent".equals(sa.getParam("Duration"))) { return false; } @@ -174,7 +174,7 @@ public class AnimateAi extends SpellAbilityAi { } } - if (!SpellAbilityAi.isSorcerySpeed(sa, aiPlayer) && !"Permanent".equals(sa.getParam("Duration"))) { + if (!isSorcerySpeed(sa, aiPlayer) && !"Permanent".equals(sa.getParam("Duration"))) { if (sa.hasParam("Crew") && c.isCreature()) { // Do not try to crew a vehicle which is already a creature return false; 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 a223fddb600..0a18dc6cda1 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java @@ -1069,7 +1069,7 @@ public class ChangeZoneAi extends SpellAbilityAi { } if (!immediately && (!game.getPhaseHandler().getNextTurn().equals(ai) || game.getPhaseHandler().getPhase().isBefore(PhaseType.END_OF_TURN)) - && !sa.hasParam("PlayerTurn") && !SpellAbilityAi.isSorcerySpeed(sa, ai) + && !sa.hasParam("PlayerTurn") && !isSorcerySpeed(sa, ai) && !ComputerUtil.activateForCost(sa, ai)) { return false; } diff --git a/forge-ai/src/main/java/forge/ai/ability/CloneAi.java b/forge-ai/src/main/java/forge/ai/ability/CloneAi.java index 30534590125..05a411a3299 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CloneAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CloneAi.java @@ -242,7 +242,7 @@ public class CloneAi extends SpellAbilityAi { // don't use instant speed clone abilities outside computers // Combat_Begin step if (!ph.is(PhaseType.COMBAT_BEGIN) - && ph.isPlayerTurn(ai) && !SpellAbilityAi.isSorcerySpeed(sa, ai) + && ph.isPlayerTurn(ai) && !isSorcerySpeed(sa, ai) && !sa.hasParam("ActivationPhases") && sa.hasParam("Duration")) { return false; } diff --git a/forge-ai/src/main/java/forge/ai/ability/CounterAi.java b/forge-ai/src/main/java/forge/ai/ability/CounterAi.java index 669e89903f9..8c974f8c66a 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CounterAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CounterAi.java @@ -123,7 +123,7 @@ public class CounterAi extends SpellAbilityAi { if (toPay <= usableManaSources) { // If this is a reusable Resource, feel free to play it most of the time - if (!SpellAbilityAi.playReusable(ai, sa)) { + if (!playReusable(ai, sa)) { return false; } } @@ -287,7 +287,7 @@ public class CounterAi extends SpellAbilityAi { if (toPay <= usableManaSources) { // If this is a reusable Resource, feel free to play it most // of the time - if (!SpellAbilityAi.playReusable(ai,sa) || (MyRandom.getRandom().nextFloat() < .4)) { + if (!playReusable(ai,sa) || (MyRandom.getRandom().nextFloat() < .4)) { return false; } } diff --git a/forge-ai/src/main/java/forge/ai/ability/CountersMoveAi.java b/forge-ai/src/main/java/forge/ai/ability/CountersMoveAi.java index ba109e0fe70..6ff4caa7212 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CountersMoveAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CountersMoveAi.java @@ -36,7 +36,7 @@ public class CountersMoveAi extends SpellAbilityAi { } } - if (!SpellAbilityAi.playReusable(ai, sa)) { + if (!playReusable(ai, sa)) { return false; } 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 232080c5dde..fc808c6e874 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java @@ -437,7 +437,7 @@ public class CountersPutAi extends CountersAi { } if (sa.usesTargeting()) { - if (!ai.getGame().getStack().isEmpty() && !SpellAbilityAi.isSorcerySpeed(sa, ai)) { + if (!ai.getGame().getStack().isEmpty() && !isSorcerySpeed(sa, ai)) { // only evaluates case where all tokens are placed on a single target if (sa.getMinTargets() < 2) { if (ComputerUtilCard.canPumpAgainstRemoval(ai, sa)) { @@ -550,7 +550,7 @@ public class CountersPutAi extends CountersAi { if (sa.isCurse()) { choice = chooseCursedTarget(list, type, amount, ai); } else { - if (type.equals("P1P1") && !SpellAbilityAi.isSorcerySpeed(sa, ai)) { + if (type.equals("P1P1") && !isSorcerySpeed(sa, ai)) { for (Card c : list) { if (ComputerUtilCard.shouldPumpCard(ai, sa, c, amount, amount, Lists.newArrayList())) { choice = c; @@ -623,7 +623,7 @@ public class CountersPutAi extends CountersAi { return false; } // Instant +1/+1 - if (type.equals("P1P1") && !SpellAbilityAi.isSorcerySpeed(sa, ai)) { + if (type.equals("P1P1") && !isSorcerySpeed(sa, ai)) { if (!(ph.getNextTurn() == ai && ph.is(PhaseType.END_OF_TURN) && abCost.isReusuableResource())) { return false; // only if next turn and cost is reusable } diff --git a/forge-ai/src/main/java/forge/ai/ability/CountersPutAllAi.java b/forge-ai/src/main/java/forge/ai/ability/CountersPutAllAi.java index 99bbd790199..b256e8a376e 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CountersPutAllAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CountersPutAllAi.java @@ -137,7 +137,7 @@ public class CountersPutAllAi extends SpellAbilityAi { } } - if (SpellAbilityAi.playReusable(ai, sa)) { + if (playReusable(ai, sa)) { return chance; } diff --git a/forge-ai/src/main/java/forge/ai/ability/DamageAiBase.java b/forge-ai/src/main/java/forge/ai/ability/DamageAiBase.java index dc2a3632c1f..a9032a6d4ed 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DamageAiBase.java +++ b/forge-ai/src/main/java/forge/ai/ability/DamageAiBase.java @@ -1,14 +1,11 @@ package forge.ai.ability; -import com.google.common.collect.Iterables; - import forge.ai.ComputerUtil; import forge.ai.ComputerUtilCombat; import forge.ai.SpellAbilityAi; import forge.game.Game; import forge.game.card.Card; import forge.game.card.CardCollectionView; -import forge.game.card.CardPredicates; import forge.game.keyword.Keyword; import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseType; @@ -46,11 +43,6 @@ public abstract class DamageAiBase extends SpellAbilityAi { } protected boolean shouldTgtP(final Player comp, final SpellAbility sa, final int d, final boolean noPrevention) { - // TODO: once the "planeswalker redirection" rule is removed completely, just remove this code and - // remove the "burn Planeswalkers" code in the called method. - return shouldTgtP(comp, sa, d, noPrevention, false); - } - protected boolean shouldTgtP(final Player comp, final SpellAbility sa, final int d, final boolean noPrevention, final boolean noPlaneswalkerRedirection) { int restDamage = d; final Game game = comp.getGame(); Player enemy = comp.getWeakestOpponent(); @@ -89,13 +81,6 @@ public abstract class DamageAiBase extends SpellAbilityAi { } } - // burn Planeswalkers - // TODO: Must be removed completely when the "planeswalker redirection" rule is removed. - if (!noPlaneswalkerRedirection - && Iterables.any(enemy.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANESWALKERS)) { - return true; - } - if (avoidTargetP(comp, sa)) { return false; } @@ -131,7 +116,7 @@ public abstract class DamageAiBase extends SpellAbilityAi { // chance to burn player based on current hand size if (hand.size() > 2) { float value = 0; - if (SpellAbilityAi.isSorcerySpeed(sa, comp)) { + if (isSorcerySpeed(sa, comp)) { //lower chance for sorcery as other spells may be cast in main2 if (phase.isPlayerTurn(comp) && phase.is(PhaseType.MAIN2)) { value = 1.0f * restDamage / enemy.getLife(); 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 9df9721744d..0bfc1d85d43 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java @@ -1,28 +1,10 @@ package forge.ai.ability; -import java.util.Arrays; -import java.util.List; -import java.util.Map; - -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.tuple.Pair; - import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.Lists; import com.google.common.collect.Maps; - -import forge.ai.AiController; -import forge.ai.AiProps; -import forge.ai.ComputerUtil; -import forge.ai.ComputerUtilAbility; -import forge.ai.ComputerUtilCard; -import forge.ai.ComputerUtilCombat; -import forge.ai.ComputerUtilCost; -import forge.ai.ComputerUtilMana; -import forge.ai.PlayerControllerAi; -import forge.ai.SpecialCardAi; -import forge.ai.SpellAbilityAi; +import forge.ai.*; import forge.card.MagicColor; import forge.card.mana.ManaCost; import forge.game.Game; @@ -30,11 +12,7 @@ import forge.game.GameEntity; import forge.game.GameObject; import forge.game.ability.AbilityUtils; import forge.game.ability.ApiType; -import forge.game.card.Card; -import forge.game.card.CardCollection; -import forge.game.card.CardLists; -import forge.game.card.CardPredicates; -import forge.game.card.CounterEnumType; +import forge.game.card.*; import forge.game.cost.Cost; import forge.game.cost.CostPartMana; import forge.game.keyword.Keyword; @@ -51,6 +29,12 @@ import forge.game.staticability.StaticAbilityMustTarget; import forge.game.zone.ZoneType; import forge.util.Aggregates; import forge.util.MyRandom; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.Pair; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; public class DamageDealAi extends DamageAiBase { @Override @@ -630,7 +614,7 @@ public class DamageDealAi extends DamageAiBase { if (tgt.canTgtPlaneswalker()) { // We can damage planeswalkers with this, consider targeting. Card c = dealDamageChooseTgtPW(ai, sa, dmg, noPrevention, enemy, false); - if (c != null && !shouldTgtP(ai, sa, dmg, noPrevention, true)) { + if (c != null && !shouldTgtP(ai, sa, dmg, noPrevention)) { tcs.add(c); if (divided) { int assignedDamage = ComputerUtilCombat.getEnoughDamageToKill(c, dmg, source, false, noPrevention); @@ -746,17 +730,17 @@ public class DamageDealAi extends DamageAiBase { } return false; } - // TODO: Improve Damage, we shouldn't just target the player just because we can if (sa.canTarget(enemy) && sa.canAddMoreTarget()) { - if (((phase.is(PhaseType.END_OF_TURN) && phase.getNextTurn().equals(ai)) - || (SpellAbilityAi.isSorcerySpeed(sa, ai) && phase.is(PhaseType.MAIN2)) - || ("PingAfterAttack".equals(logic) && phase.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) && phase.isPlayerTurn(ai)) - || immediately || shouldTgtP(ai, sa, dmg, noPrevention)) && - (!avoidTargetP(ai, sa))) { - tcs.add(enemy); - if (divided) { - sa.addDividedAllocation(enemy, dmg); - break; + if ((phase.is(PhaseType.END_OF_TURN) && phase.getNextTurn().equals(ai)) + || (isSorcerySpeed(sa, ai) && phase.is(PhaseType.MAIN2)) + || immediately) { + boolean pingAfterAttack = "PingAfterAttack".equals(logic) && phase.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) && phase.isPlayerTurn(ai); + if ((pingAfterAttack && !avoidTargetP(ai, sa)) || shouldTgtP(ai, sa, dmg, noPrevention)) { + tcs.add(enemy); + if (divided) { + sa.addDividedAllocation(enemy, dmg); + break; + } } } } @@ -836,7 +820,7 @@ public class DamageDealAi extends DamageAiBase { if (!positive && !(saMe instanceof AbilitySub)) { return false; } - if (!urgent && !SpellAbilityAi.playReusable(ai, saMe)) { + if (!urgent && !playReusable(ai, saMe)) { return false; } return true; diff --git a/forge-ai/src/main/java/forge/ai/ability/DayTimeAi.java b/forge-ai/src/main/java/forge/ai/ability/DayTimeAi.java index e114d50332e..d390d9214af 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DayTimeAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DayTimeAi.java @@ -16,7 +16,7 @@ public class DayTimeAi extends SpellAbilityAi { if ((sa.getHostCard().isCreature() && sa.getPayCosts().hasTapCost()) || sa.getPayCosts().hasManaCost()) { // If it involves a cost that may put us at a disadvantage, better activate before own turn if possible - if (!SpellAbilityAi.isSorcerySpeed(sa, aiPlayer)) { + if (!isSorcerySpeed(sa, aiPlayer)) { return ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn() == aiPlayer; } else { return ph.is(PhaseType.MAIN2, aiPlayer); // Give other things a chance to be cast (e.g. Celestus) diff --git a/forge-ai/src/main/java/forge/ai/ability/DebuffAi.java b/forge-ai/src/main/java/forge/ai/ability/DebuffAi.java index 02fc95813d7..b491dab0b6f 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DebuffAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DebuffAi.java @@ -60,7 +60,7 @@ public class DebuffAi extends SpellAbilityAi { || !game.getStack().isEmpty()) { // Instant-speed pumps should not be cast outside of combat when the // stack is empty, unless there are specific activation phase requirements - if (!SpellAbilityAi.isSorcerySpeed(sa, ai) && !sa.hasParam("ActivationPhases")) { + if (!isSorcerySpeed(sa, ai) && !sa.hasParam("ActivationPhases")) { return false; } } diff --git a/forge-ai/src/main/java/forge/ai/ability/DestroyAi.java b/forge-ai/src/main/java/forge/ai/ability/DestroyAi.java index 61026745190..829e27d5de1 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DestroyAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DestroyAi.java @@ -174,7 +174,7 @@ public class DestroyAi extends SpellAbilityAi { if (CardLists.getNotType(list, "Creature").isEmpty()) { list = ComputerUtilCard.prioritizeCreaturesWorthRemovingNow(ai, list, false); } - if (!SpellAbilityAi.playReusable(ai, sa)) { + if (!playReusable(ai, sa)) { list = CardLists.filter(list, Predicates.not(CardPredicates.hasCounter(CounterEnumType.SHIELD, 1))); list = CardLists.filter(list, new Predicate() { diff --git a/forge-ai/src/main/java/forge/ai/ability/DigAi.java b/forge-ai/src/main/java/forge/ai/ability/DigAi.java index c8e8e2b8c76..ef23477cf72 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DigAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DigAi.java @@ -101,13 +101,13 @@ public class DigAi extends SpellAbilityAi { } } - if (SpellAbilityAi.playReusable(ai, sa)) { + if (playReusable(ai, sa)) { return true; } if ((!game.getPhaseHandler().getNextTurn().equals(ai) || game.getPhaseHandler().getPhase().isBefore(PhaseType.END_OF_TURN)) - && !sa.hasParam("PlayerTurn") && !SpellAbilityAi.isSorcerySpeed(sa, ai) + && !sa.hasParam("PlayerTurn") && !isSorcerySpeed(sa, ai) && (ai.getCardsIn(ZoneType.Hand).size() > 1 || game.getPhaseHandler().getPhase().isBefore(PhaseType.DRAW)) && !ComputerUtil.activateForCost(sa, ai)) { return false; diff --git a/forge-ai/src/main/java/forge/ai/ability/DigMultipleAi.java b/forge-ai/src/main/java/forge/ai/ability/DigMultipleAi.java index 4c63daf2d02..8945350c112 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DigMultipleAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DigMultipleAi.java @@ -62,13 +62,13 @@ public class DigMultipleAi extends SpellAbilityAi { return false; } - if (SpellAbilityAi.playReusable(ai, sa)) { + if (playReusable(ai, sa)) { return true; } if ((!game.getPhaseHandler().getNextTurn().equals(ai) || game.getPhaseHandler().getPhase().isBefore(PhaseType.END_OF_TURN)) - && !sa.hasParam("PlayerTurn") && !SpellAbilityAi.isSorcerySpeed(sa, ai) + && !sa.hasParam("PlayerTurn") && !isSorcerySpeed(sa, ai) && (ai.getCardsIn(ZoneType.Hand).size() > 1 || game.getPhaseHandler().getPhase().isBefore(PhaseType.DRAW)) && !ComputerUtil.activateForCost(sa, ai)) { return false; diff --git a/forge-ai/src/main/java/forge/ai/ability/DigUntilAi.java b/forge-ai/src/main/java/forge/ai/ability/DigUntilAi.java index 968b6473ee1..2c4dabd9ee2 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DigUntilAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DigUntilAi.java @@ -23,7 +23,7 @@ public class DigUntilAi extends SpellAbilityAi { Card source = sa.getHostCard(); final String logic = sa.getParamOrDefault("AILogic", ""); double chance = .4; // 40 percent chance with instant speed stuff - if (SpellAbilityAi.isSorcerySpeed(sa, ai)) { + if (isSorcerySpeed(sa, ai)) { chance = .667; // 66.7% chance for sorcery speed (since it will // never activate EOT) } diff --git a/forge-ai/src/main/java/forge/ai/ability/DrawAi.java b/forge-ai/src/main/java/forge/ai/ability/DrawAi.java index b3fc55fb51a..a1f0d31e9ec 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DrawAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DrawAi.java @@ -168,7 +168,7 @@ public class DrawAi extends SpellAbilityAi { @Override protected boolean checkPhaseRestrictions(Player ai, SpellAbility sa, PhaseHandler ph, String logic) { if ((!ph.getNextTurn().equals(ai) || ph.getPhase().isBefore(PhaseType.END_OF_TURN)) - && !sa.hasParam("PlayerTurn") && !SpellAbilityAi.isSorcerySpeed(sa, ai) + && !sa.hasParam("PlayerTurn") && !isSorcerySpeed(sa, ai) && ai.getCardsIn(ZoneType.Hand).size() > 1 && !ComputerUtil.activateForCost(sa, ai) && !"YawgmothsBargain".equals(logic)) { return false; 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 265b74cc85f..d0022ccffe0 100644 --- a/forge-ai/src/main/java/forge/ai/ability/FightAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/FightAi.java @@ -30,7 +30,6 @@ public class FightAi extends SpellAbilityAi { // everything is defined or targeted above, can't do anything there unless a specific logic is set if (sa.hasParam("Defined") && !sa.usesTargeting()) { - // TODO extend Logic for cards like Arena return true; } diff --git a/forge-ai/src/main/java/forge/ai/ability/FlipOntoBattlefieldAi.java b/forge-ai/src/main/java/forge/ai/ability/FlipOntoBattlefieldAi.java index 3fcee8a8341..f8317f8c23d 100644 --- a/forge-ai/src/main/java/forge/ai/ability/FlipOntoBattlefieldAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/FlipOntoBattlefieldAi.java @@ -20,7 +20,7 @@ public class FlipOntoBattlefieldAi extends SpellAbilityAi { PhaseHandler ph = sa.getHostCard().getGame().getPhaseHandler(); String logic = sa.getParamOrDefault("AILogic", ""); - if (!SpellAbilityAi.isSorcerySpeed(sa, aiPlayer) && sa.getPayCosts().hasManaCost()) { + if (!isSorcerySpeed(sa, aiPlayer) && sa.getPayCosts().hasManaCost()) { return ph.is(PhaseType.END_OF_TURN); } 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 0432459e431..02e9bed7a78 100644 --- a/forge-ai/src/main/java/forge/ai/ability/LifeGainAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/LifeGainAi.java @@ -111,7 +111,7 @@ public class LifeGainAi extends SpellAbilityAi { return lifeCritical || activateForCost || (ph.getNextTurn().equals(ai) && !ph.getPhase().isBefore(PhaseType.END_OF_TURN)) - || sa.hasParam("PlayerTurn") || SpellAbilityAi.isSorcerySpeed(sa, ai); + || sa.hasParam("PlayerTurn") || isSorcerySpeed(sa, ai); } /* @@ -180,8 +180,8 @@ public class LifeGainAi extends SpellAbilityAi { return true; } - if (SpellAbilityAi.isSorcerySpeed(sa, ai) - || sa.getSubAbility() != null || SpellAbilityAi.playReusable(ai, sa)) { + if (isSorcerySpeed(sa, ai) + || sa.getSubAbility() != null || playReusable(ai, sa)) { return true; } diff --git a/forge-ai/src/main/java/forge/ai/ability/LifeLoseAi.java b/forge-ai/src/main/java/forge/ai/ability/LifeLoseAi.java index c9ce8e01d1d..b7f75808bc5 100644 --- a/forge-ai/src/main/java/forge/ai/ability/LifeLoseAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/LifeLoseAi.java @@ -144,7 +144,7 @@ public class LifeLoseAi extends SpellAbilityAi { return false; } - if (SpellAbilityAi.isSorcerySpeed(sa, ai) || sa.hasParam("ActivationPhases") || SpellAbilityAi.playReusable(ai, sa) + if (isSorcerySpeed(sa, ai) || sa.hasParam("ActivationPhases") || playReusable(ai, sa) || ComputerUtil.activateForCost(sa, ai)) { return true; } diff --git a/forge-ai/src/main/java/forge/ai/ability/ManifestAi.java b/forge-ai/src/main/java/forge/ai/ability/ManifestAi.java index c53f7ab8c7c..43b777b9789 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ManifestAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ManifestAi.java @@ -61,7 +61,7 @@ public class ManifestAi extends SpellAbilityAi { if (!buff) { return false; } - } else if (!SpellAbilityAi.isSorcerySpeed(sa, ai)) { + } else if (!isSorcerySpeed(sa, ai)) { return false; } } else { @@ -152,7 +152,7 @@ public class ManifestAi extends SpellAbilityAi { } } // Probably should be a little more discerning on playing during OPPs turn - if (SpellAbilityAi.playReusable(ai, sa)) { + if (playReusable(ai, sa)) { return true; } if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_ATTACKERS)) { diff --git a/forge-ai/src/main/java/forge/ai/ability/MillAi.java b/forge-ai/src/main/java/forge/ai/ability/MillAi.java index 0b5c53382a5..0211d25123a 100644 --- a/forge-ai/src/main/java/forge/ai/ability/MillAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/MillAi.java @@ -51,7 +51,7 @@ public class MillAi extends SpellAbilityAi { return (ph.is(PhaseType.MAIN1) || ph.is(PhaseType.MAIN2)) && ph.isPlayerTurn(ai); // Chandra, Torch of Defiance and similar } if (!sa.isPwAbility()) { // Planeswalker abilities are only activated at sorcery speed - if ("You".equals(sa.getParam("Defined")) && !(!SpellAbilityAi.isSorcerySpeed(sa, ai) && ph.is(PhaseType.END_OF_TURN) + if ("You".equals(sa.getParam("Defined")) && !(!isSorcerySpeed(sa, ai) && ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn().equals(ai))) { return false; // only self-mill at opponent EOT } 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 a8f6f1972bb..3d7c3705418 100644 --- a/forge-ai/src/main/java/forge/ai/ability/PermanentAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/PermanentAi.java @@ -1,21 +1,15 @@ package forge.ai.ability; -import org.apache.commons.lang3.StringUtils; - import com.google.common.base.Predicates; import com.google.common.collect.Iterables; - import forge.ai.ComputerUtil; import forge.ai.ComputerUtilCost; import forge.ai.ComputerUtilMana; import forge.ai.SpellAbilityAi; +import forge.card.CardStateName; import forge.card.CardType.Supertype; import forge.card.mana.ManaCost; -import forge.game.card.Card; -import forge.game.card.CardCollection; -import forge.game.card.CardCollectionView; -import forge.game.card.CardLists; -import forge.game.card.CardPredicates; +import forge.game.card.*; import forge.game.cost.Cost; import forge.game.keyword.KeywordInterface; import forge.game.mana.ManaCostBeingPaid; @@ -24,6 +18,7 @@ import forge.game.phase.PhaseType; import forge.game.player.Player; import forge.game.spellability.SpellAbility; import forge.game.zone.ZoneType; +import org.apache.commons.lang3.StringUtils; public class PermanentAi extends SpellAbilityAi { @@ -49,19 +44,19 @@ public class PermanentAi extends SpellAbilityAi { */ @Override protected boolean checkApiLogic(final Player ai, final SpellAbility sa) { - final Card card = sa.getHostCard(); + final Card source = sa.getHostCard(); // check on legendary - if (!card.ignoreLegendRule() && ai.isCardInPlay(card.getName())) { + if (!source.ignoreLegendRule() && ai.isCardInPlay(source.getName())) { // TODO check the risk we'd lose the effect with bad timing - if (!card.hasSVar("AILegendaryException")) { + if (!source.hasSVar("AILegendaryException")) { // AiPlayDecision.WouldDestroyLegend return false; } else { - String specialRule = card.getSVar("AILegendaryException"); + String specialRule = source.getSVar("AILegendaryException"); if ("TwoCopiesAllowed".equals(specialRule)) { // One extra copy allowed on the battlefield, e.g. Brothers Yamazaki - if (CardLists.count(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.nameEquals(card.getName())) > 1) { + if (CardLists.count(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.nameEquals(source.getName())) > 1) { return false; } } else if ("AlwaysAllowed".equals(specialRule)) { @@ -73,23 +68,7 @@ public class PermanentAi extends SpellAbilityAi { } } - /* -- not used anymore after Ixalan (Planeswalkers are now legendary, not unique by subtype) -- - if (card.isPlaneswalker()) { - CardCollection list = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), - CardPredicates.Presets.PLANESWALKERS); - for (String type : card.getType().getSubtypes()) { // determine - // planewalker - // subtype - final CardCollection cl = CardLists.getType(list, type); - if (!cl.isEmpty()) { - // AiPlayDecision.WouldDestroyOtherPlaneswalker - return false; - } - break; - } - }*/ - - if (card.getType().hasSupertype(Supertype.World)) { + if (source.getType().hasSupertype(Supertype.World)) { CardCollection list = CardLists.getType(ai.getCardsIn(ZoneType.Battlefield), "World"); if (!list.isEmpty()) { // AiPlayDecision.WouldDestroyWorldEnchantment @@ -101,7 +80,6 @@ public class PermanentAi extends SpellAbilityAi { if (mana.countX() > 0) { // Set PayX here to maximum value. final int xPay = ComputerUtilCost.getMaxXValue(sa, ai, false); - final Card source = sa.getHostCard(); if (source.hasConverge()) { int nColors = ComputerUtilMana.getConvergeCount(sa, ai); for (int i = 1; i <= xPay; i++) { @@ -123,7 +101,7 @@ public class PermanentAi extends SpellAbilityAi { } } else if (mana.isZero()) { // if mana is zero, but card mana cost does have X, then something is wrong - ManaCost cardCost = card.getManaCost(); + ManaCost cardCost = source.getManaCost(); if (cardCost != null && cardCost.countX() > 0) { // AiPlayDecision.CantPlayAi return false; @@ -143,6 +121,38 @@ public class PermanentAi extends SpellAbilityAi { sa.setXManaCostPaid(xPay); } + if ("ChaliceOfTheVoid".equals(source.getSVar("AICurseEffect"))) { + int maxX = sa.getXManaCostPaid(); // as set above + CardCollection otherChalices = CardLists.filter(ai.getGame().getCardsIn(ZoneType.Battlefield), CardPredicates.nameEquals("Chalice of the Void")); + outer: for (int i = 0; i <= maxX; i++) { + for (Card chalice : otherChalices) { + if (chalice.getCounters(CounterEnumType.CHARGE) == i) { + continue outer; // already disabled, no point in adding another one + } + } + // assume the AI knows the deck lists of its opponents and if we see a card in a certain zone except for the library or hand, + // it likely won't be cast unless it's bounced back (ideally, this should also somehow account for hidden information such as face down cards in exile) + final int manaValue = i; + CardCollection aiCards = CardLists.filter(ai.getAllCards(), card -> (card.isInZone(ZoneType.Library) || !card.isInZone(ZoneType.Hand)) + && card.getState(CardStateName.Original).getManaCost() != null + && card.getState(CardStateName.Original).getManaCost().getCMC() == manaValue); + CardCollection oppCards = CardLists.filter(ai.getStrongestOpponent().getAllCards(), card -> (card.isInZone(ZoneType.Library) || !card.isInZone(ZoneType.Hand)) + && card.getState(CardStateName.Original).getManaCost() != null + && card.getState(CardStateName.Original).getManaCost().getCMC() == manaValue); + if (manaValue == 0) { + aiCards = CardLists.filter(aiCards, Predicates.not(CardPredicates.isType("Land"))); + oppCards = CardLists.filter(oppCards, Predicates.not(CardPredicates.isType("Land"))); + // also filter out other Chalices in our own deck + aiCards = CardLists.filter(aiCards, Predicates.not(CardPredicates.nameEquals("Chalice of the Void"))); + } + if (oppCards.size() > 3 && oppCards.size() >= aiCards.size() * 2) { + sa.setXManaCostPaid(manaValue); + return true; + } + } + return false; + } + if (sa.hasParam("Announce") && sa.getParam("Announce").startsWith("Multikicker")) { // String announce = sa.getParam("Announce"); ManaCost mkCost = sa.getMultiKickerManaCost(); @@ -152,13 +162,13 @@ public class PermanentAi extends SpellAbilityAi { mCost = ManaCost.combine(mCost, mkCost); ManaCostBeingPaid mcbp = new ManaCostBeingPaid(mCost); if (!ComputerUtilMana.canPayManaCost(mcbp, sa, ai, false)) { - card.setKickerMagnitude(i); + source.setKickerMagnitude(i); sa.setSVar("Multikicker", String.valueOf(i)); break; } - card.setKickerMagnitude(i + 1); + source.setKickerMagnitude(i + 1); } - if (isZeroCost && card.getKickerMagnitude() == 0) { + if (isZeroCost && source.getKickerMagnitude() == 0) { // Bail if the card cost was {0} and no multikicker was paid (e.g. Everflowing Chalice). // TODO: update this if there's ever a card where it makes sense to play it for {0} with no multikicker return false; @@ -166,13 +176,13 @@ public class PermanentAi extends SpellAbilityAi { } // don't play cards without being able to pay the upkeep for - for (KeywordInterface inst : card.getKeywords()) { + for (KeywordInterface inst : source.getKeywords()) { String ability = inst.getOriginal(); if (ability.startsWith("UpkeepCost")) { final String[] k = ability.split(":"); final String costs = k[1]; - final SpellAbility emptyAbility = new SpellAbility.EmptySa(card, ai); + final SpellAbility emptyAbility = new SpellAbility.EmptySa(source, ai); emptyAbility.setPayCosts(new Cost(costs, true)); emptyAbility.setTargetRestrictions(sa.getTargetRestrictions()); emptyAbility.setCardState(sa.getCardState()); @@ -186,8 +196,8 @@ public class PermanentAi extends SpellAbilityAi { } // check for specific AI preferences - if (card.hasSVar("AICastPreference")) { - String pref = card.getSVar("AICastPreference"); + if (source.hasSVar("AICastPreference")) { + String pref = source.getSVar("AICastPreference"); String[] groups = StringUtils.split(pref, "|"); boolean dontCast = false; for (String group : groups) { @@ -205,7 +215,7 @@ public class PermanentAi extends SpellAbilityAi { // Only cast unless there are X or more cards like this on the battlefield under AI control already, CardCollectionView valid = param.contains("Globally") ? ai.getGame().getCardsIn(ZoneType.Battlefield) : ai.getCardsIn(ZoneType.Battlefield); - CardCollection ctrld = CardLists.filter(valid, CardPredicates.nameEquals(card.getName())); + CardCollection ctrld = CardLists.filter(valid, CardPredicates.nameEquals(source.getName())); int numControlled = 0; if (param.endsWith("WithoutOppAuras")) { @@ -242,7 +252,7 @@ public class PermanentAi extends SpellAbilityAi { // if there are X-1 mana sources in play but the AI has an extra land in hand CardCollection m = ComputerUtilMana.getAvailableManaSources(ai, true); int extraMana = CardLists.count(ai.getCardsIn(ZoneType.Hand), CardPredicates.Presets.LANDS) > 0 ? 1 : 0; - if (card.getName().equals("Illusions of Grandeur")) { + if (source.getName().equals("Illusions of Grandeur")) { // TODO: this is currently hardcoded for specific Illusions-Donate cost reduction spells, need to make this generic. extraMana += Math.min(3, CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), Predicates.or(CardPredicates.nameEquals("Sapphire Medallion"), CardPredicates.nameEquals("Helm of Awakening"))).size()) * 2; // each cost-reduction spell accounts for {1} in both Illusions and Donate } @@ -260,7 +270,7 @@ public class PermanentAi extends SpellAbilityAi { break; // disregard other preferences, always cast as a last resort } } else if (param.equals("OnlyFromZone")) { - if (!card.getZone().getZoneType().toString().equals(value)) { + if (!source.getZone().getZoneType().toString().equals(value)) { dontCast = true; break; // limit casting to a specific zone only } diff --git a/forge-ai/src/main/java/forge/ai/ability/PermanentCreatureAi.java b/forge-ai/src/main/java/forge/ai/ability/PermanentCreatureAi.java index 4a026859c2d..ccd93c27dbf 100644 --- a/forge-ai/src/main/java/forge/ai/ability/PermanentCreatureAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/PermanentCreatureAi.java @@ -235,11 +235,19 @@ public class PermanentCreatureAi extends PermanentAi { * worth it. Not sure what 4. is for. 5. needs to be updated to ensure * that the net toughness is still positive after static effects. */ + // AiPlayDecision.WouldBecomeZeroToughnessCreature + if (card.hasStartOfKeyword("etbCounter") || mana.countX() != 0 + || card.hasETBTrigger(false) || card.hasETBReplacement() || card.hasSVar("NoZeroToughnessAI")) { + return true; + } + final Card copy = CardUtil.getLKICopy(card); ComputerUtilCard.applyStaticContPT(game, copy, null); - // AiPlayDecision.WouldBecomeZeroToughnessCreature - return copy.getNetToughness() > 0 || copy.hasStartOfKeyword("etbCounter") || mana.countX() != 0 - || copy.hasETBTrigger(false) || copy.hasETBReplacement() || copy.hasSVar("NoZeroToughnessAI"); + if (copy.getNetToughness() > 0) { + return true; + } + + return false; } } diff --git a/forge-ai/src/main/java/forge/ai/ability/ProtectAi.java b/forge-ai/src/main/java/forge/ai/ability/ProtectAi.java index d7571ff5d3a..2af0c498087 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ProtectAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ProtectAi.java @@ -163,7 +163,7 @@ public class ProtectAi extends SpellAbilityAi { protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) { final boolean notAiMain1 = !(ph.getPlayerTurn() == ai && ph.getPhase() == PhaseType.MAIN1); // sorceries can only give protection in order to create an unblockable attacker - return !SpellAbilityAi.isSorcerySpeed(sa, ai) || !notAiMain1; + return !isSorcerySpeed(sa, ai) || !notAiMain1; } @Override diff --git a/forge-ai/src/main/java/forge/ai/ability/PumpAi.java b/forge-ai/src/main/java/forge/ai/ability/PumpAi.java index bf0ce6ad9d4..4bcfa5dbe84 100644 --- a/forge-ai/src/main/java/forge/ai/ability/PumpAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/PumpAi.java @@ -87,7 +87,7 @@ public class PumpAi extends PumpAiBase { return true; } - return SpellAbilityAi.isSorcerySpeed(sa, ai) || (ph.getNextTurn().equals(ai) && !ph.getPhase().isBefore(PhaseType.END_OF_TURN)); + return isSorcerySpeed(sa, ai) || (ph.getNextTurn().equals(ai) && !ph.getPhase().isBefore(PhaseType.END_OF_TURN)); } else if (logic.equals("Aristocrat")) { final boolean isThreatened = ComputerUtil.predictThreatenedObjects(ai, null, true).contains(sa.getHostCard()); if (!ph.is(PhaseType.COMBAT_DECLARE_BLOCKERS) && !isThreatened) { @@ -118,7 +118,7 @@ public class PumpAi extends PumpAiBase { || ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS))) { // Instant-speed pumps should not be cast outside of combat when the // stack is empty - return sa.isCurse() || SpellAbilityAi.isSorcerySpeed(sa, ai) || main1Preferred; + return sa.isCurse() || isSorcerySpeed(sa, ai) || main1Preferred; } return true; } @@ -292,10 +292,6 @@ public class PumpAi extends PumpAiBase { if (numDefense.contains("X") && sa.getSVar("X").equals("Count$xPaid")) { // Set PayX here to maximum value. int xPay = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger()); - if (sourceName.equals("Necropolis Fiend")) { - xPay = Math.min(xPay, sa.getActivatingPlayer().getCardsIn(ZoneType.Graveyard).size()); - sa.setSVar("X", Integer.toString(xPay)); - } sa.setXManaCostPaid(xPay); defense = xPay; if (numDefense.equals("-X")) { @@ -371,7 +367,7 @@ public class PumpAi extends PumpAiBase { if (ComputerUtilCard.shouldPumpCard(ai, sa, card, defense, attack, keywords, false)) { return true; } else if (containsUsefulKeyword(ai, keywords, card, sa, attack)) { - if (game.getPhaseHandler().isPreCombatMain() && SpellAbilityAi.isSorcerySpeed(sa, ai) || + if (game.getPhaseHandler().isPreCombatMain() && isSorcerySpeed(sa, ai) || game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_ATTACKERS, ai) || game.getPhaseHandler().is(PhaseType.COMBAT_BEGIN, ai)) { Card pumped = ComputerUtilCard.getPumpedCreature(ai, sa, card, 0, 0, keywords); diff --git a/forge-ai/src/main/java/forge/ai/ability/RevealAi.java b/forge-ai/src/main/java/forge/ai/ability/RevealAi.java index 1d93a384df3..0cb1870a97e 100644 --- a/forge-ai/src/main/java/forge/ai/ability/RevealAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/RevealAi.java @@ -4,7 +4,6 @@ import com.google.common.collect.Iterables; import forge.ai.AiPlayDecision; import forge.ai.PlayerControllerAi; -import forge.ai.SpellAbilityAi; import forge.game.ability.AbilityUtils; import forge.game.card.Card; import forge.game.cost.Cost; @@ -26,7 +25,7 @@ public class RevealAi extends RevealAiBase { boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(.667, sa.getActivationsThisTurn() + 1); - if (SpellAbilityAi.playReusable(ai, sa)) { + if (playReusable(ai, sa)) { randomReturn = true; } return randomReturn; diff --git a/forge-ai/src/main/java/forge/ai/ability/RevealHandAi.java b/forge-ai/src/main/java/forge/ai/ability/RevealHandAi.java index 077589ce3cf..5935d301ce2 100644 --- a/forge-ai/src/main/java/forge/ai/ability/RevealHandAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/RevealHandAi.java @@ -1,6 +1,5 @@ package forge.ai.ability; -import forge.ai.SpellAbilityAi; import forge.game.player.Player; import forge.game.spellability.SpellAbility; import forge.util.MyRandom; @@ -20,7 +19,7 @@ public class RevealHandAi extends RevealAiBase { boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(.667, sa.getActivationsThisTurn() + 1); - if (SpellAbilityAi.playReusable(ai, sa)) { + if (playReusable(ai, sa)) { randomReturn = true; } diff --git a/forge-ai/src/main/java/forge/ai/ability/ScryAi.java b/forge-ai/src/main/java/forge/ai/ability/ScryAi.java index c9bf4e7d1d6..470bbdc461f 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ScryAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ScryAi.java @@ -57,7 +57,7 @@ public class ScryAi extends SpellAbilityAi { // even if there's no mana cost. if (sa.getPayCosts().hasTapCost() && (sa.getPayCosts().hasManaCost() || (sa.getHostCard() != null && sa.getHostCard().isCreature())) - && !SpellAbilityAi.isSorcerySpeed(sa, ai)) { + && !isSorcerySpeed(sa, ai)) { return ph.getNextTurn() == ai && ph.is(PhaseType.END_OF_TURN); } @@ -69,7 +69,7 @@ public class ScryAi extends SpellAbilityAi { // in the playerturn Scry should only be done in Main1 or in upkeep if able if (ph.isPlayerTurn(ai)) { - if (SpellAbilityAi.isSorcerySpeed(sa, ai)) { + if (isSorcerySpeed(sa, ai)) { return ph.is(PhaseType.MAIN1) || sa.isPwAbility(); } else { return ph.is(PhaseType.UPKEEP); @@ -121,12 +121,12 @@ public class ScryAi extends SpellAbilityAi { } double chance = .4; // 40 percent chance of milling with instant speed stuff - if (SpellAbilityAi.isSorcerySpeed(sa, ai)) { + if (isSorcerySpeed(sa, ai)) { chance = .667; // 66.7% chance for sorcery speed (since it will never activate EOT) } boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(chance, sa.getActivationsThisTurn() + 1); - if (SpellAbilityAi.playReusable(ai, sa)) { + if (playReusable(ai, sa)) { randomReturn = true; } diff --git a/forge-ai/src/main/java/forge/ai/ability/SurveilAi.java b/forge-ai/src/main/java/forge/ai/ability/SurveilAi.java index 0d93d65c3a3..955ca018906 100644 --- a/forge-ai/src/main/java/forge/ai/ability/SurveilAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/SurveilAi.java @@ -54,13 +54,13 @@ public class SurveilAi extends SpellAbilityAi { // even if there's no mana cost. if (sa.getPayCosts().hasTapCost() && (sa.getPayCosts().hasManaCost() || (sa.getHostCard() != null && sa.getHostCard().isCreature())) - && !SpellAbilityAi.isSorcerySpeed(sa, ai)) { + && !isSorcerySpeed(sa, ai)) { return ph.getNextTurn() == ai && ph.is(PhaseType.END_OF_TURN); } // in the player's turn Surveil should only be done in Main1 or in Upkeep if able if (ph.isPlayerTurn(ai)) { - if (SpellAbilityAi.isSorcerySpeed(sa, ai)) { + if (isSorcerySpeed(sa, ai)) { return ph.is(PhaseType.MAIN1) || sa.isPwAbility(); } else { return ph.is(PhaseType.UPKEEP); @@ -104,12 +104,12 @@ public class SurveilAi extends SpellAbilityAi { } double chance = .4; // 40 percent chance for instant speed - if (SpellAbilityAi.isSorcerySpeed(sa, ai)) { + if (isSorcerySpeed(sa, ai)) { chance = .667; // 66.7% chance for sorcery speed (since it will never activate EOT) } boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(chance, sa.getActivationsThisTurn() + 1); - if (SpellAbilityAi.playReusable(ai, sa)) { + if (playReusable(ai, sa)) { randomReturn = true; } diff --git a/forge-ai/src/main/java/forge/ai/ability/TapAi.java b/forge-ai/src/main/java/forge/ai/ability/TapAi.java index 6f600e4c55c..9422efdc341 100644 --- a/forge-ai/src/main/java/forge/ai/ability/TapAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/TapAi.java @@ -21,7 +21,7 @@ public class TapAi extends TapAiBase { if (turn.isOpponentOf(ai) && phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) { // Tap things down if it's Human's turn } else if (turn.equals(ai)) { - if (SpellAbilityAi.isSorcerySpeed(sa, ai) && phase.getPhase().isBefore(PhaseType.COMBAT_BEGIN)) { + if (isSorcerySpeed(sa, ai) && phase.getPhase().isBefore(PhaseType.COMBAT_BEGIN)) { // Cast it if it's a sorcery. } else if (phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) { // Aggro Brains are willing to use TapEffects aggressively instead of defensively @@ -33,7 +33,7 @@ public class TapAi extends TapAiBase { // Don't tap down after blockers return false; } - } else if (!SpellAbilityAi.playReusable(ai, sa)) { + } else if (!playReusable(ai, sa)) { // Generally don't want to tap things with an Instant during Players turn outside of combat return false; } @@ -46,8 +46,11 @@ public class TapAi extends TapAiBase { final Card source = sa.getHostCard(); final Cost abCost = sa.getPayCosts(); - if ("GoblinPolkaBand".equals(sa.getParam("AILogic"))) { + final String aiLogic = sa.getParamOrDefault("AILogic", ""); + if ("GoblinPolkaBand".equals(aiLogic)) { return SpecialCardAi.GoblinPolkaBand.consider(ai, sa); + } else if ("Arena".equals(aiLogic)) { + return SpecialCardAi.Arena.consider(ai, sa); } if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source, sa)) { diff --git a/forge-ai/src/main/java/forge/ai/ability/TapAiBase.java b/forge-ai/src/main/java/forge/ai/ability/TapAiBase.java index 940c667ecf6..8e7ac2bff0c 100644 --- a/forge-ai/src/main/java/forge/ai/ability/TapAiBase.java +++ b/forge-ai/src/main/java/forge/ai/ability/TapAiBase.java @@ -325,6 +325,15 @@ public abstract class TapAiBase extends SpellAbilityAi { @Override public boolean chkAIDrawback(SpellAbility sa, Player ai) { final Card source = sa.getHostCard(); + final boolean oppTargetsChoice = sa.hasParam("TargetingPlayer"); + + if (oppTargetsChoice && sa.getActivatingPlayer().equals(ai) && !sa.isTrigger()) { + // canPlayAI (sa activated by ai) + Player targetingPlayer = AbilityUtils.getDefinedPlayers(source, sa.getParam("TargetingPlayer"), sa).get(0); + sa.setTargetingPlayer(targetingPlayer); + sa.getTargets().clear(); + return targetingPlayer.getController().chooseTargetsFor(sa); + } boolean randomReturn = true; diff --git a/forge-ai/src/main/java/forge/ai/ability/TokenAi.java b/forge-ai/src/main/java/forge/ai/ability/TokenAi.java index ca3277e4f1a..f1d89d1ce2e 100644 --- a/forge-ai/src/main/java/forge/ai/ability/TokenAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/TokenAi.java @@ -133,7 +133,7 @@ public class TokenAi extends SpellAbilityAi { } } if ((ph.isPlayerTurn(ai) || ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) - && !sa.hasParam("ActivationPhases") && !sa.hasParam("PlayerTurn") && !SpellAbilityAi.isSorcerySpeed(sa, ai) + && !sa.hasParam("ActivationPhases") && !sa.hasParam("PlayerTurn") && !isSorcerySpeed(sa, ai) && !haste && !pwMinus) { return false; } diff --git a/forge-ai/src/main/java/forge/ai/ability/VentureAi.java b/forge-ai/src/main/java/forge/ai/ability/VentureAi.java index 842fff98bed..1160f7d5a9d 100644 --- a/forge-ai/src/main/java/forge/ai/ability/VentureAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/VentureAi.java @@ -20,7 +20,7 @@ public class VentureAi extends SpellAbilityAi { // If this has a mana cost, do it at opponent's EOT if able to prevent spending mana early; if sorcery, do it in Main2 PhaseHandler ph = aiPlayer.getGame().getPhaseHandler(); if (sa.getPayCosts().hasManaCost() || sa.getPayCosts().hasTapCost()) { - if (SpellAbilityAi.isSorcerySpeed(sa, aiPlayer)) { + if (isSorcerySpeed(sa, aiPlayer)) { return ph.is(PhaseType.MAIN2, aiPlayer); } else { return ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn() == aiPlayer; diff --git a/forge-core/src/main/java/forge/util/TextUtil.java b/forge-core/src/main/java/forge/util/TextUtil.java index 9496391736a..53305344d0d 100644 --- a/forge-core/src/main/java/forge/util/TextUtil.java +++ b/forge-core/src/main/java/forge/util/TextUtil.java @@ -1,5 +1,6 @@ package forge.util; +import java.text.DecimalFormat; import java.text.Normalizer; import java.util.ArrayList; import java.util.List; @@ -42,6 +43,10 @@ public class TextUtil { return Normalizer.normalize(text, Normalizer.Form.NFD); } + private static final DecimalFormat df = new DecimalFormat("#.##"); + public static String decimalFormat(float value) { + return df.format(value); + } /** * Safely converts an object to a String. * 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 7726682a192..a8de4e54fc2 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java @@ -1582,10 +1582,6 @@ public class AbilityUtils { host.addRemembered(sa.getTargets()); } - if (sa.hasParam("ImprintTargets") && sa.usesTargeting()) { - host.addImprintedCards(sa.getTargets().getTargetCards()); - } - if (sa.hasParam("RememberCostMana")) { host.clearRemembered(); ManaCostBeingPaid activationMana = new ManaCostBeingPaid(sa.getPayCosts().getTotalMana()); diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java index 2fab13d9f4a..4cee72092bc 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java @@ -1252,6 +1252,10 @@ public class ChangeZoneEffect extends SpellAbilityEffect { player.shuffle(sa); } + if (sa.hasParam("Reorder")) { + chosenCards = new CardCollection(decider.getController().orderMoveToZoneList(chosenCards, destination, sa)); + } + // remove Controlled While Searching if (controlTimestamp != null) { player.removeController(controlTimestamp); diff --git a/forge-game/src/main/java/forge/game/ability/effects/FightEffect.java b/forge-game/src/main/java/forge/game/ability/effects/FightEffect.java index b9d7bba8d09..b4ab672fa2f 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/FightEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/FightEffect.java @@ -62,10 +62,6 @@ public class FightEffect extends DamageBaseEffect { return; } - if (sa.hasParam("RememberObjects")) { - host.addRemembered(AbilityUtils.getDefinedObjects(host, sa.getParam("RememberObjects"), sa)); - } - Player controller = host.getController(); boolean isOptional = sa.hasParam("Optional"); @@ -147,7 +143,6 @@ public class FightEffect extends DamageBaseEffect { } private void dealDamage(final SpellAbility sa, Card fighterA, Card fighterB) { - boolean usedDamageMap = true; CardDamageMap damageMap = sa.getDamageMap(); CardDamageMap preventMap = sa.getPreventMap(); 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 ae647c13939..b6510980f80 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -6941,10 +6941,29 @@ public class Card extends GameEntity implements Comparable, IHasSVars { if (StringUtils.isNotBlank(set)) { cp = StaticData.instance().getVariantCards().getCard(name, set); - return cp == null ? StaticData.instance().getCommonCards().getCard(name, set) : cp; + if (cp != null) { + return cp; + } + cp = StaticData.instance().getCommonCards().getCard(name, set); + if (cp != null) { + return cp; + } } + //no specific set for variant cp = StaticData.instance().getVariantCards().getCard(name); - return cp != null ? cp : StaticData.instance().getCommonCards().getCardFromEditions(name, CardArtPreference.LATEST_ART_ALL_EDITIONS); + if (cp != null) { + return cp; + } + //try to get from user preference if available + CardDb.CardArtPreference cardArtPreference = StaticData.instance().getCardArtPreference(); + if (cardArtPreference == null) //fallback + cardArtPreference = CardArtPreference.ORIGINAL_ART_CORE_EXPANSIONS_REPRINT_ONLY; + cp = StaticData.instance().getCommonCards().getCardFromEditions(name, cardArtPreference); + if (cp != null) { + return cp; + } + //lastoption + return StaticData.instance().getCommonCards().getCard(name); } /** diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbilityDisableTriggers.java b/forge-game/src/main/java/forge/game/staticability/StaticAbilityDisableTriggers.java index 823907c8d00..5190c20342d 100644 --- a/forge-game/src/main/java/forge/game/staticability/StaticAbilityDisableTriggers.java +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbilityDisableTriggers.java @@ -70,7 +70,11 @@ public class StaticAbilityDisableTriggers { if (trigMode.equals(TriggerType.ChangesZone)) { // Cause of the trigger – the card changing zones - if (!stAb.matchesValidParam("ValidCause", runParams.get(AbilityKey.Card))) { + Card moved = (Card) runParams.get(AbilityKey.Card); + if ("Battlefield".equals(regtrig.getParam("Origin"))) { + moved = (Card) runParams.get(AbilityKey.CardLKI); + } + if (!stAb.matchesValidParam("ValidCause", moved)) { return false; } if (!stAb.matchesValidParam("Destination", runParams.get(AbilityKey.Destination))) { diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbilityPanharmonicon.java b/forge-game/src/main/java/forge/game/staticability/StaticAbilityPanharmonicon.java index bf66cbe34b3..14f164f47b7 100644 --- a/forge-game/src/main/java/forge/game/staticability/StaticAbilityPanharmonicon.java +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbilityPanharmonicon.java @@ -89,7 +89,11 @@ public class StaticAbilityPanharmonicon { if (trigMode.equals(TriggerType.ChangesZone)) { // Cause of the trigger – the card changing zones - if (!stAb.matchesValidParam("ValidCause", runParams.get(AbilityKey.Card))) { + Card moved = (Card) runParams.get(AbilityKey.Card); + if ("Battlefield".equals(trigger.getParam("Origin"))) { + moved = (Card) runParams.get(AbilityKey.CardLKI); + } + if (!stAb.matchesValidParam("ValidCause", moved)) { return false; } if (!stAb.matchesValidParam("Origin", runParams.get(AbilityKey.Origin))) { diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerChangesZone.java b/forge-game/src/main/java/forge/game/trigger/TriggerChangesZone.java index 413d2eca648..28cf8df3017 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerChangesZone.java +++ b/forge-game/src/main/java/forge/game/trigger/TriggerChangesZone.java @@ -120,19 +120,8 @@ public class TriggerChangesZone extends Trigger { } } - if (hasParam("ValidCause")) { - if (!runParams.containsKey(AbilityKey.Cause)) { - return false; - } - SpellAbility cause = (SpellAbility) runParams.get(AbilityKey.Cause); - if (cause == null) { - return false; - } - if (!matchesValid(cause, getParam("ValidCause").split(","))) { - if (!matchesValid(cause.getHostCard(), getParam("ValidCause").split(","))) { - return false; - } - } + if (!matchesValidParam("ValidCause", runParams.get(AbilityKey.Cause))) { + return false; } if (hasParam("Fizzle")) { diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerChangesZoneAll.java b/forge-game/src/main/java/forge/game/trigger/TriggerChangesZoneAll.java index 55e62d6843e..b77f8cf9a07 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerChangesZoneAll.java +++ b/forge-game/src/main/java/forge/game/trigger/TriggerChangesZoneAll.java @@ -25,7 +25,9 @@ public class TriggerChangesZoneAll extends Trigger { if (!matchesValidParam("ValidCause", runParams.get(AbilityKey.Cause))) { return false; - } else if (hasParam("ValidAmount")) { + } + + if (hasParam("ValidAmount")) { int right = AbilityUtils.calculateAmount(hostCard, getParam("ValidAmount").substring(2), this); if (!Expressions.compare(this.filterCards(table).size(), getParam("ValidAmount").substring(0, 2), right)) { return false; } } diff --git a/forge-gui-mobile/src/forge/adventure/data/AdventureEventData.java b/forge-gui-mobile/src/forge/adventure/data/AdventureEventData.java index c30e99c6ccc..62adc0eaeae 100644 --- a/forge-gui-mobile/src/forge/adventure/data/AdventureEventData.java +++ b/forge-gui-mobile/src/forge/adventure/data/AdventureEventData.java @@ -187,7 +187,9 @@ public class AdventureEventData implements Serializable { List legalBlocks = new ArrayList<>(); for (CardBlock b : src) { // for each block - boolean isOkay = !(b.getSets().isEmpty() && b.getCntBoostersDraft() > 0); + if (b.getSets().isEmpty() || (b.getCntBoostersDraft() < 1)) + continue; + boolean isOkay = true; for (CardEdition c : b.getSets()) { if (!allEditions.contains(c)) { isOkay = false; diff --git a/forge-gui-mobile/src/forge/adventure/scene/NewGameScene.java b/forge-gui-mobile/src/forge/adventure/scene/NewGameScene.java index 91d3dd1cb64..af022b901ec 100644 --- a/forge-gui-mobile/src/forge/adventure/scene/NewGameScene.java +++ b/forge-gui-mobile/src/forge/adventure/scene/NewGameScene.java @@ -13,6 +13,7 @@ import forge.Forge; import forge.adventure.data.DifficultyData; import forge.adventure.data.HeroListData; import forge.adventure.util.*; +import forge.adventure.stage.WorldStage; import forge.adventure.world.WorldSave; import forge.card.CardEdition; import forge.card.ColorSet; @@ -199,6 +200,7 @@ public class NewGameScene extends UIScene { editionIds[starterEdition.getCurrentIndex()], 0);//maybe replace with enum GamePlayerUtil.getGuiPlayer().setName(selectedName.getText()); SoundSystem.instance.changeBackgroundTrack(); + WorldStage.getInstance().setupNewGame(); Forge.switchScene(GameScene.instance()); }; Forge.setTransitionScreen(new TransitionScreen(runnable, null, false, true, "Generating World...")); diff --git a/forge-gui-mobile/src/forge/adventure/scene/PlayerStatisticScene.java b/forge-gui-mobile/src/forge/adventure/scene/PlayerStatisticScene.java index 1d51a099308..9b224eb2e1d 100644 --- a/forge-gui-mobile/src/forge/adventure/scene/PlayerStatisticScene.java +++ b/forge-gui-mobile/src/forge/adventure/scene/PlayerStatisticScene.java @@ -32,6 +32,7 @@ import forge.localinstance.achievements.CardActivationAchievements; import forge.localinstance.achievements.PlaneswalkerAchievements; import forge.model.FModel; import forge.player.GamePlayerUtil; +import forge.util.TextUtil; import org.apache.commons.lang3.tuple.Pair; import java.util.Map; @@ -216,7 +217,7 @@ public class PlayerStatisticScene extends UIScene { totalLoss.setText(String.valueOf(Current.player().getStatistic().totalLoss())); } if (lossWinRatio != null) { - lossWinRatio.setText(Float.toString(Current.player().getStatistic().winLossRatio())); + lossWinRatio.setText(TextUtil.decimalFormat(Current.player().getStatistic().winLossRatio())); } if (eventMatchWins != null) { eventMatchWins.setText(String.valueOf(Current.player().getStatistic().eventWins())); diff --git a/forge-gui-mobile/src/forge/adventure/stage/WorldStage.java b/forge-gui-mobile/src/forge/adventure/stage/WorldStage.java index 39ec6bbfcba..65e71295760 100644 --- a/forge-gui-mobile/src/forge/adventure/stage/WorldStage.java +++ b/forge-gui-mobile/src/forge/adventure/stage/WorldStage.java @@ -43,6 +43,7 @@ public class WorldStage extends GameStage implements SaveFileContent { protected ArrayList> enemies = new ArrayList<>(); private final static Float dieTimer = 20f;//todo config private Float globalTimer = 0f; + private transient boolean newGame = false; NavArrowActor navArrow; public WorldStage() { @@ -333,15 +334,24 @@ public class WorldStage extends GameStage implements SaveFileContent { } } + public void setupNewGame(){ + newGame = true; //On a new game, we want to automatically enter any POI the player overlaps with. + } + @Override public void enter() { getPlayerSprite().LoadPos(); getPlayerSprite().setMovementDirection(Vector2.Zero); - for (Actor actor : foregroundSprites.getChildren()) { - if (actor.getClass() == PointOfInterestMapSprite.class) { - PointOfInterestMapSprite point = (PointOfInterestMapSprite) actor; - if (player.collideWith(point.getBoundingRect())) { - collidingPoint = point; + if (newGame) { + newGame = false; + } + else { + for (Actor actor : foregroundSprites.getChildren()) { + if (actor.getClass() == PointOfInterestMapSprite.class) { + PointOfInterestMapSprite point = (PointOfInterestMapSprite) actor; + if (player.collideWith(point.getBoundingRect())) { + collidingPoint = point; + } } } } diff --git a/forge-gui/res/adventure/common/custom_card_pics/Hallowed Sigil.fullborder.jpg b/forge-gui/res/adventure/common/custom_card_pics/Hallowed Sigil.fullborder.jpg new file mode 100644 index 00000000000..73264fca8e8 Binary files /dev/null and b/forge-gui/res/adventure/common/custom_card_pics/Hallowed Sigil.fullborder.jpg differ diff --git a/forge-gui/res/adventure/common/custom_card_pics/Power of Valyx.fullborder.jpg b/forge-gui/res/adventure/common/custom_card_pics/Power of Valyx.fullborder.jpg new file mode 100644 index 00000000000..555669cb6c7 Binary files /dev/null and b/forge-gui/res/adventure/common/custom_card_pics/Power of Valyx.fullborder.jpg differ diff --git a/forge-gui/res/adventure/common/custom_card_pics/Sigil of Torment.fullborder.jpg b/forge-gui/res/adventure/common/custom_card_pics/Sigil of Torment.fullborder.jpg new file mode 100644 index 00000000000..2f2c0f9579e Binary files /dev/null and b/forge-gui/res/adventure/common/custom_card_pics/Sigil of Torment.fullborder.jpg differ diff --git a/forge-gui/res/adventure/common/custom_cards/hallowed_sigil.txt b/forge-gui/res/adventure/common/custom_cards/hallowed_sigil.txt new file mode 100644 index 00000000000..f5e6d9e9700 --- /dev/null +++ b/forge-gui/res/adventure/common/custom_cards/hallowed_sigil.txt @@ -0,0 +1,7 @@ +Name:Hallowed Sigil +ManaCost:no cost +Colors:white +Types:Enchantment +A:AB$ Pump | Cost$ PayShards<4> T | ValidTgts$ Creature.YouCtrl | KW$ Hexproof | SubAbility$ Eject | ActivationLimit$ 1 | SpellDescription$ Target creature you control gains hexproof until end of turn. Exile Hallowed Sigil. +SVar:Eject:DB$ ChangeZone | Defined$ Self | Origin$ Battlefield | Destination$ Exile +Oracle:{M},{T}:Target creature you control gains hexproof until end of turn. Exile Hallowed Sigil. diff --git a/forge-gui/res/adventure/common/custom_cards/sigil_of_torment.txt b/forge-gui/res/adventure/common/custom_cards/sigil_of_torment.txt new file mode 100644 index 00000000000..22e6086f09e --- /dev/null +++ b/forge-gui/res/adventure/common/custom_cards/sigil_of_torment.txt @@ -0,0 +1,8 @@ +Name:Sigil of Torment +ManaCost:no cost +Colors:black +Types:Enchantment +A:AB$ Destroy | Cost$ 4 B T PayShards<5> | ValidTgts$ Creature | TgtPrompt$ Select target creature | ActivationLimit$ 1 | SubAbility$ DBLifeGain | SpellDescription$ Destroy target creature. You gain 3 life. Exile Sigil of Torment. +SVar:DBLifeGain:DB$ GainLife | Defined$ You | LifeAmount$ 3 | SubAbility$ Eject +SVar:Eject:DB$ ChangeZone | Defined$ Self | Origin$ Battlefield | Destination$ Exile +Oracle:{M},{T}: Destroy target creature. You gain 3 life. Exile Sigil of Torment. diff --git a/forge-gui/res/adventure/common/custom_cards/valyx_boss_effect.txt b/forge-gui/res/adventure/common/custom_cards/valyx_boss_effect.txt new file mode 100644 index 00000000000..ce73b9bb2ae --- /dev/null +++ b/forge-gui/res/adventure/common/custom_cards/valyx_boss_effect.txt @@ -0,0 +1,8 @@ +Name:Power of Valyx +ManaCost:no cost +Colors:black +Types:Enchantment +K:Hexproof +A:AB$ Destroy | Cost$ 4 B T Sac<1/Creature.YouCtrl/creature you control>| ValidTgts$ Creature | TgtPrompt$ Select target creature | SubAbility$ DBLifeGain | SpellDescription$ Destroy target creature. You gain 3 life. +SVar:DBLifeGain:DB$ GainLife | Defined$ You | LifeAmount$ 3 +Oracle:{M},{T}, Sacrifice a creature: Destroy target creature. You gain 3 life. diff --git a/forge-gui/res/adventure/common/decks/miniboss/valyx.dck b/forge-gui/res/adventure/common/decks/miniboss/valyx.dck new file mode 100644 index 00000000000..71c1a9830b2 --- /dev/null +++ b/forge-gui/res/adventure/common/decks/miniboss/valyx.dck @@ -0,0 +1,23 @@ +[metadata] +Name=valyx +[Main] +3 Damnation|MM3|1 +2 Deathrender|CNS|1 +2 Deathrender|LRW|1 +1 Doomed Dissenter|DBL|1 +2 Doomed Dissenter|GN3|1 +1 Ecstatic Awakener|DBL|1 +2 Ecstatic Awakener|MID|1 +3 Indulgent Tormentor|PM15|1 +3 Lord of the Void|GTC|1 +4 Mark of the Oni|BOK|1 +3 Murder|CMR|1 +1 Phyrexian Reclamation|C15|1 +1 Phyrexian Reclamation|J22|1 +2 Reaper from the Abyss|J22|1 +3 Sign in Blood|SCD|1 +1 Skirsdag High Priest|C14|1 +2 Skirsdag High Priest|J22|1 +19 Swamp|MOM|1 +1 Swamp|MOM|3 +4 Westvale Abbey|SOI|1 diff --git a/forge-gui/res/adventure/common/decks/standard/cultist.dck b/forge-gui/res/adventure/common/decks/standard/cultist.dck new file mode 100644 index 00000000000..fb5a5e767f6 --- /dev/null +++ b/forge-gui/res/adventure/common/decks/standard/cultist.dck @@ -0,0 +1,48 @@ +[metadata] +Name=cultist +[Avatar] + +[Main] +2 Bloodgift Demon|SCD|1 +2 Bloodsoaked Champion|CLB|1 +2 Bloodsoaked Champion|NCC|1 +2 Demon of Catastrophes|J22|1 +1 Doomed Dissenter|BBD|1 +1 Doomed Dissenter|MB1|1 +2 Ecstatic Awakener|DBL|1 +2 Ecstatic Awakener|MID|1 +2 Feaster of Fools|MH1|1 +1 Grave Pact|10E|1 +2 Grave Pact|CM2|1 +1 Grave Pact|COM|1 +2 Graven Archfiend|YSNC|1 +1 Grim Haruspex|C19|1 +1 Grim Haruspex|CLB|1 +2 Harvester of Souls|CN2|1 +2 Herald of Torment|BNG|1 +1 Murder|CMR|1 +2 Murder|SNC|1 +2 Sign in Blood|ARC|1 +1 Sign in Blood|STA|1 +4 Skirsdag High Priest|2XM|1 +11 Swamp|MOM|1 +2 Swamp|SHM|1 +2 Swamp|SHM|2 +5 Swamp|SHM|3 +2 Swamp|SHM|4 +[Sideboard] +2 Culling Dais|2XM|1 +2 Furnace Celebration|CMR|1 +4 Glaring Spotlight|GTC|1 +2 Grim Return|M14|1 +1 Lord of the Void|GTC|1 +2 Ravenous Demon|DKA|1 +2 Reaper from the Abyss|C14|1 +[Planes] + +[Schemes] + +[Conspiracy] + +[Dungeon] + diff --git a/forge-gui/res/adventure/common/maps/main.tiled-project b/forge-gui/res/adventure/common/maps/main.tiled-project new file mode 100644 index 00000000000..28056787000 --- /dev/null +++ b/forge-gui/res/adventure/common/maps/main.tiled-project @@ -0,0 +1,12 @@ +{ + "automappingRulesFile": "", + "commands": [ + ], + "compatibilityVersion": 1100, + "extensionsPath": "extensions", + "folders": [ + "." + ], + "propertyTypes": [ + ] +} diff --git a/forge-gui/res/adventure/common/maps/map/desertbuildingtiles.tsx b/forge-gui/res/adventure/common/maps/map/desertbuildingtiles.tsx deleted file mode 100644 index f36c022034a..00000000000 --- a/forge-gui/res/adventure/common/maps/map/desertbuildingtiles.tsx +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/forge-gui/res/adventure/common/maps/map/dreamhalls.tmx b/forge-gui/res/adventure/common/maps/map/dreamhalls.tmx index d24a8e9ce9f..acb0174139d 100644 --- a/forge-gui/res/adventure/common/maps/map/dreamhalls.tmx +++ b/forge-gui/res/adventure/common/maps/map/dreamhalls.tmx @@ -10,6 +10,7 @@ + eJzFVNEOgyAMLG/yYXOGH102/Ssz4mcMszbrTsTCTHYJgYDcXYst0RuPjv6Cqz+HZ/U/pjEZ44hJ9w7fBvbi1NC8vf8e69mT78zerj1AzKgrvNqHK/AFYw51zLc0L6A7KM3gt/Gi75qYI99dFIdoiS/RxXhLb3WE3n9mzKdoO/aVy/Nezn7VlX08R20iu2arbonjDN09zpK3EqQmdb3X5rlFV///M6+nrl1Xeq61hjSk9rAfjexnz1drz8ohxy+cuC+9Bf1bIbWn443wXhfoZ0TbnlULa59BoLda1NR8C16fd0IQ @@ -17,7 +18,7 @@ - eJzL4GFgyBjFo3gUj+JRPIpH8YjAALfm5xk= + eJw7Zs7AcGwUj+JRPIpH8SgexSMCAwA4PvgW diff --git a/forge-gui/res/adventure/common/maps/map/grove_8.tmx b/forge-gui/res/adventure/common/maps/map/grove_8.tmx index 55824f6c443..9d3767482fc 100644 --- a/forge-gui/res/adventure/common/maps/map/grove_8.tmx +++ b/forge-gui/res/adventure/common/maps/map/grove_8.tmx @@ -31,11 +31,6 @@ - - - - - diff --git a/forge-gui/res/adventure/common/maps/map/naktamun.tmx b/forge-gui/res/adventure/common/maps/map/naktamun.tmx index 4a9104791b2..ff3edbae4ae 100644 --- a/forge-gui/res/adventure/common/maps/map/naktamun.tmx +++ b/forge-gui/res/adventure/common/maps/map/naktamun.tmx @@ -1,5 +1,5 @@ - + @@ -518,7 +518,82 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 74,74,74,74,74,74,74,74,74,74,74,74,74,74,74,74,74,74,74,74,74,74,74,74,74,4657,32,4184,4184,4184,4184,4184,4184,4184,32,4657,74,74,74,74,74,74,74,74,74,74,74,74,74,74,74,74,74,74,74,74,74,74,74,74,74,74,74,74, @@ -933,73 +1008,6 @@ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -1052,7 +1060,7 @@ ] } ] - + @@ -1061,6 +1069,5 @@ - diff --git a/forge-gui/res/adventure/common/maps/map/gym.tmx b/forge-gui/res/adventure/common/maps/map/naktamun/gym.tmx similarity index 97% rename from forge-gui/res/adventure/common/maps/map/gym.tmx rename to forge-gui/res/adventure/common/maps/map/naktamun/gym.tmx index f7f2011477c..ae151d1f100 100644 --- a/forge-gui/res/adventure/common/maps/map/gym.tmx +++ b/forge-gui/res/adventure/common/maps/map/naktamun/gym.tmx @@ -1,9 +1,19 @@ - - - - + + + + + + + + + + + + + + 2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452, @@ -58,14 +68,6 @@ 2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452,2452 - - - - - - - - @@ -178,37 +180,37 @@ - + - + - + - + - + - + - + diff --git a/forge-gui/res/adventure/common/maps/map/unhallowed_abbey_1F.tmx b/forge-gui/res/adventure/common/maps/map/unhallowed_abbey_1F.tmx new file mode 100644 index 00000000000..ab32f4a5db2 --- /dev/null +++ b/forge-gui/res/adventure/common/maps/map/unhallowed_abbey_1F.tmx @@ -0,0 +1,196 @@ + + + + + + + + + + eJzt1dEJgCAQBmAfDW6DaJxs1GiEapxoj4o8uEwl9ZSIfriX5Pz4oUhVQqiXjxnOu1xxuTnj6/tF12WU6kt7x74nId66TwNC1HA9x2cLs40mNWzuES6bmkcmOAfPzGcctmmGJMW27WG3Wfcb4d7Xtx/rltj/+5brO8i43V6mf0vUftI31XTZvnCZITa3SW2cTt/fZvwf2XzTjc0GSO1G/w== + + + + + eJxjYBhZwI2TeEyJXkrwcLD3CTvxGARIUT+KR/EoHsWjeBSP4sGFARkE62E= + + + + + eJzt1M0NgzAMBWDfOCSZIHQI1imz0K7VH9img5AIWaRRRZ4NgqqqpXeAyHwxikL0/fWq3nOUu5cdncYfP++aulk8uSvprbMgPa0jetg5sdLns9vO5fVoPu3yP7sDtmRexERt1JWYiI24GrNkI+5JaXJ55bx/97fdIeRqdGYX+nqlWyttNj99V3JPSuwlU3pPonbJlLqpfTFTeA+dmd+VTI2b9/BZ94r9pxkBhlPcCw== + + + + + eJzt1TEOgCAQBMCrkQ9YGZ+KPNVY+wUbL8ELGljW7ja5hkCmgA0ibTnn+vydmuGuu2j2ILJNz1GzXDsC31zj974l8uxWk2n3miwbMa2NuiNx1923c2gXtINI0B6ivR+xGWavzTStrZPvu0vmj2KaNsm4eeDdXx51O/I= + + + + + + + + eJxjYBgFo2AU0AtIazEwyGihimlqMjBoadLWXnOgnRZo9roC7XSjsb3I4J8k/eyiF2hD4yvwQLAiD4Qvz4MQw6dvqAJi/TtUwWj8Du/4xQYKNPHzRwFuAABfLAtS + + + + + eJxjYBgFo2BkAwWegXbBKBgFo2AUjIJRMLQBAEtjAC0= + + + + + + + + + + + + [ + { + "type": "item", + "probability": 1, + "count": 1, + "itemName": "Cultist's Key" + } +] + + + + + + + [{ + "type": "randomCard", + "count": 2, + "colors": [ "colorID" ] +},{ + "type": "randomCard", + "count": 1, + "probability": 0.5, + "rarity": [ "rare" ], + "colors": [ "colorID" ] +},{ + "type": "randomCard", + "count": 3, + "addMaxCount": 2 +}] + + + + + [ + { + "editions": [ "SOI", "EMN", "MID", "VOW" ], + "type": "card", + "count": 10, + "rarity": [ "Common" ] + }, + { + "editions": [ "SOI", "EMN", "MID", "VOW" ], + "type": "card", + "count": 3, + "rarity": [ "Uncommon" ] + }, + { + "editions": [ "SOI", "EMN", "MID", "VOW" ], + "type": "card", + "count": 1, + "rarity": [ "Rare", "Mythic Rare" ] + } +] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [{ + "text":"This door is locked", + "options":[ + { "name":"Leave" }, + { + "name":"Unlock with Cultist's Key", + "condition":[{"item":"Cultist's Key"}], + "text":"The gate is unlocked.", + "options":[{"name":"Continue.", "action":[ {"deleteMapObject":-1},{"removeItem":"Cultist's Key"}]} ] + } + ] +}] + + + + + + + + + + + diff --git a/forge-gui/res/adventure/common/maps/map/unhallowed_abbey_2F.tmx b/forge-gui/res/adventure/common/maps/map/unhallowed_abbey_2F.tmx new file mode 100644 index 00000000000..eae956d2c8d --- /dev/null +++ b/forge-gui/res/adventure/common/maps/map/unhallowed_abbey_2F.tmx @@ -0,0 +1,411 @@ + + + + + + + + + + + + + + eJxjYBi8wI2TMKaFnU8JmPuYynYTYye17SbFTmrZjctOdDOx2UGJ3SB903gxMUh8Nw9x6si1FxsAmYmPj6yfHLuxhScuc7DJDXV76aUfpG8lB3l6l3NQnpdgdhMbzpTaiW43MfZSy050u/EBatuJbDe+dEurOgnZHnz84WgvtcIWAIOHLCo= + + + + + eJx7ws7A8GQI4UJ54jE1zCAVD4Sd+Oynt33UjD9y7B6u4QyzE91uevkRXYxe4YzN/8MtXvHZTUt34Ms/tPY/NrMHMv8OZN1ArDtM+BhQQBM3+XaBzCIFIwNS9Q4GDAAw7Fue + + + + + eJzV1F0KwkAMBOB9788F2tIzCD2NnsIDqNeqtb2bK7IQFpvMBFcwMBS2bL/koZnrEOYfpM+C3Dk1ISz1fo7N99z0/mU+4lOrO2Az8yImaqMuYyI24npMy0bcQZjn0U5enXPeQZkV6aGEm2ytp1KuVaXcQ4e5U8u5W8yt8s16ifdW57y9007mp+8ye5KxNZPdk6htmawr7Wv1jvxP05lletz8jtwRbP8ybGn78R/qCRGT22E= + + + + + eJzl1G0OQDAMBuD+ZnoFcRXBwdzFvTDugcwSZsOqPhJv0tik2xM/CuCbkQFAGR5XH/CbidjviwWffdbktF1mFaky11y26zszXO9T3PZom+p2450S18/JLWYrR+Xa+q64tjTjne3s1qj2Puep7t3n/+hSZ0HPICXUOfT913DYHKavzWmatq5llu85TTM294m86ZpFzQDLEj8K + + + + + + + + eJxjYBgFo2AUDDZQKE89sx5rMjA80SRdjlLwH2SuFulyo4A0MBq/o2AU4AcAe0YOPQ== + + + + + + [{ + "type": "randomCard", + "count": 2, + "colors": [ "colorID" ] +},{ + "type": "randomCard", + "count": 1, + "probability": 0.5, + "rarity": [ "rare" ], + "colors": [ "colorID" ] +},{ + "type": "randomCard", + "count": 3, + "addMaxCount": 2 +}] + + + + + + + + + + + + + + + + + + [ + { + "editions": [ "VOW" ], + "type": "card", + "count": 10, + "rarity": [ "Common" ] + }, + { + "editions": [ "VOW" ], + "type": "card", + "count": 3, + "rarity": [ "Uncommon" ] + }, + { + "editions": [ "VOW" ], + "type": "card", + "count": 1, + "rarity": [ "Rare", "Mythic Rare" ] + } +] + + + + + + [{ + "type": "randomCard", + "count": 2, + "colors": [ "colorID" ] +},{ + "type": "randomCard", + "count": 1, + "probability": 0.5, + "rarity": [ "rare" ], + "colors": [ "colorID" ] +},{ + "type": "randomCard", + "count": 3, + "addMaxCount": 2 +}] + + + + + [ + { + "editions": [ "MID" ], + "type": "card", + "count": 10, + "rarity": [ "Common" ] + }, + { + "editions": [ "MID" ], + "type": "card", + "count": 3, + "rarity": [ "Uncommon" ] + }, + { + "editions": [ "MID" ], + "type": "card", + "count": 1, + "rarity": [ "Rare", "Mythic Rare" ] + } +] + + + + + + [{ + "type": "randomCard", + "count": 2, + "colors": [ "colorID" ] +},{ + "type": "randomCard", + "count": 1, + "probability": 0.5, + "rarity": [ "rare" ], + "colors": [ "colorID" ] +},{ + "type": "randomCard", + "count": 3, + "addMaxCount": 2 +}] + + + + + [{ + "type": "randomCard", + "count": 2, + "colors": [ "colorID" ] +},{ + "type": "randomCard", + "count": 1, + "probability": 0.5, + "rarity": [ "rare" ], + "colors": [ "colorID" ] +},{ + "type": "randomCard", + "count": 3, + "addMaxCount": 2 +}] + + + + + [ + { + "editions": [ "SOI" ], + "type": "card", + "count": 10, + "rarity": [ "Common" ] + }, + { + "editions": [ "SOI" ], + "type": "card", + "count": 3, + "rarity": [ "Uncommon" ] + }, + { + "editions": [ "SOI" ], + "type": "card", + "count": 1, + "rarity": [ "Rare", "Mythic Rare" ] + } +] + + + + + + [ + { + "editions": [ "EMN" ], + "type": "card", + "count": 10, + "rarity": [ "Common" ] + }, + { + "editions": [ "EMN" ], + "type": "card", + "count": 3, + "rarity": [ "Uncommon" ] + }, + { + "editions": [ "EMN" ], + "type": "card", + "count": 1, + "rarity": [ "Rare", "Mythic Rare" ] + } +] + + + + + + + + + + + + + + + + + + + + + + + + + + + + [{ + "text":"A translucent, shimmering red field blocks your path. Pained screams echo through the room behind you.", + "options":[ + { "name":"Leave." } + ] +}] + + + + + [ + { + "text":"A captive lies tied to the altar. Glowing red runes encircle them.", + "options":[ + { + "text":"As the third captive is freed, you hear the sound of shattering as the barrier in the center of the chamber fails. Time to end this.", + "action":[{"deleteMapObject":-1},{"advanceMapFlag":"gate"}], + "name":"Free them." + "options":[{ + "condition":[{"getMapFlag":{"key":"gate","op":">=","val":3}}], + "action":[{"deleteMapObject":108}], + "name":"ok" }] + }, + { "name":"Leave." } + ] + } + +] + + + + + [ + { + "text":"A captive lies tied to the altar. Glowing red runes encircle them.", + "options":[ + { + "text":"As the third captive is freed, you hear the sound of shattering as the barrier in the center of the chamber fails. Time to end this.", + "action":[{"deleteMapObject":-1},{"advanceMapFlag":"gate"}], + "name":"Free them." + "options":[{ + "condition":[{"getMapFlag":{"key":"gate","op":">=","val":3}}], + "action":[{"deleteMapObject":108}], + "name":"ok" }] + }, + { "name":"Leave." } + ] + } + +] + + + + + [ + { + "text":"A captive lies tied to the altar. Glowing red runes encircle them.", + "options":[ + { + "text":"As the third captive is freed, you hear the sound of shattering as the barrier in the center of the chamber fails. Time to end this.", + "action":[{"deleteMapObject":-1},{"advanceMapFlag":"gate"}], + "name":"Free them." + "options":[{ + "condition":[{"getMapFlag":{"key":"gate","op":">=","val":3}}], + "action":[{"deleteMapObject":108}], + "name":"ok" }] + }, + { "name":"Leave." } + ] + } + +] + + + + + [{ + "text":"*The large, imposing demon before you smirks*\n Ah, so you must be the one who's been freeing my sacrifices...and the volunteer to be my new one. Tell me, mortal, as your last words that aren't a howl of pain - why challenge me?", + "options":[{ + "name":"Because you're a monster, and you should be stopped!", + "text":"*The demon sneers.*\n Ah, a noble *hero*. I should have guessed. Your kind die like anyone else when your power runs dry - allow me to demonstrate!", + "options":[{ + "name":"End", + "action":[{"deleteMapObject":113}] + }] + }, + { + "name":"I want power. I'll take it from what's left of you.", + "text":"*The demon chuckles.*\n I'll commend your ambition, if not your sense. Fight hard enough, and I might let you replace that failure you dealt with upstairs.", + "options":[{ + "name":"End", + "action":[{"deleteMapObject":113}] + }] + }, + { + "name":"Honestly, you just looked like you'd be a good fight.", + "text":"*The demon blinks in surprise, then laughs.*\n Well, if that's what you seek, you'll find more than you bargained for here. I hope you enjoy the last battle of your life, *mortal*.", + "options":[{ + "name":"End", + "action":[{"deleteMapObject":113}] + }] + }, + { + "name":"...", + "text":"*The fiend's eyes narrow.*\n Too scared for words? So be it, mortal. You'll die all the same.", + "options":[{ + "name":"End", + "action":[{"deleteMapObject":113}] + }] + }] +}] + + + + + [{ + "text":"*With a snarl of pain, the demon collapses to the floor.* \n Congratulations, mortal, you've bested me. In exchange for my life, I offer a lesson - the same killing power I wield.", + "options":[{ + "name":"I have no need for power from something as vile as you. Die!", + "text":"*The demon's eyes widen in fear.* \n No! I will not be destroyed by- \n *A final blast of power reduces him to mana in the air of this place, and something clatters to the ground. A holy symbol of this place - or rather, what it once was. As the unholy energy around it fades, you can still feel magic coursing through it.*" + "options":[{ + "name":"End", + "action":[{"addItem":"Hallowed Sigil"},{"deleteMapObject":116}] + }] + }, + { + "name":"If you knew anything worth teaching me directly, I wouldn't have been able to defeat you.", + "text":"*The demon's eyes widen in fear.* \n No! I will not be destroyed by- \n *A final blast of power reduces him to mana in the air of this place, and something clatters to the ground. A holy symbol of this place - or rather, what it once was. As the unholy energy around it fades, you can still feel magic coursing through it.*" + "options":[{ + "name":"End", + "action":[{"addItem":"Hallowed Sigil"},{"deleteMapObject":116}] + }] + }, + { + "name":"...Very well, even a monster like you deserves mercy. *Once.*", + "text":"*The demon smiles, moving his hand in an arcane gesture.* \n *As you unconsciously mimic it, you feel a dark, repulsive power crystallize in your hand. \n *The demon smiles as he begins to fade into a cloud of smoke.* \n Very well, mortal. My power is yours to wield...until next time." + "options":[{ + "name":"End", + "action":[{"addItem":"Unhallowed Sigil"},{"deleteMapObject":116}] + }] + }, + { + "name":"As you should. I'll take your offer.", + "text":"*The demon smiles, moving his hand in an arcane gesture.* \n *As you unconsciously mimic it, you feel a dark, repulsive power crystallize in your hand. \n *The demon smiles as he begins to fade into a cloud of smoke.* \n Very well, mortal. My power is yours to wield...until next time." + "options":[{ + "name":"End", + "action":[{"addItem":"Unhallowed Sigil"},{"deleteMapObject":116}] + }] + }] +}] + + { "startBattleWithCard": [ "Mox Jet", "Power of Valyx"] +} + + + + + diff --git a/forge-gui/res/adventure/common/maps/tileset/DarkAbbeyTiles.png b/forge-gui/res/adventure/common/maps/tileset/DarkAbbeyTiles.png new file mode 100644 index 00000000000..2af614a0e4c Binary files /dev/null and b/forge-gui/res/adventure/common/maps/tileset/DarkAbbeyTiles.png differ diff --git a/forge-gui/res/adventure/common/maps/tileset/DarkAbbeyTiles.tsx b/forge-gui/res/adventure/common/maps/tileset/DarkAbbeyTiles.tsx new file mode 100644 index 00000000000..28524076808 --- /dev/null +++ b/forge-gui/res/adventure/common/maps/tileset/DarkAbbeyTiles.tsx @@ -0,0 +1,4 @@ + + + + diff --git a/forge-gui/res/adventure/common/maps/tileset/desertbuildingtiles.png b/forge-gui/res/adventure/common/maps/tileset/desertbuildingtiles.png index edacd848194..2a1f1ca3ea5 100644 Binary files a/forge-gui/res/adventure/common/maps/tileset/desertbuildingtiles.png and b/forge-gui/res/adventure/common/maps/tileset/desertbuildingtiles.png differ diff --git a/forge-gui/res/adventure/common/maps/tileset/desertbuildingtiles.tsx b/forge-gui/res/adventure/common/maps/tileset/desertbuildingtiles.tsx new file mode 100644 index 00000000000..cf06f6599b0 --- /dev/null +++ b/forge-gui/res/adventure/common/maps/tileset/desertbuildingtiles.tsx @@ -0,0 +1,1406 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/forge-gui/res/adventure/common/sprites/enemy/construct/golem.png b/forge-gui/res/adventure/common/sprites/enemy/construct/golem.png index a2e64d6d223..4dcec721bb4 100644 Binary files a/forge-gui/res/adventure/common/sprites/enemy/construct/golem.png and b/forge-gui/res/adventure/common/sprites/enemy/construct/golem.png differ diff --git a/forge-gui/res/adventure/common/sprites/enemy/construct/golem_2.png b/forge-gui/res/adventure/common/sprites/enemy/construct/golem_2.png index f6eb64de096..1f5cb6a8ceb 100644 Binary files a/forge-gui/res/adventure/common/sprites/enemy/construct/golem_2.png and b/forge-gui/res/adventure/common/sprites/enemy/construct/golem_2.png differ diff --git a/forge-gui/res/adventure/common/sprites/enemy/fiend/valyx.atlas b/forge-gui/res/adventure/common/sprites/enemy/fiend/valyx.atlas new file mode 100644 index 00000000000..8ca0984d795 --- /dev/null +++ b/forge-gui/res/adventure/common/sprites/enemy/fiend/valyx.atlas @@ -0,0 +1,68 @@ +valyx.png +size: 136,96 +format: RGBA8888 +filter: Nearest,Nearest +repeat: none +Avatar + xy: 0, 0 + size: 16, 16 +Idle + xy: 0, 16 + size: 24, 24 +Idle + xy: 24, 16 + size: 24, 24 +Idle + xy: 48, 16 + size: 24, 24 +Idle + xy: 72, 16 + size: 24, 24 +Walk + xy: 0, 40 + size: 24, 24 +Walk + xy: 24, 40 + size: 24, 24 +Walk + xy: 48, 40 + size: 24, 24 +Walk + xy: 72, 40 + size: 24, 24 +Attack + xy: 0, 64 + size: 24, 24 +Attack + xy: 24, 64 + size: 24, 24 +Attack + xy: 48, 64 + size: 24, 24 +Attack + xy: 72, 64 + size: 24, 24 +Hit + xy: 0, 88 + size: 24, 24 +Hit + xy: 24, 88 + size: 24, 24 +Hit + xy: 48, 88 + size: 24, 24 +Hit + xy: 72, 88 + size: 24, 24 +Death + xy: 0, 112 + size: 24, 24 +Death + xy: 24, 112 + size: 24, 24 +Death + xy: 48, 112 + size: 24, 24 +Death + xy: 72, 112 + size: 24, 24 \ No newline at end of file diff --git a/forge-gui/res/adventure/common/sprites/enemy/fiend/valyx.png b/forge-gui/res/adventure/common/sprites/enemy/fiend/valyx.png new file mode 100644 index 00000000000..4dac80504b5 Binary files /dev/null and b/forge-gui/res/adventure/common/sprites/enemy/fiend/valyx.png differ diff --git a/forge-gui/res/adventure/common/sprites/enemy/humanoid/human/knight/false_knight.atlas b/forge-gui/res/adventure/common/sprites/enemy/humanoid/human/knight/false_knight.atlas new file mode 100644 index 00000000000..777f85e76d7 --- /dev/null +++ b/forge-gui/res/adventure/common/sprites/enemy/humanoid/human/knight/false_knight.atlas @@ -0,0 +1,68 @@ +false_knight.png +size: 64,96 +format: RGBA8888 +filter: Nearest,Nearest +repeat: none +Avatar + xy: 0, 0 + size: 16, 16 +Idle + xy: 0, 16 + size: 16, 16 +Idle + xy: 16, 16 + size: 16, 16 +Idle + xy: 32, 16 + size: 16, 16 +Idle + xy: 48, 16 + size: 16, 16 +Walk + xy: 0, 32 + size: 16, 16 +Walk + xy: 16, 32 + size: 16, 16 +Walk + xy: 32, 32 + size: 16, 16 +Walk + xy: 48, 32 + size: 16, 16 +Attack + xy: 0, 48 + size: 16, 16 +Attack + xy: 16, 48 + size: 16, 16 +Attack + xy: 32, 48 + size: 16, 16 +Attack + xy: 48, 48 + size: 16, 16 +Hit + xy: 0, 64 + size: 16, 16 +Hit + xy: 16, 64 + size: 16, 16 +Hit + xy: 32, 64 + size: 16, 16 +Hit + xy: 48, 64 + size: 16, 16 +Death + xy: 0, 80 + size: 16, 16 +Death + xy: 16, 80 + size: 16, 16 +Death + xy: 32, 80 + size: 16, 16 +Death + xy: 48, 80 + size: 16, 16 \ No newline at end of file diff --git a/forge-gui/res/adventure/common/sprites/enemy/humanoid/human/knight/false_knight.png b/forge-gui/res/adventure/common/sprites/enemy/humanoid/human/knight/false_knight.png new file mode 100644 index 00000000000..5211715edd2 Binary files /dev/null and b/forge-gui/res/adventure/common/sprites/enemy/humanoid/human/knight/false_knight.png differ diff --git a/forge-gui/res/adventure/common/sprites/enemy/humanoid/human/warlock/false_monk.atlas b/forge-gui/res/adventure/common/sprites/enemy/humanoid/human/warlock/false_monk.atlas new file mode 100644 index 00000000000..df33fba0a9d --- /dev/null +++ b/forge-gui/res/adventure/common/sprites/enemy/humanoid/human/warlock/false_monk.atlas @@ -0,0 +1,68 @@ +false_monk.png +size: 64,96 +format: RGBA8888 +filter: Nearest,Nearest +repeat: none +Avatar + xy: 0, 0 + size: 16, 16 +Idle + xy: 0, 16 + size: 16, 16 +Idle + xy: 16, 16 + size: 16, 16 +Idle + xy: 32, 16 + size: 16, 16 +Idle + xy: 48, 16 + size: 16, 16 +Walk + xy: 0, 32 + size: 16, 16 +Walk + xy: 16, 32 + size: 16, 16 +Walk + xy: 32, 32 + size: 16, 16 +Walk + xy: 48, 32 + size: 16, 16 +Attack + xy: 0, 48 + size: 16, 16 +Attack + xy: 16, 48 + size: 16, 16 +Attack + xy: 32, 48 + size: 16, 16 +Attack + xy: 48, 48 + size: 16, 16 +Hit + xy: 0, 64 + size: 16, 16 +Hit + xy: 16, 64 + size: 16, 16 +Hit + xy: 32, 64 + size: 16, 16 +Hit + xy: 48, 64 + size: 16, 16 +Death + xy: 0, 80 + size: 16, 16 +Death + xy: 16, 80 + size: 16, 16 +Death + xy: 32, 80 + size: 16, 16 +Death + xy: 48, 80 + size: 16, 16 \ No newline at end of file diff --git a/forge-gui/res/adventure/common/sprites/enemy/humanoid/human/warlock/false_monk.png b/forge-gui/res/adventure/common/sprites/enemy/humanoid/human/warlock/false_monk.png new file mode 100644 index 00000000000..112f3e06f75 Binary files /dev/null and b/forge-gui/res/adventure/common/sprites/enemy/humanoid/human/warlock/false_monk.png differ diff --git a/forge-gui/res/adventure/common/sprites/enemy/humanoid/human/warlock/high_cultist.atlas b/forge-gui/res/adventure/common/sprites/enemy/humanoid/human/warlock/high_cultist.atlas new file mode 100644 index 00000000000..c2c7608c554 --- /dev/null +++ b/forge-gui/res/adventure/common/sprites/enemy/humanoid/human/warlock/high_cultist.atlas @@ -0,0 +1,68 @@ +high_cultist.png +size: 64,96 +format: RGBA8888 +filter: Nearest,Nearest +repeat: none +Avatar + xy: 0, 0 + size: 16, 16 +Idle + xy: 0, 16 + size: 16, 16 +Idle + xy: 16, 16 + size: 16, 16 +Idle + xy: 32, 16 + size: 16, 16 +Idle + xy: 48, 16 + size: 16, 16 +Walk + xy: 0, 32 + size: 16, 16 +Walk + xy: 16, 32 + size: 16, 16 +Walk + xy: 32, 32 + size: 16, 16 +Walk + xy: 48, 32 + size: 16, 16 +Attack + xy: 0, 48 + size: 16, 16 +Attack + xy: 16, 48 + size: 16, 16 +Attack + xy: 32, 48 + size: 16, 16 +Attack + xy: 48, 48 + size: 16, 16 +Hit + xy: 0, 64 + size: 16, 16 +Hit + xy: 16, 64 + size: 16, 16 +Hit + xy: 32, 64 + size: 16, 16 +Hit + xy: 48, 64 + size: 16, 16 +Death + xy: 0, 80 + size: 16, 16 +Death + xy: 16, 80 + size: 16, 16 +Death + xy: 32, 80 + size: 16, 16 +Death + xy: 48, 80 + size: 16, 16 \ No newline at end of file diff --git a/forge-gui/res/adventure/common/sprites/enemy/humanoid/human/warlock/high_cultist.png b/forge-gui/res/adventure/common/sprites/enemy/humanoid/human/warlock/high_cultist.png new file mode 100644 index 00000000000..f90ebb16ff5 Binary files /dev/null and b/forge-gui/res/adventure/common/sprites/enemy/humanoid/human/warlock/high_cultist.png differ diff --git a/forge-gui/res/adventure/common/sprites/items.atlas b/forge-gui/res/adventure/common/sprites/items.atlas index cfec35a2852..a3a5d676160 100644 --- a/forge-gui/res/adventure/common/sprites/items.atlas +++ b/forge-gui/res/adventure/common/sprites/items.atlas @@ -468,4 +468,9 @@ CartoucheOfAmbition CartoucheOfZeal xy:320,240 size:16,16 - +HallowedSigil + xy:320,256 + size:16,16 +UnhallowedSigil + xy:320,272 + size:16,16 diff --git a/forge-gui/res/adventure/common/sprites/items.png b/forge-gui/res/adventure/common/sprites/items.png index c05441b3629..504fa8e2dad 100644 Binary files a/forge-gui/res/adventure/common/sprites/items.png and b/forge-gui/res/adventure/common/sprites/items.png differ diff --git a/forge-gui/res/adventure/common/world/biomes/luxa.json b/forge-gui/res/adventure/common/world/biomes/luxa.json index cf93e5705c9..82412d8857d 100644 --- a/forge-gui/res/adventure/common/world/biomes/luxa.json +++ b/forge-gui/res/adventure/common/world/biomes/luxa.json @@ -43,8 +43,8 @@ "x": 0.5, "y": 0.5, "structureAtlasPath": "world/tilesets/terrain.atlas", - "sourcePath": "world/models/fill.png", - "maskPath": "world/masks/fill.png", + "sourcePath": "world/structures/models/fill.png", + "maskPath": "world/structures/masks/fill.png", "height": 0.99, "width": 0.9, "periodicOutput": false, diff --git a/forge-gui/res/adventure/common/world/biomes/outlands.json b/forge-gui/res/adventure/common/world/biomes/outlands.json index ff3d4b8ceba..1c4173e964d 100644 --- a/forge-gui/res/adventure/common/world/biomes/outlands.json +++ b/forge-gui/res/adventure/common/world/biomes/outlands.json @@ -32,8 +32,8 @@ "x": 0.5, "y": 0.5, "structureAtlasPath": "world/structures/structures.atlas", - "sourcePath": "world/models/fill.png", - "maskPath": "world/masks/fill.png", + "sourcePath": "world/structures/models/fill.png", + "maskPath": "world/structures/masks/fill.png", "height": 0.99, "width": 0.9, "periodicOutput": false, diff --git a/forge-gui/res/adventure/common/world/biomes/white.json b/forge-gui/res/adventure/common/world/biomes/white.json index 6bf5c73f376..0f3ac671de3 100644 --- a/forge-gui/res/adventure/common/world/biomes/white.json +++ b/forge-gui/res/adventure/common/world/biomes/white.json @@ -107,7 +107,8 @@ "CaveW6", "OrthodoxyBasilica", "Nahiri Encampment", - "MageTower White" + "MageTower White", + "UnhallowedAbbey" ], "structures": [ { diff --git a/forge-gui/res/adventure/common/world/enemies.json b/forge-gui/res/adventure/common/world/enemies.json index 6596fe86d44..5b7ed284d30 100644 --- a/forge-gui/res/adventure/common/world/enemies.json +++ b/forge-gui/res/adventure/common/world/enemies.json @@ -7840,6 +7840,168 @@ "BiomeWhite" ] }, +{ + "name": "False Knight", + "sprite": "sprites/enemy/humanoid/human/knight/false_knight.atlas", + "deck": [ + "decks/standard/death_knight.dck" + ], + "ai": "", + "spawnRate": 1, + "difficulty": 0.1, + "speed": 30, + "life": 18, + "rewards": [ + { + "type": "deckCard", + "probability": 1, + "count": 2, + "addMaxCount": 4, + "rarity": [ + "common" + ] + }, + { + "type": "deckCard", + "probability": 0.5, + "count": 1, + "addMaxCount": 2, + "rarity": [ + "uncommon" + ], + "cardTypes": [ + "Creature", + "Artifact", + "Enchantment", + "Instant", + "Sorcery" + ] + }, + { + "type": "deckCard", + "probability": 0.25, + "count": 1, + "addMaxCount": 1, + "rarity": [ + "rare", + "mythicrare" + ], + "cardTypes": [ + "Creature", + "Artifact", + "Enchantment", + "Instant", + "Sorcery" + ] + }, + { + "type": "deckCard", + "probability": 0.1, + "count": 1, + "rarity": [ + "rare" + ], + "cardTypes": [ + "Land" + ] + }, + { + "type": "gold", + "probability": 0.3, + "count": 10, + "addMaxCount": 90 + } + ], + "colors": "B", + "questTags": [ + "Disguised", + "Soldier", + "Human", + "Knight", + "Unholy", + "IdentityBlack" + ] +}, +{ + "name": "False Monk", + "sprite": "sprites/enemy/humanoid/human/warlock/false_monk.atlas", + "deck": [ + "decks/standard/cultist.dck" + ], + "randomizeDeck": false, + "spawnRate": 1, + "difficulty": 0.1, + "speed": 24, + "life": 15, + "rewards": [ + { + "type": "deckCard", + "probability": 1 + "count": 2, + "addMaxCount": 2, + "rarity": [ + "common" + ] + }, + { + "type": "deckCard", + "probability": 0.5, + "count": 1, + "addMaxCount": 1, + "rarity": [ + "uncommon" + ], + "cardTypes": [ + "Creature", + "Artifact", + "Enchantment", + "Instant", + "Sorcery" + ] + }, + { + "type": "deckCard", + "probability": 0.25, + "count": 1, + "addMaxCount": 1, + "rarity": [ + "rare", + "mythicrare" + ], + "cardTypes": [ + "Creature", + "Artifact", + "Enchantment", + "Instant", + "Sorcery" + ] + }, + { + "type": "deckCard", + "probability": 0.1, + "count": 1, + "rarity": [ + "rare" + ], + "cardTypes": [ + "Land" + ] + }, + { + "type": "gold", + "probability": 0.3, + "count": 10, + "addMaxCount": 90 + } + ], + "colors": "B", + "questTags": [ + "Human", + "Disguised", + "Unholy", + "IdentityBlack", + ] +}, { "name": "Farmer", "nameOverride": "", @@ -11673,6 +11835,86 @@ null ] }, +{ + "name": "High Cultist", + "sprite": "sprites/enemy/humanoid/human/warlock/high_cultist.atlas", + "deck": [ + "decks/standard/cultist.dck" + ], + "randomizeDeck": false, + "spawnRate": 1, + "difficulty": 0.1, + "speed": 24, + "life": 30, + "rewards": [ + { + "type": "deckCard", + "probability": 1, + "count": 2, + "addMaxCount": 4, + "rarity": [ + "common" + ] + }, + { + "type": "deckCard", + "probability": 0.5, + "count": 1, + "addMaxCount": 2, + "rarity": [ + "uncommon" + ], + "cardTypes": [ + "Creature", + "Artifact", + "Enchantment", + "Instant", + "Sorcery" + ] + }, + { + "type": "deckCard", + "probability": 0.25, + "count": 1, + "addMaxCount": 1, + "rarity": [ + "rare", + "mythicrare" + ], + "cardTypes": [ + "Creature", + "Artifact", + "Enchantment", + "Instant", + "Sorcery" + ] + }, + { + "type": "deckCard", + "probability": 0.1, + "count": 1, + "rarity": [ + "rare" + ], + "cardTypes": [ + "Land" + ] + }, + { + "type": "gold", + "probability": 0.3, + "count": 10, + "addMaxCount": 90 + } + ], + "colors": "B", + "questTags": [ + "Human", + "Disguised", + "Unholy", + "IdentityBlack", + ] +}, { "name": "High Elf", "sprite": "sprites/enemy/humanoid/elf/druid_2.atlas", @@ -21035,6 +21277,69 @@ "BiomeRed" ] }, +{ + "name": "Valyx Feaster of Torment", + "sprite": "sprites/enemy/fiend/valyx.atlas", + "deck": [ + "decks/miniboss/valyx.dck" + ], + "ai": "", + "spawnRate": 1, + "difficulty": 0.1, + "speed": 31, + "life": 80, + "rewards": [ + { + "type": "deckCard", + "probability": 1, + "count": 2, + "addMaxCount": 4, + "rarity": [ + "common" + ] + }, + { + "type": "deckCard", + "probability": 1, + "count": 2, + "addMaxCount": 2, + "rarity": [ + "uncommon" + ], + "cardTypes": [ + "Creature", + "Artifact", + "Enchantment", + "Instant", + "Sorcery" + ] + }, + { + "type": "deckCard", + "probability": 1, + "count": 2, + "addMaxCount": 1, + "rarity": [ + "rare", + "mythicrare" + ], + "cardTypes": [ + "Creature", + "Artifact", + "Enchantment", + "Instant", + "Sorcery" + ] + } + ], + "colors": "B", + "questTags": [ + "Demon", + "Humanoid", + "Unholy", + "IdentityBlack" + ] +}, { "name": "Vampire", "sprite": "sprites/enemy/undead/vampire.atlas", diff --git a/forge-gui/res/adventure/common/world/items.json b/forge-gui/res/adventure/common/world/items.json index 434984aace0..dab5ea941ba 100644 --- a/forge-gui/res/adventure/common/world/items.json +++ b/forge-gui/res/adventure/common/world/items.json @@ -1234,5 +1234,32 @@ "Slobad's Iron Boots" ] } -} +}, + { + "name": "Hallowed Sigil", + "description": "Turn a creature hexproof until end of turn.", + "equipmentSlot": "Neck", + "iconName": "HallowedSigil", + "effect": { + "startBattleWithCard": [ + "Hallowed Sigil" + ] + } + }, + { + "name": "Unhallowed Sigil", + "description": "Devour the life of an enemy creature, killing it.", + "equipmentSlot": "Right", + "iconName": "UnhallowedSigil", + "effect": { + "startBattleWithCard": [ + "Sigil of Torment" + ] + } + }, + { + "name": "Cultist's Key", + "iconName": "StrangeKey", + "questItem": true + } ] diff --git a/forge-gui/res/adventure/common/world/points_of_interest.json b/forge-gui/res/adventure/common/world/points_of_interest.json index a877535b4c2..969e10f2d64 100644 --- a/forge-gui/res/adventure/common/world/points_of_interest.json +++ b/forge-gui/res/adventure/common/world/points_of_interest.json @@ -3130,6 +3130,18 @@ "Planeswalker" ] }, +{ + "name": "UnhallowedAbbey", + "type": "dungeon", + "count": 1, + "spriteAtlas": "maps/tileset/buildings.atlas", + "sprite": "Monastery", + "map": "../common/maps/map/unhallowed_abbey_1F.tmx", + "radiusFactor": 0.8, + "questTags": [ + "UnhallowedAbbey" + ] +}, { "name": "VampireCastle", "type": "dungeon", diff --git a/forge-gui/res/cardsfolder/a/amethyst_dragon_explosive_crystal.txt b/forge-gui/res/cardsfolder/a/amethyst_dragon_explosive_crystal.txt index c2338666ee4..8abdf33ff66 100644 --- a/forge-gui/res/cardsfolder/a/amethyst_dragon_explosive_crystal.txt +++ b/forge-gui/res/cardsfolder/a/amethyst_dragon_explosive_crystal.txt @@ -12,5 +12,5 @@ ALTERNATE Name:Explosive Crystal ManaCost:4 R Types:Sorcery Adventure -A:SP$ DealDamage | ValidTgts$ Any to distribute damage to | NumDmg$ 4 | TargetMin$ 0 | TargetMax$ 4 | DividedAsYouChoose$ 4 | SpellDescription$ CARDNAME deals 4 damage divided as you choose among any number of targets. +A:SP$ DealDamage | ValidTgts$ Any | NumDmg$ 4 | TargetMin$ 0 | TargetMax$ 4 | DividedAsYouChoose$ 4 | SpellDescription$ CARDNAME deals 4 damage divided as you choose among any number of targets. Oracle:Explosive Crystal deals 4 damage divided as you choose among any number of targets. diff --git a/forge-gui/res/cardsfolder/a/arena.txt b/forge-gui/res/cardsfolder/a/arena.txt index 4f75021fb8b..c0edecbf5b2 100644 --- a/forge-gui/res/cardsfolder/a/arena.txt +++ b/forge-gui/res/cardsfolder/a/arena.txt @@ -1,9 +1,7 @@ Name:Arena ManaCost:no cost Types:Land -A:AB$ Tap | Cost$ 3 T | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | SubAbility$ DBTap | AlwaysRemember$ True | SpellDescription$ Tap target creature you control and target creature of an opponent's choice they control. Those creatures fight each other. (Each deals damage equal to its power to the other.) -SVar:DBTap:DB$ Tap | TargetingPlayer$ Player.Opponent | TargetingPlayerControls$ True | ValidTgts$ Creature | TgtPrompt$ Select target creature you control | SubAbility$ DBFight | AlwaysRemember$ True -SVar:DBFight:DB$ Fight | Defined$ Remembered | SubAbility$ DBCleanup -SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True -AI:RemoveDeck:All +A:AB$ Tap | Cost$ 3 T | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | SubAbility$ DBTap | AILogic$ Arena | SpellDescription$ Tap target creature you control and target creature of an opponent's choice they control. Those creatures fight each other. (Each deals damage equal to its power to the other.) +SVar:DBTap:DB$ Tap | TargetingPlayer$ Player.Opponent | TargetingPlayerControls$ True | ValidTgts$ Creature | TgtPrompt$ Select target creature you control | SubAbility$ DBFight +SVar:DBFight:DB$ Fight | Defined$ Targeted Oracle:{3}, {T}: Tap target creature you control and target creature of an opponent's choice they control. Those creatures fight each other. (Each deals damage equal to its power to the other.) diff --git a/forge-gui/res/cardsfolder/c/chalice_of_the_void.txt b/forge-gui/res/cardsfolder/c/chalice_of_the_void.txt index c2720da0301..69b28af8184 100644 --- a/forge-gui/res/cardsfolder/c/chalice_of_the_void.txt +++ b/forge-gui/res/cardsfolder/c/chalice_of_the_void.txt @@ -7,5 +7,5 @@ SVar:TrigCounter:DB$ Counter | Defined$ TriggeredSpellAbility SVar:X:Count$xPaid SVar:Y:Count$CardCounters.CHARGE SVar:AICurseEffect:ChaliceOfTheVoid -AI:RemoveDeck:All +AI:RemoveDeck:Random Oracle:Chalice of the Void enters the battlefield with X charge counters on it.\nWhenever a player casts a spell with mana value equal to the number of charge counters on Chalice of the Void, counter that spell. diff --git a/forge-gui/res/cardsfolder/c/chandra_fire_of_kaladesh_chandra_roaring_flame.txt b/forge-gui/res/cardsfolder/c/chandra_fire_of_kaladesh_chandra_roaring_flame.txt index bc77dbf71f5..668d609e63f 100644 --- a/forge-gui/res/cardsfolder/c/chandra_fire_of_kaladesh_chandra_roaring_flame.txt +++ b/forge-gui/res/cardsfolder/c/chandra_fire_of_kaladesh_chandra_roaring_flame.txt @@ -4,7 +4,7 @@ Types:Legendary Creature Human Shaman PT:2/2 T:Mode$ SpellCast | ValidCard$ Card.Red | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUntap | TriggerDescription$ Whenever you cast a red spell, untap CARDNAME. SVar:TrigUntap:DB$ Untap | Defined$ Self -A:AB$ DealDamage | Cost$ T | ValidTgts$ Player,Planeswalker | TgtPrompt$ Select target player or planeswalker | NumDmg$ 1 | SubAbility$ DBTransform | AILogic$ PingAfterAttack | SpellDescription$ CARDNAME deals 1 damage to target player or planeswalker. If CARDNAME has dealt 3 or more damage this turn, exile her, then return her to the battlefield transformed under her owner's control. +A:AB$ DealDamage | Cost$ T | ValidTgts$ Player,Planeswalker | TgtPrompt$ Select target player or planeswalker | NumDmg$ 1 | SubAbility$ DBTransform | AILogic$ PingAfterAttack | SpellDescription$ CARDNAME deals 1 damage to target player or planeswalker. If NICKNAME has dealt 3 or more damage this turn, exile her, then return her to the battlefield transformed under her owner's control. SVar:DBTransform:DB$ ChangeZone | Origin$ Battlefield | Destination$ Exile | RememberChanged$ True | SubAbility$ DBReturn | ConditionCheckSVar$ X | ConditionSVarCompare$ GE3 | StackDescription$ If CARDNAME has dealt 3 or more damage this turn, exile her, then return her to the battlefield transformed under her owner's control. SVar:DBReturn:DB$ ChangeZone | Defined$ Remembered | Origin$ Exile | Destination$ Battlefield | Transformed$ True | SubAbility$ DBCleanup | StackDescription$ SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True diff --git a/forge-gui/res/cardsfolder/c/cleaver_skaab.txt b/forge-gui/res/cardsfolder/c/cleaver_skaab.txt index 7c471cab7cc..d0eeb448a99 100644 --- a/forge-gui/res/cardsfolder/c/cleaver_skaab.txt +++ b/forge-gui/res/cardsfolder/c/cleaver_skaab.txt @@ -2,7 +2,7 @@ Name:Cleaver Skaab ManaCost:3 U Types:Creature Zombie Horror PT:2/4 -A:AB$ CopyPermanent | Cost$ 3 T Sac<1/Zombie.Other/another zombie> | Defined$ Sacrificed | NumCopies$ 2 | AILogic$ AtOppEOT +A:AB$ CopyPermanent | Cost$ 3 T Sac<1/Zombie.Other/another zombie> | Defined$ Sacrificed | NumCopies$ 2 | AILogic$ AtOppEOT | SpellDescription$ Create two tokens that are copies of the sacrificed creature. DeckNeeds:Type$Zombie DeckHas:Ability$Sacrifice|Token SVar:AIPreference:SacCost$Zombie.Other diff --git a/forge-gui/res/cardsfolder/c/congregation_at_dawn.txt b/forge-gui/res/cardsfolder/c/congregation_at_dawn.txt index d9109e29af9..e335be6e6f2 100644 --- a/forge-gui/res/cardsfolder/c/congregation_at_dawn.txt +++ b/forge-gui/res/cardsfolder/c/congregation_at_dawn.txt @@ -1,5 +1,5 @@ Name:Congregation at Dawn ManaCost:G G W Types:Instant -A:SP$ ChangeZone | Cost$ G G W | Origin$ Library | Destination$ Library | LibraryPosition$ 0 | ChangeType$ Creature | ChangeNum$ 3 | SpellDescription$ Search your library for up to three creature cards, reveal them, then shuffle and put those cards on top in any order. +A:SP$ ChangeZone | Cost$ G G W | Origin$ Library | Destination$ Library | LibraryPosition$ 0 | ChangeType$ Creature | ChangeNum$ 3 | Reorder$ True | SpellDescription$ Search your library for up to three creature cards, reveal them, then shuffle and put those cards on top in any order. Oracle:Search your library for up to three creature cards, reveal them, then shuffle and put those cards on top in any order. diff --git a/forge-gui/res/cardsfolder/d/dwarven_recruiter.txt b/forge-gui/res/cardsfolder/d/dwarven_recruiter.txt index 455de4be4d7..465835dc385 100644 --- a/forge-gui/res/cardsfolder/d/dwarven_recruiter.txt +++ b/forge-gui/res/cardsfolder/d/dwarven_recruiter.txt @@ -3,7 +3,7 @@ ManaCost:2 R Types:Creature Dwarf PT:2/2 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChangeZone | TriggerDescription$ When CARDNAME enters the battlefield, search your library for any number of Dwarf cards, reveal them, then shuffle and put those cards on top in any order. -SVar:TrigChangeZone:DB$ ChangeZone | ChangeNum$ X | ChangeType$ Dwarf | Origin$ Library | Destination$ Library | LibraryPosition$ 0 +SVar:TrigChangeZone:DB$ ChangeZone | ChangeNum$ X | ChangeType$ Dwarf | Origin$ Library | Destination$ Library | LibraryPosition$ 0 | Reorder$ True SVar:X:Count$InYourLibrary.Dwarf AI:RemoveDeck:All DeckNeeds:Type$Dwarf diff --git a/forge-gui/res/cardsfolder/g/goblin_recruiter.txt b/forge-gui/res/cardsfolder/g/goblin_recruiter.txt index cd96d2a1b06..4d67d3c93a7 100644 --- a/forge-gui/res/cardsfolder/g/goblin_recruiter.txt +++ b/forge-gui/res/cardsfolder/g/goblin_recruiter.txt @@ -3,7 +3,7 @@ ManaCost:1 R Types:Creature Goblin PT:1/1 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChangeZone | TriggerDescription$ When CARDNAME enters the battlefield, search your library for any number of Goblin cards, reveal them, then shuffle and put those cards on top in any order. -SVar:TrigChangeZone:DB$ ChangeZone | ChangeNum$ X | ChangeType$ Goblin | Origin$ Library | Destination$ Library | LibraryPosition$ 0 +SVar:TrigChangeZone:DB$ ChangeZone | ChangeNum$ X | ChangeType$ Goblin | Origin$ Library | Destination$ Library | LibraryPosition$ 0 | Reorder$ True SVar:X:Count$InYourLibrary.Goblin DeckNeeds:Type$Goblin #TODO: The AI generally is able to use this card, but will basically place all of its goblins on top of the library in no specific order, which is not very smart. Might need some improvement before RemoveDeck is removed. Currently adding a restriction to at least only play it if the AI has enough lands out on the battlefield already. diff --git a/forge-gui/res/cardsfolder/i/insidious_dreams.txt b/forge-gui/res/cardsfolder/i/insidious_dreams.txt index 07b630d560e..bc8b070bcb9 100644 --- a/forge-gui/res/cardsfolder/i/insidious_dreams.txt +++ b/forge-gui/res/cardsfolder/i/insidious_dreams.txt @@ -1,7 +1,7 @@ Name:Insidious Dreams ManaCost:3 B Types:Instant -A:SP$ ChangeZone | Cost$ 3 B Discard | Origin$ Library | Destination$ Library | ChangeType$ Card | ChangeNum$ X | LibraryPosition$ 0 | Mandatory$ True | SpellDescription$ Search your library for X cards, then shuffle and put those cards on top in any order. +A:SP$ ChangeZone | Cost$ 3 B Discard | Origin$ Library | Destination$ Library | ChangeType$ Card | ChangeNum$ X | LibraryPosition$ 0 | Mandatory$ True | Reorder$ True | SpellDescription$ Search your library for X cards, then shuffle and put those cards on top in any order. SVar:X:Count$xPaid AI:RemoveDeck:All Oracle:As an additional cost to cast this spell, discard X cards.\nSearch your library for X cards, then shuffle and put those cards on top in any order. diff --git a/forge-gui/res/cardsfolder/l/lim_duls_vault.txt b/forge-gui/res/cardsfolder/l/lim_duls_vault.txt index 6252acb4d04..732e3a72526 100644 --- a/forge-gui/res/cardsfolder/l/lim_duls_vault.txt +++ b/forge-gui/res/cardsfolder/l/lim_duls_vault.txt @@ -7,7 +7,7 @@ SVar:CheckLifePaid:DB$ StoreSVar | SVar$ LifePaid | Type$ Number | Expression$ 1 SVar:DBResetRem:DB$ Cleanup | ClearRemembered$ True | SubAbility$ GoToBottom SVar:GoToBottom:DB$ Dig | DigNum$ 5 | ChangeNum$ All | DestinationZone$ Library | LibraryPosition$ -1 | NoLooking$ True | SubAbility$ DBLookAgain | StackDescription$ None SVar:DBLookAgain:DB$ PeekAndReveal | PeekAmount$ 5 | NoReveal$ True | RememberPeeked$ True | StackDescription$ None -SVar:DBShuffle:DB$ ChangeZone | Origin$ Library | Destination$ Library | LibraryPosition$ 0 | ChangeType$ Card.IsRemembered | ChangeNum$ 5 | SubAbility$ DBReset | Hidden$ True | SelectPrompt$ Pick 1 on the top of library | Mandatory$ True | NoReveal$ True | NoLooking$ True | StackDescription$ None +SVar:DBShuffle:DB$ ChangeZone | Origin$ Library | Destination$ Library | LibraryPosition$ 0 | ChangeType$ Card.IsRemembered | ChangeNum$ 5 | SubAbility$ DBReset | Hidden$ True | SelectPrompt$ Pick 1 on the top of library | Mandatory$ True | NoReveal$ True | NoLooking$ True | Reorder$ True | StackDescription$ None SVar:DBReset:DB$ StoreSVar | SVar$ LifePaid | Type$ Number | Expression$ 0 | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:LifePaid:Number$0 diff --git a/forge-gui/res/cardsfolder/m/magus_of_the_arena.txt b/forge-gui/res/cardsfolder/m/magus_of_the_arena.txt index 7fd0ebcff07..417d170dd33 100644 --- a/forge-gui/res/cardsfolder/m/magus_of_the_arena.txt +++ b/forge-gui/res/cardsfolder/m/magus_of_the_arena.txt @@ -2,9 +2,7 @@ Name:Magus of the Arena ManaCost:4 R R Types:Creature Human Wizard PT:5/5 -A:AB$ Tap | Cost$ 3 T | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | SubAbility$ DBTap | AlwaysRemember$ True | SpellDescription$ Tap target creature you control and target creature of an opponent's choice they control. Those creatures fight each other. (Each deals damage equal to its power to the other.) -SVar:DBTap:DB$ Tap | TargetingPlayer$ Player.Opponent | TargetingPlayerControls$ True | ValidTgts$ Creature | TgtPrompt$ Select target creature you control | SubAbility$ DBFight | AlwaysRemember$ True -SVar:DBFight:DB$ Fight | Defined$ Remembered | SubAbility$ DBCleanup -SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True -AI:RemoveDeck:All +A:AB$ Tap | Cost$ 3 T | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | SubAbility$ DBTap | AILogic$ Arena | SpellDescription$ Tap target creature you control and target creature of an opponent's choice they control. Those creatures fight each other. (Each deals damage equal to its power to the other.) +SVar:DBTap:DB$ Tap | TargetingPlayer$ Player.Opponent | TargetingPlayerControls$ True | ValidTgts$ Creature | TgtPrompt$ Select target creature you control | SubAbility$ DBFight +SVar:DBFight:DB$ Fight | Defined$ Targeted Oracle:{3}, {T}: Tap target creature you control and target creature of an opponent's choice they control. Those creatures fight each other. (Each deals damage equal to its power to the other.) diff --git a/forge-gui/res/cardsfolder/n/nova_pentacle.txt b/forge-gui/res/cardsfolder/n/nova_pentacle.txt index df84516ff16..66427dae7f8 100644 --- a/forge-gui/res/cardsfolder/n/nova_pentacle.txt +++ b/forge-gui/res/cardsfolder/n/nova_pentacle.txt @@ -2,7 +2,7 @@ Name:Nova Pentacle ManaCost:4 Types:Artifact A:AB$ ChooseSource | Cost$ 3 T | Choices$ Card,Emblem | AILogic$ NeedsPrevention | SubAbility$ DBEffect | SpellDescription$ The next time a source of your choice would deal damage to you this turn, that damage is dealt to target creature of an opponent's choice instead. -SVar:DBEffect:DB$ Effect | TargetingPlayer$ Player.Opponent | ValidTgts$ Creature | TgtPrompt$ Select target creature to redirect the damage to | ReplacementEffects$ SelflessDamage | ExileOnMoved$ Battlefield | RememberObjects$ Targeted | ExileOnMoved$ Battlefield | SubAbility$ DBCleanup | ConditionDefined$ ChosenCard | ConditionPresent$ Card,Emblem +SVar:DBEffect:DB$ Effect | TargetingPlayer$ Player.Opponent | ValidTgts$ Creature | TgtPrompt$ Select target creature to redirect the damage to | ReplacementEffects$ SelflessDamage | ExileOnMoved$ Battlefield | RememberObjects$ Targeted | SubAbility$ DBCleanup | ConditionDefined$ ChosenCard | ConditionPresent$ Card,Emblem SVar:SelflessDamage:Event$ DamageDone | ValidTarget$ You | ValidSource$ Card.ChosenCardStrict,Emblem.ChosenCard | ReplaceWith$ SelflessDmg | DamageTarget$ Remembered | Description$ The next time a source of your choice would deal damage to you this turn, that damage is dealt to target creature of an opponent's choice instead. SVar:SelflessDmg:DB$ ReplaceEffect | VarName$ Affected | VarValue$ Remembered | VarType$ Card | SubAbility$ ExileEffect SVar:ExileEffect:DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile diff --git a/forge-gui/res/cardsfolder/p/parallax_tide.txt b/forge-gui/res/cardsfolder/p/parallax_tide.txt index 69652377342..4fe48baf073 100644 --- a/forge-gui/res/cardsfolder/p/parallax_tide.txt +++ b/forge-gui/res/cardsfolder/p/parallax_tide.txt @@ -2,10 +2,9 @@ Name:Parallax Tide ManaCost:2 U U Types:Enchantment K:Fading:5 -A:AB$ ChangeZone | Cost$ SubCounter<1/FADE> | ValidTgts$ Land | TgtPrompt$ Select target land | ImprintTargets$ True | Origin$ Battlefield | Destination$ Exile | SpellDescription$ Exile target land. +A:AB$ ChangeZone | Cost$ SubCounter<1/FADE> | ValidTgts$ Land | TgtPrompt$ Select target land | Origin$ Battlefield | Destination$ Exile | SpellDescription$ Exile target land. T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ TrigReturn | TriggerDescription$ When CARDNAME leaves the battlefield, each player returns to the battlefield all cards they own exiled with CARDNAME. -SVar:TrigReturn:DB$ ChangeZone | Defined$ Imprinted | Origin$ Exile | Destination$ Battlefield | SubAbility$ DBCleanup -SVar:DBCleanup:DB$ Cleanup | ClearImprinted$ True +SVar:TrigReturn:DB$ ChangeZone | Defined$ ExiledWith | Origin$ Exile | Destination$ Battlefield SVar:PlayMain1:TRUE AI:RemoveDeck:All Oracle:Fading 5 (This enchantment enters the battlefield with five fade counters on it. At the beginning of your upkeep, remove a fade counter from it. If you can't, sacrifice it.)\nRemove a fade counter from Parallax Tide: Exile target land.\nWhen Parallax Tide leaves the battlefield, each player returns to the battlefield all cards they own exiled with Parallax Tide. diff --git a/forge-gui/res/cardsfolder/p/parallax_wave.txt b/forge-gui/res/cardsfolder/p/parallax_wave.txt index d13ba0eda71..3887d9bb792 100644 --- a/forge-gui/res/cardsfolder/p/parallax_wave.txt +++ b/forge-gui/res/cardsfolder/p/parallax_wave.txt @@ -2,10 +2,9 @@ Name:Parallax Wave ManaCost:2 W W Types:Enchantment K:Fading:5 -A:AB$ ChangeZone | Cost$ SubCounter<1/FADE> | ValidTgts$ Creature | TgtPrompt$ Select target creature | ImprintTargets$ True | Origin$ Battlefield | Destination$ Exile | SpellDescription$ Exile target creature. +A:AB$ ChangeZone | Cost$ SubCounter<1/FADE> | ValidTgts$ Creature | TgtPrompt$ Select target creature | Origin$ Battlefield | Destination$ Exile | SpellDescription$ Exile target creature. T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ TrigReturn | TriggerDescription$ When CARDNAME leaves the battlefield, each player returns to the battlefield all cards they own exiled with CARDNAME. -SVar:TrigReturn:DB$ ChangeZone | Defined$ Imprinted | Origin$ Exile | Destination$ Battlefield | SubAbility$ DBCleanup -SVar:DBCleanup:DB$ Cleanup | ClearImprinted$ True +SVar:TrigReturn:DB$ ChangeZone | Defined$ ExiledWith | Origin$ Exile | Destination$ Battlefield SVar:PlayMain1:TRUE AI:RemoveDeck:All Oracle:Fading 5 (This enchantment enters the battlefield with five fade counters on it. At the beginning of your upkeep, remove a fade counter from it. If you can't, sacrifice it.)\nRemove a fade counter from Parallax Wave: Exile target creature.\nWhen Parallax Wave leaves the battlefield, each player returns to the battlefield all cards they own exiled with Parallax Wave. diff --git a/forge-gui/res/cardsfolder/p/preston_the_vanisher.txt b/forge-gui/res/cardsfolder/p/preston_the_vanisher.txt index a528124be6a..3a56155c8dc 100644 --- a/forge-gui/res/cardsfolder/p/preston_the_vanisher.txt +++ b/forge-gui/res/cardsfolder/p/preston_the_vanisher.txt @@ -2,7 +2,7 @@ Name:Preston, the Vanisher ManaCost:3 W Types:Legendary Creature Rabbit Wizard PT:2/5 -T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Creature.nonToken+wasNotCast+YouCtrl | TriggerZones$ Battlefield | Execute$ TrigCopyPermanent | TriggerDescription$ Whenever another nontoken creature enters the battlefield under your control, if it wasn't cast, create a token that's a copy of that creature except it's a 0/1 white Illusion. +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Creature.Other+nonToken+wasNotCast+YouCtrl | TriggerZones$ Battlefield | Execute$ TrigCopyPermanent | TriggerDescription$ Whenever another nontoken creature enters the battlefield under your control, if it wasn't cast, create a token that's a copy of that creature except it's a 0/1 white Illusion. SVar:TrigCopyPermanent:DB$ CopyPermanent | Defined$ TriggeredCardLKICopy | NumCopies$ 1 | SetColor$ White | SetCreatureTypes$ Illusion | SetPower$ 0 | SetToughness$ 1 A:AB$ ChangeZone | Cost$ 1 W Sac<5/Illusion> | ValidTgts$ Permanent.nonLand | Origin$ Battlefield | Destination$ Exile | TgtPrompt$ Select target nonland permanent | SpellDescription$ Exile target nonland permanent. DeckHas:Ability$Token|Sacrifice & Type$Illusion diff --git a/forge-gui/res/cardsfolder/s/sacred_ground.txt b/forge-gui/res/cardsfolder/s/sacred_ground.txt index eacec41e420..bbf4aaf875f 100644 --- a/forge-gui/res/cardsfolder/s/sacred_ground.txt +++ b/forge-gui/res/cardsfolder/s/sacred_ground.txt @@ -1,8 +1,7 @@ Name:Sacred Ground ManaCost:1 W Types:Enchantment -T:Mode$ Sacrificed | ValidCard$ Land.YouOwn | ValidCause$ SpellAbility.OppCtrl | Execute$ TrigReturn | TriggerZones$ Battlefield | TriggerDescription$ Whenever a spell or ability an opponent controls causes a land to be put into your graveyard from the battlefield, return that card to the battlefield. -T:Mode$ Destroyed | ValidCauser$ Player.Opponent | ValidCard$ Land.YouOwn | Execute$ TrigReturn | Secondary$ True | TriggerZones$ Battlefield | TriggerDescription$ Whenever a spell or ability an opponent controls causes a land to be put into your graveyard from the battlefield, return that card to the battlefield. -SVar:TrigReturn:DB$ ChangeZone | Defined$ TriggeredCard | Origin$ Graveyard | Destination$ Battlefield +T:Mode$ ChangesZone | ValidCard$ Land.YouOwn | ValidCause$ SpellAbility.OppCtrl | Execute$ TrigReturn | TriggerZones$ Battlefield | TriggerDescription$ Whenever a spell or ability an opponent controls causes a land to be put into your graveyard from the battlefield, return that card to the battlefield. +SVar:TrigReturn:DB$ ChangeZone | Defined$ TriggeredNewCardLKICopy | Origin$ Graveyard | Destination$ Battlefield AI:RemoveDeck:Random Oracle:Whenever a spell or ability an opponent controls causes a land to be put into your graveyard from the battlefield, return that card to the battlefield. diff --git a/forge-gui/res/cardsfolder/s/scouting_trek.txt b/forge-gui/res/cardsfolder/s/scouting_trek.txt index dcb2d5b1301..05bd132aa0e 100644 --- a/forge-gui/res/cardsfolder/s/scouting_trek.txt +++ b/forge-gui/res/cardsfolder/s/scouting_trek.txt @@ -1,7 +1,7 @@ Name:Scouting Trek ManaCost:1 G Types:Sorcery -A:SP$ ChangeZone | Cost$ 1 G | ChangeNum$ X | ChangeType$ Land.Basic | Origin$ Library | Destination$ Library | LibraryPosition$ 0 | SpellDescription$ Search your library for any number of basic land cards, reveal those cards, then shuffle and put them on top. +A:SP$ ChangeZone | Cost$ 1 G | ChangeNum$ X | ChangeType$ Land.Basic | Origin$ Library | Destination$ Library | Reorder$ True | LibraryPosition$ 0 | SpellDescription$ Search your library for any number of basic land cards, reveal those cards, then shuffle and put them on top. SVar:X:Count$InYourLibrary.Land.Basic AI:RemoveDeck:All Oracle:Search your library for any number of basic land cards, reveal those cards, then shuffle and put them on top. diff --git a/forge-gui/res/cardsfolder/s/skyshroud_ambush.txt b/forge-gui/res/cardsfolder/s/skyshroud_ambush.txt index 5a93b826b81..b2b01e03d15 100644 --- a/forge-gui/res/cardsfolder/s/skyshroud_ambush.txt +++ b/forge-gui/res/cardsfolder/s/skyshroud_ambush.txt @@ -2,9 +2,9 @@ Name:Skyshroud Ambush ManaCost:1 G Types:Instant A:SP$ Pump | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Choose target creature you control | ImprintCards$ ThisTargetedCard | SubAbility$ DBFight | StackDescription$ Target creature you control [{c:ThisTargetedCard}] | SpellDescription$ Target creature you control fights target creature you don't control. When the creature you control wins the fight, draw a card. -SVar:DBFight:DB$ Fight | Defined$ ParentTarget | ValidTgts$ Creature.YouDontCtrl | TgtPrompt$ Choose target creature you don't control | RememberObjects$ ThisTargetedCard | SubAbility$ DBEffect | StackDescription$ fights target creature you don't control [{c:ThisTargetedCard}]. When the creature you control wins the fight, draw a card. -SVar:DBEffect:DB$ Effect | Triggers$ TrigWin | RememberObjects$ Remembered | ImprintCards$ Imprinted | Duration$ UntilStateBasedActionChecked | SubAbility$ DBCleanup +SVar:DBFight:DB$ Fight | Defined$ ParentTarget | ValidTgts$ Creature.YouDontCtrl | TgtPrompt$ Choose target creature you don't control | SubAbility$ DBEffect | StackDescription$ fights target creature you don't control [{c:ThisTargetedCard}]. When the creature you control wins the fight, draw a card. +SVar:DBEffect:DB$ Effect | Triggers$ TrigWin | RememberObjects$ ParentTarget | ImprintCards$ Imprinted | Duration$ UntilStateBasedActionChecked | SubAbility$ DBCleanup SVar:TrigWin:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Creature.IsRemembered | IsPresent$ Creature.IsImprinted | NoResolvingCheck$ True | Execute$ TrigDraw | TriggerDescription$ When the creature you control wins the fight, draw a card. SVar:TrigDraw:DB$ Draw | NumCards$ 1 -SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True | ClearImprinted$ True +SVar:DBCleanup:DB$ Cleanup | ClearImprinted$ True Oracle:Target creature you control fights target creature you don't control. When the creature you control wins the fight, draw a card. diff --git a/forge-gui/res/cardsfolder/s/sovereigns_of_lost_alara.txt b/forge-gui/res/cardsfolder/s/sovereigns_of_lost_alara.txt index 0ae08402be7..914e29e3aae 100644 --- a/forge-gui/res/cardsfolder/s/sovereigns_of_lost_alara.txt +++ b/forge-gui/res/cardsfolder/s/sovereigns_of_lost_alara.txt @@ -5,6 +5,7 @@ PT:4/5 K:Exalted T:Mode$ Attacks | ValidCard$ Creature.YouCtrl | Alone$ True | TriggerZones$ Battlefield | OptionalDecider$ You | Execute$ TrigSearch | TriggerDescription$ Whenever a creature you control attacks alone, you may search your library for an Aura card that could enchant that creature, put it onto the battlefield attached to that creature, then shuffle. SVar:TrigSearch:DB$ Pump | RememberObjects$ TriggeredAttacker | SubAbility$ DBMoveAura -SVar:DBMoveAura:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Aura.CanEnchantRemembered+YouCtrl | AttachedTo$ Remembered | ChangeNum$ 1 | Hidden$ True | ShuffleNonMandatory$ True +SVar:DBMoveAura:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Aura.CanEnchantRemembered+YouCtrl | AttachedTo$ Remembered | ChangeNum$ 1 | Hidden$ True | ShuffleNonMandatory$ True | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True AI:RemoveDeck:Random Oracle:Exalted (Whenever a creature you control attacks alone, that creature gets +1/+1 until end of turn.)\nWhenever a creature you control attacks alone, you may search your library for an Aura card that could enchant that creature, put it onto the battlefield attached to that creature, then shuffle. diff --git a/forge-gui/res/cardsfolder/t/trailblazers_torch.txt b/forge-gui/res/cardsfolder/t/trailblazers_torch.txt index 839b6253893..de5e6e8bb48 100644 --- a/forge-gui/res/cardsfolder/t/trailblazers_torch.txt +++ b/forge-gui/res/cardsfolder/t/trailblazers_torch.txt @@ -4,6 +4,6 @@ Types:Artifact Equipment T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigInitiative | TriggerDescription$ When CARDNAME enters the battlefield, you take the initiative. SVar:TrigInitiative:DB$ TakeInitiative T:Mode$ AttackerBlocked | ValidCard$ Creature.EquippedBy | Execute$ TrigDamage | TriggerDescription$ Whenever equipped creature becomes blocked, it deals 2 damage to each creature blocking it. -SVar:TrigDamage:DB$ DamageAll | NumDmg$ 2 | ValidCards$ Creature.blockingEquipped +SVar:TrigDamage:DB$ DamageAll | NumDmg$ 2 | ValidCards$ Creature.blockingEquipped | DamageSource$ TriggeredAttackerLKICopy K:Equip:1 Oracle:When Trailblazer's Torch enters the battlefield, you take the initiative.\nWhenever equipped creature becomes blocked, it deals 2 damage to each creature blocking it.\nEquip {1} ({1}: Attach to target creature you control. Equip only as a sorcery.) diff --git a/forge-gui/res/cardsfolder/u/ulvenwald_tracker.txt b/forge-gui/res/cardsfolder/u/ulvenwald_tracker.txt index cdd07d0b52a..995fb89f839 100644 --- a/forge-gui/res/cardsfolder/u/ulvenwald_tracker.txt +++ b/forge-gui/res/cardsfolder/u/ulvenwald_tracker.txt @@ -2,7 +2,7 @@ Name:Ulvenwald Tracker ManaCost:G Types:Creature Human Shaman PT:1/1 -A:AB$ Pump | Cost$ 1 G T | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | SubAbility$ TrackerFight | StackDescription$ None -SVar:TrackerFight:DB$ Fight | Defined$ ParentTarget | ValidTgts$ Creature | TargetUnique$ True | TgtPrompt$ Select another target creature | SpellDescription$ Target creature you control fights another target creature. +A:AB$ Pump | Cost$ 1 G T | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | SubAbility$ TrackerFight | StackDescription$ None | SpellDescription$ Target creature you control fights another target creature. +SVar:TrackerFight:DB$ Fight | Defined$ ParentTarget | ValidTgts$ Creature | TargetUnique$ True | TgtPrompt$ Select another target creature AI:RemoveDeck:All Oracle:{1}{G}, {T}: Target creature you control fights another target creature. diff --git a/forge-gui/res/cardsfolder/upcoming/denethor_stone_seer.txt b/forge-gui/res/cardsfolder/upcoming/denethor_stone_seer.txt index d1e5a9bc74a..dc15feaad89 100644 --- a/forge-gui/res/cardsfolder/upcoming/denethor_stone_seer.txt +++ b/forge-gui/res/cardsfolder/upcoming/denethor_stone_seer.txt @@ -5,6 +5,6 @@ PT:1/3 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigScry | TriggerDescription$ When CARDNAME enters the battlefield, scry 2. SVar:TrigScry:DB$ Scry | ScryNum$ 2 A:AB$ BecomeMonarch | Cost$ 3 R T Sac<1/NICKNAME> | ValidTgts$ Player | SubAbility$ DBDamage | SpellDescription$ Target player becomes the monarch. NICKNAME deals 3 damage to any target. -SVar:DBPower:DB$ DealDamage | ValidTgts$ Any | NumDmg$ 3 +SVar:DBDamage:DB$ DealDamage | ValidTgts$ Any | NumDmg$ 3 DeckHas:Ability$Sacrifice -Oracle:When Denethor, Stone Seer enters the battlefield, scry 2.\n{3}{R}, {T}, Sacrifice Denethor: Target player becomes the monarch. Denethor deals 3 damage to any target. \ No newline at end of file +Oracle:When Denethor, Stone Seer enters the battlefield, scry 2.\n{3}{R}, {T}, Sacrifice Denethor: Target player becomes the monarch. Denethor deals 3 damage to any target. diff --git a/forge-gui/res/cardsfolder/upcoming/esquire_of_the_king.txt b/forge-gui/res/cardsfolder/upcoming/esquire_of_the_king.txt index 12474efae9a..69df924c10c 100644 --- a/forge-gui/res/cardsfolder/upcoming/esquire_of_the_king.txt +++ b/forge-gui/res/cardsfolder/upcoming/esquire_of_the_king.txt @@ -2,7 +2,6 @@ Name:Esquire of the King ManaCost:W Types:Creature Human Soldier PT:1/1 -K:Flying A:AB$ PumpAll | NumAtt$ +1 | NumDef$ +1 | Cost$ 4 W T | ValidCards$ Creature.YouCtrl | ReduceCost$ X | SpellDescription$ Creatures you control get +1/+1 until end of turn. This ability costs {2} less to activate if you control a legendary creature. SVar:X:Count$Compare Y GE1.2.0 SVar:Y:Count$Valid Creature.Legendary+YouCtrl diff --git a/forge-gui/res/cardsfolder/upcoming/grima_sarumans_footman.txt b/forge-gui/res/cardsfolder/upcoming/grima_sarumans_footman.txt new file mode 100644 index 00000000000..a21495bc5a0 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/grima_sarumans_footman.txt @@ -0,0 +1,11 @@ +Name:Grima, Saruman's Footman +ManaCost:2 U B +Types:Legendary Creature Human Advisor +PT:1/4 +S:Mode$ CantBlockBy | ValidAttacker$ Creature.Self | Description$ CARDNAME can't be blocked. +T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | CombatDamage$ True | Execute$ TrigDigUntil | TriggerZones$ Battlefield | TriggerDescription$ Whenever NICKNAME deals combat damage to a player, that player exiles cards from the top of their library until they exile an instant or sorcery card. You may cast that card without paying its mana cost. Then that player puts the exiled cards that weren't cast this way on the bottom of their library in a random order. +SVar:TrigDigUntil:DB$ DigUntil | Defined$ TriggeredTarget | Valid$ Instant,Sorcery | ValidDescription$ instant or sorcery | FoundDestination$ Exile | RevealedDestination$ Exile | RememberFound$ True | ImprintRevealed$ True | IsCurse$ True | SubAbility$ DBPlay +SVar:DBPlay:DB$ Play | ValidZone$ Exile | Valid$ Instant.IsRemembered,Sorcery.IsRemembered | ValidSA$ Spell | WithoutManaCost$ True | Optional$ True | SubAbility$ DBRestRandomOrder +SVar:DBRestRandomOrder:DB$ ChangeZoneAll | ChangeType$ Card.IsImprinted | Origin$ Exile | Destination$ Library | LibraryPosition$ -1 | RandomOrder$ True | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True | ClearImprinted$ True +Oracle:Grima, Saruman's Footman can't be blocked.\nWhenever Grima deals combat damage to a player, that player exiles cards from the top of their library until they exile an instant or sorcery card. You may cast that card without paying its mana cost. Then that player puts the exiled cards that weren't cast this way on the bottom of their library in a random order. diff --git a/forge-gui/res/cardsfolder/upcoming/gwaihir_greatest_of_the_eagles.txt b/forge-gui/res/cardsfolder/upcoming/gwaihir_greatest_of_the_eagles.txt new file mode 100644 index 00000000000..b90e43a8695 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/gwaihir_greatest_of_the_eagles.txt @@ -0,0 +1,14 @@ +Name:Gwaihir, Greatest of the Eagles +ManaCost:4 W +Types:Legendary Creature Bird Noble +PT:5/5 +K:Flying +T:Mode$ Attacks | ValidCard$ Card.Self | TriggerZones$ Battlefield | Execute$ TrigPump | TriggerDescription$ Whenever NICKNAME attacks, target attacking creature gains flying until end of turn. +SVar:TrigPump:DB$ Pump | ValidTgts$ Creature.attacking | TgtPrompt$ Select target attacking creature | KW$ Flying +T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | TriggerZones$ Battlefield | CheckSVar$ LifeGained | SVarCompare$ GE3 | Execute$ TrigToken | TriggerDescription$ At the beginning of your end step, if you gained 3 or more life this turn, create a 3/3 white Bird creature token with flying and "Whenever this creature attacks, target attacking creature gains flying until end of turn." +SVar:TrigToken:DB$ Token | TokenScript$ w_3_3_bird_flying_attacks +SVar:LifeGained:Count$LifeYouGainedThisTurn +SVar:HasAttackEffect:TRUE +DeckHints:Ability$LifeGain +DeckHas:Ability$Token +Oracle:Flying\nWhenever Gwaihir attacks, target attacking creature gains flying until end of turn.\nAt the beginning of your end step, if you gained 3 or more life this turn, create a 3/3 white Bird creature token with flying and "Whenever this creature attacks, target attacking creature gains flying until end of turn." diff --git a/forge-gui/res/cardsfolder/upcoming/haldir_lorien_lieutenant.txt b/forge-gui/res/cardsfolder/upcoming/haldir_lorien_lieutenant.txt new file mode 100644 index 00000000000..c0cec6ba29d --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/haldir_lorien_lieutenant.txt @@ -0,0 +1,11 @@ +Name:Haldir, Lorien Lieutenant +ManaCost:X G +Types:Legendary Creature Elf Soldier +PT:0/0 +K:Vigilance +K:etbCounter:P1P1:X +SVar:X:Count$xPaid +A:AB$ PumpAll | Cost$ 5 G | ValidCards$ Creature.Elf+StrictlyOther+YouCtrl | NumAtt$ Y | NumDef$ Y | KW$ Vigilance | SpellDescription$ Until end of turn, other Elves you control gain vigilance and get +1/+1 for each +1/+1 counter on NICKNAME. +SVar:Y:Count$CardCounters.P1P1 +DeckHas:Ability$Counters +Oracle:Haldir, Lorien Lieutenant enters the battlefield with X +1/+1 counters on it.\nVigilance\n{5}{G}: Until end of turn, other Elves you control gain vigilance and get +1/+1 for each +1/+1 counter on Haldir. diff --git a/forge-gui/res/cardsfolder/upcoming/lobelia_defender_of_bag_end.txt b/forge-gui/res/cardsfolder/upcoming/lobelia_defender_of_bag_end.txt new file mode 100644 index 00000000000..dc1ec2861e6 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/lobelia_defender_of_bag_end.txt @@ -0,0 +1,12 @@ +Name:Lobelia, Defender of Bag End +ManaCost:2 B +Types:Legendary Creature Halfling Citizen +PT:2/2 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigExile | TriggerDescription$ When NICKNAME enters the battlefield, look at the top card of each opponent's library and exile those cards face down. +SVar:TrigExile:DB$ Dig | DigNum$ 1 | ChangeNum$ All | Defined$ Opponent | DestinationZone$ Exile | ExileFaceDown$ True +A:AB$ Charm | Cost$ T Sac<1/Artifact> | Choices$ DBEffect,DBLoseGain +SVar:DBEffect:DB$ Effect | StaticAbilities$ STPlay | SpellDescription$ Until end of turn, you may play a card exiled with NICKNAME without paying its mana cost. +SVar:STPlay:Mode$ Continuous | MayPlay$ True | WithoutManaCost$ True | Affected$ Card.ExiledWithEffectSource | AffectedZone$ Exile | MayPlayLimit$ 1 +SVar:DBLoseGain:DB$ LoseLife | Defined$ Opponent | LifeAmount$ 2 | SubAbility$ DBGain2 | SpellDescription$ Each opponent loses 2 life and you gain 2 life. +SVar:DBGain2:DB$ GainLife | Defined$ You | LifeAmount$ 2 +Oracle:When Lobelia enters the battlefield, look at the top card of each opponent's library and exile those cards face down.\n{T}, Sacrifice an artifact: Choose one —\n• Until end of turn, you may play a card exiled with Lobelia without paying its mana cost.\n• Each opponent loses 2 life and you gain 2 life. diff --git a/forge-gui/res/cardsfolder/upcoming/lord_of_the_nazgul.txt b/forge-gui/res/cardsfolder/upcoming/lord_of_the_nazgul.txt new file mode 100644 index 00000000000..a3dd4aaaf1e --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/lord_of_the_nazgul.txt @@ -0,0 +1,12 @@ +Name:Lord of the Nazgul +ManaCost:3 U B +Types:Legendary Creature Wraith Noble +PT:4/4 +K:Flying +S:Mode$ Continuous | Affected$ Creature.Wraith+YouCtrl | AddKeyword$ Protection from Ringbearers | Description$ Wraiths you control have protection from Ring-bearers. +T:Mode$ SpellCast | ValidCard$ Instant,Sorcery | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigToken | TriggerDescription$ Whenever you cast an instant or sorcery spell, create a 3/3 black Wraith creature token with menace. Then if you control nine or more Wraiths, Wraiths you control have base power and toughness 9/9 until end of turn. +SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ b_3_3_wraith_menace | TokenOwner$ You | SubAbility$ DBAnimateAll +SVar:DBAnimateAll:DB$ AnimateAll | ValidCards$ Wraith.YouCtrl | Power$ 9 | Toughness$ 9 | ConditionPresent$ Card.Wraith+YouCtrl | ConditionCompare$ GE9 +SVar:BuffedBy:Instant,Sorcery +DeckHints:Type$Wraith +Oracle:Flying\nWraiths you control have protection from Ring-bearers.\nWhenever you cast an instant or sorcery spell, create a 3/3 black Wraith creature token with menace. Then if you control nine or more Wraiths, Wraiths you control have base power and toughness 9/9 until end of turn. diff --git a/forge-gui/res/cardsfolder/upcoming/shelob_dread_weaver.txt b/forge-gui/res/cardsfolder/upcoming/shelob_dread_weaver.txt new file mode 100644 index 00000000000..2c9ff92bfe5 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/shelob_dread_weaver.txt @@ -0,0 +1,13 @@ +Name:Shelob, Dread Weaver +ManaCost:3 B +Types:Legendary Creature Spider Demon +PT:3/3 +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Creature.OppCtrl+nonToken | TriggerZones$ Battlefield | Execute$ TrigExile | TriggerDescription$ Whenever a nontoken creature an opponent controls dies, exile it. +SVar:TrigExile:DB$ ChangeZone | Defined$ TriggeredNewCardLKICopy | Origin$ Graveyard | Destination$ Exile +A:AB$ PutCounter | Cost$ 2 B ExiledMoveToGrave<1/Creature.ExiledWithSource> | CounterType$ P1P1 | CounterNum$ 2 | SubAbility$ TrigDraw | SpellDescription$ Put two +1/+1 counters on NICKNAME. +SVar:TrigDraw:DB$ Draw | NumCards$ 1 | SpellDescription$ Draw a card. +A:AB$ ChangeZone | Cost$ X 1 B | TgtPrompt$ Select target creature | Origin$ Exile | Destination$ Battlefield | Tapped$ True | ValidTgts$ Creature.ExiledWithSource+cmcEQX | Hidden$ True | Mandatory$ True | ChangeNum$ 1 | GainControl$ True | SpellDescription$ Put target creature card with mana value X exiled with NICKNAME onto the battlefield tapped under your control. +SVar:X:Count$xPaid +SVar:PlayMain1:TRUE +DeckHas:Ability$Counters +Oracle:Whenever a nontoken creature an opponent controls dies, exile it.\n{2}{B}, Put a creature card exiled with Shelob, Dread Weaver into its owner's graveyard: Put two +1/+1 counters on Shelob. Draw a card.\n{X}{1}{B}: Put target creature card with mana value X exiled with Shelob onto the battlefield tapped under your control. diff --git a/forge-gui/res/cardsfolder/v/viviens_invocation.txt b/forge-gui/res/cardsfolder/v/viviens_invocation.txt index a71dadb9e07..08354085b8d 100644 --- a/forge-gui/res/cardsfolder/v/viviens_invocation.txt +++ b/forge-gui/res/cardsfolder/v/viviens_invocation.txt @@ -1,8 +1,9 @@ Name:Vivien's Invocation ManaCost:5 G G Types:Sorcery -A:SP$ Dig | Cost$ 5 G G | DigNum$ 7 | ChangeNum$ 1 | ChangeValid$ Creature | Optional$ True | RestRandomOrder$ True | DestinationZone$ Battlefield | ForceRevealToController$ True | SpellDescription$ Look at the top seven cards of your library. You may put a creature card from among them onto the battlefield. Put the rest on the bottom of your library in a random order. When a creature is put onto the battlefield this way, it deals damage equal to its power to target creature an opponent controls. -T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Creature | ValidCause$ Card.Self | Execute$ DBDealDamage | Secondary$ True | TriggerDescription$ When a creature is put onto the battlefield this way, it deals damage equals to its power to target creature an opponent controls. -SVar:DBDealDamage:DB$ DealDamage | ValidTgts$ Creature.OppCtrl | TgtPrompt$ Select target creature an opponent controls | NumDmg$ X | DamageSource$ TriggeredCard -SVar:X:TriggeredCard$CardPower +A:SP$ Dig | Cost$ 5 G G | DigNum$ 7 | ChangeNum$ 1 | ChangeValid$ Creature | Optional$ True | RestRandomOrder$ True | DestinationZone$ Battlefield | ForceRevealToController$ True | RememberChanged$ True | SubAbility$ DBTrig | SpellDescription$ Look at the top seven cards of your library. You may put a creature card from among them onto the battlefield. Put the rest on the bottom of your library in a random order. When a creature is put onto the battlefield this way, it deals damage equal to its power to target creature an opponent controls. +SVar:DBTrig:DB$ ImmediateTrigger | RememberObjects$ Remembered | Execute$ DBDealDamage | ConditionDefined$ Remembered | ConditionPresent$ Card | SubAbility$ DBCleanup | TriggerDescription$ When a creature is put onto the battlefield this way, it deals damage equals to its power to target creature an opponent controls. +SVar:DBDealDamage:DB$ DealDamage | ValidTgts$ Creature.OppCtrl | TgtPrompt$ Select target creature an opponent controls | NumDmg$ X | DamageSource$ DelayTriggerRememberedLKI +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +SVar:X:TriggerRemembered$CardPower Oracle:Look at the top seven cards of your library. You may put a creature card from among them onto the battlefield. Put the rest on the bottom of your library in a random order. When a creature is put onto the battlefield this way, it deals damage equal to its power to target creature an opponent controls. diff --git a/forge-gui/res/editions/Explorer Anthology 3.txt b/forge-gui/res/editions/Explorer Anthology 3.txt new file mode 100644 index 00000000000..5d252afe466 --- /dev/null +++ b/forge-gui/res/editions/Explorer Anthology 3.txt @@ -0,0 +1,27 @@ +[metadata] +Code=EA3 +Date=2023-07-18 +Name=Explorer Anthology 3 +Type=Online +ScryfallCode=EA3 + +[cards] +1 R Cyclonic Rift @Chris Rahn +2 R Abbot of Keral Keep @Deruchenko Alexander +3 U Shrapnel Blast @Hideaki Takamura +4 R Eidolon of Blossoms @Min Yum +5 R Chord of Calling @Karl Kopinski +6 M Worldspine Wurm @Richard Wright +7 U Golgari Charm @Zoltan Boros +8 U Simic Charm @Zoltan Boros +9 C Izzet Charm @Zoltan Boros +10 U Gruul Charm @Zoltan Boros +11 U Orzhov Charm @Zoltan Boros +12 R Voice of Resurgence @Winona Nelson +13 R Deathrite Shaman @Steve Argyle +14 R Canopy Vista @Adam Paquette +15 R Cinder Glade @Adam Paquette +16 R Smoldering Marsh @Adam Paquette +17 R Sunken Hollow @Adam Paquette +18 R Prairie Stream @Adam Paquette +19 R Thespian's Stage @John Avon diff --git a/forge-gui/res/editions/Historic Anthology 7.txt b/forge-gui/res/editions/Historic Anthology 7.txt new file mode 100644 index 00000000000..deecf089f30 --- /dev/null +++ b/forge-gui/res/editions/Historic Anthology 7.txt @@ -0,0 +1,27 @@ +[metadata] +Code=HA7 +Date=2023-07-18 +Name=Historic Anthology 7 +Type=Online +ScryfallCode=HA7 + +[cards] +1 M Sun Titan @Todd Lockwood +2 M Frost Titan @Mike Bierek +3 M Vendilion Clique @Willian Murai +4 M Grave Titan @Nils Hamm +5 M Inferno Titan @Kev Walker +6 C Tribal Flames @Zack Stella +7 M Primeval Titan @Aleksi Briclot +8 C Wild Nacatl @Wayne Reynolds +9 U Acidic Slime @Karl Kopinski +10 R Tooth and Nail @Jesper Ejsing +11 U Bloodbraid Elf @Raymond Swanland +12 U Mortarpod @Eric Deschamps +13 U Worn Powerstone @Henry G. Higginbotham +14 R Sword of Fire and Ice @Mark Zug +15 R Fiery Islet @Richard Wright +16 R Sunbaked Canyon @Yeong-Hao Han +17 R Nurturing Peatland @Sam Burley +18 R Silent Clearing @Seb McKinnon +19 R Waterlogged Grove @John Avon diff --git a/forge-gui/res/editions/Jumpstart.txt b/forge-gui/res/editions/Jumpstart.txt index c4623890abe..d7ac7753e20 100644 --- a/forge-gui/res/editions/Jumpstart.txt +++ b/forge-gui/res/editions/Jumpstart.txt @@ -93,7 +93,7 @@ ScryfallCode=JMP 85 R Angel of the Dire Hour @Jack Wang 86 R Angelic Arbiter @Steve Argyle 87 C Angelic Edict @Trevor Claxton -88 U Angelic Page @Chris Rahn +88 C Angelic Page @Chris Rahn 89 R Archon of Justice @Jason Chan 90 R Archon of Redemption @Steven Belledin 91 C Battlefield Promotion @Scott Murphy diff --git a/forge-gui/res/formats/Archived/Explorer/2023-07-18.txt b/forge-gui/res/formats/Archived/Explorer/2023-07-18.txt new file mode 100644 index 00000000000..d8b3d05c42e --- /dev/null +++ b/forge-gui/res/formats/Archived/Explorer/2023-07-18.txt @@ -0,0 +1,8 @@ +[format] +Name:Explorer (EA3) +Type:Archived +Subtype:Pioneer +Effective:2023-07-18 +Sets:XLN, RIX, DOM, M19, GRN, G18, RNA, WAR, M20, ELD, THB, IKO, M21, ZNR, KHM, STX, AFR, MID, VOW, NEO, SNC, EA1, DMU, BRO, EA2, ONE, MOM, MAT, EA3 +Banned:Expressive Iteration; Field of the Dead; Kethis, the Hidden Hand; Leyline of Abundance; Lurrus of the Dream-Den; Nexus of Fate; Oko, Thief of Crowns; Once Upon a Time; Teferi, Time Raveler; Tibalt's Trickery; Underworld Breach; Uro, Titan of Nature's Wrath; Veil of Summer; Wilderness Reclamation; Winota, Joiner of Forces +Additional:Abandoned Sarcophagus; Abundant Maw; Abzan Battle Priest; Abzan Falconer; Accursed Horde; Accursed Witch; Adaptive Snapjaw; Adorned Pouncer; Advanced Stitchwing; Aerial Guide; Aerial Responder; Aeronaut Admiral; Aether Chaser; Aether Hub; Aether Inspector; Aether Meltdown; Aether Poisoner; Aether Swooper; Aether Theorist; Aether Tradewinds; Aetherborn Marauder; Aetherflux Reservoir; Aethersphere Harvester; Aetherstorm Roc; Aetherstream Leopard; Aethertorch Renegade; Aetherworks Marvel; Ahn-Crop Champion; Ahn-Crop Crasher; Aim High; Ainok Bond-Kin; Airdrop Aeronauts; Ajani Unyielding; Alchemist's Greeting; Alley Evasion; Alley Strangler; Alms of the Vein; Altered Ego; Always Watching; Ammit Eternal; Anafenza, Kin-Tree Spirit; Ancestral Statue; Ancient Crab; Angel of Invention; Angel of Sanctions; Angel of the God-Pharaoh; Angelic Edict; Angelic Purge; Anger of the Gods; Anguished Unmaking; Animation Module; Anointed Procession; Anointer Priest; Apothecary Geist; Appeal // Authority; Appetite for the Unnatural; Approach of the Second Sun; Arborback Stomper; Archangel Avacyn; Archfiend of Ifnir; Arlinn Kord; Armorcraft Judge; As Foretold; Assassin's Strike; Assault Formation; Assembled Alphas; Astral Cornucopia; Asylum Visitor; Atarka's Command; Attune with Aether; Audacious Infiltrator; Auger Spree; Aurelia, the Warleader; Authority of the Consuls; Avacyn's Judgment; Aven Initiate; Aven Mindcensor; Aven of Enduring Hope; Aven Wind Guide; Aviary Mechanic; Awaken the Bear; Baleful Ammit; Ballista Charger; Baral, Chief of Compliance; Baral's Expertise; Barrage of Expendables; Barricade Breaker; Bastion Mastodon; Bathe in Dragonfire; Battering Krasis; Battlefield Scavenger; Bedlam Reveler; Belligerent Sliver; Beneath the Sands; Binding Mummy; Biting Rain; Bitterbow Sharpshooters; Black Cat; Blessed Alliance; Blessed Spirits; Blighted Bat; Blood Host; Blood Mist; Bloodbond Vampire; Bloodbriar; Bloodhall Priest; Bloodhunter Bat; Bloodlust Inciter; Bloodmad Vampire; Bloodrage Brawler; Blooming Marsh; Blossoming Defense; Blur of Blades; Blur Sliver; Bogbrew Witch; Bomat Bazaar Barge; Bomat Courier; Bonded Construct; Bone Picker; Bone Saw; Bonescythe Sliver; Bontu the Glorified; Bontu's Last Reckoning; Bontu's Monument; Borderland Marauder; Borderland Minotaur; Boros Elite; Borrowed Grace; Borrowed Hostility; Borrowed Malevolence; Botanical Sanctum; Bound by Moonsilver; Brain in a Jar; Brain Maggot; Breaching Hippocamp; Bred for the Hunt; Briarbridge Patrol; Bristling Hydra; Bruna, the Fading Light; Brushstrider; Brute Strength; Bubbling Cauldron; Built to Last; Built to Smash; Burn from Within; Burning-Fist Minotaur; Burning-Tree Emissary; Burnished Hart; By Force; Bygone Bishop; Byway Courier; Call the Bloodline; Canyon Slough; Carrier Thrall; Cartouche of Ambition; Cartouche of Knowledge; Cartouche of Solidarity; Cartouche of Strength; Cartouche of Zeal; Cascading Cataracts; Cast Out; Cataclysmic Gearhulk; Cathar's Companion; Cemetery Recruitment; Censor; Ceremonious Rejection; Certain Death; Champion of Rhonas; Champion of Wits; Chandra, Pyromaster; Chandra, Torch of Defiance; Chandra's Defeat; Chandra's Revolution; Charging Badger; Chief of the Foundry; Choked Estuary; Cinder Elemental; Claim // Fame; Cloudblazer; Cogworker's Puzzleknot; Collateral Damage; Collected Company; Collective Brutality; Collective Defiance; Collective Effort; Combat Celebrant; Combustible Gearhulk; Commencement of Festivities; Commit // Memory; Compelling Argument; Compelling Deterrence; Compulsory Rest; Concealed Courtyard; Conduit of Storms; Confirm Suspicions; Confiscation Coup; Confront the Unknown; Consign // Oblivion; Consulate Skygate; Consulate Turret; Contraband Kingpin; Conviction; Coralhelm Guide; Corpse Hauler; Countervailing Winds; Countless Gears Renegade; Courageous Outrider; Crawling Sensation; Creeping Mold; Crested Sunmare; Crocanura; Crocodile of the Crossing; Crow of Dark Tidings; Cruel Reality; Crux of Fate; Crypt of the Eternals; Cryptbreaker; Cryptic Serpent; Cryptolith Fragment; Cryptolith Rite; Cultivator's Caravan; Curator of Mysteries; Curious Homunculus; Cut // Ribbons; Dance with Devils; Daredevil Dragster; Daring Demolition; Daring Sleuth; Dark Intimations; Dark Salvation; Dauntless Aven; Dauntless Cathar; Dauntless Onslaught; Dawn Gryff; Dawnfeather Eagle; Death Wind; Deathcap Cultivator; Death's Approach; Decimator of the Provinces; Declaration in Stone; Decoction Module; Deem Worthy; Defiant Greatmaw; Defiant Salvager; Demolition Stomper; Demon of Dark Schemes; Demonic Pact; Deny Existence; Depala, Pilot Exemplar; Deranged Whelp; Descend upon the Sinful; Desert Cerodon; Desert of the Fervent; Desert of the Glorified; Desert of the Indomitable; Desert of the Mindful; Desert of the True; Desert's Hold; Destined // Lead; Devils' Playground; Devilthorn Fox; Devouring Light; Die Young; Diffusion Sliver; Dinrova Horror; Diregraf Colossus; Disallow; Disposal Mummy; Dispossess; Dissenter's Deliverance; Distended Mindbender; Djeru's Renunciation; Djeru's Resolve; Docent of Perfection; Doom Blade; Doomfall; Douse in Gloom; Dovin Baan; Drag Under; Dragon Fodder; Dragon Hatchling; Dragon Mantle; Dragonloft Idol; Dragonlord's Servant; Dragonmaster Outcast; Drainpipe Vermin; Drake Haven; Drana, Liberator of Malakir; Dread Wanderer; Driven // Despair; Drogskol Shieldmate; Dromoka's Command; Drownyard Behemoth; Drownyard Explorers; Drunau Corpse Trawler; Dukhara Peafowl; Dune Beetle; Dusk // Dawn; Dusk Feaster; Duskwatch Recruiter; Dutiful Attendant; Dwynen's Elite; Dynavolt Tower; Eager Construct; Earthshaker Khenra; Eddytrail Hawk; Edifice of Authority; Elder Deep-Fiend; Eldritch Evolution; Electrostatic Pummeler; Elemental Uprising; Elusive Krasis; Elvish Visionary; Ember-Eye Wolf; Embraal Bruiser; Empyreal Voyager; Emrakul, the Promised End; Engineered Might; Enlarge; Enraged Giant; Epiphany at the Drownyard; Era of Innovation; Erdwal Illuminator; Essence Extraction; Essence Flux; Eternal of Harsh Truths; Eternal Scourge; Eternal Thirst; Ever After; Exemplar of Strength; Exultant Cultist; Eyeblight Assassin; Fabrication Module; Failure // Comply; Fairgrounds Warden; Faith of the Devoted; Faith Unbroken; Faithbearer Paladin; Falkenrath Gorger; Fan Bearer; Fanatic of Mogis; Farm // Market; Fatal Push; Fateful Showdown; Fen Hauler; Feral Prowler; Fervent Paincaster; Festering Mummy; Festering Newt; Fetid Pools; Fevered Visions; Fiend Binder; Fiery Temper; Filigree Familiar; Final Reward; Firebrand Archer; Fireforger's Puzzleknot; Flame Lash; Flameblade Adept; Flameblade Angel; Flames of the Firebrand; Fleeting Memories; Fleshbag Marauder; Floodwaters; Flurry of Horns; Fogwalker; Foreboding Ruins; Forge Devil; Forgotten Creation; Forsake the Worldly; Fortified Village; Fortify; Fortuitous Find; Foundry Hornet; Foundry Inspector; Foundry Screecher; Foundry Street Denizen; Fourth Bridge Prowler; Fragmentize; Freejam Regent; Fretwork Colony; Frontline Rebel; Fumigate; Furious Reprisal; Furnace Whelp; Furyblade Vampire; Galvanic Bombardment; Game Trail; Gate to the Afterlife; Gatstaf Arsonists; Gavony Unhallowed; Gearseeker Serpent; Gearshift Ace; Geier Reach Bandit; Geier Reach Sanitarium; Geist of the Archives; Geralf's Masterpiece; Ghoulcaller's Accomplice; Gideon of the Trials; Gideon's Intervention; Gifted Aetherborn; Gilded Cerodon; Gisa and Geralf; Gisa's Bidding; Gisela, the Broken Blade; Glimmer of Genius; Glint; Glint-Nest Crane; Glint-Sleeve Artisan; Glint-Sleeve Siphoner; Glorious End; Glory-Bound Initiate; Glorybringer; Gnarlwood Dryad; Goblin Dark-Dwellers; Goblin Rally; Goblin Shortcutter; God-Pharaoh's Gift; Goldnight Castigator; Gonti, Lord of Luxury; Graf Harvest; Graf Mole; Graf Rats; Grapple with the Past; Greenbelt Rampager; Grim Flayer; Grind // Dust; Grisly Salvage; Grotesque Mutation; Groundskeeper; Gryff's Boon; Guardian of Pilgrims; Gust Walker; Hamlet Captain; Hanweir Battlements; Hanweir Garrison; Hanweir Militia Captain; Hapatra, Vizier of Poisons; Hardened Scales; Harmless Offering; Harnessed Lightning; Harsh Mentor; Harvest Hand; Hashep Oasis; Haunted Dead; Hazardous Conditions; Haze of Pollen; Hazoret the Fervent; Hazoret's Monument; Heart of Kiran; Heaven // Earth; Hedron Archive; Heir of Falkenrath; Hekma Sentinels; Herald of Anguish; Herald of the Fair; Heron's Grace Champion; Hidden Stockpile; Hieroglyphic Illumination; High Sentinels of Arashin; Highspire Artisan; Highspire Infusion; Hinterland Drake; Hinterland Logger; Hive Stirrings; Hollow One; Homing Lightning; Honored Crop-Captain; Hooded Brawler; Hope Against Hope; Hope of Ghirapur; Hope Tender; Hordeling Outburst; Hornet Queen; Horror of the Broken Lands; Hour of Devastation; Hour of Promise; Hour of Revelation; Howlpack Resurgence; Howlpack Wolf; Humble the Brute; Hungry Flames; Ice Over; Ifnir Deadlands; Illusionist's Stratagem; Imminent Doom; Impeccable Timing; Implement of Combustion; Implement of Examination; Implement of Malice; Imprisoned in the Moon; In Oketra's Name; Incendiary Flow; Incorrigible Youths; Incremental Growth; Indomitable Creativity; Indulgent Aristocrat; Ingenious Skaab; Initiate's Companion; Insatiable Gorgers; Insolent Neonate; Inspiring Call; Inspiring Statuary; Inspiring Vantage; Insult // Injury; Intrepid Provisioner; Invasive Surgery; Inventor's Apprentice; Inventors' Fair; Inventor's Goggles; Invigorated Rampage; Ipnu Rivulet; Ironclad Slayer; Irontread Crusher; Irrigated Farmland; Ishkanah, Grafwidow; Jace, Unraveler of Secrets; Jace's Scrutiny; Just the Wind; Kalastria Nightwatch; Kambal, Consul of Allocation; Kari Zev, Skyship Raider; Kari Zev's Expertise; Kefnet the Mindful; Kefnet's Monument; Key to the City; Khenra Charioteer; Khenra Eternal; Khenra Scrapper; Kindly Stranger; Kolaghan's Command; Kujar Seedsculptor; Laboratory Brute; Labyrinth Guardian; Languish; Lathnu Sailback; Launch Party; Lawless Broker; Lay Claim; Leaf Gilder; Leave // Chance; Leave in the Dust; Leeching Sliver; Lethal Sting; Lifecraft Cavalry; Lifecrafter's Bestiary; Lifecrafter's Gift; Lightning Axe; Lightning Diadem; Lightning Shrieker; Lightwalker; Liliana, Death's Majesty; Liliana, the Last Hope; Liliana's Defeat; Liliana's Elite; Liliana's Mastery; Liliana's Reaver; Live Fast; Lone Rider; Long Road Home; Longtusk Cub; Lord of the Accursed; Lost Legacy; Lunarch Mantle; Lupine Prototype; Mad Prophet; Magma Jet; Magma Spray; Magmaroth; Magmatic Chasm; Magnifying Glass; Majestic Myriarch; Make Mischief; Make Obsolete; Malakir Cullblade; Malakir Familiar; Malfunction; Manaweft Sliver; Manglehorn; Manic Scribe; Marauding Boneslasher; Marionette Master; Markov Crusader; Master Trinketeer; Maulfist Revolutionary; Maulfist Squad; Maverick Thopterist; Maze's End; Merchant's Dockhand; Merciless Javelineer; Merciless Resolve; Mercurial Geists; Metallic Mimic; Metallic Rebuke; Metallurgic Summonings; Metalwork Colossus; Miasmic Mummy; Midnight Oil; Midnight Scavengers; Mind's Dilation; Mindwrack Demon; Minister of Inquiries; Minotaur Skullcleaver; Minotaur Sureshot; Mirage Mirror; Mirrorwing Dragon; Mobile Garrison; Mockery of Nature; Monstrous Onslaught; Moonlight Hunt; Morkrut Necropod; Mournwillow; Mouth // Feed; Mugging; Murderer's Axe; Murmuring Phantasm; Naga Oracle; Naga Vitalist; Nahiri, the Harbinger; Nahiri's Wrath; Narnam Cobra; Narnam Renegade; Nature's Way; Nearheath Chaplain; Nebelgast Herald; Nef-Crop Entangler; Neglected Heirloom; Neheb, the Eternal; Neheb, the Worthy; Nest of Scarabs; Never // Return; New Perspectives; Nicol Bolas, God-Pharaoh; Night Market Aeronaut; Night Market Lookout; Nightmare; Nimble Innovator; Nimble Obstructionist; Nimble-Blade Khenra; Nimbus Swimmer; Nissa, Steward of Elements; Nissa, Vital Force; Noose Constrictor; Noosegraf Mob; Noxious Gearhulk; Nyx-Fleece Ram; Oashra Cultivator; Oasis Ritualist; Oath of Ajani; Obelisk Spider; Obsessive Skinner; Odric, Lunarch Marshal; Ogre Battledriver; Ogre Slumlord; Ojutai's Command; Ojutai's Summons; Oketra the True; Oketra's Attendant; Oketra's Avenger; Oketra's Monument; Olivia, Mobilized for War; Olivia's Bloodsworn; Olivia's Dragoon; Ominous Sphinx; Ongoing Investigation; Onward // Victory; Open Fire; Ornamental Courage; Ornery Kudu; Ornithopter; Outland Boar; Outnumber; Ovalchase Dragster; Overwhelming Splendor; Oviya Pashiri, Sage Lifecrafter; Pacification Array; Pack Guardian; Pack Rat; Padeem, Consul of Innovation; Panharmonicon; Paradox Engine; Paradoxical Outcome; Path of Bravery; Pathmaker Initiate; Patron of the Valiant; Peacewalker Colossus; Peel from Reality; Peema Aether-Seer; Peema Outrider; Perilous Vault; Permeating Mass; Phyrexian Revoker; Pia Nalaar; Pick the Brain; Pieces of the Puzzle; Pilgrim's Eye; Pitiless Vizier; Planar Bridge; Pore Over the Pages; Port Town; Pouncing Cheetah; Prakhata Pillar-Bug; Precise Strike; Predatory Sliver; Prepare // Fight; Prescient Chimera; Pride Sovereign; Primeval Bounty; Prized Amalgam; Propeller Pioneer; Protection of the Hekma; Prowling Serpopard; Pull from Tomorrow; Puncturing Blow; Puncturing Light; Pursue Glory; Putrefy; Pyre Hound; Quarry Hauler; Quicksmith Genius; Quicksmith Rebel; Rageblood Shaman; Rags // Riches; Raise Dead; Ramunap Excavator; Ramunap Ruins; Rashmi, Eternities Crafter; Ratchet Bomb; Rattlechains; Ravenous Bloodseeker; Ravenous Intruder; Razaketh, the Foulblooded; Reaper of Flight Moonsilver; Reason // Believe; Reckless Fireweaver; Reckless Racer; Reckless Scholar; Reduce // Rubble; Refurbish; Refuse // Cooperate; Regal Caracal; Relentless Dead; Renegade Map; Renegade Rallier; Renegade Tactics; Renegade Wheelsmith; Renewed Faith; Reservoir Walker; Resilient Khenra; Rest in Peace; Restoration Gearsmith; Restoration Specialist; Return to the Ranks; Reverse Engineer; Revoke Privileges; Revolutionary Rebuff; Rhonas the Indomitable; Rhonas's Monument; Rhonas's Stalwart; Riddle of Lightning; Ride Down; Ridgescale Tusker; Riparian Tiger; Rise from the Tides; Rise of the Dark Realms; Rishkar, Peema Renegade; Rishkar's Expertise; River Hoopoe; Rogue Refiner; Ruin Rat; Ruinous Gremlin; Rumbling Baloth; Runeclaw Bear; Runed Servitor; Rush of Adrenaline; Rush of Vitality; Ruthless Disposal; Ruthless Sniper; Sacred Cat; Sage of Ancient Lore; Sage of Shaila's Claim; Saheeli Rai; Salivating Gremlins; Samut, the Tested; Samut, Voice of Dissent; Sand Strangler; Sandsteppe Outcast; Sandwurm Convergence; Sarkhan's Rage; Scarab Feast; Scattered Groves; Scavenger Grounds; Scour the Laboratory; Scourge Wolf; Scrap Trawler; Scrapheap Scrounger; Scrapper Champion; Seasons Past; Second Harvest; Seeker of Insight; Seer of the Last Tomorrow; Seismic Elemental; Select for Inspection; Self-Assembler; Selfless Cathar; Selfless Spirit; Sengir Vampire; Sentinel Sliver; Servant of the Conduit; Servant of the Scale; Servo Exhibition; Servo Schematic; Shadow of the Grave; Shadowstorm Vizier; Shambleshark; Shambling Goblin; Shed Weakness; Shefet Dunes; Shefet Monitor; Sheltered Thicket; Shielded Aether Thief; Shimmerscale Drake; Shipwreck Moray; Shiv's Embrace; Shreds of Sanity; Shrewd Negotiation; Shrill Howler; Sidewinder Naga; Siege Dragon; Siege Modification; Sifter Wurm; Sigarda, Heron's Grace; Sigarda's Aid; Sigardian Priest; Sigil of the Empty Throne; Sigil of Valor; Sigiled Starfish; Sign in Blood; Silumgar's Command; Sin Prodder; Sixth Sense; Sky Skiff; Skyship Plunderer; Skyship Stalker; Skysovereign, Consul Flagship; Skywhaler's Shot; Slate Street Ruffian; Slayer's Plate; Slither Blade; Sliver Hive; Sly Requisitioner; Solemnity; Solitary Camel; Somberwald Stag; Sorin, Grim Nemesis; Soul of the Harvest; Soul Separator; Soulblade Djinn; Soul-Scar Mage; Soulstinger; Spectral Shepherd; Speedway Fanatic; Spell Queller; Spellweaver Eternal; Sphinx's Revelation; Spire of Industry; Spire Patrol; Spirebluff Canal; Spireside Infiltrator; Spirit of the Hunt; Splendid Agony; Spontaneous Mutation; Sporemound; Spring // Mind; Springleaf Drum; Sram, Senior Edificer; Sram's Expertise; Stab Wound; Start // Finish; Startled Awake; Steadfast Cathar; Steelform Sliver; Stensia Masquerade; Steward of Solidarity; Stinging Shot; Stitcher's Graft; Stitchwing Skaab; Strength of Arms; Striking Sliver; Striped Riverwinder; Stromkirk Condemned; Stromkirk Occultist; Struggle // Survive; Subjugator Angel; Summary Dismissal; Sunscorched Desert; Sunscourge Champion; Sunset Pyramid; Supernatural Stamina; Supply Caravan; Supreme Will; Swan Song; Swarm of Bloodflies; Sweatworks Brawler; Sweep Away; Sweltering Suns; Swift Spinner; Synchronized Strike; Tah-Crop Elite; Tajuru Pathwarden; Take Inventory; Tamiyo, Field Researcher; Tamiyo's Journal; Tandem Tactics; Tattered Haunter; Temmet, Vizier of Naktamun; Terrarion; Tezzeret the Schemer; Tezzeret's Ambition; Tezzeret's Touch; Thalia, Heretic Cathar; Thalia's Lancers; Thalia's Lieutenant; The Gitrog Monster; The Locust God; The Scarab God; The Scorpion God; Thing in the Ice; Thopter Arrest; Thorned Moloch; Those Who Serve; Thoughtseize; Thraben Foulbloods; Thraben Inspector; Thraben Standard Bearer; Thresher Lizard; Thriving Rhino; Thriving Turtle; Throne of the God-Pharaoh; Thunderbreak Regent; Tightening Coils; Toolcraft Exemplar; Topplegeist; Torch Fiend; Torment of Hailfire; Torment of Scarabs; Torrential Gearhulk; Town Gossipmonger; Traverse the Ulvenwald; Treasure Keeper; Tree of Perdition; Trespasser's Curse; Trial of Ambition; Trial of Knowledge; Trial of Solidarity; Trial of Strength; Trial of Zeal; Triskaidekaphobia; Trophy Mage; True-Faith Censer; Typhoid Rats; Ulamog, the Ceaseless Hunger; Ulrich of the Krallenhorde; Ulrich's Kindred; Ulvenwald Captive; Ulvenwald Hydra; Ulvenwald Mysteries; Unbridled Growth; Unburden; Unconventional Tactics; Underhanded Designs; Unesh, Criosphinx Sovereign; Universal Solvent; Unlicensed Disintegration; Unquenchable Thirst; Untethered Express; Vengeful Rebel; Verdurous Gearhulk; Vessel of Nascency; Veteran Cathar; Veteran Motorist; Vile Manifestation; Village Messenger; Virulent Plague; Visionary Augmenter; Vizier of Deferment; Vizier of Many Faces; Vizier of Remedies; Vizier of the Anointed; Vizier of the Menagerie; Vizier of Tumbling Sands; Voldaren Pariah; Voltaic Brawler; Voyage's End; Wailing Ghoul; Wall of Forgotten Pharaohs; Wander in Death; Warfire Javelineer; Wasp of the Bitter End; Waste Not; Wasteland Scorpion; Watchers of the Dead; Watchful Naga; Wayward Servant; Weaponcraft Enthusiast; Weaver of Lightning; Weirded Vampire; Weirding Wood; Weldfast Engineer; Weldfast Monitor; Weldfast Wingsmith; Welding Sparks; Westvale Abbey; Wharf Infiltrator; Whelming Wave; Whir of Invention; Whirler Rogue; Whirler Virtuoso; Whirlermaker; Wight of Precinct Six; Wild Wanderer; Wildest Dreams; Wild-Field Scarecrow; Winding Constrictor; Wind-Kin Raiders; Winds of Rebuke; Winged Shepherd; Wispweaver Angel; Witch's Familiar; Woodborn Behemoth; Woodweaver's Puzzleknot; Workshop Assistant; Wretched Gryff; Yahenni, Undying Partisan; Yahenni's Expertise; Young Pyromancer; Zada, Hedron Grinder; Zealot of the God-Pharaoh; Zendikar's Roil diff --git a/forge-gui/res/formats/Archived/Historic/2023-07-18.txt b/forge-gui/res/formats/Archived/Historic/2023-07-18.txt new file mode 100644 index 00000000000..2748dd0bcee --- /dev/null +++ b/forge-gui/res/formats/Archived/Historic/2023-07-18.txt @@ -0,0 +1,7 @@ +[format] +Name:Historic (HA7) +Type:Archived +Subtype:Arena +Effective:2023-07-18 +Sets:XLN, RIX, DOM, M19, ANA, PANA, GRN, G18, RNA, WAR, M20, ELD, HA1, THB, HA2, IKO, HA3, M21, JMP, AJMP, AKR, ANB, ZNR, KLR, KHM, HA4, STX, STA, HA5, AFR, J21, MID, VOW, YMID, NEO, YNEO, SNC, YSNC, HBG, HA6, EA1, DMU, YDMU, BRO, BRR, YBRO, EA2, ONE, YONE, SIR, SIS, MOM, MUL, MAT, LTR, HA7, EA3 +Banned:Agent of Treachery; Brainstorm; Channel; Counterspell; Dark Ritual; Demonic Tutor; Field of the Dead; Lightning Bolt; Memory Lapse; Mishra's Bauble; Natural Order; Nexus of Fate; Oko, Thief of Crowns; Once Upon a Time; Ragavan, Nimble Pilferer; Swords to Plowshares; Thassa's Oracle; Tibalt's Trickery; Time Warp; Uro, Titan of Nature's Wrath; Veil of Summer; Wilderness Reclamation diff --git a/forge-gui/res/formats/Sanctioned/Historic.txt b/forge-gui/res/formats/Sanctioned/Historic.txt index a01e6b751be..72db9a831e2 100644 --- a/forge-gui/res/formats/Sanctioned/Historic.txt +++ b/forge-gui/res/formats/Sanctioned/Historic.txt @@ -4,5 +4,5 @@ Type:Digital Subtype:Arena Effective:2019-11-21 Order:142 -Sets:XLN, RIX, DOM, M19, ANA, PANA, GRN, G18, RNA, WAR, M20, ELD, HA1, THB, HA2, IKO, HA3, M21, JMP, AJMP, AKR, ANB, ZNR, KLR, KHM, HA4, STX, STA, HA5, AFR, J21, MID, VOW, YMID, NEO, YNEO, SNC, YSNC, HBG, HA6, EA1, DMU, YDMU, BRO, BRR, YBRO, EA2, ONE, YONE, SIR, SIS, MOM, MUL, MAT, LTR +Sets:XLN, RIX, DOM, M19, ANA, PANA, GRN, G18, RNA, WAR, M20, ELD, HA1, THB, HA2, IKO, HA3, M21, JMP, AJMP, AKR, ANB, ZNR, KLR, KHM, HA4, STX, STA, HA5, AFR, J21, MID, VOW, YMID, NEO, YNEO, SNC, YSNC, HBG, HA6, EA1, DMU, YDMU, BRO, BRR, YBRO, EA2, ONE, YONE, SIR, SIS, MOM, MUL, MAT, LTR, HA7, EA3 Banned:Agent of Treachery; Brainstorm; Channel; Counterspell; Dark Ritual; Demonic Tutor; Field of the Dead; Lightning Bolt; Memory Lapse; Mishra's Bauble; Natural Order; Nexus of Fate; Oko, Thief of Crowns; Once Upon a Time; Ragavan, Nimble Pilferer; Swords to Plowshares; Thassa's Oracle; Tibalt's Trickery; Time Warp; Uro, Titan of Nature's Wrath; Veil of Summer; Wilderness Reclamation diff --git a/forge-gui/res/skins/default/sprite_sleeves.png b/forge-gui/res/skins/default/sprite_sleeves.png index d6e97d099b8..8182a942d9b 100644 Binary files a/forge-gui/res/skins/default/sprite_sleeves.png and b/forge-gui/res/skins/default/sprite_sleeves.png differ diff --git a/forge-gui/res/skins/default/sprite_sleeves2.png b/forge-gui/res/skins/default/sprite_sleeves2.png index 4916506f01e..0a5f0565eb1 100644 Binary files a/forge-gui/res/skins/default/sprite_sleeves2.png and b/forge-gui/res/skins/default/sprite_sleeves2.png differ diff --git a/forge-gui/res/tokenscripts/b_3_3_wraith_menace.txt b/forge-gui/res/tokenscripts/b_3_3_wraith_menace.txt new file mode 100644 index 00000000000..2c3eae44304 --- /dev/null +++ b/forge-gui/res/tokenscripts/b_3_3_wraith_menace.txt @@ -0,0 +1,7 @@ +Name:Wraith Token +ManaCost:no cost +Types:Creature Wraith +Colors:black +PT:3/3 +K:Menace +Oracle:Menace diff --git a/forge-gui/res/tokenscripts/w_3_3_bird_flying_attacks.txt b/forge-gui/res/tokenscripts/w_3_3_bird_flying_attacks.txt new file mode 100644 index 00000000000..6a8856f7c6a --- /dev/null +++ b/forge-gui/res/tokenscripts/w_3_3_bird_flying_attacks.txt @@ -0,0 +1,10 @@ +Name:Bird Token +ManaCost:no cost +Types:Creature Bird +Colors:white +PT:3/3 +K:Flying +T:Mode$ Attacks | ValidCard$ Card.Self | TriggerZones$ Battlefield | Execute$ TrigPump | TriggerDescription$ Whenever this creature attacks, target attacking creature gains flying until end of turn. +SVar:TrigPump:DB$ Pump | ValidTgts$ Creature.attacking | TgtPrompt$ Select target attacking creature | KW$ Flying +SVar:HasAttackEffect:TRUE +Oracle:Flying\nWhenever this creature attacks, target attacking creature gains flying until end of turn.