Merge remote-tracking branch 'upstream/master' into deck-importer-decks-file-format

This commit is contained in:
leriomaggio
2021-08-31 08:49:41 +01:00
27 changed files with 215 additions and 62 deletions

View File

@@ -233,14 +233,12 @@ public class ComputerUtilCost {
} }
public static boolean checkForManaSacrificeCost(final Player ai, final Cost cost, final Card source, final SpellAbility sourceAbility) { 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; return true;
} }
for (final CostPart part : cost.getCostParts()) { for (final CostPart part : cost.getCostParts()) {
if (part instanceof CostSacrifice) { if (part instanceof CostSacrifice) {
if (!ai.isAI()) {
return false;
}
CardCollection list = new CardCollection(); CardCollection list = new CardCollection();
final CardCollection exclude = new CardCollection(); final CardCollection exclude = new CardCollection();
if (AiCardMemory.getMemorySet(ai, MemorySet.PAYS_SAC_COST) != null) { if (AiCardMemory.getMemorySet(ai, MemorySet.PAYS_SAC_COST) != null) {

View File

@@ -1291,6 +1291,27 @@ public class PlayerControllerAi extends PlayerController {
return SpellApiToAi.Converter.get(api).chooseCardName(player, sa, faces); return SpellApiToAi.Converter.get(api).chooseCardName(player, sa, faces);
} }
@Override
public Card chooseDungeon(Player ai, List<PaperCard> dungeonCards, String message) {
// TODO: improve the conditions that define which dungeon is a viable option to choose
List<String> 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 @Override
public List<Card> chooseCardsForSplice(SpellAbility sa, List<Card> cards) { public List<Card> chooseCardsForSplice(SpellAbility sa, List<Card> cards) {
// sort from best to worst // sort from best to worst

View File

@@ -2,10 +2,8 @@ package forge.ai.ability;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import forge.ai.AiPlayDecision; import forge.ai.AiPlayDecision;
import forge.ai.AiProps;
import forge.ai.PlayerControllerAi; import forge.ai.PlayerControllerAi;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.card.ICardFace;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode; import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.SpellAbility; 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 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<ICardFace> faces) {
// TODO: improve the conditions that define which dungeon is a viable option to choose
List<String> 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);
}
} }

View File

@@ -239,7 +239,8 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
System.out.println("[LOG]: (Lazy) Loading Card: " + cardName); System.out.println("[LOG]: (Lazy) Loading Card: " + cardName);
rulesByName.put(cardName, cr); rulesByName.put(cardName, cr);
boolean reIndexNecessary = false; 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 // look for all possible editions
for (CardEdition e : editions) { for (CardEdition e : editions) {
List<CardInSet> cardsInSet = e.getCardInSet(cardName); // empty collection if not present List<CardInSet> cardsInSet = e.getCardInSet(cardName); // empty collection if not present
@@ -249,10 +250,9 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
} }
} }
} else { } else {
CardEdition e = editions.get(setCode); List<CardInSet> cardsInSet = ed.getCardInSet(cardName); // empty collection if not present
List<CardInSet> cardsInSet = e.getCardInSet(cardName); // empty collection if not present
for (CardInSet cis : cardsInSet) { for (CardInSet cis : cardsInSet) {
addSetCard(e, cis, cr); addSetCard(ed, cis, cr);
reIndexNecessary = true; reIndexNecessary = true;
} }
} }

View File

@@ -199,29 +199,36 @@ public final class CardEdition implements Comparable<CardEdition> {
* @param collectorNumber: Input collectorNumber tro transform in a Sorting Key * @param collectorNumber: Input collectorNumber tro transform in a Sorting Key
* @return A 5-digits zero-padded collector number + any non-numerical parts attached. * @return A 5-digits zero-padded collector number + any non-numerical parts attached.
*/ */
private static final Map<String, String> sortableCollNumberLookup = new HashMap<>();
public static String getSortableCollectorNumber(final String collectorNumber){ public static String getSortableCollectorNumber(final String collectorNumber){
String sortableCollNr = collectorNumber; String inputCollNumber = collectorNumber;
if (sortableCollNr == null || sortableCollNr.length() == 0) if (collectorNumber == null || collectorNumber.length() == 0)
sortableCollNr = "50000"; // very big number of 5 digits to have them in last positions 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) // Now, for proper sorting, let's zero-pad the collector number (if integer)
int collNr; int collNr;
String sortableCollNr;
try { try {
collNr = Integer.parseInt(sortableCollNr); collNr = Integer.parseInt(inputCollNumber);
sortableCollNr = String.format("%05d", collNr); sortableCollNr = String.format("%05d", collNr);
} catch (NumberFormatException ex) { } catch (NumberFormatException ex) {
String nonNumeric = sortableCollNr.replaceAll("[0-9]", ""); String nonNumSub = inputCollNumber.replaceAll("[0-9]", "");
String onlyNumeric = sortableCollNr.replaceAll("[^0-9]", ""); String onlyNumSub = inputCollNumber.replaceAll("[^0-9]", "");
try { try {
collNr = Integer.parseInt(onlyNumeric); collNr = Integer.parseInt(onlyNumSub);
} catch (NumberFormatException exon) { } catch (NumberFormatException exon) {
collNr = 0; // this is the case of ONLY-letters collector numbers collNr = 0; // this is the case of ONLY-letters collector numbers
} }
if ((collNr > 0) && (sortableCollNr.startsWith(onlyNumeric))) // e.g. 12a, 37+, 2018f, if ((collNr > 0) && (inputCollNumber.startsWith(onlyNumSub))) // e.g. 12a, 37+, 2018f,
sortableCollNr = String.format("%05d", collNr) + nonNumeric; sortableCollNr = String.format("%05d", collNr) + nonNumSub;
else // e.g. WS6, S1 else // e.g. WS6, S1
sortableCollNr = nonNumeric + String.format("%05d", collNr); sortableCollNr = nonNumSub + String.format("%05d", collNr);
} }
sortableCollNumberLookup.put(inputCollNumber, sortableCollNr);
return sortableCollNr; return sortableCollNr;
} }

View File

@@ -463,8 +463,11 @@ public class CardPool extends ItemPool<PaperCard> {
*/ */
public CardPool getFilteredPool(Predicate<PaperCard> predicate) { public CardPool getFilteredPool(Predicate<PaperCard> predicate) {
CardPool filteredPool = new CardPool(); CardPool filteredPool = new CardPool();
for (PaperCard pc : this.items.keySet()) { Iterator<PaperCard> cardsInPool = this.items.keySet().iterator();
if (predicate.apply(pc)) filteredPool.add(pc); while (cardsInPool.hasNext()){
PaperCard c = cardsInPool.next();
if (predicate.apply(c))
filteredPool.add(c, this.items.get(c));
} }
return filteredPool; return filteredPool;
} }

View File

@@ -51,7 +51,7 @@ public final class PaperCard implements Comparable<IPaperCard>, InventoryItemFro
(see getCollectorNumber()) (see getCollectorNumber())
*/ */
private String collectorNumber; private String collectorNumber;
private final String artist; private String artist;
private final int artIndex; private final int artIndex;
private final boolean foil; private final boolean foil;
private Boolean hasImage; private Boolean hasImage;
@@ -73,6 +73,8 @@ public final class PaperCard implements Comparable<IPaperCard>, InventoryItemFro
@Override @Override
public String getCollectorNumber() { public String getCollectorNumber() {
if (collectorNumber == null)
collectorNumber = IPaperCard.NO_COLLECTOR_NUMBER;
return collectorNumber; return collectorNumber;
} }
@@ -103,6 +105,8 @@ public final class PaperCard implements Comparable<IPaperCard>, InventoryItemFro
@Override @Override
public String getArtist() { public String getArtist() {
if (this.artist == null)
artist = IPaperCard.NO_ARTIST_NAME;
return artist; return artist;
} }

View File

@@ -30,7 +30,7 @@ import java.util.concurrent.ConcurrentHashMap;
import com.google.common.base.Function; import com.google.common.base.Function;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.collect.Iterables; import com.google.common.collect.Maps;
import forge.item.InventoryItem; import forge.item.InventoryItem;
/** /**
@@ -137,27 +137,26 @@ public class ItemPool<T extends InventoryItem> implements Iterable<Entry<T, Inte
return count; return count;
} }
public final int countAll(Predicate<T> condition) { public int countAll(Predicate<T> condition){
int count = 0; int count = 0;
for (Entry<T, Integer> e : this) { for (Integer v : Maps.filterKeys(this.items, condition).values())
if (condition.apply(e.getKey())) { count += v;
count += e.getValue();
}
}
return count; return count;
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public final <U extends InventoryItem> int countAll(Predicate<U> condition, Class<U> cls) { public final <U extends InventoryItem> int countAll(Predicate<U> condition, Class<U> cls) {
int count = 0; int count = 0;
Iterable<T> matchingKeys = Iterables.filter(this.items.keySet(), new Predicate<T>() { Map<T, Integer> matchingKeys = Maps.filterKeys(this.items, new Predicate<T>() {
@Override @Override
public boolean apply(T item) { 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) for (Integer i : matchingKeys.values()) {
count += this.items.get(key); count += i;
}
return count; return count;
} }

View File

@@ -11,6 +11,7 @@ import forge.game.card.Card;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions; import forge.game.spellability.TargetRestrictions;
import forge.util.Aggregates;
public class ChooseTypeEffect extends SpellAbilityEffect { public class ChooseTypeEffect extends SpellAbilityEffect {
@@ -63,8 +64,13 @@ public class ChooseTypeEffect extends SpellAbilityEffect {
if (!validTypes.isEmpty()) { if (!validTypes.isEmpty()) {
for (final Player p : tgtPlayers) { for (final Player p : tgtPlayers) {
String choice;
if ((tgt == null) || p.canBeTargetedBy(sa)) { 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")) { if (!sa.hasParam("ChooseType2")) {
card.setChosenType(choice); card.setChosenType(choice);
} else { } else {

View File

@@ -8,7 +8,6 @@ import com.google.common.base.Predicates;
import forge.StaticData; import forge.StaticData;
import forge.card.CardRulesPredicates; import forge.card.CardRulesPredicates;
import forge.card.ICardFace;
import forge.game.Game; import forge.game.Game;
import forge.game.ability.AbilityKey; import forge.game.ability.AbilityKey;
import forge.game.ability.SpellAbilityEffect; 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. // Create a new dungeon card chosen by player in command zone.
List<PaperCard> dungeonCards = StaticData.instance().getVariantCards().getAllCards( List<PaperCard> dungeonCards = StaticData.instance().getVariantCards().getAllCards(
Predicates.compose(CardRulesPredicates.Presets.IS_DUNGEON, PaperCard.FN_GET_RULES)); Predicates.compose(CardRulesPredicates.Presets.IS_DUNGEON, PaperCard.FN_GET_RULES));
List<ICardFace> faces = new ArrayList<>();
for (PaperCard pc : dungeonCards) {
faces.add(pc.getRules().getMainPart());
}
String message = Localizer.getInstance().getMessage("lblChooseDungeon"); String message = Localizer.getInstance().getMessage("lblChooseDungeon");
String chosen = player.getController().chooseCardName(sa, faces, message); Card dungeon = player.getController().chooseDungeon(player, dungeonCards, message);
Card dungeon = Card.fromPaperCard(StaticData.instance().getVariantCards().getUniqueByName(chosen), player);
game.getTriggerHandler().suppressMode(TriggerType.ChangesZone); game.getTriggerHandler().suppressMode(TriggerType.ChangesZone);
game.getAction().moveTo(ZoneType.Command, dungeon, sa); game.getAction().moveTo(ZoneType.Command, dungeon, sa);

View File

@@ -261,6 +261,8 @@ public abstract class PlayerController {
public abstract String chooseCardName(SpellAbility sa, Predicate<ICardFace> cpp, String valid, String message); public abstract String chooseCardName(SpellAbility sa, Predicate<ICardFace> cpp, String valid, String message);
public abstract String chooseCardName(SpellAbility sa, List<ICardFace> faces, String message); public abstract String chooseCardName(SpellAbility sa, List<ICardFace> faces, String message);
public abstract Card chooseDungeon(Player player, List<PaperCard> dungeonCards, String message);
// better to have this odd method than those if playerType comparison in ChangeZone // better to have this odd method than those if playerType comparison in ChangeZone
public abstract Card chooseSingleCardForZoneChange(ZoneType destination, List<ZoneType> origin, SpellAbility sa, CardCollection fetchList, DelayedReveal delayedReveal, String selectPrompt, boolean isOptional, Player decider); public abstract Card chooseSingleCardForZoneChange(ZoneType destination, List<ZoneType> origin, SpellAbility sa, CardCollection fetchList, DelayedReveal delayedReveal, String selectPrompt, boolean isOptional, Player decider);

View File

@@ -85,6 +85,7 @@ public abstract class StatTypeFilter<T extends InventoryItem> extends ToggleButt
buttonMap.get(statTypes).setText(String.valueOf(count)); buttonMap.get(statTypes).setText(String.valueOf(count));
} }
} }
getWidget().revalidate(); getWidget().revalidate();
} }
} }

View File

@@ -89,4 +89,24 @@ public class CardDbTestLazyCardLoading extends ForgeCardMockTestCase {
assertNotNull(aetherVialCard); assertNotNull(aetherVialCard);
assertEquals(aetherVialCard.getName(), expectedCardName); 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);
}
} }

View File

@@ -681,6 +681,12 @@ public class PlayerControllerForTests extends PlayerController {
return null; return null;
} }
@Override
public Card chooseDungeon(Player player, List<PaperCard> dungeonCards, String message) {
// TODO Auto-generated method stub
return null;
}
@Override @Override
public List<Card> chooseCardsForSplice(SpellAbility sa, List<Card> cards) { public List<Card> chooseCardsForSplice(SpellAbility sa, List<Card> cards) {
return Lists.newArrayList(); return Lists.newArrayList();

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.)

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -348,11 +348,14 @@ ScryfallCode=SLD
579 L Forest @Johannes Voss 579 L Forest @Johannes Voss
581 M Lucille @Jason Felix 581 M Lucille @Jason Felix
582 R Brainstorm @Mark Poole 582 R Brainstorm @Mark Poole
585 R Terramorphic Expanse @
589 R Arcane Signet @Dan Frazier 589 R Arcane Signet @Dan Frazier
591 R Crash Through @Tyler Walpole 591 R Crash Through @Tyler Walpole
603 M Eldrazi Monument @Cosmin Podar 603 M Eldrazi Monument @Cosmin Podar
604 R Ornithopter @Cosmin Podar 604 R Ornithopter @Cosmin Podar
605 R Panharmonicon @Cosmin Podar
606 R Swiftfoot Boots @Cosmin Podar 606 R Swiftfoot Boots @Cosmin Podar
607 R Rogue's Passage @Cosmin Podar
[tokens] [tokens]
b_1_1_faerie_rogue_flying b_1_1_faerie_rogue_flying

View File

@@ -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

View File

@@ -3235,6 +3235,12 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
return face == null ? "" : face.getName(); return face == null ? "" : face.getName();
} }
@Override
public Card chooseDungeon(Player player, List<PaperCard> dungeonCards, String message) {
PaperCard dungeon = getGui().one(message, dungeonCards);
return Card.fromPaperCard(dungeon, player);
}
@Override @Override
public List<Card> chooseCardsForSplice(SpellAbility sa, List<Card> cards) { public List<Card> chooseCardsForSplice(SpellAbility sa, List<Card> cards) {
GameEntityViewMap<Card, CardView> gameCacheSplice = GameEntityView.getMap(cards); GameEntityViewMap<Card, CardView> gameCacheSplice = GameEntityView.getMap(cards);