Compare commits

..

23 Commits

Author SHA1 Message Date
Hans Mackowiak
82c9cd4ef5 Update ManaReflectedEffect.java
fixed
2025-11-12 10:37:21 +01:00
Hans Mackowiak
4a35b51bf2 Merge branch 'master' into chooseColorEnum 2025-11-12 10:02:21 +01:00
Hans Mackowiak
0faae58c1d Merge branch 'master' into chooseColorEnum 2025-10-20 10:00:51 +02:00
Hans Mackowiak
6900bcb8dd Update StaticAbilityContinuous.java 2025-10-10 10:39:05 +02:00
Hans Mackowiak
7497e3e8cd Update CardFactory.java 2025-10-10 10:37:27 +02:00
Hans Mackowiak
93d9c78b8e Update SpellAbilityEffect.java 2025-10-10 10:35:43 +02:00
Hans Mackowiak
0cd9126b5c Update AbilityManaPart.java 2025-10-10 10:35:01 +02:00
Hans Mackowiak
ba47164862 Update CardState.java 2025-10-10 10:34:20 +02:00
Hans Mackowiak
0170f5fe56 Update Player.java 2025-10-10 10:33:37 +02:00
Hans Mackowiak
b07428c02a Update GameActionUtil.java 2025-10-10 10:32:48 +02:00
Hans Mackowiak
24242aa908 Merge branch 'master' into chooseColorEnum 2025-10-10 10:29:24 +02:00
Hans Mackowiak
07814d9e82 Update InputSelectCardsForConvokeOrImprovise.java 2025-09-16 16:43:54 +02:00
Hans Mackowiak
9bf45c2e62 Update PlayerControllerAi.java 2025-09-16 16:42:44 +02:00
Hans Mackowiak
31147739f1 Update ChangeTextEffect.java 2025-09-16 16:41:49 +02:00
Hans Mackowiak
74b1467837 Update ManaEffect.java 2025-09-16 16:37:42 +02:00
Hans Mackowiak
a85dc13cd8 Merge branch 'master' into chooseColorEnum 2025-09-16 16:33:35 +02:00
Hans Mackowiak
2ed01fb285 Player: use chooseColor instead for initCommanderColor 2025-09-15 14:13:12 +02:00
Hans Mackowiak
3621c02165 Player: unify 'choose a color before the game begins' 2025-09-15 14:13:12 +02:00
Hans Mackowiak
9e3f23315e CardState replace byte color with ColorSet 2025-09-15 14:13:12 +02:00
Hans Mackowiak
1033a0b595 remove some cases of ColorSet.iterator 2025-09-15 14:13:12 +02:00
Hans Mackowiak
b49efb28f9 PlayerController: chooseColor returns MagicColor.Color 2025-09-15 14:13:12 +02:00
Hans Mackowiak
07ee5ccddf PlayerControllerHuman: translate chooseColor 2025-09-15 14:13:12 +02:00
Hans Mackowiak
21e458237d MagicColor: use Enum for Human chooseColors 2025-09-15 14:13:12 +02:00
401 changed files with 152 additions and 851 deletions

View File

@@ -18,7 +18,7 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: fix md links for Gollum
run: find ${{ github.workspace }}/docs/ -type f -name "*.md" -exec sed -i -E 's|(\[[^]]+]\()([^)]+\/)*([^).]+).md(#)*([[:alnum:]]*)\)|\1\3\4\5)|g' '{}' \;
run: find ${{ github.workspace }}/docs/ -type f -name "*.md" -exec sed -i -E 's|(\[[^]]+]\()([^)]+\/)*([^).]+).md\)|\1\3)|g' '{}' \;
- name: fix image links for Gollum
run: find ${{ github.workspace }}/docs/ -type f -name "*.png" -exec mv '{}' ${{ github.workspace }}/docs/ \;
- uses: Andrew-Chen-Wang/github-wiki-action@v5

View File

@@ -241,7 +241,8 @@ public class PlayerControllerAi extends PlayerController {
public Map<Byte, Integer> specifyManaCombo(SpellAbility sa, ColorSet colorSet, int manaAmount, boolean different) {
Map<Byte, Integer> result = new HashMap<>();
for (int i = 0; i < manaAmount; ++i) {
Byte chosen = chooseColor("", sa, colorSet);
MagicColor.Color chosenColor = chooseColor("", sa, colorSet);
Byte chosen = chosenColor == null ? (byte)0 : chosenColor.getColorMask();
if (result.containsKey(chosen)) {
result.put(chosen, result.get(chosen) + 1);
} else {
@@ -1022,19 +1023,22 @@ public class PlayerControllerAi extends PlayerController {
}
@Override
public byte chooseColorAllowColorless(String message, Card card, ColorSet colors) {
public MagicColor.Color chooseColorAllowColorless(String message, Card card, ColorSet colors) {
final String c = ComputerUtilCard.getMostProminentColor(player.getCardsIn(ZoneType.Hand));
byte chosenColorMask = MagicColor.fromName(c);
if ((colors.getColor() & chosenColorMask) != 0) {
return chosenColorMask;
return MagicColor.Color.fromByte(chosenColorMask);
}
return Iterables.getFirst(colors, MagicColor.Color.COLORLESS).getColorMask();
return Iterables.getFirst(colors, MagicColor.Color.COLORLESS);
}
@Override
public byte chooseColor(String message, SpellAbility sa, ColorSet colors) {
public MagicColor.Color chooseColor(String message, SpellAbility sa, ColorSet colors) {
if (colors.countColors() == 0) {
return null;
}
if (colors.countColors() < 2) {
return Iterables.getFirst(colors, MagicColor.Color.WHITE).getColorMask();
return Iterables.getFirst(colors, MagicColor.Color.WHITE);
}
// You may switch on sa.getApi() here and use sa.getParam("AILogic")
CardCollectionView hand = player.getCardsIn(ZoneType.Hand);
@@ -1045,9 +1049,9 @@ public class PlayerControllerAi extends PlayerController {
byte chosenColorMask = MagicColor.fromName(c);
if ((colors.getColor() & chosenColorMask) != 0) {
return chosenColorMask;
return MagicColor.Color.fromByte(chosenColorMask);
}
return Iterables.getFirst(colors, MagicColor.Color.WHITE).getColorMask();
return Iterables.getFirst(colors, MagicColor.Color.WHITE);
}
@Override

View File

@@ -1,12 +1,10 @@
package forge.card;
import forge.card.mana.ManaCost;
import forge.util.Lang;
import org.apache.commons.lang3.StringUtils;
import java.util.*;
import java.util.Map.Entry;
import java.util.regex.PatternSyntaxException;
import java.util.stream.Collectors;
//
@@ -187,25 +185,7 @@ final class CardFace implements ICardFace, Cloneable {
}
void assignMissingFieldsToVariant(CardFace variant) {
if(variant.oracleText == null) {
if(variant.flavorName != null && this.oracleText != null) {
try {
Lang lang = Lang.getInstance();
//Rudimentary name replacement. Can't do pronouns, ability words, or flavored keywords. Need to define variant text manually for that.
//Regex here checks for the name following either a word boundary or a literal "\n" string, since those haven't yet been converted to line breaks.
String flavoredText = this.oracleText.replaceAll("(?<=\\b|\\\\n)" + this.name + "\\b", variant.flavorName);
flavoredText = flavoredText.replaceAll("(?<=\\b|\\\\n)" + lang.getNickName(this.name) + "\\b", lang.getNickName(variant.flavorName));
variant.oracleText = flavoredText;
}
catch (PatternSyntaxException ignored) {
// Old versions of Android are weird about patterns sometimes. I don't *think* this is such a case but
// the documentation is unreliable. May be worth removing this once we're sure it's not a problem.
variant.oracleText = this.oracleText;
}
}
else
variant.oracleText = this.oracleText;
}
if(variant.oracleText == null) variant.oracleText = this.oracleText;
if(variant.manaCost == null) variant.manaCost = this.manaCost;
if(variant.color == null) variant.color = ColorSet.fromManaCost(variant.manaCost);

View File

@@ -504,11 +504,16 @@ public final class CardRules implements ICardCharacteristics {
CardFace variantMain = ((CardFace) mainPart).getOrCreateFunctionalVariant(variantName);
variantMain.setFlavorName(nameParts[0]);
//Rudimentary name replacement. Can't do nicknames, pronouns, ability words, or flavored keywords. Need to define variants manually for that.
if(mainPart.getOracleText().contains(mainPart.getName()))
variantMain.setOracleText(mainPart.getOracleText().replace(mainPart.getName(), nameParts[0]));
((CardFace) mainPart).assignMissingFieldsToVariant(variantMain);
if(otherPart != null) {
CardFace variantOther = ((CardFace) otherPart).getOrCreateFunctionalVariant(variantName);
variantOther.setFlavorName(nameParts[1]);
if(otherPart.getOracleText().contains(otherPart.getName()))
variantMain.setOracleText(otherPart.getOracleText().replace(otherPart.getName(), nameParts[1]));
((CardFace) otherPart).assignMissingFieldsToVariant(variantOther);
}

View File

@@ -1027,22 +1027,6 @@ public class DeckRecognizer {
};
}
/**
* Get the magic color by the localised/translated name.
* @param localisedName String of localised color name.
* @return The string of the magic color.
*/
public static String getColorNameByLocalisedName(String localisedName) {
Localizer localizer = Localizer.getInstance();
if(localisedName.equals(localizer.getMessage("lblWhite"))) return MagicColor.Constant.WHITE;
if(localisedName.equals(localizer.getMessage("lblBlue"))) return MagicColor.Constant.BLUE;
if(localisedName.equals(localizer.getMessage("lblBlack"))) return MagicColor.Constant.BLACK;
if(localisedName.equals(localizer.getMessage("lblRed"))) return MagicColor.Constant.RED;
if(localisedName.equals(localizer.getMessage("lblGreen"))) return MagicColor.Constant.GREEN;
return "";
}
public static boolean isDeckName(final String lineAsIs) {
if (lineAsIs == null)
return false;

View File

@@ -78,7 +78,7 @@ public class GameAction {
private boolean holdCheckingStaticAbilities = false;
private final static Comparator<StaticAbility> effectOrder = Comparator.comparing(StaticAbility::isCharacteristicDefining).reversed()
.thenComparing(StaticAbility::getTimestamp);
.thenComparing(s -> s.getHostCard().getLayerTimestamp());
public GameAction(Game game0) {
game = game0;
@@ -1103,16 +1103,11 @@ public class GameAction {
if (stAb.checkMode(StaticAbilityMode.Continuous) && stAb.zonesCheck()) {
staticAbilities.add(stAb);
}
}
if (!co.getStaticCommandList().isEmpty()) {
staticList.add(co);
}
for (StaticAbility stAb : co.getHiddenStaticAbilities()) {
if (stAb.checkMode(StaticAbilityMode.Continuous) && stAb.zonesCheck()) {
staticAbilities.add(stAb);
}
}
return true;
}
if (!co.getStaticCommandList().isEmpty()) {
staticList.add(co);
}
return true;
}
}, true);
@@ -1338,7 +1333,7 @@ public class GameAction {
// now the earliest one left is the correct choice
List<StaticAbility> statics = Lists.newArrayList(dependencyGraph.vertexSet());
statics.sort(Comparator.comparing(StaticAbility::getTimestamp));
statics.sort(Comparator.comparing(s -> s.getHostCard().getLayerTimestamp()));
return statics.get(0);
}

View File

@@ -11,7 +11,6 @@ import forge.game.card.Card;
import forge.game.card.CardZoneTable;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityStackInstance;
import forge.game.trigger.TriggerType;
import forge.game.zone.ZoneType;
import forge.util.Lang;
@@ -48,7 +47,7 @@ public class AirbendEffect extends SpellAbilityEffect {
final CardZoneTable triggerList = CardZoneTable.getSimultaneousInstance(sa);
for (Card c : getCardsfromTargets(sa)) {
for (Card c : getTargetCards(sa)) {
final Card gameCard = game.getCardState(c, null);
// gameCard is LKI in that case, the card is not in game anymore
// or the timestamp did change
@@ -56,27 +55,21 @@ public class AirbendEffect extends SpellAbilityEffect {
if (gameCard == null || !c.equalsWithGameTimestamp(gameCard) || gameCard.isPhasedOut()) {
continue;
}
if (!gameCard.canExiledBy(sa, true)) {
continue;
}
handleExiledWith(gameCard, sa);
Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
AbilityKey.addCardZoneTableParams(moveParams, triggerList);
SpellAbilityStackInstance si = null;
if (gameCard.isInZone(ZoneType.Stack)) {
SpellAbility stackSA = game.getStack().getSpellMatchingHost(gameCard);
si = game.getStack().getInstanceMatchingSpellAbilityID(stackSA);
}
Card movedCard = game.getAction().exile(gameCard, sa, moveParams);
if (movedCard == null || !movedCard.isInZone(ZoneType.Exile)) {
continue;
}
if (si != null) {
// GameAction.changeZone should really take care of cleaning up SASI when a card from the stack is removed.
game.getStack().remove(si);
}
// Effect to cast for 2 from exile
Card eff = createEffect(sa, movedCard.getOwner(), "Airbend" + movedCard, hostCard.getImageKey());
eff.addRemembered(movedCard);
@@ -91,7 +84,6 @@ public class AirbendEffect extends SpellAbilityEffect {
game.getAction().moveToCommand(eff, sa);
}
triggerList.triggerChangesZoneAll(game, sa);
handleExiledWith(triggerList.allCards(), sa);

View File

@@ -30,27 +30,28 @@ public class ChangeTextEffect extends SpellAbilityEffect {
final String changedColorWordOriginal, changedColorWordNew;
if (sa.hasParam("ChangeColorWord")) {
byte originalColor = 0;
// all instances are Choose Choose
MagicColor.Color originalColor = null;
final String[] changedColorWordsArray = sa.getParam("ChangeColorWord").split(" ");
if (changedColorWordsArray[0].equals("Choose")) {
originalColor = sa.getActivatingPlayer().getController().chooseColor(
Localizer.getInstance().getMessage("lblChooseColorReplace"), sa, ColorSet.WUBRG);
changedColorWordOriginal = TextUtil.capitalize(MagicColor.toLongString(originalColor));
changedColorWordOriginal = TextUtil.capitalize(originalColor.getName());
} else {
changedColorWordOriginal = changedColorWordsArray[0];
originalColor = MagicColor.fromName(changedColorWordOriginal);
originalColor = MagicColor.Color.fromByte(MagicColor.fromName(changedColorWordOriginal));
}
if (changedColorWordsArray[1].equals("Choose")) {
final ColorSet possibleNewColors;
if (originalColor == 0) { // no original color (ie. any or absent)
if (originalColor == null) { // no original color (ie. any or absent)
possibleNewColors = ColorSet.WUBRG;
} else { // may choose any except original color
possibleNewColors = ColorSet.fromMask(originalColor).inverse();
possibleNewColors = ColorSet.fromEnums(originalColor).inverse();
}
final byte newColor = sa.getActivatingPlayer().getController().chooseColor(
MagicColor.Color newColor = sa.getActivatingPlayer().getController().chooseColor(
Localizer.getInstance().getMessage("lblChooseNewColor"), sa, possibleNewColors);
changedColorWordNew = TextUtil.capitalize(MagicColor.toLongString(newColor));
changedColorWordNew = TextUtil.capitalize(newColor.getName());
} else {
changedColorWordNew = changedColorWordsArray[1];
}

View File

@@ -114,15 +114,15 @@ public class ManaEffect extends SpellAbilityEffect {
// just use the first possible color.
choice = colorsProduced[differentChoice ? nMana : 0];
} else {
byte chosenColor = chooser.getController().chooseColor(Localizer.getInstance().getMessage("lblSelectManaProduce"), sa,
MagicColor.Color chosenColor = chooser.getController().chooseColor(Localizer.getInstance().getMessage("lblSelectManaProduce"), sa,
differentChoice && (colorsNeeded == null || colorsNeeded.length <= nMana) ? fullOptions : colorOptions);
if (chosenColor == 0)
if (chosenColor == null)
throw new RuntimeException("ManaEffect::resolve() /*combo mana*/ - " + p + " color mana choice is empty for " + card.getName());
if (differentChoice) {
fullOptions = ColorSet.fromMask(fullOptions.getColor() - chosenColor);
fullOptions = ColorSet.fromMask(fullOptions.getColor() - chosenColor.getColorMask());
}
choice = MagicColor.toShortString(chosenColor);
choice = chosenColor.getShortName();
}
if (nMana > 0) {
@@ -157,13 +157,13 @@ public class ManaEffect extends SpellAbilityEffect {
mask |= MagicColor.fromName(colorsNeeded.charAt(nChar));
}
colorMenu = mask == 0 ? ColorSet.WUBRG : ColorSet.fromMask(mask);
byte val = chooser.getController().chooseColor(Localizer.getInstance().getMessage("lblSelectManaProduce"), sa, colorMenu);
if (0 == val) {
MagicColor.Color val = chooser.getController().chooseColor(Localizer.getInstance().getMessage("lblSelectManaProduce"), sa, colorMenu);
if (val == null) {
throw new RuntimeException("ManaEffect::resolve() /*any mana*/ - " + p + " color mana choice is empty for " + card.getName());
}
game.getAction().notifyOfValue(sa, card, MagicColor.toSymbol(val), p);
abMana.setExpressChoice(MagicColor.toShortString(val));
game.getAction().notifyOfValue(sa, card, val.getSymbol(), p);
abMana.setExpressChoice(val.getShortName());
}
else if (abMana.isSpecialMana()) {
String type = abMana.getOrigProduced().split("Special ")[1];
@@ -178,22 +178,22 @@ public class ManaEffect extends SpellAbilityEffect {
for (ManaCostShard s : enchanted.getManaCost()) {
ColorSet cs = ColorSet.fromMask(s.getColorMask());
byte chosenColor;
MagicColor.Color chosenColor;
if (cs.isColorless())
continue;
if (s.isOr2Generic()) { // CR 106.8
chosenColor = chooser.getController().chooseColorAllowColorless(Localizer.getInstance().getMessage("lblChooseSingleColorFromTarget", s.toString()), card, cs);
if (chosenColor == MagicColor.COLORLESS) {
if (chosenColor == MagicColor.Color.COLORLESS) {
generic += 2;
continue;
}
}
else if (cs.isMonoColor())
chosenColor = s.getColorMask();
chosenColor = MagicColor.Color.fromByte(s.getColorMask());
else /* (cs.isMulticolor()) */ {
chosenColor = chooser.getController().chooseColor(Localizer.getInstance().getMessage("lblChooseSingleColorFromTarget", s.toString()), sa, cs);
}
sb.append(MagicColor.toShortString(chosenColor));
sb.append(chosenColor.getShortName());
sb.append(' ');
}
if (generic > 0) {
@@ -239,8 +239,8 @@ public class ManaEffect extends SpellAbilityEffect {
if (cs.isMonoColor())
sb.append(MagicColor.toShortString(s.getColorMask()));
else /* (cs.isMulticolor()) */ {
byte chosenColor = chooser.getController().chooseColor(Localizer.getInstance().getMessage("lblChooseSingleColorFromTarget", s.toString()), sa, cs);
sb.append(MagicColor.toShortString(chosenColor));
MagicColor.Color chosenColor = chooser.getController().chooseColor(Localizer.getInstance().getMessage("lblChooseSingleColorFromTarget", s.toString()), sa, cs);
sb.append(chosenColor.getShortName());
}
}
}

View File

@@ -95,7 +95,7 @@ public class ManaReflectedEffect extends SpellAbilityEffect {
}
if (mask == 0 && !expressChoiceColors.isEmpty() && colors.contains("colorless")) {
baseMana = MagicColor.toShortString(player.getController().chooseColorAllowColorless(Localizer.getInstance().getMessage("lblSelectManaProduce"), sa.getHostCard(), ColorSet.fromMask(mask)));
baseMana = player.getController().chooseColorAllowColorless(Localizer.getInstance().getMessage("lblSelectManaProduce"), sa.getHostCard(), ColorSet.fromMask(mask)).getShortName();
} else {
// Nothing set previously so ask player if needed
if (mask == 0) {
@@ -104,17 +104,17 @@ public class ManaReflectedEffect extends SpellAbilityEffect {
} else if (colors.size() == 1) {
baseMana = MagicColor.toShortString(colors.iterator().next());
} else if (colors.contains("colorless")) {
baseMana = MagicColor.toShortString(player.getController().chooseColorAllowColorless(Localizer.getInstance().getMessage("lblSelectManaProduce"), sa.getHostCard(), ColorSet.fromNames(colors)));
baseMana = player.getController().chooseColorAllowColorless(Localizer.getInstance().getMessage("lblSelectManaProduce"), sa.getHostCard(), ColorSet.fromNames(colors)).getShortName();
} else {
baseMana = MagicColor.toShortString(player.getController().chooseColor(Localizer.getInstance().getMessage("lblSelectManaProduce"), sa, ColorSet.fromNames(colors)));
baseMana = player.getController().chooseColor(Localizer.getInstance().getMessage("lblSelectManaProduce"), sa, ColorSet.fromNames(colors)).getShortName();
}
} else {
colorMenu = ColorSet.fromMask(mask);
byte color = sa.getActivatingPlayer().getController().chooseColor(Localizer.getInstance().getMessage("lblSelectManaProduce"), sa, colorMenu);
if (color == 0) {
MagicColor.Color color = sa.getActivatingPlayer().getController().chooseColor(Localizer.getInstance().getMessage("lblSelectManaProduce"), sa, colorMenu);
if (color == null) {
System.err.println("Unexpected behavior in ManaReflectedEffect: " + sa.getActivatingPlayer() + " - color mana choice is empty for " + sa.getHostCard().getName());
}
baseMana = MagicColor.toShortString(color);
baseMana = color.getShortName();
}
}

View File

@@ -34,15 +34,15 @@ public class ReplaceManaEffect extends SpellAbilityEffect {
// replace type and amount
replaced = sa.getParam("ReplaceMana");
if ("Any".equals(replaced)) {
byte rs = player.getController().chooseColor("Choose a color", sa, ColorSet.WUBRG);
replaced = MagicColor.toShortString(rs);
MagicColor.Color rs = player.getController().chooseColor("Choose a color", sa, ColorSet.WUBRG);
replaced = rs.getShortName();
}
} else if (sa.hasParam("ReplaceType")) {
// replace color and colorless
String color = sa.getParam("ReplaceType");
if ("Any".equals(color)) {
byte rs = player.getController().chooseColor("Choose a color", sa, ColorSet.WUBRG);
color = MagicColor.toShortString(rs);
MagicColor.Color rs = player.getController().chooseColor("Choose a color", sa, ColorSet.WUBRG);
color = rs.getShortName();
} else {
// convert in case Color Word used
color = MagicColor.toShortString(color);

View File

@@ -209,7 +209,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
private boolean renowned;
private boolean solved;
private boolean tributed;
private StaticAbility suspectedStatic = null;
private Card suspectedEffect = null;
private SpellAbility manifestedSA;
private SpellAbility cloakedSA;
@@ -6758,15 +6758,15 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
return true;
}
public StaticAbility getSuspectedStatic() {
return this.suspectedStatic;
public Card getSuspectedEffect() {
return this.suspectedEffect;
}
public void setSuspectedStatic(StaticAbility stAb) {
this.suspectedStatic = stAb;
public void setSuspectedEffect(Card effect) {
this.suspectedEffect = effect;
}
public final boolean isSuspected() {
return suspectedStatic != null;
return suspectedEffect != null;
}
public final boolean setSuspected(final boolean suspected) {
@@ -6779,15 +6779,23 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
return true;
}
String s = "Mode$ Continuous | AffectedDefined$ Self | AddKeyword$ Menace | AddStaticAbility$ SuspectedCantBlockBy";
suspectedStatic = StaticAbility.create(s, this, currentState, true);
suspectedStatic.putParam("Timestamp", String.valueOf(getGame().getNextTimestamp()));
suspectedEffect = SpellAbilityEffect.createEffect(null, this, this.getController(), "Suspected Effect", getImageKey(), getGame().getNextTimestamp());
suspectedEffect.setRenderForUI(false);
suspectedEffect.addRemembered(this);
String s = "Mode$ Continuous | AffectedDefined$ RememberedCard | EffectZone$ Command | AddKeyword$ Menace | AddStaticAbility$ SuspectedCantBlockBy";
StaticAbility suspectedStatic = suspectedEffect.addStaticAbility(s);
String effect = "Mode$ CantBlock | ValidCard$ Creature.Self | Description$ CARDNAME can't block.";
suspectedStatic.setSVar("SuspectedCantBlockBy", effect);
GameCommand until = SpellAbilityEffect.exileEffectCommand(getGame(), suspectedEffect);
addLeavesPlayCommand(until);
getGame().getAction().moveToCommand(suspectedEffect, null);
} else {
suspectedStatic = null;
if (isSuspected()) {
getGame().getAction().exileEffect(suspectedEffect);
suspectedEffect = null;
}
}
return true;
}
@@ -7285,15 +7293,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
}
}
public final FCollectionView<StaticAbility> getHiddenStaticAbilities() {
FCollection<StaticAbility> result = new FCollection<>();
// Suspected
if (this.isInPlay() && this.isSuspected()) {
result.add(suspectedStatic);
}
return result;
}
public final FCollectionView<Trigger> getTriggers() {
return currentState.getTriggers();
}

View File

@@ -315,7 +315,7 @@ public class CardCopyService {
newCopy.setSaddled(copyFrom.isSaddled());
if (newCopy.isSaddled()) newCopy.setSaddledByThisTurn(copyFrom.getSaddledByThisTurn());
if (copyFrom.isSuspected()) {
newCopy.setSuspectedStatic(copyFrom.getSuspectedStatic().copy(newCopy, true));
newCopy.setSuspectedEffect(getLKICopy(copyFrom.getSuspectedEffect(), cachedMap));
}
newCopy.setDamageHistory(copyFrom.getDamageHistory());

View File

@@ -3100,10 +3100,10 @@ public class Player extends GameEntity implements Comparable<Player> {
Player p = cmd.getController();
String prompt = Localizer.getInstance().getMessage("lblChooseAColorFor", cmd.getName());
SpellAbility cmdColorsa = new SpellAbility.EmptySa(ApiType.ChooseColor, cmd, p);
byte chosenColor = p.getController().chooseColor(prompt, cmdColorsa, ColorSet.WUBRG);
cmd.setChosenColors(List.of(MagicColor.toLongString(chosenColor)));
MagicColor.Color chosenColor = p.getController().chooseColor(prompt, cmdColorsa, ColorSet.WUBRG);
cmd.setChosenColors(List.of(chosenColor.getName()));
p.getGame().getAction().notifyOfValue(cmdColorsa, cmd,
Localizer.getInstance().getMessage("lblPlayerPickedChosen", p.getName(), MagicColor.toLongString(chosenColor)), p);
Localizer.getInstance().getMessage("lblPlayerPickedChosen", p.getName(), chosenColor.getName()), p);
}
}

View File

@@ -6,6 +6,7 @@ import com.google.common.collect.Multimap;
import forge.LobbyPlayer;
import forge.card.ColorSet;
import forge.card.ICardFace;
import forge.card.MagicColor;
import forge.card.mana.ManaCost;
import forge.card.mana.ManaCostShard;
import forge.deck.Deck;
@@ -265,8 +266,8 @@ public abstract class PlayerController {
public abstract boolean chooseFlipResult(SpellAbility sa, Player flipper, boolean[] results, boolean call);
public abstract byte chooseColor(String message, SpellAbility sa, ColorSet colors);
public abstract byte chooseColorAllowColorless(String message, Card c, ColorSet colors);
public abstract MagicColor.Color chooseColor(String message, SpellAbility sa, ColorSet colors);
public abstract MagicColor.Color chooseColorAllowColorless(String message, Card c, ColorSet colors);
public abstract List<String> chooseColors(String message, SpellAbility sa, int min, int max, List<String> options);
public abstract ICardFace chooseSingleCardFace(SpellAbility sa, String message, Predicate<ICardFace> cpp, String name);

View File

@@ -297,9 +297,7 @@ public class StaticAbility extends CardTraitBase implements IIdentifiable, Clone
* conditions are fulfilled.
*/
private boolean shouldApplyContinuousAbility(final StaticAbilityLayer layer, final boolean previousRun) {
return layers.contains(layer) && checkConditions(StaticAbilityMode.Continuous) && ( previousRun ||
getHostCard().getStaticAbilities().contains(this) ||
getHostCard().getHiddenStaticAbilities().contains(this));
return layers.contains(layer) && checkConditions(StaticAbilityMode.Continuous) && (previousRun || getHostCard().getStaticAbilities().contains(this));
}
public final Cost getAttackCost(final Card attacker, final GameEntity target, final List<Card> attackersWithOptionalCost) {
@@ -585,13 +583,6 @@ public class StaticAbility extends CardTraitBase implements IIdentifiable, Clone
.result();
}
public long getTimestamp() {
if (hasParam("Timestamp")) {
return Long.valueOf(getParam("Timestamp"));
}
return getHostCard().getLayerTimestamp();
}
@Override
public void setHostCard(Card host) {
super.setHostCard(host);

View File

@@ -25,6 +25,7 @@ import forge.GameCommand;
import forge.card.*;
import forge.game.Game;
import forge.game.StaticEffect;
import forge.game.StaticEffects;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.card.*;
@@ -92,11 +93,12 @@ public final class StaticAbilityContinuous {
final List<Player> affectedPlayers = StaticAbilityContinuous.getAffectedPlayers(stAb);
final Game game = hostCard.getGame();
final StaticEffect se = game.getStaticEffects().getStaticEffect(stAb);
final StaticEffects effects = game.getStaticEffects();
final StaticEffect se = effects.getStaticEffect(stAb);
se.setAffectedCards(affectedCards);
se.setAffectedPlayers(affectedPlayers);
se.setParams(params);
se.setTimestamp(stAb.getTimestamp());
se.setTimestamp(hostCard.getLayerTimestamp());
// nothing more to do
if (stAb.hasParam("Affected") && affectedPlayers.isEmpty() && affectedCards.isEmpty()) {

View File

@@ -470,13 +470,16 @@ public class PlayerControllerForTests extends PlayerController {
}
@Override
public byte chooseColor(String message, SpellAbility sa, ColorSet colors) {
return Iterables.getFirst(colors, MagicColor.Color.WHITE).getColorMask();
public MagicColor.Color chooseColor(String message, SpellAbility sa, ColorSet colors) {
if (colors.countColors() == 0) {
return null;
}
return Iterables.getFirst(colors, MagicColor.Color.WHITE);
}
@Override
public byte chooseColorAllowColorless(String message, Card card, ColorSet colors) {
return Iterables.getFirst(colors, MagicColor.Color.COLORLESS).getColorMask();
public MagicColor.Color chooseColorAllowColorless(String message, Card card, ColorSet colors) {
return Iterables.getFirst(colors, MagicColor.Color.COLORLESS);
}
private CardCollection chooseItems(CardCollectionView items, int amount) {

View File

@@ -1556,7 +1556,7 @@
},
{
"name": "Amulet of the Deceiver",
"equipmentSlot": "Neck",
"equipmentSlot": "Head",
"iconName": "MoxJet",
"effect": {
"lifeModifier": -1,

View File

@@ -1343,7 +1343,7 @@
},
{
"name": "Helm of Obedience",
"equipmentSlot": "Neck",
"equipmentSlot": "Head",
"iconName": "MoxJet",
"effect": {
"lifeModifier": -1,

View File

@@ -1556,7 +1556,7 @@
},
{
"name": "Amulet of the Deceiver",
"equipmentSlot": "Neck",
"equipmentSlot": "Head",
"iconName": "MoxJet",
"effect": {
"lifeModifier": -1,

View File

@@ -145,4 +145,3 @@ Final Fantasy, 3/6/FIN, FIN
Alchemy: Innistrad, 3/6/ISD, YMID
Edge of Eternities, 3/6/EOE, EOE
Marvel's Spider-Man, 3/6/SPM, SPM
Avatar: The Last Airbender, 3/6/TLA, TLA

View File

@@ -1,23 +0,0 @@
Name:Aang, Swift Savior
ManaCost:1 W U
Types:Legendary Creature Human Avatar Ally
PT:2/3
K:Flash
K:Flying
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigAirbend | TriggerDescription$ When NICKNAME enters, airbend up to one other target creature or spell. (Exile it. While it's exiled, its owner may cast it for {2} rather than its mana cost.)
SVar:TrigAirbend:DB$ Airbend | ValidTgts$ Creature.Other,Card.inZoneStack | TgtPrompt$ Select up to another target creature or spell | TgtZone$ Battlefield,Stack | TargetMin$ 0
A:AB$ SetState | Cost$ Waterbend<8> | Defined$ Self | Mode$ Transform | SpellDescription$ Transform NICKNAME.
AlternateMode:DoubleFaced
Oracle:Flash\nFlying\nWhen Aang enters, airbend up to one other target creature or spell. (Exile it. While it's exiled, its owner may cast it for {2} rather than its mana cost.)\nWaterbend {8}: Transform Aang.
ALTERNATE
Name:Aang and La, Ocean's Fury
ManaCost:no cost
Types:Legendary Creature Avatar Spirit Ally
PT:5/5
K:Reach
K:Trample
T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigPutCounterAll | TriggerDescription$ Whenever NICKNAME attack, put a +1/+1 counter on each tapped creature you control.
SVar:TrigPutCounterAll:DB$ PutCounterAll | ValidCards$ Creature.YouCtrl+tapped | CounterType$ P1P1 | CounterNum$ 1
Oracle:Reach, trample\nWhenever Aang and La attack, put a +1/+1 counter on each tapped creature you control.

View File

@@ -1,10 +0,0 @@
Name:Benevolent River Spirit
ManaCost:U U
Types:Creature Spirit
PT:4/5
S:Mode$ RaiseCost | ValidCard$ Card.Self | Activator$ You | Type$ Spell | Cost$ Waterbend<5> | EffectZone$ All | Description$ As an additional cost to cast this spell, waterbend {5}. (While paying a waterbend cost, you can tap your artifacts and creatures to help. Each one pays for {1}.)
K:Flying
K:Ward:2
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigScry | TriggerDescription$ When this creature enters, scry 2.
SVar:TrigScry:DB$ Scry | ScryNum$ 2
Oracle:As an additional cost to cast this spell, waterbend {5}. (While paying a waterbend cost, you can tap your artifacts and creatures to help. Each one pays for {1}.)\nFlying, ward {2} (Whenever this creature becomes the target of a spell or ability an opponent controls, counter it unless that player pays {2}.)\nWhen this creature enters, scry 2.

View File

@@ -1,8 +0,0 @@
Name:Crashing Wave
ManaCost:U U
Types:Sorcery
S:Mode$ RaiseCost | ValidCard$ Card.Self | Activator$ You | Type$ Spell | Cost$ Waterbend<X> | EffectZone$ All | Description$ As an additional cost to cast this spell, waterbend {X}. (While paying a waterbend cost, you can tap your artifacts and creatures to help. Each one pays for {1}.)
A:SP$ Tap | TargetMin$ 0 | TargetMax$ X | TgtPrompt$ Select up to X target creatures to tap | ValidTgts$ Creature | SubAbility$ DBPutCounter
SVar:DBPutCounter:DB$ PutCounter | Choices$ Creature.tapped+OppCtrl | ChoiceTitle$ Choose any number of tapped creatures your opponents control to distribute counters to | CounterType$ STUN | CounterNum$ 3 | ChoiceAmount$ 3 | DividedAsYouChoose$ 3
SVar:X:Count$xPaid
Oracle:As an additional cost to cast this spell, waterbend {X}. (While paying a waterbend cost, you can tap your artifacts and creatures to help. Each one pays for {1}.)\nTap up to X target creatures, then distribute three stun counters among tapped creatures your opponents control. (If a permanent with a stun counter would become untapped, remove one from it instead.)

View File

@@ -1,10 +0,0 @@
Name:Foggy Swamp Visions
ManaCost:1 B B
Types:Sorcery
S:Mode$ RaiseCost | ValidCard$ Card.Self | Activator$ You | Type$ Spell | Cost$ Waterbend<X> | EffectZone$ All | Description$ As an additional cost to cast this spell, waterbend {X}. (While paying a waterbend cost, you can tap your artifacts and creatures to help. Each one pays for {1}.)
A:SP$ ChangeZone | Origin$ Graveyard | Destination$ Exile | ValidTgts$ Creature | TgtPrompt$ Choose X target creature cards from graveyards | TargetMin$ X | TargetMax$ X | SubAbility$ DBClone | RememberChanged$ True | SpellDescription$ Exile X target creature cards from graveyards. For each creature card exiled this way, create a token that's a copy of it. At the beginning of your next end step, sacrifice those tokens.
SVar:DBClone:DB$ CopyPermanent | Defined$ Remembered | NumCopies$ 1 | AtEOT$ YourSacrifice | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:X:Count$xPaid
DeckHas:Ability$Token|Graveyard
Oracle:As an additional cost to cast this spell, waterbend {X}. (While paying a waterbend cost, you can tap your artifacts and creatures to help. Each one pays for {1}.)\nExile X target creature cards from graveyards. For each creature card exiled this way, create a token that's a copy of it. At the beginning of your next end step, sacrifice those tokens.

View File

@@ -1,11 +0,0 @@
Name:Hama, the Bloodbender
ManaCost:2 UB UB UB
Types:Legendary Creature Human Warlock
PT:3/3
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigMill | TriggerDescription$ When NICKNAME enters, target opponent mills three cards. Exile up to one noncreature, nonland card from that player's graveyard. For as long as you control NICKNAME, you may cast the exiled card during your turn by waterbending {X} rather than paying its mana cost, where X is its mana value. (While paying a waterbend cost, you can tap your artifacts and creatures to help. Each one pays for {1}.)
SVar:TrigMill:DB$ Mill | NumCards$ 3 | ValidTgts$ Opponent | TgtPrompt$ Select target opponent to mill | SubAbility$ DBChangeZone
SVar:DBChangeZone:DB$ ChangeZone | Optional$ True | Origin$ Graveyard | Destination$ Exile | DefinedPlayer$ Targeted | Chooser$ You | ChangeType$ Card.nonCreature+nonLand | ChangeNum$ 1 | IsCurse$ True | RememberChanged$ True | SubAbility$ DBEffect
SVar:DBEffect:DB$ Effect | StaticAbilities$ STPlay | RememberObjects$ Remembered | Duration$ AsLongAsControl | ExileOnMoved$ Exile | SubAbility$ DBCleanup
SVar:STPlay:Mode$ Continuous | MayPlay$ True | Condition$ PlayerTurn | Affected$ Card.IsRemembered | AffectedZone$ Exile | MayPlayAltManaCost$ Waterbend<ConvertedManaCost> | Description$ You may cast that card during your turn by waterbending {X} rather than paying its mana cost, where X is its mana value.
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
Oracle:When Hama enters, target opponent mills three cards. Exile up to one noncreature, nonland card from that player's graveyard. For as long as you control Hama, you may cast the exiled card during your turn by waterbending {X} rather than paying its mana cost, where X is its mana value. (While paying a waterbend cost, you can tap your artifacts and creatures to help. Each one pays for {1}.)

View File

@@ -1,13 +0,0 @@
Name:Katara, Seeking Revenge
ManaCost:3 UB
Types:Legendary Creature Human Warrior Ally
PT:3/3
S:Mode$ OptionalCost | EffectZone$ All | ValidCard$ Card.Self | ValidSA$ Spell | Cost$ Waterbend<2> | Description$ As an additional cost to cast this spell, you may waterbend {2}. (While paying a waterbend cost, you can tap your artifacts and creatures to help. Each one pays for {1}.)
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDraw | TriggerDescription$ When NICKNAME enters, draw a card, then discard a card unless her additional cost was paid.
SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ 1 | SubAbility$ DBDiscard
SVar:DBDiscard:DB$ Discard | Defined$ You | NumCards$ X | Mode$ TgtChoose
S:Mode$ Continuous | Affected$ Card.Self | AddPower$ Y | AddToughness$ Y | Description$ NICKNAME gets +1/+1 for each Lesson card in your graveyard.
SVar:X:Count$OptionalGenericCostPaid.0.1
SVar:Y:Count$ValidGraveyard Lesson.YouOwn
DeckHas:Ability$Discard
Oracle:As an additional cost to cast this spell, you may waterbend {2}. (While paying a waterbend cost, you can tap your artifacts and creatures to help. Each one pays for {1}.)\nWhen Katara enters, draw a card, then discard a card unless her additional cost was paid.\nKatara gets +1/+1 for each Lesson card in your graveyard.

View File

@@ -3,8 +3,8 @@ ManaCost:W
Types:Legendary Creature Lemur Bat Ally
PT:1/1
K:Flying
S:Mode$ ReduceCost | ValidCard$ Creature.nonLemur+withFlying+YouCtrl | Type$ Spell | Activator$ You | Amount$ 1 | Condition$ PlayerTurn | CheckSVar$ X | SVarCompare$ EQ0 | Description$ The first non-Lemur creature spell with flying you cast during each of your turns costs {1} less to cast.
S:Mode$ ReduceCost | ValidCard$ Creature.nonLemur+hasFlying+YouCtrl | Type$ Spell | Activator$ You | Amount$ 1 | Condition$ PlayerTurn | CheckSVar$ X | SVarCompare$ EQ0 | Description$ The first non-Lemur creature spell with flying you cast during each of your turns costs {1} less to cast.
T:Mode$ ChangesZone | ValidCard$ Creature.Other+YouCtrl+withFlying | Origin$ Any | Destination$ Battlefield | Execute$ TrigPump | TriggerZones$ Battlefield | TriggerDescription$ Whenever another creature you control with flying enters, NICKNAME gets +1/+1 until end of turn.
SVar:TrigPump:DB$ Pump | Defined$ Self | NumAtt$ +1 | NumDef$ +1
SVar:X:Count$ThisTurnCast_Creature.nonLemur+withFlying+YouCtrl
SVar:X:Count$ThisTurnCast_Creature.nonLemur+hasFlying+YouCtrl
Oracle:Flying\nThe first non-Lemur creature spell with flying you cast during each of your turns costs {1} less to cast.\nWhenever another creature you control with flying enters, Momo gets +1/+1 until end of turn.

View File

@@ -1,12 +0,0 @@
Name:Planetarium of Wan Shi Tong
ManaCost:6
Types:Legendary Artifact
A:AB$ Scry | Cost$ 1 T | ScryNum$ 2 | SpellDescription$ Scry 2.
T:Mode$ Scry | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigPeek | PresentDefined$ Remembered | IsPresent$ Card | PresentCompare$ EQ0 | TriggerDescription$ Whenever you scry or surveil, look at the top card of your library. You may cast that card without paying its mana cost. Do this only once each turn. (Look at the card after you scry or surveil.)
T:Mode$ Surveil | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigPeek | Secondary$ True | PresentDefined$ Remembered | IsPresent$ Card | PresentCompare$ EQ0 | TriggerDescription$ Whenever you scry or surveil, look at the top card of your library. You may cast that card without paying its mana cost. Do this only once each turn. (Look at the card after you scry or surveil.)
SVar:TrigPeek:DB$ PeekAndReveal | NoReveal$ True | SubAbility$ TrigPlay
SVar:TrigPlay:DB$ Play | Defined$ TopOfLibrary | WithoutManaCost$ True | ValidSA$ Spell | Optional$ True | RememberPlayed$ True | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ EQ0
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ DBCleanup | Static$ True
T:Mode$ TurnBegin | ValidPlayer$ Player | Execute$ DBCleanup | Static$ True
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
Oracle:{1}, {T}: Scry 2.\nWhenever you scry or surveil, look at the top card of your library. You may cast that card without paying its mana cost. Do this only once each turn. (Look at the card after you scry or surveil.)

View File

@@ -1,12 +0,0 @@
Name:Spirit Water Revival
ManaCost:1 U U
Types:Sorcery
S:Mode$ OptionalCost | EffectZone$ All | ValidCard$ Card.Self | ValidSA$ Spell | Cost$ Waterbend<6> | Description$ As an additional cost to cast this spell, you may waterbend {6}. (While paying a waterbend cost, you can tap your artifacts and creatures to help. Each one pays for {1}.)
A:SP$ ChangeZoneAll | ChangeType$ Card.YouOwn | Origin$ Graveyard | Destination$ Library | Shuffle$ True | ConditionCheckSVar$ X | SubAbility$ DBDraw | SpellDescription$ Draw two cards. If this spell's additional cost was paid, instead shuffle your graveyard into your library, draw seven cards, and you have no maximum hand size for the rest of the game. Exile CARDNAME.
SVar:DBDraw:DB$ Draw | NumCards$ Y | SubAbility$ DBEffect
SVar:DBEffect:DB$ Effect | StaticAbilities$ STHandSize | Duration$ Permanent | ConditionCheckSVar$ X | SubAbility$ DBExile
SVar:STHandSize:Mode$ Continuous | Affected$ You | SetMaxHandSize$ Unlimited | Description$ You have no maximum hand size for the rest of the game.
SVar:DBExile:DB$ ChangeZone | Origin$ Stack | Destination$ Exile
SVar:X:Count$OptionalGenericCostPaid.1.0
SVar:Y:Count$OptionalGenericCostPaid.7.2
Oracle:As an additional cost to cast this spell, you may waterbend {6}. (While paying a waterbend cost, you can tap your artifacts and creatures to help. Each one pays for {1}.)\nDraw two cards. If this spell's additional cost was paid, instead shuffle your graveyard into your library, draw seven cards, and you have no maximum hand size for the rest of the game.\nExile Spirit Water Revival.

View File

@@ -1,8 +0,0 @@
Name:Tolls of War
ManaCost:W B
Types:Enchantment
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigClue | TriggerDescription$ When this enchantment enters, create a Clue token. (It's an artifact with "{2}, Sacrifice this token: Draw a card.")
SVar:TrigClue:DB$ Token | TokenAmount$ 1 | TokenScript$ c_a_clue_draw | TokenOwner$ You
T:Mode$ Sacrificed | ValidCard$ Permanent.YouCtrl | Execute$ TrigAlly | TriggerZones$ Battlefield | ValidPlayer$ You | PlayerTurn$ True | ActivationLimit$ 1 | TriggerDescription$ Whenever you sacrifice a permanent during your turn, create a 1/1 white Ally creature token. This ability triggers only once each turn.
SVar:TrigAlly:DB$ Token | TokenScript$ w_1_1_ally
Oracle:When this enchantment enters, create a Clue token. (It's an artifact with "{2}, Sacrifice this token: Draw a card.")\nWhenever you sacrifice a permanent during your turn, create a 1/1 white Ally creature token. This ability triggers only once each turn.

View File

@@ -1,7 +0,0 @@
Name:True Ancestry
ManaCost:1 G
Types:Sorcery Lesson
A:SP$ ChangeZone | Origin$ Graveyard | Destination$ Hand | ValidTgts$ Permanent.YouOwn | TargetMin$ 0 | TargetMax$ 1 | TgtPrompt$ Choose up to one target creature card in your graveyard | SubAbility$ DBToken | SpellDescription$ Return up to one target permanent card from your graveyard to your hand. Create a Clue token. (It's an artifact with "{2}, Sacrifice this token: Draw a card.")
SVar:DBToken:DB$ Token | TokenAmount$ 1 | TokenScript$ c_a_clue_draw | TokenOwner$ You
DeckHints:Ability$Graveyard|Token
Oracle:Return up to one target permanent card from your graveyard to your hand.\nCreate a Clue token. (It's an artifact with "{2}, Sacrifice this token: Draw a card.")

Some files were not shown because too many files have changed in this diff Show More