diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java index ff73d875785..c9eb6634f57 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java @@ -91,9 +91,6 @@ public class ComputerUtil { } } - source.setCastSA(sa); - sa.setLastStateBattlefield(game.getLastStateBattlefield()); - sa.setLastStateGraveyard(game.getLastStateGraveyard()); sa.setHostCard(game.getAction().moveToStack(source, sa)); } @@ -219,9 +216,6 @@ public class ComputerUtil { final Card source = sa.getHostCard(); if (sa.isSpell() && !source.isCopiedSpell()) { - source.setCastSA(sa); - sa.setLastStateBattlefield(game.getLastStateBattlefield()); - sa.setLastStateGraveyard(game.getLastStateGraveyard()); sa.setHostCard(game.getAction().moveToStack(source, sa)); } @@ -246,9 +240,6 @@ public class ComputerUtil { final Card source = sa.getHostCard(); if (sa.isSpell() && !source.isCopiedSpell()) { - source.setCastSA(sa); - sa.setLastStateBattlefield(game.getLastStateBattlefield()); - sa.setLastStateGraveyard(game.getLastStateGraveyard()); sa.setHostCard(game.getAction().moveToStack(source, sa)); } @@ -267,9 +258,6 @@ public class ComputerUtil { final Card source = newSA.getHostCard(); if (newSA.isSpell() && !source.isCopiedSpell()) { - source.setCastSA(newSA); - sa.setLastStateBattlefield(game.getLastStateBattlefield()); - sa.setLastStateGraveyard(game.getLastStateGraveyard()); newSA.setHostCard(game.getAction().moveToStack(source, sa)); if (newSA.getApi() == ApiType.Charm && !newSA.isWrapper()) { @@ -290,9 +278,6 @@ public class ComputerUtil { if (ComputerUtilCost.canPayCost(sa, ai)) { final Card source = sa.getHostCard(); if (sa.isSpell() && !source.isCopiedSpell()) { - source.setCastSA(sa); - sa.setLastStateBattlefield(game.getLastStateBattlefield()); - sa.setLastStateGraveyard(game.getLastStateGraveyard()); sa.setHostCard(game.getAction().moveToStack(source, sa)); } diff --git a/forge-core/src/main/java/forge/ImageKeys.java b/forge-core/src/main/java/forge/ImageKeys.java index e07f836566d..f2b976f3008 100644 --- a/forge-core/src/main/java/forge/ImageKeys.java +++ b/forge-core/src/main/java/forge/ImageKeys.java @@ -113,7 +113,11 @@ public final class ImageKeys { } //try fullborder... if (filename.contains(".full")) { - file = findFile(dir, TextUtil.fastReplace(filename, ".full", ".fullborder")); + String fullborderFile = TextUtil.fastReplace(filename, ".full", ".fullborder"); + file = findFile(dir, fullborderFile); + if (file != null) { return file; } + // if there's an art variant try without it + file = findFile(dir, TextUtil.fastReplace(fullborderFile, "1.fullborder", ".fullborder")); if (file != null) { return file; } } //if an image, like phenomenon or planes is missing .full in their filenames but you have an existing images that have .full/.fullborder diff --git a/forge-core/src/main/java/forge/card/CardEdition.java b/forge-core/src/main/java/forge/card/CardEdition.java index 66cb689feed..ce6289bb5f5 100644 --- a/forge-core/src/main/java/forge/card/CardEdition.java +++ b/forge-core/src/main/java/forge/card/CardEdition.java @@ -192,6 +192,7 @@ public final class CardEdition implements Comparable { // immutable public boolean getSmallSetOverride() { return smallSetOverride; } public String getBoosterMustContain() { return boosterMustContain; } public CardInSet[] getCards() { return cards; } + public boolean isModern() { return getDate().after(parseDate("2003-07-27")); } //8ED and above are modern except some promo cards and others public Map getTokens() { return tokenNormalized; } diff --git a/forge-core/src/main/java/forge/card/CardRules.java b/forge-core/src/main/java/forge/card/CardRules.java index 322c003b6f2..a27b60adf2c 100644 --- a/forge-core/src/main/java/forge/card/CardRules.java +++ b/forge-core/src/main/java/forge/card/CardRules.java @@ -222,7 +222,12 @@ public final class CardRules implements ICardCharacteristics { public boolean canBeBrawlCommander() { CardType type = mainPart.getType(); - return (type.isLegendary() && type.isCreature()) || type.isPlaneswalker(); + return type.isLegendary() && (type.isCreature() || type.isPlaneswalker()); + } + + public boolean canBeTinyLeadersCommander() { + CardType type = mainPart.getType(); + return type.isLegendary() && (type.isCreature() || type.isPlaneswalker()); } public String getMeldWith() { diff --git a/forge-core/src/main/java/forge/card/CardRulesPredicates.java b/forge-core/src/main/java/forge/card/CardRulesPredicates.java index 6679b3ad673..66dca47b592 100644 --- a/forge-core/src/main/java/forge/card/CardRulesPredicates.java +++ b/forge-core/src/main/java/forge/card/CardRulesPredicates.java @@ -594,8 +594,10 @@ public final class CardRulesPredicates { public static final Predicate IS_VANGUARD = CardRulesPredicates.coreType(true, CardType.CoreType.Vanguard); public static final Predicate IS_CONSPIRACY = CardRulesPredicates.coreType(true, CardType.CoreType.Conspiracy); public static final Predicate IS_NON_LAND = CardRulesPredicates.coreType(false, CardType.CoreType.Land); - public static final Predicate CAN_BE_BRAWL_COMMANDER = Predicates.or(Presets.IS_PLANESWALKER, - Predicates.and(Presets.IS_CREATURE, Presets.IS_LEGENDARY)); + public static final Predicate CAN_BE_BRAWL_COMMANDER = Predicates.and(Presets.IS_LEGENDARY, + Predicates.or(Presets.IS_CREATURE, Presets.IS_PLANESWALKER)); + public static final Predicate CAN_BE_TINY_LEADERS_COMMANDER = Predicates.and(Presets.IS_LEGENDARY, + Predicates.or(Presets.IS_CREATURE, Presets.IS_PLANESWALKER)); /** The Constant IS_NON_CREATURE_SPELL. **/ public static final Predicate IS_NON_CREATURE_SPELL = com.google.common.base.Predicates diff --git a/forge-core/src/main/java/forge/deck/DeckFormat.java b/forge-core/src/main/java/forge/deck/DeckFormat.java index 3ea356c6a6d..e369fccc075 100644 --- a/forge-core/src/main/java/forge/deck/DeckFormat.java +++ b/forge-core/src/main/java/forge/deck/DeckFormat.java @@ -463,6 +463,9 @@ public enum DeckFormat { if (this.equals(DeckFormat.Brawl)) { return rules.canBeBrawlCommander(); } + if (this.equals(DeckFormat.TinyLeaders)) { + return rules.canBeTinyLeadersCommander(); + } return rules.canBeCommander(); } diff --git a/forge-core/src/main/java/forge/util/TextUtil.java b/forge-core/src/main/java/forge/util/TextUtil.java index 4974a8e087f..06a706b20f0 100644 --- a/forge-core/src/main/java/forge/util/TextUtil.java +++ b/forge-core/src/main/java/forge/util/TextUtil.java @@ -5,6 +5,8 @@ import forge.item.PaperCard; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; +import com.google.common.collect.ImmutableSortedMap; + import java.util.ArrayList; import java.util.List; import java.util.Locale; @@ -17,6 +19,22 @@ import java.util.Map.Entry; */ public class TextUtil { + static ImmutableSortedMap romanMap = ImmutableSortedMap.naturalOrder() + .put(1000, "M").put(900, "CM") + .put(500, "D").put(400, "CD") + .put(100, "C").put(90, "XC") + .put(50, "L").put(40, "XL") + .put(10, "X").put(9, "IX") + .put(5, "V").put(4, "IV").put(1, "I").build(); + + public final static String toRoman(int number) { + if (number <= 0) { + return ""; + } + int l = romanMap.floorKey(number); + return romanMap.get(l) + toRoman(number-l); + } + /** * Safely converts an object to a String. * diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java index 16b67a2d4e7..16ec6662f0d 100644 --- a/forge-game/src/main/java/forge/game/GameAction.java +++ b/forge-game/src/main/java/forge/game/GameAction.java @@ -547,6 +547,13 @@ public class GameAction { c.setCastSA(null); } else if (zoneTo.is(ZoneType.Stack)) { c.setCastFrom(zoneFrom.getZoneType()); + if (cause != null && cause.isSpell() && c.equals(cause.getHostCard()) && !c.isCopiedSpell()) { + cause.setLastStateBattlefield(game.getLastStateBattlefield()); + cause.setLastStateGraveyard(game.getLastStateGraveyard()); + c.setCastSA(cause); + } else { + c.setCastSA(null); + } } else if (!(zoneTo.is(ZoneType.Battlefield) && zoneFrom.is(ZoneType.Stack))) { c.setCastFrom(null); c.setCastSA(null); diff --git a/forge-game/src/main/java/forge/game/GameActionUtil.java b/forge-game/src/main/java/forge/game/GameActionUtil.java index b54c727370f..6d9ed807964 100644 --- a/forge-game/src/main/java/forge/game/GameActionUtil.java +++ b/forge-game/src/main/java/forge/game/GameActionUtil.java @@ -22,8 +22,10 @@ import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Sets; +import forge.card.MagicColor; import forge.card.mana.ManaCost; import forge.card.mana.ManaCostParser; +import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityUtils; import forge.game.ability.ApiType; import forge.game.card.*; @@ -32,9 +34,15 @@ import forge.game.cost.Cost; import forge.game.keyword.KeywordInterface; import forge.game.player.Player; import forge.game.player.PlayerController; +import forge.game.replacement.ReplacementEffect; +import forge.game.replacement.ReplacementHandler; +import forge.game.replacement.ReplacementLayer; import forge.game.spellability.*; import forge.game.trigger.Trigger; +import forge.game.trigger.TriggerHandler; +import forge.game.trigger.TriggerType; import forge.game.zone.ZoneType; +import forge.util.Lang; import forge.util.TextUtil; import org.apache.commons.lang3.StringUtils; @@ -363,10 +371,11 @@ public final class GameActionUtil { } SpellAbility result = null; final Card host = sa.getHostCard(); + final Game game = host.getGame(); final Player activator = sa.getActivatingPlayer(); final PlayerController pc = activator.getController(); - host.getGame().getAction().checkStaticAbilities(false); + game.getAction().checkStaticAbilities(false); boolean reset = false; @@ -429,7 +438,60 @@ public final class GameActionUtil { int v = pc.chooseNumberForKeywordCost(sa, cost, ki, str, Integer.MAX_VALUE); if (v > 0) { - host.addReplacementEffect(CardFactoryUtil.makeEtbCounter("etbCounter:P1P1:" + v, host, false)); + + final Card eff = new Card(game.nextCardId(), game); + eff.setTimestamp(game.getNextTimestamp()); + eff.setName(c.getName() + "'s Effect"); + eff.addType("Effect"); + eff.setToken(true); // Set token to true, so when leaving play it gets nuked + eff.setOwner(activator); + + eff.setImageKey(c.getImageKey()); + eff.setColor(MagicColor.COLORLESS); + eff.setImmutable(true); + // try to get the SpellAbility from the mana ability + //eff.setEffectSource((SpellAbility)null); + + eff.addRemembered(host); + + String abStr = "DB$ PutCounter | Defined$ ReplacedCard | CounterType$ P1P1 | ETB$ True | CounterNum$ " + v; + + SpellAbility saAb = AbilityFactory.getAbility(abStr, c); + + CardFactoryUtil.setupETBReplacementAbility(saAb); + + String desc = "It enters the battlefield with "; + desc += Lang.nounWithNumeral(v, CounterType.P1P1.getName() + " counter"); + desc += " on it."; + + String repeffstr = "Event$ Moved | ValidCard$ Card.IsRemembered | Destination$ Battlefield | Description$ " + desc; + + ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, eff, true); + re.setLayer(ReplacementLayer.Other); + re.setOverridingAbility(saAb); + + eff.addReplacementEffect(re); + + // Forgot Trigger + String trig = "Mode$ ChangesZone | ValidCard$ Card.IsRemembered | Origin$ Stack | Destination$ Any | TriggerZones$ Command | Static$ True"; + String forgetEffect = "DB$ Pump | ForgetObjects$ TriggeredCard"; + String exileEffect = "DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile" + + " | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ EQ0"; + + SpellAbility saForget = AbilityFactory.getAbility(forgetEffect, eff); + AbilitySub saExile = (AbilitySub) AbilityFactory.getAbility(exileEffect, eff); + saForget.setSubAbility(saExile); + + final Trigger parsedTrigger = TriggerHandler.parseTrigger(trig, eff, true); + parsedTrigger.setOverridingAbility(saForget); + eff.addTrigger(parsedTrigger); + eff.updateStateForView(); + + // TODO: Add targeting to the effect so it knows who it's dealing with + game.getTriggerHandler().suppressMode(TriggerType.ChangesZone); + game.getAction().moveTo(ZoneType.Command, eff, null); + game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); + if (result == null) { result = sa.copy(); } 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 0be3279d3e4..1d278409976 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -6353,6 +6353,10 @@ public class Card extends GameEntity implements Comparable { removeSVar("PayX"); // Temporary AI X announcement variable removeSVar("IsCastFromPlayEffect"); // Temporary SVar indicating that the spell is cast indirectly via AF Play setSunburstValue(0); // Sunburst + setXManaCostPaid(0); + setXManaCostPaidByColor(null); + setKickerMagnitude(0); + setPseudoMultiKickerMagnitude(0); } public final int getFinalChapterNr() { diff --git a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java index e60f3e16bd1..88307959cdb 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java @@ -20,7 +20,6 @@ package forge.game.card; import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.base.Predicates; -import com.google.common.base.Strings; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; @@ -3010,24 +3009,43 @@ public class CardFactoryUtil { inst.addTrigger(parsedTrigger); } else if (keyword.startsWith("Saga")) { - // Saga there doesn't need Max value anymore? final String[] k = keyword.split(":"); - final String[] abs = k[2].split(","); + final List abs = Arrays.asList(k[2].split(",")); + if (abs.size() != Integer.valueOf(k[1])) { + throw new RuntimeException("Saga max differ from Ability amount"); + } - int i = 1; - for (String ab : abs) { - SpellAbility sa = AbilityFactory.getAbility(card, ab); - sa.setChapter(i); + int idx = 0; + int skipId = 0; + for(String ab : abs) { + idx += 1; + if (idx <= skipId) { + continue; + } - // TODO better logic for Roman numbers - // In the Description try to merge Chapter trigger with the Same Effect - String trigStr = "Mode$ CounterAdded | ValidCard$ Card.Self | TriggerZones$ Battlefield" - + "| CounterType$ LORE | CounterAmount$ EQ" + i - + "| TriggerDescription$ " + Strings.repeat("I", i) + " - " + sa.getDescription(); - final Trigger t = TriggerHandler.parseTrigger(trigStr, card, intrinsic); - t.setOverridingAbility(sa); - inst.addTrigger(t); - ++i; + skipId = idx + abs.subList(idx - 1, abs.size()).lastIndexOf(ab); + StringBuilder desc = new StringBuilder(); + for (int i = idx; i <= skipId; i++) { + if (i != idx) { + desc.append(", "); + } + desc.append(TextUtil.toRoman(i)); + } + + for (int i = idx; i <= skipId; i++) { + SpellAbility sa = AbilityFactory.getAbility(card, ab); + sa.setChapter(i); + + StringBuilder trigStr = new StringBuilder("Mode$ CounterAdded | ValidCard$ Card.Self | TriggerZones$ Battlefield"); + trigStr.append("| CounterType$ LORE | CounterAmount$ EQ").append(i); + if (i != idx) { + trigStr.append(" | Secondary$ True"); + } + trigStr.append("| TriggerDescription$ ").append(desc).append(" — ").append(sa.getDescription()); + final Trigger t = TriggerHandler.parseTrigger(trigStr.toString(), card, intrinsic); + t.setOverridingAbility(sa); + inst.addTrigger(t); + } } } else if (keyword.equals("Soulbond")) { // Setup ETB trigger for card with Soulbond keyword diff --git a/forge-game/src/main/java/forge/game/combat/Combat.java b/forge-game/src/main/java/forge/game/combat/Combat.java index 4fc83734d74..4c85ccd5cac 100644 --- a/forge-game/src/main/java/forge/game/combat/Combat.java +++ b/forge-game/src/main/java/forge/game/combat/Combat.java @@ -882,6 +882,10 @@ public class Combat { return true; // is blocking something at the moment } + if (!blocker.isLKI()) { + return false; + } + CombatLki lki = lkiCache.get(blocker); return null != lki && !lki.isAttacker; // was blocking something anyway } @@ -892,7 +896,11 @@ public class Combat { if (blockers != null && blockers.contains(blocker)) { return true; // is blocking the attacker's band at the moment } - + + if (!blocker.isLKI()) { + return false; + } + CombatLki lki = lkiCache.get(blocker); return null != lki && !lki.isAttacker && lki.relatedBands.contains(ab); // was blocking that very band } diff --git a/forge-game/src/main/java/forge/game/mana/ManaPool.java b/forge-game/src/main/java/forge/game/mana/ManaPool.java index e90e6eb9b50..d11afbe776c 100644 --- a/forge-game/src/main/java/forge/game/mana/ManaPool.java +++ b/forge-game/src/main/java/forge/game/mana/ManaPool.java @@ -254,7 +254,7 @@ public class ManaPool extends ManaConversionMatrix implements Iterable { } } if (mana.addsCounters(sa)) { - mana.getManaAbility().createETBCounters(host); + mana.getManaAbility().createETBCounters(host, this.owner); } if (mana.triggersWhenSpent()) { mana.getManaAbility().addTriggersWhenSpent(sa, host); diff --git a/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java b/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java index da064a4cbd6..510458a6189 100644 --- a/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java +++ b/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java @@ -6,12 +6,12 @@ * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ @@ -19,9 +19,11 @@ package forge.game.spellability; import com.google.common.collect.Lists; import com.google.common.collect.Maps; + import forge.card.ColorSet; import forge.card.MagicColor; import forge.card.mana.ManaAtom; +import forge.game.Game; import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityKey; import forge.game.card.Card; @@ -34,6 +36,8 @@ import forge.game.replacement.*; import forge.game.trigger.Trigger; import forge.game.trigger.TriggerHandler; import forge.game.trigger.TriggerType; +import forge.game.zone.ZoneType; +import forge.util.Lang; import forge.util.TextUtil; import org.apache.commons.lang3.StringUtils; @@ -46,7 +50,7 @@ import java.util.regex.Pattern; *

* Abstract AbilityMana class. *

- * + * * @author Forge * @version $Id$ */ @@ -78,7 +82,7 @@ public class AbilityManaPart implements java.io.Serializable { *

* Constructor for AbilityMana. *

- * + * * @param sourceCard * a {@link forge.game.card.Card} object. */ @@ -111,7 +115,7 @@ public class AbilityManaPart implements java.io.Serializable { *

* produceMana. *

- * + * * @param produced * a {@link java.lang.String} object. * @param player @@ -169,7 +173,7 @@ public class AbilityManaPart implements java.io.Serializable { * cannotCounterPaidWith. *

* @param saBeingPaid - * + * * @return a {@link java.lang.String} object. */ public boolean cannotCounterPaidWith(SpellAbility saBeingPaid) { @@ -186,7 +190,7 @@ public class AbilityManaPart implements java.io.Serializable { * addKeywords. *

* @param saBeingPaid - * + * * @return a {@link java.lang.String} object. */ public boolean addKeywords(SpellAbility saBeingPaid) { @@ -205,7 +209,7 @@ public class AbilityManaPart implements java.io.Serializable { *

* getKeywords. *

- * + * * @return a {@link java.lang.String} object. */ public String getKeywords() { @@ -217,7 +221,7 @@ public class AbilityManaPart implements java.io.Serializable { * addsCounters. *

* @param saBeingPaid - * + * * @return a {@link java.lang.String} object. */ public boolean addsCounters(SpellAbility saBeingPaid) { @@ -227,10 +231,26 @@ public class AbilityManaPart implements java.io.Serializable { /** * createETBCounters */ - public void createETBCounters(Card c) { + public void createETBCounters(Card c, Player controller) { String[] parse = this.addsCounters.split("_"); // Convert random SVars if there are other cards with this effect if (c.isValid(parse[0], c.getController(), c, null)) { + final Game game = this.sourceCard.getGame(); + final Card eff = new Card(game.nextCardId(), game); + eff.setTimestamp(game.getNextTimestamp()); + eff.setName(sourceCard.getName() + "'s Effect"); + eff.addType("Effect"); + eff.setToken(true); // Set token to true, so when leaving play it gets nuked + eff.setOwner(controller); + + eff.setImageKey(sourceCard.getImageKey()); + eff.setColor(MagicColor.COLORLESS); + eff.setImmutable(true); + // try to get the SpellAbility from the mana ability + //eff.setEffectSource((SpellAbility)null); + + eff.addRemembered(c); + String abStr = "DB$ PutCounter | Defined$ ReplacedCard | CounterType$ " + parse[1] + " | ETB$ True | CounterNum$ " + parse[2]; @@ -240,15 +260,37 @@ public class AbilityManaPart implements java.io.Serializable { } CardFactoryUtil.setupETBReplacementAbility(sa); - String repeffstr = "Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield " - + " | Secondary$ True | Description$ CARDNAME" - + " enters the battlefield with " + CounterType.valueOf(parse[1]).getName() + " counters."; + String desc = "It enters the battlefield with "; + desc += Lang.nounWithNumeral(parse[2], CounterType.valueOf(parse[1]).getName() + " counter"); + desc += " on it."; - ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, c, false); + String repeffstr = "Event$ Moved | ValidCard$ Card.IsRemembered | Destination$ Battlefield | Description$ " + desc; + + ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, eff, true); re.setLayer(ReplacementLayer.Other); re.setOverridingAbility(sa); - c.addReplacementEffect(re); + eff.addReplacementEffect(re); + + // Forgot Trigger + String trig = "Mode$ ChangesZone | ValidCard$ Card.IsRemembered | Origin$ Stack | Destination$ Any | TriggerZones$ Command | Static$ True"; + String forgetEffect = "DB$ Pump | ForgetObjects$ TriggeredCard"; + String exileEffect = "DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile" + + " | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ EQ0"; + + SpellAbility saForget = AbilityFactory.getAbility(forgetEffect, eff); + AbilitySub saExile = (AbilitySub) AbilityFactory.getAbility(exileEffect, eff); + saForget.setSubAbility(saExile); + + final Trigger parsedTrigger = TriggerHandler.parseTrigger(trig, eff, true); + parsedTrigger.setOverridingAbility(saForget); + eff.addTrigger(parsedTrigger); + eff.updateStateForView(); + + // TODO: Add targeting to the effect so it knows who it's dealing with + game.getTriggerHandler().suppressMode(TriggerType.ChangesZone); + game.getAction().moveTo(ZoneType.Command, eff, null); + game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); } } @@ -269,7 +311,7 @@ public class AbilityManaPart implements java.io.Serializable { *

* getManaRestrictions. *

- * + * * @return a {@link java.lang.String} object. */ public String getManaRestrictions() { @@ -280,7 +322,7 @@ public class AbilityManaPart implements java.io.Serializable { *

* meetsManaRestrictions. *

- * + * * @param sa * a {@link forge.game.spellability.SpellAbility} object. * @return a boolean. @@ -296,7 +338,7 @@ public class AbilityManaPart implements java.io.Serializable { if (restriction.equals("nonSpell")) { return !sa.isSpell(); } - + if (restriction.equals("CumulativeUpkeep")) { if (sa.isCumulativeupkeep()) { return true; @@ -349,7 +391,7 @@ public class AbilityManaPart implements java.io.Serializable { *

* mana. *

- * + * * @return a {@link java.lang.String} object. */ public final String mana() { @@ -438,7 +480,7 @@ public class AbilityManaPart implements java.io.Serializable { *

* canProduce. *

- * + * * @param s * a {@link java.lang.String} object. * @return a boolean. @@ -468,7 +510,7 @@ public class AbilityManaPart implements java.io.Serializable { *

* isBasic. *

- * + * * @return a boolean. */ public final boolean isBasic() { @@ -541,7 +583,7 @@ public class AbilityManaPart implements java.io.Serializable { public Card getSourceCard() { return sourceCard; } - + public void setSourceCard(final Card host) { sourceCard = host; } diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java index 75479eaadfc..2bfe8b388a4 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java @@ -1299,6 +1299,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit String announce = getParam("Announce"); if (StringUtils.isBlank(announce)) { mapParams.put("Announce", variable); + originalMapParams.put("Announce", variable); return; } String[] announcedOnes = TextUtil.split(announce, ','); @@ -1308,6 +1309,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit } } mapParams.put("Announce", announce + ";" + variable); + originalMapParams.put("Announce", announce + ";" + variable); } public boolean isXCost() { diff --git a/forge-gui-android/AndroidManifest.xml b/forge-gui-android/AndroidManifest.xml index 5edbcfad66e..9a984611ed7 100644 --- a/forge-gui-android/AndroidManifest.xml +++ b/forge-gui-android/AndroidManifest.xml @@ -6,7 +6,7 @@ + android:targetSdkVersion="26" /> diff --git a/forge-gui-android/pom.xml b/forge-gui-android/pom.xml index 76158aee896..9afc4dcc9ab 100644 --- a/forge-gui-android/pom.xml +++ b/forge-gui-android/pom.xml @@ -142,7 +142,7 @@ true - 25 + 26 true ${project.basedir}/AndroidManifest.xml @@ -183,7 +183,7 @@ false - 25 + 26 false diff --git a/forge-gui-android/project.properties b/forge-gui-android/project.properties index 735ea3db305..94206b9059a 100644 --- a/forge-gui-android/project.properties +++ b/forge-gui-android/project.properties @@ -9,4 +9,4 @@ # Project target. project.type=0 -target=android-20 +target=android-26 diff --git a/forge-gui-desktop/src/main/java/forge/screens/deckeditor/controllers/CEditorConstructed.java b/forge-gui-desktop/src/main/java/forge/screens/deckeditor/controllers/CEditorConstructed.java index eec8c20c98a..0d72be9a4e5 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/deckeditor/controllers/CEditorConstructed.java +++ b/forge-gui-desktop/src/main/java/forge/screens/deckeditor/controllers/CEditorConstructed.java @@ -103,7 +103,7 @@ public final class CEditorConstructed extends CDeckEditor { case TinyLeaders: allSections.add(DeckSection.Commander); - commanderFilter = CardRulesPredicates.Presets.CAN_BE_COMMANDER; + commanderFilter = CardRulesPredicates.Presets.CAN_BE_TINY_LEADERS_COMMANDER; commanderPool = ItemPool.createFrom(FModel.getMagicDb().getCommonCards().getAllCards(Predicates.compose(commanderFilter, PaperCard.FN_GET_RULES)), PaperCard.class); normalPool = ItemPool.createFrom(FModel.getMagicDb().getCommonCards().getAllCards(), PaperCard.class); diff --git a/forge-gui-mobile/src/forge/card/CardImageRenderer.java b/forge-gui-mobile/src/forge/card/CardImageRenderer.java index b2094fbaa15..8bedd01ae42 100644 --- a/forge-gui-mobile/src/forge/card/CardImageRenderer.java +++ b/forge-gui-mobile/src/forge/card/CardImageRenderer.java @@ -35,6 +35,9 @@ import org.apache.commons.lang3.StringUtils; import java.util.ArrayList; import java.util.List; +import static forge.card.CardRenderer.CROP_MULTIPLIER; +import static forge.card.CardRenderer.isModernFrame; + public class CardImageRenderer { private static final float BASE_IMAGE_WIDTH = 360; private static final float BASE_IMAGE_HEIGHT = 504; @@ -357,13 +360,19 @@ public class CardImageRenderer { float new_yRotate = (dispH - new_w) /2; boolean rotateSplit = FModel.getPreferences().getPrefBoolean(ForgePreferences.FPref.UI_ROTATE_SPLIT_CARDS); boolean rotatePlane = FModel.getPreferences().getPrefBoolean(ForgePreferences.FPref.UI_ROTATE_PLANE_OR_PHENOMENON); + float croppedArea = isModernFrame(card) ? CROP_MULTIPLIER : 0.97f; + float minusxy = isModernFrame(card) ? 0.0f : 0.13f*radius; + if (card.getCurrentState().getSetCode().equals("LEA")||card.getCurrentState().getSetCode().equals("LEB")) { + croppedArea = 0.975f; + minusxy = 0.135f*radius; + } if (rotatePlane && (card.getCurrentState().isPhenomenon() || card.getCurrentState().isPlane())) { if (Forge.enableUIMask){ if (ImageCache.isExtendedArt(card)) g.drawRotatedImage(image, new_x, new_y, new_w, new_h, new_x + new_w / 2, new_y + new_h / 2, -90); else { g.drawRotatedImage(FSkin.getBorders().get(0), new_x, new_y, new_w, new_h, new_x + new_w / 2, new_y + new_h / 2, -90); - g.drawRotatedImage(ImageCache.croppedBorderImage(image, fullborder), new_x+radius/2, new_y+radius/2, new_w*0.96f, new_h*0.96f, (new_x+radius/2) + (new_w*0.96f) / 2, (new_y+radius/2) + (new_h*0.96f) / 2, -90); + g.drawRotatedImage(ImageCache.croppedBorderImage(image, fullborder), new_x+radius/2-minusxy, new_y+radius/2-minusxy, new_w*croppedArea, new_h*croppedArea, (new_x+radius/2-minusxy) + (new_w*croppedArea) / 2, (new_y+radius/2-minusxy) + (new_h*croppedArea) / 2, -90); } } else g.drawRotatedImage(image, new_x, new_y, new_w, new_h, new_x + new_w / 2, new_y + new_h / 2, -90); @@ -374,7 +383,7 @@ public class CardImageRenderer { g.drawRotatedImage(image, new_x, new_y, new_w, new_h, new_x + new_w / 2, new_y + new_h / 2, isAftermath ? 90 : -90); else { g.drawRotatedImage(FSkin.getBorders().get(ImageCache.getFSkinBorders(card)), new_x, new_y, new_w, new_h, new_x + new_w / 2, new_y + new_h / 2, isAftermath ? 90 : -90); - g.drawRotatedImage(ImageCache.croppedBorderImage(image, fullborder), new_x + radius / 2, new_y + radius / 2, new_w * 0.96f, new_h * 0.96f, (new_x + radius / 2) + (new_w * 0.96f) / 2, (new_y + radius / 2) + (new_h * 0.96f) / 2, isAftermath ? 90 : -90); + g.drawRotatedImage(ImageCache.croppedBorderImage(image, fullborder), new_x + radius / 2-minusxy, new_y + radius / 2-minusxy, new_w * croppedArea, new_h * croppedArea, (new_x + radius / 2-minusxy) + (new_w * croppedArea) / 2, (new_y + radius / 2-minusxy) + (new_h * croppedArea) / 2, isAftermath ? 90 : -90); } } else g.drawRotatedImage(image, new_x, new_y, new_w, new_h, new_x + new_w / 2, new_y + new_h / 2, isAftermath ? 90 : -90); @@ -384,7 +393,7 @@ public class CardImageRenderer { g.drawImage(image, x, y, w, h); else { g.drawImage(ImageCache.getBorderImage(card, canshow), x, y, w, h); - g.drawImage(ImageCache.croppedBorderImage(image, fullborder), x + radius / 2.4f, y + radius / 2, w * 0.96f, h * 0.96f); + g.drawImage(ImageCache.croppedBorderImage(image, fullborder), x + radius / 2.4f-minusxy, y + radius / 2-minusxy, w * croppedArea, h * croppedArea); } } else { if (canshow && !ImageKeys.getTokenKey(ImageKeys.MORPH_IMAGE).equals(card.getState(altState).getImageKey())) diff --git a/forge-gui-mobile/src/forge/card/CardRenderer.java b/forge-gui-mobile/src/forge/card/CardRenderer.java index 6dee11cb8cc..6dcc0c48681 100644 --- a/forge-gui-mobile/src/forge/card/CardRenderer.java +++ b/forge-gui-mobile/src/forge/card/CardRenderer.java @@ -105,6 +105,7 @@ public class CardRenderer { private static final float NAME_COST_THRESHOLD = Utils.scale(200); private static final float BORDER_THICKNESS = Utils.scale(1); public static final float PADDING_MULTIPLIER = 0.021f; + public static final float CROP_MULTIPLIER = 0.96f; private static Map counterFonts = new HashMap<>(); private static final Color counterBackgroundColor = new Color(0f, 0f, 0f, 0.9f); @@ -142,6 +143,49 @@ public class CardRenderer { } } + public static boolean isModernFrame(IPaperCard c) { + if (c == null) + return false; + + CardEdition ed = FModel.getMagicDb().getEditions().get(c.getEdition()); + if (ed != null) { + switch (ed.getCode()) { + case "MED": + case "ME2": + case "ME3": + case "ME4": + case "TSB": + return false; + default: + return ed.isModern(); + } + } + + return false; + } + + public static boolean isModernFrame(CardView c) { + if (c == null) + return false; + + CardView.CardStateView state = c.getCurrentState(); + CardEdition ed = FModel.getMagicDb().getEditions().get(state.getSetCode()); + if (ed != null) { + switch (ed.getCode()) { + case "MED": + case "ME2": + case "ME3": + case "ME4": + case "TSB": + return false; + default: + return ed.isModern(); + } + } + + return false; + } + public static float getCardListItemHeight(boolean compactMode) { if (compactMode) { return MANA_SYMBOL_SIZE + 2 * FList.PADDING; @@ -402,7 +446,12 @@ public class CardRenderer { public static void drawCard(Graphics g, IPaperCard pc, float x, float y, float w, float h, CardStackPosition pos) { Texture image = new RendererCachedCardImage(pc, false).getImage(); float radius = (h - w)/8; - + float croppedArea = isModernFrame(pc) ? CROP_MULTIPLIER : 0.97f; + float minusxy = isModernFrame(pc) ? 0.0f : 0.13f*radius; + if (pc.getEdition().equals("LEA")||pc.getEdition().equals("LEB")) { + croppedArea = 0.975f; + minusxy = 0.135f*radius; + } if (image != null) { if (image == ImageCache.defaultImage) { CardImageRenderer.drawCardImage(g, CardView.getCardForUi(pc), false, x, y, w, h, pos); @@ -413,7 +462,7 @@ public class CardRenderer { g.drawImage(image, x, y, w, h); else { g.drawImage(ImageCache.getBorderImage(pc), x, y, w, h); - g.drawImage(ImageCache.croppedBorderImage(image, fullborder), x + radius / 2.4f, y + radius / 2, w * 0.96f, h * 0.96f); + g.drawImage(ImageCache.croppedBorderImage(image, fullborder), x + radius / 2.4f-minusxy, y + radius / 2-minusxy, w * croppedArea, h * croppedArea); } } else g.drawImage(image, x, y, w, h); @@ -437,7 +486,12 @@ public class CardRenderer { Texture image = new RendererCachedCardImage(card, false).getImage(); FImage sleeves = MatchController.getPlayerSleeve(card.getOwner()); float radius = (h - w)/8; - + float croppedArea = isModernFrame(card) ? CROP_MULTIPLIER : 0.97f; + float minusxy = isModernFrame(card) ? 0.0f : 0.13f*radius; + if (card.getCurrentState().getSetCode().equals("LEA")||card.getCurrentState().getSetCode().equals("LEB")) { + croppedArea = 0.975f; + minusxy = 0.135f*radius; + } if (image != null) { if (image == ImageCache.defaultImage) { CardImageRenderer.drawCardImage(g, card, false, x, y, w, h, pos); @@ -450,7 +504,7 @@ public class CardRenderer { g.drawRotatedImage(image, x, y, w, h, x + w / 2, y + h / 2, -90); else { g.drawRotatedImage(FSkin.getBorders().get(0), x, y, w, h, x + w / 2, y + h / 2, -90); - g.drawRotatedImage(ImageCache.croppedBorderImage(image, fullborder), x+radius/2.3f, y+radius/2, w*0.96f, h*0.96f, (x+radius/2.3f) + (w*0.96f) / 2, (y+radius/2) + (h*0.96f) / 2, -90); + g.drawRotatedImage(ImageCache.croppedBorderImage(image, fullborder), x+radius/2.3f-minusxy, y+radius/2-minusxy, w*croppedArea, h*croppedArea, (x+radius/2.3f-minusxy) + (w*croppedArea) / 2, (y+radius/2-minusxy) + (h*croppedArea) / 2, -90); } } else g.drawRotatedImage(image, x, y, w, h, x + w / 2, y + h / 2, -90); @@ -461,7 +515,7 @@ public class CardRenderer { else { boolean t = (card.getCurrentState().getOriginalColors() != card.getCurrentState().getColors()) || card.getCurrentState().hasChangeColors(); g.drawBorderImage(ImageCache.getBorderImage(card, canshow), ImageCache.getTint(card), x, y, w, h, t); //tint check for changed colors - g.drawImage(ImageCache.croppedBorderImage(image, fullborder), x + radius / 2.4f, y + radius / 2, w * 0.96f, h * 0.96f); + g.drawImage(ImageCache.croppedBorderImage(image, fullborder), x + radius / 2.4f-minusxy, y + radius / 2-minusxy, w * croppedArea, h * croppedArea); } } else { if (canshow) @@ -1119,8 +1173,14 @@ public class CardRenderer { public static void drawFoilEffect(Graphics g, CardView card, float x, float y, float w, float h, boolean inZoomer) { float new_x = x; float new_y = y; float new_w = w; float new_h = h; float radius = (h - w)/8; + float croppedArea = isModernFrame(card) ? CROP_MULTIPLIER : 0.97f; + float minusxy = isModernFrame(card) ? 0.0f : 0.13f*radius; + if (card.getCurrentState().getSetCode().equals("LEA")||card.getCurrentState().getSetCode().equals("LEB")) { + croppedArea = 0.975f; + minusxy = 0.135f*radius; + } if (Forge.enableUIMask) { - new_x += radius/2.4f; new_y += radius/2; new_w = w * 0.96f; new_h = h * 0.96f; + new_x += radius/2.4f-minusxy; new_y += radius/2-minusxy; new_w = w * croppedArea; new_h = h * croppedArea; } if (isPreferenceEnabled(FPref.UI_OVERLAY_FOIL_EFFECT) && MatchController.instance.mayView(card)) { boolean rotateSplit = isPreferenceEnabled(FPref.UI_ROTATE_SPLIT_CARDS) && card.isSplitCard() && inZoomer; diff --git a/forge-gui-mobile/src/forge/card/CardZoom.java b/forge-gui-mobile/src/forge/card/CardZoom.java index 8608cbbc82f..72d7d7c978f 100644 --- a/forge-gui-mobile/src/forge/card/CardZoom.java +++ b/forge-gui-mobile/src/forge/card/CardZoom.java @@ -219,7 +219,22 @@ public class CardZoom extends FOverlay { float w = getWidth(); float h = getHeight(); float messageHeight = FDialog.MSG_HEIGHT; - float maxCardHeight = h - 2 * messageHeight; + float AspectRatioMultiplier = 2; + switch (Forge.extrawide) { + case "default": + AspectRatioMultiplier = 3; //good for tablets with 16:10 or similar + break; + case "wide": + AspectRatioMultiplier = 2.5f; + break; + case "extrawide": + AspectRatioMultiplier = 2; //good for tall phones with 21:9 or similar + break; + default: + AspectRatioMultiplier = 3; + break; + } + float maxCardHeight = h - AspectRatioMultiplier * messageHeight; //maxheight of currently zoomed card float cardWidth, cardHeight, y; diff --git a/forge-gui-mobile/src/forge/deck/FDeckEditor.java b/forge-gui-mobile/src/forge/deck/FDeckEditor.java index 2f74d266722..ac553fb40ab 100644 --- a/forge-gui-mobile/src/forge/deck/FDeckEditor.java +++ b/forge-gui-mobile/src/forge/deck/FDeckEditor.java @@ -856,6 +856,9 @@ public class FDeckEditor extends TabPageScreen { case Brawl: isLegalCommander = card.getRules().canBeBrawlCommander(); break; + case TinyLeaders: + isLegalCommander = card.getRules().canBeTinyLeadersCommander(); + break; case Oathbreaker: isLegalCommander = card.getRules().canBeOathbreaker(); captionSuffix = localizer.getMessage("lblOathbreaker"); diff --git a/forge-gui/res/blockdata/blocks.txt b/forge-gui/res/blockdata/blocks.txt index 5d062be3068..67d158df0af 100644 --- a/forge-gui/res/blockdata/blocks.txt +++ b/forge-gui/res/blockdata/blocks.txt @@ -84,4 +84,4 @@ Modern Horizons, 3/6/WAR, MH1 Core Set 2020, 3/6/M20, M20 Throne of Eldraine, 3/6/ELD, ELD Theros Beyond Death, 3/6/THB, THB -Mystery Booster, 3/6/MB1, MB1 \ No newline at end of file +Mystery Booster, 3/6/THB, MB1 \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/c/cavalier_of_dawn.txt b/forge-gui/res/cardsfolder/c/cavalier_of_dawn.txt index 513fd7f037c..601d7d96ee4 100644 --- a/forge-gui/res/cardsfolder/c/cavalier_of_dawn.txt +++ b/forge-gui/res/cardsfolder/c/cavalier_of_dawn.txt @@ -4,8 +4,9 @@ Types:Creature Elemental Knight PT:4/6 K:Vigilance T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDestroy | TriggerDescription$ When CARDNAME enters the battlefield, destroy up to one target nonland permanent. Its controller creates a 3/3 colorless Golem artifact creature token. -SVar:TrigDestroy:DB$ Destroy | TargetMin$ 0 | TargetMax$ 1 | ValidTgts$ Permanent.nonLand | TgtPrompt$ Select target nonland permanent | SubAbility$ DBToken -SVar:DBToken:DB$ Token | TokenAmount$ 1 | TokenScript$ c_3_3_a_golem | TokenOwner$ TargetedController | LegacyImage$ c 3 3 a golem m20 +SVar:TrigDestroy:DB$ Destroy | TargetMin$ 0 | TargetMax$ 1 | ValidTgts$ Permanent.nonLand | TgtPrompt$ Select target nonland permanent | RememberLKI$ True | SubAbility$ DBToken +SVar:DBToken:DB$ Token | TokenAmount$ 1 | TokenScript$ c_3_3_a_golem | TokenOwner$ RememberedController | LegacyImage$ c 3 3 a golem m20 | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigChangeZone | TriggerController$ TriggeredCardController | TriggerDescription$ When CARDNAME dies, return target artifact or enchantment card from your graveyard to your hand. SVar:TrigChangeZone:DB$ ChangeZone | Origin$ Graveyard | Destination$ Hand | ValidTgts$ Artifact.YouCtrl,Enchantment.YouCtrl Oracle:Vigilance\nWhen Cavalier of Dawn enters the battlefield, destroy up to one target nonland permanent. Its controller creates a 3/3 colorless Golem artifact creature token.\nWhen Cavalier of Dawn dies, return target artifact or enchantment card from your graveyard to your hand. diff --git a/forge-gui/res/cardsfolder/g/ghastly_demise.txt b/forge-gui/res/cardsfolder/g/ghastly_demise.txt index 78ced7746b3..f04b0f2ac15 100644 --- a/forge-gui/res/cardsfolder/g/ghastly_demise.txt +++ b/forge-gui/res/cardsfolder/g/ghastly_demise.txt @@ -1,7 +1,7 @@ Name:Ghastly Demise ManaCost:B Types:Instant -A:SP$ Destroy | Cost$ B | ValidTgts$ Creature.nonBlack+toughnessLEX | TgtPrompt$ Select target nonblack creature with toughness less than or equal to the number of cards in your graveyard. | References$ X | SpellDescription$ Destroy target nonblack creature if its toughness is less than or equal to the number of cards in your graveyard. +A:SP$ Destroy | Cost$ B | ValidTgts$ Creature.nonBlack | TgtPrompt$ Select target nonblack creature | ConditionCheckSVar$ Y | ConditionSVarCompare$ LEX | References$ X,Y | StackDescription$ SpellDescription | SpellDescription$ Destroy target nonblack creature if its toughness is less than or equal to the number of cards in your graveyard. +SVar:Y:Targeted$CardToughness SVar:X:Count$InYourYard -SVar:Picture:http://www.wizards.com/global/images/magic/general/ghastly_demise.jpg Oracle:Destroy target nonblack creature if its toughness is less than or equal to the number of cards in your graveyard. diff --git a/forge-gui/res/cardsfolder/n/nissas_pilgrimage.txt b/forge-gui/res/cardsfolder/n/nissas_pilgrimage.txt index 3ad0c877919..f89143a06a0 100644 --- a/forge-gui/res/cardsfolder/n/nissas_pilgrimage.txt +++ b/forge-gui/res/cardsfolder/n/nissas_pilgrimage.txt @@ -1,9 +1,10 @@ Name:Nissa's Pilgrimage ManaCost:2 G Types:Sorcery -A:SP$ ChangeZone | Cost$ 2 G | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic+Forest | ChangeNum$ 1 | SubAbility$ DBChangeZone1 | NoShuffle$ True | SpellDescription$ Search your library for up to two basic Forest cards, reveal those cards, and put one onto the battlefield tapped and the rest into your hand. Then shuffle your library. Spell mastery — If there are two or more instant or sorcery cards in your graveyard, search your library for up to three basic Forest cards instead of two. -SVar:DBChangeZone1:DB$ChangeZone | Origin$ Library | Destination$ Hand | SubAbility$ DBChangeZone2 | ChangeType$ Land.Basic+Forest | ChangeNum$ 1 | ConditionCheckSVar$ X | ConditionSVarCompare$ LT2 | References$ X -SVar:DBChangeZone2:DB$ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic+Forest | ChangeNum$ 2 | ConditionCheckSVar$ X | ConditionSVarCompare$ GE2 | References$ X -SVar:X:Count$ValidGraveyard Instant.YouOwn,Sorcery.YouOwn -SVar:Picture:http://www.wizards.com/global/images/magic/general/nissas_pilgrimage.jpg +A:SP$ ChangeZone | Cost$ 2 G | Origin$ Library | Destination$ Library | ChangeType$ Land.Basic+Forest | ChangeNum$ X | References$ X,Y | RememberChanged$ True | SubAbility$ DBBattlefield | Shuffle$ False | StackDescription$ SpellDescription | SpellDescription$ Search your library for up to two basic Forest cards, reveal those cards, and put one onto the battlefield tapped and the rest into your hand. Then shuffle your library. Spell mastery — If there are two or more instant or sorcery cards in your graveyard, search your library for up to three basic Forest cards instead of two. +SVar:DBBattlefield:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | SubAbility$ DBHand | ChangeType$ Card.IsRemembered | ChangeNum$ 1 | Mandatory$ True | NoLooking$ True | SelectPrompt$ Select a card to go to the battlefield | Shuffle$ False | StackDescription$ None +SVar:DBHand:DB$ ChangeZone | Origin$ Library | Destination$ Hand | Defined$ Remembered | NoLooking$ True | StackDescription$ None | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +SVar:X:Count$Compare Y GE2.3.2 +SVar:Y:Count$ValidGraveyard Instant.YouOwn,Sorcery.YouOwn Oracle:Search your library for up to two basic Forest cards, reveal those cards, and put one onto the battlefield tapped and the rest into your hand. Then shuffle your library.\nSpell mastery — If there are two or more instant or sorcery cards in your graveyard, search your library for up to three basic Forest cards instead of two. diff --git a/forge-gui/res/cardsfolder/s/settle_the_wreckage.txt b/forge-gui/res/cardsfolder/s/settle_the_wreckage.txt index 521f24cb11c..c136b03296f 100644 --- a/forge-gui/res/cardsfolder/s/settle_the_wreckage.txt +++ b/forge-gui/res/cardsfolder/s/settle_the_wreckage.txt @@ -2,7 +2,7 @@ Name:Settle the Wreckage ManaCost:2 W W Types:Instant A:SP$ ChangeZoneAll | Cost$ 2 W W | ValidTgts$ Player | ChangeType$ Creature.attacking | TgtPrompt$ Select target player | Origin$ Battlefield | Destination$ Exile | RememberChanged$ True | SubAbility$ DBGetLands | SpellDescription$ Exile all attacking creatures target player controls. That player may search their library for that many basic lands, put those cards onto the battlefield tapped, then shuffle their library. -SVar:DBGetLands:DB$ ChangeZone | Optional$ True | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ X | References$ X | DefinedPlayer$ RememberedController | ShuffleNonMandatory$ True | SubAbility$ DBCleanup +SVar:DBGetLands:DB$ ChangeZone | Optional$ True | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ X | References$ X | DefinedPlayer$ TargetedPlayer | ShuffleNonMandatory$ True | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:X:Count$RememberedSize SVar:Picture:http://www.wizards.com/global/images/magic/general/settle_the_wreckage.jpg diff --git a/forge-gui/res/cardsfolder/t/the_birth_of_meletis.txt b/forge-gui/res/cardsfolder/t/the_birth_of_meletis.txt index 349f1e790d9..27021aac864 100644 --- a/forge-gui/res/cardsfolder/t/the_birth_of_meletis.txt +++ b/forge-gui/res/cardsfolder/t/the_birth_of_meletis.txt @@ -3,7 +3,7 @@ ManaCost:1 W Types:Enchantment Saga K:Saga:3:TrigChange,TrigToken,TrigGainLife SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Plains+Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic Plains card, reveal it, put it into your hand, then shuffle your library. -SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ c_0_4_wall_defender | TokenOwner$ You | LegacyImage$ c 0 4 wall defender thb | SpellDescription$ Create a 0/4 colorless Wall artifact creature token with defender. +SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ c_0_4_a_wall_defender | TokenOwner$ You | LegacyImage$ c 0 4 wall defender thb | SpellDescription$ Create a 0/4 colorless Wall artifact creature token with defender. SVar:TrigGainLife:DB$ GainLife | Defined$ You | LifeAmount$ 2 | SpellDescription$ You gain 2 life. DeckHas:Ability$LifeGain & Ability$Token Oracle:(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)\nI - Search your library for a basic Plains card, reveal it, put it into your hand, then shuffle your library.\nII - Create a 0/4 colorless Wall artifact creature token with defender.\nIII - You gain 2 life. diff --git a/forge-gui/res/cardsfolder/t/the_triumph_of_anax.txt b/forge-gui/res/cardsfolder/t/the_triumph_of_anax.txt index a7aacb3fd3c..6a6b9d25af5 100755 --- a/forge-gui/res/cardsfolder/t/the_triumph_of_anax.txt +++ b/forge-gui/res/cardsfolder/t/the_triumph_of_anax.txt @@ -1,7 +1,7 @@ Name:The Triumph of Anax ManaCost:2 R Types:Enchantment Saga -K:Saga:3:DBPump,DBPump,DBPump,DBPick +K:Saga:4:DBPump,DBPump,DBPump,DBPick SVar:DBPump:DB$ Pump | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumAtt$ +X | References$ X | KW$ Trample | SpellDescription$ Until end of turn, target creature gains trample and gets +X/+0, where X is the number of lore counters on CARDNAME. SVar:X:Count$CardCounters.LORE SVar:PlayMain1:TRUE diff --git a/forge-gui/res/editions/Theros Beyond Death.txt b/forge-gui/res/editions/Theros Beyond Death.txt index b6d93e220f7..6d8b9f91a2d 100644 --- a/forge-gui/res/editions/Theros Beyond Death.txt +++ b/forge-gui/res/editions/Theros Beyond Death.txt @@ -377,7 +377,7 @@ Prerelease=6 Boosters, 1 RareMythic+ [tokens] b_2_2_zombie -c_0_4_wall_defender +c_0_4_a_wall_defender g_1_2_spider_reach g_2_2_wolf r_x_1_elemental_trample_haste diff --git a/forge-gui/res/formats/Casual/Brawl.txt b/forge-gui/res/formats/Casual/Brawl.txt index 804db4799d5..63db92dec93 100644 --- a/forge-gui/res/formats/Casual/Brawl.txt +++ b/forge-gui/res/formats/Casual/Brawl.txt @@ -4,4 +4,4 @@ Order:101 Type:Casual Subtype:Commander Sets:GRN, RNA, WAR, M20, ELD, THB -Banned:Sorcerous Spyglass;Oko, Thief of Crowns +Banned:Golos, Tireless Pilgrim; Oko, Thief of Crowns; Sorcerous Spyglass diff --git a/forge-gui/res/formats/Sanctioned/Legacy.txt b/forge-gui/res/formats/Sanctioned/Legacy.txt index 404a575f8cd..5d6162ad978 100644 --- a/forge-gui/res/formats/Sanctioned/Legacy.txt +++ b/forge-gui/res/formats/Sanctioned/Legacy.txt @@ -3,4 +3,4 @@ Name:Legacy Order:105 Subtype:Legacy Type:Sanctioned -Banned:Adriana's Valor; Advantageous Proclamation; Assemble the Rank and Vile; Backup Plan; Brago's Favor; Deathrite Shaman; Double Stroke; Echoing Boon; Emissary's Ploy; Gitaxian Probe; Hired Heist; Hold the Perimeter; Hymn of the Wilds; Immediate Action; Incendiary Dissent; Iterative Analysis; Muzzio's Preparations; Natural Unity; Power Play; Secret Summoning; Secrets of Paradise; Sentinel Dispatch; Sovereign's Realm; Summoner's Bond; Unexpected Potential; Weight Advantage; Worldknit; Amulet of Quoz; Bronze Tablet; Contract from Below; Darkpact; Demonic Attorney; Jeweled Bird; Rebirth; Tempest Efreet; Timmerian Fiends; Ancestral Recall; Balance; Bazaar of Baghdad; Black Lotus; Channel; Chaos Orb; Demonic Consultation; Demonic Tutor; Dig Through Time; Earthcraft; Falling Star; Fastbond; Flash; Frantic Search; Goblin Recruiter; Gush; Hermit Druid; Imperial Seal; Library of Alexandria; Mana Crypt; Mana Drain; Mana Vault; Memory Jar; Mental Misstep; Mind Twist; Mind's Desire; Mishra's Workshop; Mox Emerald; Mox Jet; Mox Pearl; Mox Ruby; Mox Sapphire; Mystical Tutor; Necropotence; Oath of Druids; Sensei's Divining Top; Shahrazad; Skullclamp; Sol Ring; Strip Mine; Survival of the Fittest; Time Vault; Time Walk; Timetwister; Tinker; Tolarian Academy; Treasure Cruise; Vampiric Tutor; Wheel of Fortune; Windfall; Wrenn and Six; Yawgmoth's Bargain; Yawgmoth's Will +Banned:Adriana's Valor; Advantageous Proclamation; Assemble the Rank and Vile; Backup Plan; Brago's Favor; Deathrite Shaman; Double Stroke; Echoing Boon; Emissary's Ploy; Gitaxian Probe; Hired Heist; Hold the Perimeter; Hymn of the Wilds; Immediate Action; Incendiary Dissent; Iterative Analysis; Muzzio's Preparations; Natural Unity; Power Play; Secret Summoning; Secrets of Paradise; Sentinel Dispatch; Sovereign's Realm; Summoner's Bond; Underworld Breach; Unexpected Potential; Weight Advantage; Worldknit; Amulet of Quoz; Bronze Tablet; Contract from Below; Darkpact; Demonic Attorney; Jeweled Bird; Rebirth; Tempest Efreet; Timmerian Fiends; Ancestral Recall; Balance; Bazaar of Baghdad; Black Lotus; Channel; Chaos Orb; Demonic Consultation; Demonic Tutor; Dig Through Time; Earthcraft; Falling Star; Fastbond; Flash; Frantic Search; Goblin Recruiter; Gush; Hermit Druid; Imperial Seal; Library of Alexandria; Mana Crypt; Mana Drain; Mana Vault; Memory Jar; Mental Misstep; Mind Twist; Mind's Desire; Mishra's Workshop; Mox Emerald; Mox Jet; Mox Pearl; Mox Ruby; Mox Sapphire; Mystical Tutor; Necropotence; Oath of Druids; Sensei's Divining Top; Shahrazad; Skullclamp; Sol Ring; Strip Mine; Survival of the Fittest; Time Vault; Time Walk; Timetwister; Tinker; Tolarian Academy; Treasure Cruise; Vampiric Tutor; Wheel of Fortune; Windfall; Wrenn and Six; Yawgmoth's Bargain; Yawgmoth's Will diff --git a/forge-gui/res/formats/Sanctioned/Modern.txt b/forge-gui/res/formats/Sanctioned/Modern.txt index 5f00bc45197..78faab16590 100644 --- a/forge-gui/res/formats/Sanctioned/Modern.txt +++ b/forge-gui/res/formats/Sanctioned/Modern.txt @@ -4,4 +4,4 @@ Order:103 Subtype:Modern Type:Sanctioned Sets:8ED, MRD, DST, 5DN, CHK, BOK, SOK, 9ED, RAV, GPT, DIS, CSP, TSP, TSB, PLC, FUT, 10E, LRW, EVE, SHM, MOR, ALA, CFX, ARB, M10, ZEN, WWK, ROE, M11, SOM, MBS, NPH, M12, ISD, DKA, AVR, M13, RTR, GTC, DGM, M14, THS, BNG, JOU, M15, KTK, FRF, DTK, MM2, ORI, BFZ, OGW, SOI, EMN, KLD, AER, AKH, W17, HOU, XLN, RIX, DOM, M19, G18, GRN, RNA, WAR, MH1, M20, ELD, THB -Banned:Ancient Den; Birthing Pod; Blazing Shoal; Bridge from Below; Chrome Mox; Cloudpost; Dark Depths; Deathrite Shaman; Dig Through Time; Dread Return; Eye of Ugin; Faithless Looting; Gitaxian Probe; Glimpse of Nature; Golgari Grave-Troll; Great Furnace; Green Sun's Zenith; Hogaak, Arisen Necropolis; Hypergenesis; Krark-Clan Ironworks; Mental Misstep; Mox Opal; Mycosynth Lattice; Oko, Thief of Crowns; Ponder; Preordain; Punishing Fire; Rite of Flame; Seat of the Synod; Second Sunrise; Seething Song; Sensei's Divining Top; Skullclamp; Splinter Twin; Summer Bloom; Treasure Cruise; Tree of Tales; Umezawa's Jitte; Vault of Whispers +Banned:Ancient Den; Birthing Pod; Blazing Shoal; Bridge from Below; Chrome Mox; Cloudpost; Dark Depths; Deathrite Shaman; Dig Through Time; Dread Return; Eye of Ugin; Faithless Looting; Gitaxian Probe; Glimpse of Nature; Golgari Grave-Troll; Great Furnace; Green Sun's Zenith; Hogaak, Arisen Necropolis; Hypergenesis; Krark-Clan Ironworks; Mental Misstep; Mox Opal; Mycosynth Lattice; Oko, Thief of Crowns; Once Upon A Time; Ponder; Preordain; Punishing Fire; Rite of Flame; Seat of the Synod; Second Sunrise; Seething Song; Sensei's Divining Top; Skullclamp; Splinter Twin; Summer Bloom; Treasure Cruise; Tree of Tales; Umezawa's Jitte; Vault of Whispers diff --git a/forge-gui/res/languages/es-ES.properties b/forge-gui/res/languages/es-ES.properties index 3a7d02bfcba..d332a649577 100644 --- a/forge-gui/res/languages/es-ES.properties +++ b/forge-gui/res/languages/es-ES.properties @@ -52,8 +52,8 @@ btnResetJavaFutureCompatibilityWarnings=Restablecer las advertencias de compatib btnClearImageCache=Limpiar Caché de Imágenes btnTokenPreviewer=Previsualizador de Fichas (Token) btnCopyToClipboard=Copiar al portapapeles -cbpAutoUpdater=Auto updater -nlAutoUpdater=Select the release channel to use for updating Forge +cbpAutoUpdater=Actualizar Forge +nlAutoUpdater=Selecciona la versión a utilizar para actualizar Forge cbpSelectLanguage=Idioma nlSelectLanguage=Seleccionar idioma (excepto partida). Todavía un trabajo en progreso) (Es necesario reiniciar Forge) cbRemoveSmall=Eliminar Pequeñas Criaturas @@ -184,7 +184,7 @@ KeyboardShortcuts=Atajos de teclado #VSubmenuAchievements.java lblAchievements=Logros #VSubmenuDownloaders.java -btnCheckForUpdates=Check for Updates +btnCheckForUpdates=Comprobar Actualizaciones btnDownloadSetPics=Descargar todas las Ediciones de Cartas btnDownloadPics=Descargar todas las Cartas btnDownloadPicsHQ=Descargar todas las Cartas en calidad alta (Muy lento!) @@ -197,7 +197,7 @@ btnImportPictures=Importar Datos btnHowToPlay=Cómo jugar (Inglés) btnDownloadPrices=Descargar los precios de las cartas btnLicensing=Detalles de la licencia -lblCheckForUpdates=Check Forge server to see if there''s a more recent release +lblCheckForUpdates=Comprueba si en el servidor de Forge existe alguna versión más reciente lblDownloadPics=Descargar la imagen de la carta por defecto para cada carta. lblDownloadPicsHQ=Descargar la imagen en calidad alta de la carta por defecto para cada carta. lblDownloadSetPics=Descargue todas las imágenes de cada carta (una por cada edición donde apareció la carta) diff --git a/forge-gui/res/languages/it-IT.properties b/forge-gui/res/languages/it-IT.properties index 7af4f72f02e..dd08f210f08 100644 --- a/forge-gui/res/languages/it-IT.properties +++ b/forge-gui/res/languages/it-IT.properties @@ -52,7 +52,7 @@ btnResetJavaFutureCompatibilityWarnings=Ripristina avvisi di compatibilità Java btnClearImageCache=Cancella cache immagini btnTokenPreviewer=Anteprima token btnCopyToClipboard=Copia negli appunti -cbpAutoUpdater=Auto updater +cbpAutoUpdater=Auto updater nlAutoUpdater=Select the release channel to use for updating Forge cbpSelectLanguage=Lingua nlSelectLanguage=Seleziona la lingua (parte di gioco esclusa. Ancora in fase di sviluppo) (RIAVVIO NECESSARIO) diff --git a/forge-gui/res/languages/zh-CN.properties b/forge-gui/res/languages/zh-CN.properties index 1f8626e2456..a352fb78599 100644 --- a/forge-gui/res/languages/zh-CN.properties +++ b/forge-gui/res/languages/zh-CN.properties @@ -52,6 +52,8 @@ btnResetJavaFutureCompatibilityWarnings=重置Java兼容性警告 btnClearImageCache=清除图片缓存 btnTokenPreviewer=衍生物预览器 btnCopyToClipboard=复制到剪切板 +cbpAutoUpdater=Auto updater +nlAutoUpdater=Select the release channel to use for updating Forge cbpSelectLanguage=语言 cbpAutoUpdater=自动更新 nlAutoUpdater=选择用于更新Forge的发布渠道 @@ -197,7 +199,7 @@ btnImportPictures=导入数据 btnHowToPlay=如何玩 btnDownloadPrices=下载卡牌价格 btnLicensing=许可证详情 -lblCheckForUpdates=检查Forge服务器,查看是否有新的版本 +lblCheckForUpdates=查看是否有新的版本 lblDownloadPics=下载缺省牌的图片 lblDownloadPicsHQ=下载缺省牌的高清图片 lblDownloadSetPics=下载每张牌的图片(每张牌出现一次) diff --git a/forge-gui/res/puzzle/PS_THB7.pzl b/forge-gui/res/puzzle/PS_THB7.pzl index 0f8ed430eb9..bd9f0a62678 100644 --- a/forge-gui/res/puzzle/PS_THB7.pzl +++ b/forge-gui/res/puzzle/PS_THB7.pzl @@ -12,6 +12,5 @@ turn=1 activeplayer=ai activephase=MAIN1 humanhand=Lazotep Plating;Slaying Fire;So Tiny;Shock;Gideon's Triumph;Aspect of Manticore -humanbattlefield=The Akroan War|Counters:LORE=2|Id:2;Blood Aspirant;Flux Channeler;Naiad of Hidden Coves;Temple of Enlightenment|NoETBTrigs;Temple of Enlightenment|NoETBTrigs;Sacred Foundry|NoETBTrigs;Sacred Foundry|NoETBTrigs +humanbattlefield=The Akroan War|Counters:LORE=2|ExecuteScript:DBGainControl->1;Blood Aspirant;Flux Channeler;Naiad of Hidden Coves;Temple of Enlightenment|NoETBTrigs;Temple of Enlightenment|NoETBTrigs;Sacred Foundry|NoETBTrigs;Sacred Foundry|NoETBTrigs aibattlefield=Underworld Dreams;Underworld Dreams;Underworld Dreams;Ferocity of the Wilds;Goblin Assault Team;Temple Thief;Mire Triton;Dreadhorde Butcher|Id:1 -humanprecast=2:DBGainControl->1 diff --git a/forge-gui/res/skins/default/font1.ttf b/forge-gui/res/skins/default/font1.ttf index 4b4ecc66671..8723f9e2252 100644 Binary files a/forge-gui/res/skins/default/font1.ttf and b/forge-gui/res/skins/default/font1.ttf differ diff --git a/forge-gui/res/skins/default/sprite_border.png b/forge-gui/res/skins/default/sprite_border.png index c6be80f099a..4fe259b55c7 100644 Binary files a/forge-gui/res/skins/default/sprite_border.png and b/forge-gui/res/skins/default/sprite_border.png differ diff --git a/forge-gui/res/tokenscripts/c_0_4_wall_defender.txt b/forge-gui/res/tokenscripts/c_0_4_a_wall_defender.txt similarity index 67% rename from forge-gui/res/tokenscripts/c_0_4_wall_defender.txt rename to forge-gui/res/tokenscripts/c_0_4_a_wall_defender.txt index 03a29952252..2b5be99e3e2 100644 --- a/forge-gui/res/tokenscripts/c_0_4_wall_defender.txt +++ b/forge-gui/res/tokenscripts/c_0_4_a_wall_defender.txt @@ -1,6 +1,6 @@ Name:Wall ManaCost:no cost -Types:Creature Wall +Types:Artifact Creature Wall PT:0/4 K:Defender Oracle:Defender diff --git a/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java b/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java index a337db37099..36f124105a3 100644 --- a/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java +++ b/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java @@ -105,10 +105,7 @@ public class HumanPlaySpellAbility { if (ability.isSpell() && !ability.isCastFaceDown() && fromState == CardStateName.FaceDown) { c.turnFaceUp(); } - c.setCastSA(ability); - ability.setLastStateBattlefield(game.getLastStateBattlefield()); - ability.setLastStateGraveyard(game.getLastStateGraveyard()); - ability.setHostCard(game.getAction().moveToStack(c, null)); + ability.setHostCard(game.getAction().moveToStack(c, ability)); } if (!ability.isCopied()) { diff --git a/forge-gui/src/main/java/forge/util/ImageFetcher.java b/forge-gui/src/main/java/forge/util/ImageFetcher.java index 0ab7b0ed0e8..ef83a64f414 100644 --- a/forge-gui/src/main/java/forge/util/ImageFetcher.java +++ b/forge-gui/src/main/java/forge/util/ImageFetcher.java @@ -6,6 +6,8 @@ import java.util.HashMap; import java.util.HashSet; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import forge.FThreads; import forge.ImageKeys; @@ -50,7 +52,14 @@ public abstract class ImageFetcher { setDownload.append(ImageUtil.getDownloadUrl(paperCard, backFace)); downloadUrls.add(setDownload.toString()); - int artIndex = Integer.parseInt(imageKey.split("\\|")[2]); + int artIndex = 1; + final Pattern pattern = Pattern.compile( + "^.:([^|]*\\|){2}(\\d+).*$" + ); + Matcher matcher = pattern.matcher(imageKey); + if (matcher.matches()) { + artIndex = Integer.parseInt(matcher.group(2)); + } final StaticData data = StaticData.instance(); final String cardNum = data.getCommonCards().getCardCollectorNumber(paperCard.getName(), paperCard.getEdition(), artIndex); if (cardNum != null) {