Compare commits

..

38 Commits

Author SHA1 Message Date
Chris H
54b4f3edf1 Migrate TLA 2025-11-15 20:48:05 -05:00
tool4EvEr
e95673e0a2 Fix logic 2025-11-15 19:04:53 -05:00
Chris H
41b39e74c3 Airbend spells 2025-11-15 19:04:53 -05:00
Fulgur14
b0d5fc5709 Update hama_the_bloodbender.txt 2025-11-15 15:08:20 -05:00
Fulgur14
7b60e06551 Update crashing_wave.txt 2025-11-15 15:08:20 -05:00
Fulgur14
c3869f4fba Update crashing_wave.txt 2025-11-15 15:08:20 -05:00
Fulgur14
82305a3b1a Update spirit_water_revival.txt 2025-11-15 15:08:20 -05:00
Fulgur14
edf021949e Update spirit_water_revival.txt 2025-11-15 15:08:20 -05:00
Fulgur14
4ba2cb8737 Update crashing_wave.txt 2025-11-15 15:08:20 -05:00
Fulgur14
9a17a9676c Create water_whip.txt 2025-11-15 15:08:20 -05:00
Fulgur14
0a54cb0f11 Create katara_seeking_revenge.txt 2025-11-15 15:08:20 -05:00
Fulgur14
24beee1d4c Create spirit_water_revival.txt 2025-11-15 15:08:20 -05:00
Fulgur14
95fe9d3dd8 Create hama_the_bloodbender.txt 2025-11-15 15:08:20 -05:00
Fulgur14
a24ae850b6 Create foggy_swamp_visions.txt 2025-11-15 15:08:20 -05:00
Fulgur14
0e303c4cc7 Create crashing_wave.txt 2025-11-15 15:08:20 -05:00
Fulgur14
d0d2baef24 Create benevolent_river_spirit.txt 2025-11-15 15:08:20 -05:00
Fulgur14
2ac56916dd More waterbending cards 2025-11-15 15:08:20 -05:00
Hans Mackowiak
8f5276d10d HiddenStaticAbilities: add Statics without the need for Effect objects 2025-11-15 10:57:12 +01:00
tool4ever
c226ec5b1e Update momo_friendly_flier.txt
Closes #9165
2025-11-15 08:37:55 +00:00
Fulgur14
89dfb252e8 Changed Planetarium of Wan Shi Ton's static trigger 2025-11-14 18:40:48 +01:00
Fulgur14
940264c537 Update planetarium_of_wan_shi_tong.txt
I realized that it does need ChangesZone to handle the case where it's blinked or something.
2025-11-14 17:55:21 +01:00
Hans Mackowiak
95dcd8984e StaticAbility: add getTimestamp using getLayerTimestamp (#9160)
* StaticAbility: add getTimestamp using getLayerTimestamp
2025-11-14 17:28:18 +01:00
Fulgur14
37b5503e77 Planetarium of Wan Shi Tong (TLA) 2025-11-14 16:03:13 +01:00
aryst0krat
70b6ae1461 Fix invalid item slot #9149 2025-11-14 15:58:43 +01:00
tool4ever
78f4b13744 Update Unblockable ability source in giant_koi.txt 2025-11-14 12:32:43 +01:00
tool4ever
dff971e9e8 Update true_ancestry.txt 2025-11-14 12:29:47 +01:00
tool4ever
c049d3c905 Update tolls_of_war.txt 2025-11-14 10:19:14 +01:00
tool4ever
1dbdd49a99 Update regex for fixing markdown links
hyperlink support
2025-11-13 16:30:42 +00:00
Jetz72
b6c775eec5 Merge pull request #9155 from Jetz72/fixes20251112
Automatically swap flavor names into oracle text
2025-11-13 08:33:01 -06:00
Hans Mackowiak
164d1573e0 Update squall_gunblade_duelist.txt 2025-11-12 21:25:17 +01:00
Hans Mackowiak
80ef4c2d19 Update the_legend_of_kuruk_avatar_kuruk.txt 2025-11-12 20:46:26 +01:00
tool4ever
e114819cc4 Change mana production from U to R in fire_nation_palace 2025-11-12 16:38:14 +01:00
Jetz
cdfe5ee18b Make that failsafe not break oracle text 2025-11-12 09:50:54 -05:00
Jetz
f2feb5edf8 Failsafe in case Android decides to explode due to complicated regex syntax again. 2025-11-12 09:41:29 -05:00
Jetz
22a9b173a5 Replace flavor names into oracle text. 2025-11-12 09:28:51 -05:00
Paul Hammerton
435af883c5 update tla.rnk 2025-11-12 07:36:06 -05:00
Chris H
154b40b24d Fix typo 2025-11-12 07:36:06 -05:00
Chris H
cd09193924 Add TLA Booster info and Rankins 2025-11-12 07:36:06 -05:00
401 changed files with 851 additions and 152 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\)|\1\3)|g' '{}' \;
run: find ${{ github.workspace }}/docs/ -type f -name "*.md" -exec sed -i -E 's|(\[[^]]+]\()([^)]+\/)*([^).]+).md(#)*([[:alnum:]]*)\)|\1\3\4\5)|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,8 +241,7 @@ 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) {
MagicColor.Color chosenColor = chooseColor("", sa, colorSet);
Byte chosen = chosenColor == null ? (byte)0 : chosenColor.getColorMask();
Byte chosen = chooseColor("", sa, colorSet);
if (result.containsKey(chosen)) {
result.put(chosen, result.get(chosen) + 1);
} else {
@@ -1023,22 +1022,19 @@ public class PlayerControllerAi extends PlayerController {
}
@Override
public MagicColor.Color chooseColorAllowColorless(String message, Card card, ColorSet colors) {
public byte 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 MagicColor.Color.fromByte(chosenColorMask);
return chosenColorMask;
}
return Iterables.getFirst(colors, MagicColor.Color.COLORLESS);
return Iterables.getFirst(colors, MagicColor.Color.COLORLESS).getColorMask();
}
@Override
public MagicColor.Color chooseColor(String message, SpellAbility sa, ColorSet colors) {
if (colors.countColors() == 0) {
return null;
}
public byte chooseColor(String message, SpellAbility sa, ColorSet colors) {
if (colors.countColors() < 2) {
return Iterables.getFirst(colors, MagicColor.Color.WHITE);
return Iterables.getFirst(colors, MagicColor.Color.WHITE).getColorMask();
}
// You may switch on sa.getApi() here and use sa.getParam("AILogic")
CardCollectionView hand = player.getCardsIn(ZoneType.Hand);
@@ -1049,9 +1045,9 @@ public class PlayerControllerAi extends PlayerController {
byte chosenColorMask = MagicColor.fromName(c);
if ((colors.getColor() & chosenColorMask) != 0) {
return MagicColor.Color.fromByte(chosenColorMask);
return chosenColorMask;
}
return Iterables.getFirst(colors, MagicColor.Color.WHITE);
return Iterables.getFirst(colors, MagicColor.Color.WHITE).getColorMask();
}
@Override

View File

@@ -1,10 +1,12 @@
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;
//
@@ -185,7 +187,25 @@ final class CardFace implements ICardFace, Cloneable {
}
void assignMissingFieldsToVariant(CardFace variant) {
if(variant.oracleText == null) variant.oracleText = this.oracleText;
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.manaCost == null) variant.manaCost = this.manaCost;
if(variant.color == null) variant.color = ColorSet.fromManaCost(variant.manaCost);

View File

@@ -504,16 +504,11 @@ 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,6 +1027,22 @@ 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(s -> s.getHostCard().getLayerTimestamp());
.thenComparing(StaticAbility::getTimestamp);
public GameAction(Game game0) {
game = game0;
@@ -1103,11 +1103,16 @@ public class GameAction {
if (stAb.checkMode(StaticAbilityMode.Continuous) && stAb.zonesCheck()) {
staticAbilities.add(stAb);
}
}
if (!co.getStaticCommandList().isEmpty()) {
staticList.add(co);
}
return true;
}
if (!co.getStaticCommandList().isEmpty()) {
staticList.add(co);
}
for (StaticAbility stAb : co.getHiddenStaticAbilities()) {
if (stAb.checkMode(StaticAbilityMode.Continuous) && stAb.zonesCheck()) {
staticAbilities.add(stAb);
}
}
return true;
}
}, true);
@@ -1333,7 +1338,7 @@ public class GameAction {
// now the earliest one left is the correct choice
List<StaticAbility> statics = Lists.newArrayList(dependencyGraph.vertexSet());
statics.sort(Comparator.comparing(s -> s.getHostCard().getLayerTimestamp()));
statics.sort(Comparator.comparing(StaticAbility::getTimestamp));
return statics.get(0);
}

View File

@@ -11,6 +11,7 @@ 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;
@@ -47,7 +48,7 @@ public class AirbendEffect extends SpellAbilityEffect {
final CardZoneTable triggerList = CardZoneTable.getSimultaneousInstance(sa);
for (Card c : getTargetCards(sa)) {
for (Card c : getCardsfromTargets(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
@@ -55,21 +56,27 @@ 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);
Card movedCard = game.getAction().exile(gameCard, sa, moveParams);
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);
@@ -84,6 +91,7 @@ public class AirbendEffect extends SpellAbilityEffect {
game.getAction().moveToCommand(eff, sa);
}
triggerList.triggerChangesZoneAll(game, sa);
handleExiledWith(triggerList.allCards(), sa);

View File

@@ -30,28 +30,27 @@ public class ChangeTextEffect extends SpellAbilityEffect {
final String changedColorWordOriginal, changedColorWordNew;
if (sa.hasParam("ChangeColorWord")) {
// all instances are Choose Choose
MagicColor.Color originalColor = null;
byte originalColor = 0;
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(originalColor.getName());
changedColorWordOriginal = TextUtil.capitalize(MagicColor.toLongString(originalColor));
} else {
changedColorWordOriginal = changedColorWordsArray[0];
originalColor = MagicColor.Color.fromByte(MagicColor.fromName(changedColorWordOriginal));
originalColor = MagicColor.fromName(changedColorWordOriginal);
}
if (changedColorWordsArray[1].equals("Choose")) {
final ColorSet possibleNewColors;
if (originalColor == null) { // no original color (ie. any or absent)
if (originalColor == 0) { // no original color (ie. any or absent)
possibleNewColors = ColorSet.WUBRG;
} else { // may choose any except original color
possibleNewColors = ColorSet.fromEnums(originalColor).inverse();
possibleNewColors = ColorSet.fromMask(originalColor).inverse();
}
MagicColor.Color newColor = sa.getActivatingPlayer().getController().chooseColor(
final byte newColor = sa.getActivatingPlayer().getController().chooseColor(
Localizer.getInstance().getMessage("lblChooseNewColor"), sa, possibleNewColors);
changedColorWordNew = TextUtil.capitalize(newColor.getName());
changedColorWordNew = TextUtil.capitalize(MagicColor.toLongString(newColor));
} 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 {
MagicColor.Color chosenColor = chooser.getController().chooseColor(Localizer.getInstance().getMessage("lblSelectManaProduce"), sa,
byte chosenColor = chooser.getController().chooseColor(Localizer.getInstance().getMessage("lblSelectManaProduce"), sa,
differentChoice && (colorsNeeded == null || colorsNeeded.length <= nMana) ? fullOptions : colorOptions);
if (chosenColor == null)
if (chosenColor == 0)
throw new RuntimeException("ManaEffect::resolve() /*combo mana*/ - " + p + " color mana choice is empty for " + card.getName());
if (differentChoice) {
fullOptions = ColorSet.fromMask(fullOptions.getColor() - chosenColor.getColorMask());
fullOptions = ColorSet.fromMask(fullOptions.getColor() - chosenColor);
}
choice = chosenColor.getShortName();
choice = MagicColor.toShortString(chosenColor);
}
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);
MagicColor.Color val = chooser.getController().chooseColor(Localizer.getInstance().getMessage("lblSelectManaProduce"), sa, colorMenu);
if (val == null) {
byte val = chooser.getController().chooseColor(Localizer.getInstance().getMessage("lblSelectManaProduce"), sa, colorMenu);
if (0 == val) {
throw new RuntimeException("ManaEffect::resolve() /*any mana*/ - " + p + " color mana choice is empty for " + card.getName());
}
game.getAction().notifyOfValue(sa, card, val.getSymbol(), p);
abMana.setExpressChoice(val.getShortName());
game.getAction().notifyOfValue(sa, card, MagicColor.toSymbol(val), p);
abMana.setExpressChoice(MagicColor.toShortString(val));
}
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());
MagicColor.Color chosenColor;
byte 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.Color.COLORLESS) {
if (chosenColor == MagicColor.COLORLESS) {
generic += 2;
continue;
}
}
else if (cs.isMonoColor())
chosenColor = MagicColor.Color.fromByte(s.getColorMask());
chosenColor = s.getColorMask();
else /* (cs.isMulticolor()) */ {
chosenColor = chooser.getController().chooseColor(Localizer.getInstance().getMessage("lblChooseSingleColorFromTarget", s.toString()), sa, cs);
}
sb.append(chosenColor.getShortName());
sb.append(MagicColor.toShortString(chosenColor));
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()) */ {
MagicColor.Color chosenColor = chooser.getController().chooseColor(Localizer.getInstance().getMessage("lblChooseSingleColorFromTarget", s.toString()), sa, cs);
sb.append(chosenColor.getShortName());
byte chosenColor = chooser.getController().chooseColor(Localizer.getInstance().getMessage("lblChooseSingleColorFromTarget", s.toString()), sa, cs);
sb.append(MagicColor.toShortString(chosenColor));
}
}
}

View File

@@ -95,7 +95,7 @@ public class ManaReflectedEffect extends SpellAbilityEffect {
}
if (mask == 0 && !expressChoiceColors.isEmpty() && colors.contains("colorless")) {
baseMana = player.getController().chooseColorAllowColorless(Localizer.getInstance().getMessage("lblSelectManaProduce"), sa.getHostCard(), ColorSet.fromMask(mask)).getShortName();
baseMana = MagicColor.toShortString(player.getController().chooseColorAllowColorless(Localizer.getInstance().getMessage("lblSelectManaProduce"), sa.getHostCard(), ColorSet.fromMask(mask)));
} 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 = player.getController().chooseColorAllowColorless(Localizer.getInstance().getMessage("lblSelectManaProduce"), sa.getHostCard(), ColorSet.fromNames(colors)).getShortName();
baseMana = MagicColor.toShortString(player.getController().chooseColorAllowColorless(Localizer.getInstance().getMessage("lblSelectManaProduce"), sa.getHostCard(), ColorSet.fromNames(colors)));
} else {
baseMana = player.getController().chooseColor(Localizer.getInstance().getMessage("lblSelectManaProduce"), sa, ColorSet.fromNames(colors)).getShortName();
baseMana = MagicColor.toShortString(player.getController().chooseColor(Localizer.getInstance().getMessage("lblSelectManaProduce"), sa, ColorSet.fromNames(colors)));
}
} else {
colorMenu = ColorSet.fromMask(mask);
MagicColor.Color color = sa.getActivatingPlayer().getController().chooseColor(Localizer.getInstance().getMessage("lblSelectManaProduce"), sa, colorMenu);
if (color == null) {
byte color = sa.getActivatingPlayer().getController().chooseColor(Localizer.getInstance().getMessage("lblSelectManaProduce"), sa, colorMenu);
if (color == 0) {
System.err.println("Unexpected behavior in ManaReflectedEffect: " + sa.getActivatingPlayer() + " - color mana choice is empty for " + sa.getHostCard().getName());
}
baseMana = color.getShortName();
baseMana = MagicColor.toShortString(color);
}
}

View File

@@ -34,15 +34,15 @@ public class ReplaceManaEffect extends SpellAbilityEffect {
// replace type and amount
replaced = sa.getParam("ReplaceMana");
if ("Any".equals(replaced)) {
MagicColor.Color rs = player.getController().chooseColor("Choose a color", sa, ColorSet.WUBRG);
replaced = rs.getShortName();
byte rs = player.getController().chooseColor("Choose a color", sa, ColorSet.WUBRG);
replaced = MagicColor.toShortString(rs);
}
} else if (sa.hasParam("ReplaceType")) {
// replace color and colorless
String color = sa.getParam("ReplaceType");
if ("Any".equals(color)) {
MagicColor.Color rs = player.getController().chooseColor("Choose a color", sa, ColorSet.WUBRG);
color = rs.getShortName();
byte rs = player.getController().chooseColor("Choose a color", sa, ColorSet.WUBRG);
color = MagicColor.toShortString(rs);
} 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 Card suspectedEffect = null;
private StaticAbility suspectedStatic = null;
private SpellAbility manifestedSA;
private SpellAbility cloakedSA;
@@ -6758,15 +6758,15 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
return true;
}
public Card getSuspectedEffect() {
return this.suspectedEffect;
public StaticAbility getSuspectedStatic() {
return this.suspectedStatic;
}
public void setSuspectedEffect(Card effect) {
this.suspectedEffect = effect;
public void setSuspectedStatic(StaticAbility stAb) {
this.suspectedStatic = stAb;
}
public final boolean isSuspected() {
return suspectedEffect != null;
return suspectedStatic != null;
}
public final boolean setSuspected(final boolean suspected) {
@@ -6779,23 +6779,15 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
return true;
}
suspectedEffect = SpellAbilityEffect.createEffect(null, this, this.getController(), "Suspected Effect", getImageKey(), getGame().getNextTimestamp());
suspectedEffect.setRenderForUI(false);
suspectedEffect.addRemembered(this);
String s = "Mode$ Continuous | AffectedDefined$ Self | AddKeyword$ Menace | AddStaticAbility$ SuspectedCantBlockBy";
suspectedStatic = StaticAbility.create(s, this, currentState, true);
suspectedStatic.putParam("Timestamp", String.valueOf(getGame().getNextTimestamp()));
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 {
if (isSuspected()) {
getGame().getAction().exileEffect(suspectedEffect);
suspectedEffect = null;
}
suspectedStatic = null;
}
return true;
}
@@ -7293,6 +7285,15 @@ 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.setSuspectedEffect(getLKICopy(copyFrom.getSuspectedEffect(), cachedMap));
newCopy.setSuspectedStatic(copyFrom.getSuspectedStatic().copy(newCopy, true));
}
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);
MagicColor.Color chosenColor = p.getController().chooseColor(prompt, cmdColorsa, ColorSet.WUBRG);
cmd.setChosenColors(List.of(chosenColor.getName()));
byte chosenColor = p.getController().chooseColor(prompt, cmdColorsa, ColorSet.WUBRG);
cmd.setChosenColors(List.of(MagicColor.toLongString(chosenColor)));
p.getGame().getAction().notifyOfValue(cmdColorsa, cmd,
Localizer.getInstance().getMessage("lblPlayerPickedChosen", p.getName(), chosenColor.getName()), p);
Localizer.getInstance().getMessage("lblPlayerPickedChosen", p.getName(), MagicColor.toLongString(chosenColor)), p);
}
}

View File

@@ -6,7 +6,6 @@ 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;
@@ -266,8 +265,8 @@ public abstract class PlayerController {
public abstract boolean chooseFlipResult(SpellAbility sa, Player flipper, boolean[] results, boolean call);
public abstract MagicColor.Color chooseColor(String message, SpellAbility sa, ColorSet colors);
public abstract MagicColor.Color chooseColorAllowColorless(String message, Card c, ColorSet colors);
public abstract byte chooseColor(String message, SpellAbility sa, ColorSet colors);
public abstract byte 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,7 +297,9 @@ 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));
return layers.contains(layer) && checkConditions(StaticAbilityMode.Continuous) && ( previousRun ||
getHostCard().getStaticAbilities().contains(this) ||
getHostCard().getHiddenStaticAbilities().contains(this));
}
public final Cost getAttackCost(final Card attacker, final GameEntity target, final List<Card> attackersWithOptionalCost) {
@@ -583,6 +585,13 @@ 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,7 +25,6 @@ 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.*;
@@ -93,12 +92,11 @@ public final class StaticAbilityContinuous {
final List<Player> affectedPlayers = StaticAbilityContinuous.getAffectedPlayers(stAb);
final Game game = hostCard.getGame();
final StaticEffects effects = game.getStaticEffects();
final StaticEffect se = effects.getStaticEffect(stAb);
final StaticEffect se = game.getStaticEffects().getStaticEffect(stAb);
se.setAffectedCards(affectedCards);
se.setAffectedPlayers(affectedPlayers);
se.setParams(params);
se.setTimestamp(hostCard.getLayerTimestamp());
se.setTimestamp(stAb.getTimestamp());
// nothing more to do
if (stAb.hasParam("Affected") && affectedPlayers.isEmpty() && affectedCards.isEmpty()) {

View File

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

View File

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

View File

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

View File

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

View File

@@ -145,3 +145,4 @@ 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

@@ -0,0 +1,23 @@
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

@@ -0,0 +1,10 @@
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

@@ -0,0 +1,8 @@
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.)

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