diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java index f7e151ff2f3..7fd2d6b050a 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java @@ -233,14 +233,12 @@ public class ComputerUtilCost { } public static boolean checkForManaSacrificeCost(final Player ai, final Cost cost, final Card source, final SpellAbility sourceAbility) { - if (cost == null) { + // TODO cheating via autopay can still happen, need to get the real ai player from controlledBy + if (cost == null || !ai.isAI()) { return true; } for (final CostPart part : cost.getCostParts()) { if (part instanceof CostSacrifice) { - if (!ai.isAI()) { - return false; - } CardCollection list = new CardCollection(); final CardCollection exclude = new CardCollection(); if (AiCardMemory.getMemorySet(ai, MemorySet.PAYS_SAC_COST) != null) { diff --git a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java index 923d862b406..c3f032c24f6 100644 --- a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java +++ b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java @@ -1291,6 +1291,27 @@ public class PlayerControllerAi extends PlayerController { return SpellApiToAi.Converter.get(api).chooseCardName(player, sa, faces); } + @Override + public Card chooseDungeon(Player ai, List dungeonCards, String message) { + // TODO: improve the conditions that define which dungeon is a viable option to choose + List dungeonNames = Lists.newArrayList(); + for (PaperCard pc : dungeonCards) { + dungeonNames.add(pc.getName()); + } + + // Don't choose Tomb of Annihilation when life in danger unless we can win right away or can't lose for 0 life + if (ai.getController().isAI()) { // FIXME: is this needed? Can simulation ever run this for a non-AI player? + int lifeInDanger = (((PlayerControllerAi) ai.getController()).getAi().getIntProperty(AiProps.AI_IN_DANGER_THRESHOLD)); + if ((ai.getLife() <= lifeInDanger && !ai.cantLoseForZeroOrLessLife()) + && !(ai.getLife() > 1 && ai.getWeakestOpponent().getLife() == 1)) { + dungeonNames.remove("Tomb of Annihilation"); + } + } + + int i = MyRandom.getRandom().nextInt(dungeonNames.size()); + return Card.fromPaperCard(dungeonCards.get(i), ai); + } + @Override public List chooseCardsForSplice(SpellAbility sa, List cards) { // sort from best to worst 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 e836ad3fde2..f3e7c88642f 100644 --- a/forge-ai/src/main/java/forge/ai/ability/VentureAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/VentureAi.java @@ -2,10 +2,8 @@ package forge.ai.ability; import com.google.common.collect.Lists; import forge.ai.AiPlayDecision; -import forge.ai.AiProps; import forge.ai.PlayerControllerAi; import forge.ai.SpellAbilityAi; -import forge.card.ICardFace; import forge.game.player.Player; import forge.game.player.PlayerActionConfirmMode; import forge.game.spellability.SpellAbility; @@ -52,24 +50,4 @@ public class VentureAi extends SpellAbilityAi { return Aggregates.random(spells); // If we're here, we should choose at least something, so choose a random thing then } - // AI that chooses which dungeon to venture into - @Override - public String chooseCardName(Player ai, SpellAbility sa, List faces) { - // TODO: improve the conditions that define which dungeon is a viable option to choose - List dungeonNames = Lists.newArrayList(); - for (ICardFace face : faces) { - dungeonNames.add(face.getName()); - } - - // Don't choose Tomb of Annihilation when life in danger unless we can win right away or can't lose for 0 life - if (ai.getController().isAI()) { // FIXME: is this needed? Can simulation ever run this for a non-AI player? - int lifeInDanger = (((PlayerControllerAi) ai.getController()).getAi().getIntProperty(AiProps.AI_IN_DANGER_THRESHOLD)); - if ((ai.getLife() <= lifeInDanger && !ai.cantLoseForZeroOrLessLife()) - && !(ai.getLife() > 1 && ai.getWeakestOpponent().getLife() == 1)) { - dungeonNames.remove("Tomb of Annihilation"); - } - } - - return Aggregates.random(dungeonNames); - } } diff --git a/forge-core/src/main/java/forge/card/CardDb.java b/forge-core/src/main/java/forge/card/CardDb.java index 07b64e18ca4..c6fc9cf7b39 100644 --- a/forge-core/src/main/java/forge/card/CardDb.java +++ b/forge-core/src/main/java/forge/card/CardDb.java @@ -239,7 +239,8 @@ public final class CardDb implements ICardDatabase, IDeckGenPool { System.out.println("[LOG]: (Lazy) Loading Card: " + cardName); rulesByName.put(cardName, cr); boolean reIndexNecessary = false; - if ((setCode == null) || setCode.length() == 0 || setCode.equals(CardEdition.UNKNOWN.getCode())) { + CardEdition ed = editions.get(setCode); + if (ed == null || ed.equals(CardEdition.UNKNOWN)) { // look for all possible editions for (CardEdition e : editions) { List cardsInSet = e.getCardInSet(cardName); // empty collection if not present @@ -249,10 +250,9 @@ public final class CardDb implements ICardDatabase, IDeckGenPool { } } } else { - CardEdition e = editions.get(setCode); - List cardsInSet = e.getCardInSet(cardName); // empty collection if not present + List cardsInSet = ed.getCardInSet(cardName); // empty collection if not present for (CardInSet cis : cardsInSet) { - addSetCard(e, cis, cr); + addSetCard(ed, cis, cr); reIndexNecessary = true; } } diff --git a/forge-core/src/main/java/forge/card/CardEdition.java b/forge-core/src/main/java/forge/card/CardEdition.java index 9df2546479f..820c6865c30 100644 --- a/forge-core/src/main/java/forge/card/CardEdition.java +++ b/forge-core/src/main/java/forge/card/CardEdition.java @@ -199,29 +199,36 @@ public final class CardEdition implements Comparable { * @param collectorNumber: Input collectorNumber tro transform in a Sorting Key * @return A 5-digits zero-padded collector number + any non-numerical parts attached. */ + private static final Map sortableCollNumberLookup = new HashMap<>(); public static String getSortableCollectorNumber(final String collectorNumber){ - String sortableCollNr = collectorNumber; - if (sortableCollNr == null || sortableCollNr.length() == 0) - sortableCollNr = "50000"; // very big number of 5 digits to have them in last positions + String inputCollNumber = collectorNumber; + if (collectorNumber == null || collectorNumber.length() == 0) + inputCollNumber = "50000"; // very big number of 5 digits to have them in last positions + + String matchedCollNr = sortableCollNumberLookup.getOrDefault(inputCollNumber, null); + if (matchedCollNr != null) + return matchedCollNr; // Now, for proper sorting, let's zero-pad the collector number (if integer) int collNr; + String sortableCollNr; try { - collNr = Integer.parseInt(sortableCollNr); + collNr = Integer.parseInt(inputCollNumber); sortableCollNr = String.format("%05d", collNr); } catch (NumberFormatException ex) { - String nonNumeric = sortableCollNr.replaceAll("[0-9]", ""); - String onlyNumeric = sortableCollNr.replaceAll("[^0-9]", ""); + String nonNumSub = inputCollNumber.replaceAll("[0-9]", ""); + String onlyNumSub = inputCollNumber.replaceAll("[^0-9]", ""); try { - collNr = Integer.parseInt(onlyNumeric); + collNr = Integer.parseInt(onlyNumSub); } catch (NumberFormatException exon) { collNr = 0; // this is the case of ONLY-letters collector numbers } - if ((collNr > 0) && (sortableCollNr.startsWith(onlyNumeric))) // e.g. 12a, 37+, 2018f, - sortableCollNr = String.format("%05d", collNr) + nonNumeric; + if ((collNr > 0) && (inputCollNumber.startsWith(onlyNumSub))) // e.g. 12a, 37+, 2018f, + sortableCollNr = String.format("%05d", collNr) + nonNumSub; else // e.g. WS6, S1 - sortableCollNr = nonNumeric + String.format("%05d", collNr); + sortableCollNr = nonNumSub + String.format("%05d", collNr); } + sortableCollNumberLookup.put(inputCollNumber, sortableCollNr); return sortableCollNr; } diff --git a/forge-core/src/main/java/forge/deck/CardPool.java b/forge-core/src/main/java/forge/deck/CardPool.java index a47e4cc2498..adc51a3de62 100644 --- a/forge-core/src/main/java/forge/deck/CardPool.java +++ b/forge-core/src/main/java/forge/deck/CardPool.java @@ -463,8 +463,11 @@ public class CardPool extends ItemPool { */ public CardPool getFilteredPool(Predicate predicate) { CardPool filteredPool = new CardPool(); - for (PaperCard pc : this.items.keySet()) { - if (predicate.apply(pc)) filteredPool.add(pc); + Iterator cardsInPool = this.items.keySet().iterator(); + while (cardsInPool.hasNext()){ + PaperCard c = cardsInPool.next(); + if (predicate.apply(c)) + filteredPool.add(c, this.items.get(c)); } return filteredPool; } diff --git a/forge-core/src/main/java/forge/item/PaperCard.java b/forge-core/src/main/java/forge/item/PaperCard.java index 674db94fb7d..21bcae19280 100644 --- a/forge-core/src/main/java/forge/item/PaperCard.java +++ b/forge-core/src/main/java/forge/item/PaperCard.java @@ -51,7 +51,7 @@ public final class PaperCard implements Comparable, InventoryItemFro (see getCollectorNumber()) */ private String collectorNumber; - private final String artist; + private String artist; private final int artIndex; private final boolean foil; private Boolean hasImage; @@ -73,6 +73,8 @@ public final class PaperCard implements Comparable, InventoryItemFro @Override public String getCollectorNumber() { + if (collectorNumber == null) + collectorNumber = IPaperCard.NO_COLLECTOR_NUMBER; return collectorNumber; } @@ -103,6 +105,8 @@ public final class PaperCard implements Comparable, InventoryItemFro @Override public String getArtist() { + if (this.artist == null) + artist = IPaperCard.NO_ARTIST_NAME; return artist; } diff --git a/forge-core/src/main/java/forge/util/ItemPool.java b/forge-core/src/main/java/forge/util/ItemPool.java index 6cf22ec2162..63380622e57 100644 --- a/forge-core/src/main/java/forge/util/ItemPool.java +++ b/forge-core/src/main/java/forge/util/ItemPool.java @@ -30,7 +30,7 @@ import java.util.concurrent.ConcurrentHashMap; import com.google.common.base.Function; import com.google.common.base.Predicate; -import com.google.common.collect.Iterables; +import com.google.common.collect.Maps; import forge.item.InventoryItem; /** @@ -137,27 +137,26 @@ public class ItemPool implements Iterable condition) { + public int countAll(Predicate condition){ int count = 0; - for (Entry e : this) { - if (condition.apply(e.getKey())) { - count += e.getValue(); - } - } + for (Integer v : Maps.filterKeys(this.items, condition).values()) + count += v; return count; + } @SuppressWarnings("unchecked") public final int countAll(Predicate condition, Class cls) { int count = 0; - Iterable matchingKeys = Iterables.filter(this.items.keySet(), new Predicate() { + Map matchingKeys = Maps.filterKeys(this.items, new Predicate() { @Override public boolean apply(T item) { - return cls.isInstance(item) && condition.apply((U)item); + return cls.isInstance(item) && (condition.apply((U)item)); } }); - for (T key : matchingKeys) - count += this.items.get(key); + for (Integer i : matchingKeys.values()) { + count += i; + } return count; } diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChooseTypeEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChooseTypeEffect.java index 6a601d7257b..4aae63ff01e 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ChooseTypeEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ChooseTypeEffect.java @@ -11,6 +11,7 @@ import forge.game.card.Card; import forge.game.player.Player; import forge.game.spellability.SpellAbility; import forge.game.spellability.TargetRestrictions; +import forge.util.Aggregates; public class ChooseTypeEffect extends SpellAbilityEffect { @@ -63,8 +64,13 @@ public class ChooseTypeEffect extends SpellAbilityEffect { if (!validTypes.isEmpty()) { for (final Player p : tgtPlayers) { + String choice; if ((tgt == null) || p.canBeTargetedBy(sa)) { - String choice = p.getController().chooseSomeType(type, sa, validTypes, invalidTypes); + if (sa.hasParam("AtRandom")) { + choice = Aggregates.random(validTypes); + } else { + choice = p.getController().chooseSomeType(type, sa, validTypes, invalidTypes); + } if (!sa.hasParam("ChooseType2")) { card.setChosenType(choice); } else { diff --git a/forge-game/src/main/java/forge/game/ability/effects/VentureEffect.java b/forge-game/src/main/java/forge/game/ability/effects/VentureEffect.java index 6c17181028d..8c7cc036900 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/VentureEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/VentureEffect.java @@ -8,7 +8,6 @@ import com.google.common.base.Predicates; import forge.StaticData; import forge.card.CardRulesPredicates; -import forge.card.ICardFace; import forge.game.Game; import forge.game.ability.AbilityKey; import forge.game.ability.SpellAbilityEffect; @@ -45,13 +44,9 @@ public class VentureEffect extends SpellAbilityEffect { // Create a new dungeon card chosen by player in command zone. List dungeonCards = StaticData.instance().getVariantCards().getAllCards( Predicates.compose(CardRulesPredicates.Presets.IS_DUNGEON, PaperCard.FN_GET_RULES)); - List faces = new ArrayList<>(); - for (PaperCard pc : dungeonCards) { - faces.add(pc.getRules().getMainPart()); - } + String message = Localizer.getInstance().getMessage("lblChooseDungeon"); - String chosen = player.getController().chooseCardName(sa, faces, message); - Card dungeon = Card.fromPaperCard(StaticData.instance().getVariantCards().getUniqueByName(chosen), player); + Card dungeon = player.getController().chooseDungeon(player, dungeonCards, message); game.getTriggerHandler().suppressMode(TriggerType.ChangesZone); game.getAction().moveTo(ZoneType.Command, dungeon, sa); diff --git a/forge-game/src/main/java/forge/game/player/PlayerController.java b/forge-game/src/main/java/forge/game/player/PlayerController.java index a11be5b4fdd..73307e15989 100644 --- a/forge-game/src/main/java/forge/game/player/PlayerController.java +++ b/forge-game/src/main/java/forge/game/player/PlayerController.java @@ -261,6 +261,8 @@ public abstract class PlayerController { public abstract String chooseCardName(SpellAbility sa, Predicate cpp, String valid, String message); public abstract String chooseCardName(SpellAbility sa, List faces, String message); + + public abstract Card chooseDungeon(Player player, List dungeonCards, String message); // better to have this odd method than those if playerType comparison in ChangeZone public abstract Card chooseSingleCardForZoneChange(ZoneType destination, List origin, SpellAbility sa, CardCollection fetchList, DelayedReveal delayedReveal, String selectPrompt, boolean isOptional, Player decider); diff --git a/forge-gui-desktop/src/main/java/forge/itemmanager/filters/StatTypeFilter.java b/forge-gui-desktop/src/main/java/forge/itemmanager/filters/StatTypeFilter.java index cfc2041798a..7535365afed 100644 --- a/forge-gui-desktop/src/main/java/forge/itemmanager/filters/StatTypeFilter.java +++ b/forge-gui-desktop/src/main/java/forge/itemmanager/filters/StatTypeFilter.java @@ -85,6 +85,7 @@ public abstract class StatTypeFilter extends ToggleButt buttonMap.get(statTypes).setText(String.valueOf(count)); } } + getWidget().revalidate(); } } diff --git a/forge-gui-desktop/src/test/java/forge/card/CardDbTestLazyCardLoading.java b/forge-gui-desktop/src/test/java/forge/card/CardDbTestLazyCardLoading.java index fc169986934..2ca5b7b828a 100644 --- a/forge-gui-desktop/src/test/java/forge/card/CardDbTestLazyCardLoading.java +++ b/forge-gui-desktop/src/test/java/forge/card/CardDbTestLazyCardLoading.java @@ -89,4 +89,24 @@ public class CardDbTestLazyCardLoading extends ForgeCardMockTestCase { assertNotNull(aetherVialCard); assertEquals(aetherVialCard.getName(), expectedCardName); } + + @Test + public void tesLoadAndGetUnsupportedCardHavingWrongSetCode(){ + String cardName = "Dominating Licid"; + String wrongSetCode = "AA"; + String expectedSetCode = "EXO"; // Exodus + CardRarity expectedCardRarity = CardRarity.Rare; + + PaperCard dominatingLycidCard = this.cardDb.getCard(cardName); + assertNull(dominatingLycidCard); + + // Load the Card (just card name + FModel.getMagicDb().attemptToLoadCard(cardName, wrongSetCode); + + dominatingLycidCard = this.cardDb.getCard(cardName); + assertNotNull(dominatingLycidCard); + assertEquals(dominatingLycidCard.getName(), cardName); + assertEquals(dominatingLycidCard.getEdition(), expectedSetCode); + assertEquals(dominatingLycidCard.getRarity(), expectedCardRarity); + } } diff --git a/forge-gui-desktop/src/test/java/forge/gamesimulationtests/util/PlayerControllerForTests.java b/forge-gui-desktop/src/test/java/forge/gamesimulationtests/util/PlayerControllerForTests.java index 3f78ac4ae49..d8bb86a4f0a 100644 --- a/forge-gui-desktop/src/test/java/forge/gamesimulationtests/util/PlayerControllerForTests.java +++ b/forge-gui-desktop/src/test/java/forge/gamesimulationtests/util/PlayerControllerForTests.java @@ -681,6 +681,12 @@ public class PlayerControllerForTests extends PlayerController { return null; } + @Override + public Card chooseDungeon(Player player, List dungeonCards, String message) { + // TODO Auto-generated method stub + return null; + } + @Override public List chooseCardsForSplice(SpellAbility sa, List cards) { return Lists.newArrayList(); diff --git a/forge-gui/res/cardsfolder/a/arden_angel.txt b/forge-gui/res/cardsfolder/a/arden_angel.txt new file mode 100644 index 00000000000..20cbab881c8 --- /dev/null +++ b/forge-gui/res/cardsfolder/a/arden_angel.txt @@ -0,0 +1,10 @@ +Name:Arden Angel +ManaCost:4 W W +Types:Creature Angel +PT:4/4 +K:Flying +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Graveyard | IsPresent$ Card.StrictlySelf | PresentZone$ Graveyard | Execute$ TrigChooseNumber | TriggerDescription$ At the beginning of your upkeep, if CARDNAME is in your graveyard, choose a number from 1 to 4 at random. If the chosen number is 1, return CARDNAME from your graveyard to the battlefield. +SVar:TrigChooseNumber:DB$ ChooseNumber | Defined$ You | Min$ 1 | Max$ 4 | Random$ True | SubAbility$ DBChangeZone +SVar:DBChangeZone:DB$ ChangeZone | ConditionCheckSVar$ X | ConditionSVarCompare$ EQ1 | Origin$ Graveyard | Destination$ Battlefield +SVar:X:Count$ChosenNumber +Oracle:Flying\nAt the beginning of your upkeep, if Arden Angel is in your graveyard, choose a number from 1 to 4 at random. If the chosen number is 1, return Arden Angel from your graveyard to the battlefield. diff --git a/forge-gui/res/cardsfolder/a/ashuzas_breath.txt b/forge-gui/res/cardsfolder/a/ashuzas_breath.txt new file mode 100644 index 00000000000..8b40649b012 --- /dev/null +++ b/forge-gui/res/cardsfolder/a/ashuzas_breath.txt @@ -0,0 +1,7 @@ +Name:Ashuza's Breath +ManaCost:1 R +Types:Sorcery +A:SP$ RepeatEach | RepeatCards$ Creature | Zone$ Battlefield | RepeatSubAbility$ DBDealDamage | SpellDescription$ For each creature, choose a number from 0 to 2 at random. CARDNAME deals that much damage to that creature. +SVar:DBDealDamage:DB$ DealDamage | NumDmg$ X | Defined$ Remembered +SVar:X:Count$Random.0.2 +Oracle:For each creature, choose a number from 0 to 2 at random. Ashuza's Breath deals that much damage to that creature. diff --git a/forge-gui/res/cardsfolder/c/camato_scout.txt b/forge-gui/res/cardsfolder/c/camato_scout.txt new file mode 100644 index 00000000000..d05d51376ce --- /dev/null +++ b/forge-gui/res/cardsfolder/c/camato_scout.txt @@ -0,0 +1,8 @@ +Name:Camato Scout +ManaCost:1 U U +Types:Creature Merfolk +PT:2/3 +K:ETBReplacement:Other:ChooseLT +SVar:ChooseLT:DB$ ChooseType | Defined$ You | AtRandom$ True | Type$ Basic Land | SpellDescription$ As CARDNAME enters the battlefield, choose a land type. +S:Mode$ Continuous | Affected$ Card.Self | AddKeyword$ ChosenTypewalk | Description$ CARDNAME has landwalk of the chosen type. +Oracle:As Camato Scout enters the battlefield, choose a basic land type at random. Camato Scout has landwalk of the chosen type. diff --git a/forge-gui/res/cardsfolder/h/hapatos_might.txt b/forge-gui/res/cardsfolder/h/hapatos_might.txt new file mode 100644 index 00000000000..70cf4494e20 --- /dev/null +++ b/forge-gui/res/cardsfolder/h/hapatos_might.txt @@ -0,0 +1,7 @@ +Name:Hapato's Might +ManaCost:2 B +Types:Instant +A:SP$ ChooseNumber | Defined$ You | Min$ 0 | Max$ 6 | Random$ True | SubAbility$ DBPump | StackDescription$ None | SpellDescription$ Target creature gets +X/+0 until end of turn, where X is a number from 0 to 6 chosen at random. +SVar:DBPump:DB$ Pump | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumAtt$ X | StackDescription$ {c:Targeted} gets +X/+0 until end of turn, where X is a number from 0 to 6 chosen at random. +SVar:X:Count$ChosenNumber +Oracle:Target creature gets +X/+0 until end of turn, where X is a number from 0 to 6 chosen at random. diff --git a/forge-gui/res/cardsfolder/l/lydari_druid.txt b/forge-gui/res/cardsfolder/l/lydari_druid.txt new file mode 100644 index 00000000000..cb41b420cd8 --- /dev/null +++ b/forge-gui/res/cardsfolder/l/lydari_druid.txt @@ -0,0 +1,10 @@ +Name:Lydari Druid +ManaCost:2 G +Types:Creature Druid +PT:2/2 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigRepeat | TriggerDescription$ When CARDNAME enters the battlefield, for each land on the battlefield, choose a basic land type at random. Those lands become the land types chosen this way. (This effect lasts indefinitely.) +SVar:TrigRepeat:DB$ RepeatEach | RepeatCards$ Land | RepeatSubAbility$ DBChooseLT | SubAbility$ DBCleanup +SVar:DBChooseLT:DB$ ChooseType | Defined$ You | AtRandom$ True | Type$ Basic Land | SubAbility$ DBAnimate +SVar:DBAnimate:DB$ Animate | Defined$ Remembered | Types$ ChosenType | RemoveLandTypes$ True | RemoveIntrinsicAbilities$ True | Duration$ Permanent +SVar:DBCleanup:DB$ Cleanup | ClearChosenType$ True +Oracle:When Lydari Druid enters the battlefield, for each land on the battlefield, choose a basic land type at random. Those lands become the land types chosen this way. (This effect lasts indefinitely.) diff --git a/forge-gui/res/cardsfolder/l/lydari_elephant.txt b/forge-gui/res/cardsfolder/l/lydari_elephant.txt new file mode 100644 index 00000000000..d99b43b672a --- /dev/null +++ b/forge-gui/res/cardsfolder/l/lydari_elephant.txt @@ -0,0 +1,8 @@ +Name:Lydari Elephant +ManaCost:4 G +Types:Creature Elephant +PT:*/* +K:ETBReplacement:Other:RandomPower +SVar:RandomPower:DB$ Animate | Defined$ Self | Power$ X | Toughness$ X | Duration$ Permanent | SpellDescription$ As CARDNAME enters the battlefield, choose two numbers from 3 to 7 at random. CARDNAME's power is equal to the first number chosen and its toughness equal to the second number chosen. +SVar:X:Count$Random.3.7 +Oracle:As Lydari Elephant enters the battlefield, choose two numbers from 3 to 7 at random. Lydari Elephant's power is equal to the first number chosen and its toughness equal to the second number chosen. diff --git a/forge-gui/res/cardsfolder/m/murgish_cemetery.txt b/forge-gui/res/cardsfolder/m/murgish_cemetery.txt new file mode 100644 index 00000000000..d2ee643de8a --- /dev/null +++ b/forge-gui/res/cardsfolder/m/murgish_cemetery.txt @@ -0,0 +1,9 @@ +Name:Murgish Cemetery +ManaCost:4 B B +Types:Enchantment +A:AB$ ChooseNumber | Cost$ 3 B Discard<1/Card> | Min$ 2 | Max$ 6 | Defined$ You | Random$ True | SubAbility$ DBToken | SpellDescription$ Create an X/X black Zombie creature token, where X is a number from 2 to 6 chosen at random. +SVar:DBToken:DB$ Token | TokenScript$ b_x_x_zombie | TokenPower$ X | TokenToughness$ X +SVar:X:Count$Random.2.6 +DeckHas:Ability$Token & Ability$Discard +DeckHints:Type$Zombie +Oracle:{3}{B}, Discard a card: Create an X/X black Zombie creature token, where X is a number from 2 to 6 chosen at random. diff --git a/forge-gui/res/cardsfolder/s/sajis_torrent.txt b/forge-gui/res/cardsfolder/s/sajis_torrent.txt new file mode 100644 index 00000000000..a0588010c58 --- /dev/null +++ b/forge-gui/res/cardsfolder/s/sajis_torrent.txt @@ -0,0 +1,9 @@ +Name:Saji's Torrent +ManaCost:1 U +Types:Instant +A:SP$ ChooseNumber | Min$ 0 | Max$ 5 | Defined$ You | Random$ True | SubAbility$ DBChoose | StackDescription$ SpellDescription | SpellDescription$ Tap X creatures, where X is a number from 0 to 5 chosen at random. +SVar:DBChoose:DB$ ChooseCard | Amount$ X | Choices$ Creature | RememberChosen$ True | SubAbility$ DBTapAll | StackDescription$ None +SVar:DBTapAll:DB$ TapAll | ValidCards$ Creature.IsRemembered | StackDescription$ None | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +SVar:X:Count$ChosenNumber +Oracle:Tap X creatures, where X is a number from 0 to 5 chosen at random. diff --git a/forge-gui/res/cardsfolder/t/tornellan_protector.txt b/forge-gui/res/cardsfolder/t/tornellan_protector.txt new file mode 100644 index 00000000000..84cf3c07076 --- /dev/null +++ b/forge-gui/res/cardsfolder/t/tornellan_protector.txt @@ -0,0 +1,9 @@ +Name:Tornellan Protector +ManaCost:2 W +Types:Creature Cleric +PT:1/2 +A:AB$ Effect | Cost$ T | ValidTgts$ Creature,Player | TgtPrompt$ Select target creature or player | RememberObjects$ Targeted | ReplacementEffects$ Protect | SpellDescription$ Until end of turn, each time damage is dealt to target creature or player, prevent X of that damage, where X is a number from 1 to 3 chosen at random each time. +SVar:Protect:Event$ DamageDone | ActiveZones$ Battlefield | ValidTarget$ Creature.IsRemembered,Player.IsRemembered | ReplaceWith$ DBReplace | PreventionEffect$ True | Description$ Until end of turn, each time damage is dealt to target creature or player, prevent X of that damage, where X is a number from 1 to 3 chosen at random each time. +SVar:DBReplace:DB$ ReplaceDamage | Amount$ X +SVar:X:Count$Random.1.3 +Oracle:{T}: Until end of turn, each time damage is dealt to target creature or player, prevent X of that damage, where X is a number from 1 to 3 chosen at random each time. diff --git a/forge-gui/res/cardsfolder/v/velican_dragon.txt b/forge-gui/res/cardsfolder/v/velican_dragon.txt new file mode 100644 index 00000000000..196dad1c877 --- /dev/null +++ b/forge-gui/res/cardsfolder/v/velican_dragon.txt @@ -0,0 +1,10 @@ +Name:Velican Dragon +ManaCost:5 R R +Types:Creature Dragon +PT:5/5 +K:Flying +T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigPump | TriggerDescription$ Whenever CARDNAME attacks or blocks, it gets +X/+0 until end of turn, where X is a number from 0 to 5 chosen at random. +T:Mode$ Blocks | ValidCard$ Card.Self | Execute$ TrigPump | Secondary$ True | TriggerDescription$ Whenever CARDNAME attacks or blocks, +X/+0 until end of turn, where X is a number from 0 to 5 chosen at random. +SVar:TrigPump:DB$ Pump | Defined$ Self | NumAtt$ X +SVar:X:Count$Random.0.5 +Oracle:Flying\nWhenever Velican Dragon attacks or blocks, it gets +X/+0 until end of turn, where X is a number from 0 to 5 chosen at random. diff --git a/forge-gui/res/editions/Secret Lair Drop Series.txt b/forge-gui/res/editions/Secret Lair Drop Series.txt index 9cc0667bd9b..b15236f5408 100644 --- a/forge-gui/res/editions/Secret Lair Drop Series.txt +++ b/forge-gui/res/editions/Secret Lair Drop Series.txt @@ -348,11 +348,14 @@ ScryfallCode=SLD 579 L Forest @Johannes Voss 581 M Lucille @Jason Felix 582 R Brainstorm @Mark Poole +585 R Terramorphic Expanse @ 589 R Arcane Signet @Dan Frazier 591 R Crash Through @Tyler Walpole 603 M Eldrazi Monument @Cosmin Podar 604 R Ornithopter @Cosmin Podar +605 R Panharmonicon @Cosmin Podar 606 R Swiftfoot Boots @Cosmin Podar +607 R Rogue's Passage @Cosmin Podar [tokens] b_1_1_faerie_rogue_flying diff --git a/forge-gui/res/editions/Sega Dreamcast Cards.txt b/forge-gui/res/editions/Sega Dreamcast Cards.txt new file mode 100644 index 00000000000..6cb4ffa12cb --- /dev/null +++ b/forge-gui/res/editions/Sega Dreamcast Cards.txt @@ -0,0 +1,17 @@ +[metadata] +Code=PSDG +Date=2001-06-28 +Name=Sega Dreamcast Cards +Type=Promo + +[cards] +1 R Arden Angel @Greg Staples +2 R Ashuza's Breath @Glen Angus +3 R Camato Scout @Christopher Moeller +4 R Hapato's Might @Jeff Easley +5 R Lydari Druid @Kev Walker +6 R Lydari Elephant @Heather Hudson +7 R Murgish Cemetery @Daren Bader +8 R Saji's Torrent @Matt Cavotta +9 R Tornellan Protector @Eric Peterson +10 R Velican Dragon @Daren Bader diff --git a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java index cfab29621f6..27dd70665c9 100644 --- a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java +++ b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java @@ -3235,6 +3235,12 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont return face == null ? "" : face.getName(); } + @Override + public Card chooseDungeon(Player player, List dungeonCards, String message) { + PaperCard dungeon = getGui().one(message, dungeonCards); + return Card.fromPaperCard(dungeon, player); + } + @Override public List chooseCardsForSplice(SpellAbility sa, List cards) { GameEntityViewMap gameCacheSplice = GameEntityView.getMap(cards);