Merge remote-tracking branch 'upstream/master' into formats/pauper

This commit is contained in:
Valerio Maggio
2023-05-24 03:43:46 +01:00
31 changed files with 345 additions and 317 deletions

View File

@@ -26,7 +26,8 @@ jobs:
close-issue-message: 'This issue was closed because it has been stalled for 5 days with no activity.'
stale-issue-label: 'no-issue-activity'
stale-pr-label: 'no-pr-activity'
exempt-pr-labels: 'awaiting-approval,work-in-progress'
exempt-issue-labels: 'keep'
exempt-pr-labels: 'awaiting-approval,work-in-progress,keep'
days-before-issue-stale: 30
days-before-pr-stale: 45
days-before-issue-close: 5

View File

@@ -655,10 +655,11 @@ public class AiController {
List<SpellAbility> all = ComputerUtilAbility.getSpellAbilities(cards, player);
try {
Collections.sort(all, saComparator); // put best spells first
Collections.sort(all, ComputerUtilAbility.saEvaluator); // put best spells first
ComputerUtilAbility.sortCreatureSpells(all);
} catch (IllegalArgumentException ex) {
System.err.println(ex.getMessage());
String assertex = ComparatorUtil.verifyTransitivity(saComparator, all);
String assertex = ComparatorUtil.verifyTransitivity(ComputerUtilAbility.saEvaluator, all);
Sentry.captureMessage(ex.getMessage() + "\nAssertionError [verifyTransitivity]: " + assertex);
}
@@ -1016,167 +1017,6 @@ public class AiController {
return false;
}
// not sure "playing biggest spell" matters?
private final static Comparator<SpellAbility> saComparator = new Comparator<SpellAbility>() {
@Override
public int compare(final SpellAbility a, final SpellAbility b) {
// sort from highest cost to lowest
// we want the highest costs first
int a1 = a.getPayCosts().getTotalMana().getCMC();
int b1 = b.getPayCosts().getTotalMana().getCMC();
// deprioritize SAs explicitly marked as preferred to be activated last compared to all other SAs
if (a.hasParam("AIActivateLast") && !b.hasParam("AIActivateLast")) {
return 1;
} else if (b.hasParam("AIActivateLast") && !a.hasParam("AIActivateLast")) {
return -1;
}
// deprioritize planar die roll marked with AIRollPlanarDieParams:LowPriority$ True
if (ApiType.RollPlanarDice == a.getApi() && a.getHostCard() != null && a.getHostCard().hasSVar("AIRollPlanarDieParams") && a.getHostCard().getSVar("AIRollPlanarDieParams").toLowerCase().matches(".*lowpriority\\$\\s*true.*")) {
return 1;
} else if (ApiType.RollPlanarDice == b.getApi() && b.getHostCard() != null && b.getHostCard().hasSVar("AIRollPlanarDieParams") && b.getHostCard().getSVar("AIRollPlanarDieParams").toLowerCase().matches(".*lowpriority\\$\\s*true.*")) {
return -1;
}
// deprioritize pump spells with pure energy cost (can be activated last,
// since energy is generally scarce, plus can benefit e.g. Electrostatic Pummeler)
int a2 = 0, b2 = 0;
if (a.getApi() == ApiType.Pump && a.getPayCosts().getCostEnergy() != null) {
if (a.getPayCosts().hasOnlySpecificCostType(CostPayEnergy.class)) {
a2 = a.getPayCosts().getCostEnergy().convertAmount();
}
}
if (b.getApi() == ApiType.Pump && b.getPayCosts().getCostEnergy() != null) {
if (b.getPayCosts().hasOnlySpecificCostType(CostPayEnergy.class)) {
b2 = b.getPayCosts().getCostEnergy().convertAmount();
}
}
if (a2 == 0 && b2 > 0) {
return -1;
} else if (b2 == 0 && a2 > 0) {
return 1;
}
// cast 0 mana cost spells first (might be a Mox)
if (a1 == 0 && b1 > 0 && ApiType.Mana != a.getApi()) {
return -1;
} else if (a1 > 0 && b1 == 0 && ApiType.Mana != b.getApi()) {
return 1;
}
if (a.getHostCard() != null && a.getHostCard().hasSVar("FreeSpellAI")) {
return -1;
} else if (b.getHostCard() != null && b.getHostCard().hasSVar("FreeSpellAI")) {
return 1;
}
if (a.getHostCard().equals(b.getHostCard()) && a.getApi() == b.getApi()) {
// Cheaper Spectacle costs should be preferred
// FIXME: Any better way to identify that these are the same ability, one with Spectacle and one not?
// (looks like it's not a full-fledged alternative cost as such, and is not processed with other alt costs)
if (a.isSpectacle() && !b.isSpectacle() && a1 < b1) {
return 1;
} else if (b.isSpectacle() && !a.isSpectacle() && b1 < a1) {
return 1;
}
}
// If both are permanent creature spells, prefer the one that evaluates higher
if (a1 == b1 && a.getApi() == ApiType.PermanentCreature && b.getApi() == ApiType.PermanentCreature) {
int evalA = ComputerUtilCard.evaluateCreature(a);
int evalB = ComputerUtilCard.evaluateCreature(b);
if (evalA > evalB) {
a1++;
} else if (evalB > evalA) {
b1++;
}
}
a1 += getSpellAbilityPriority(a);
b1 += getSpellAbilityPriority(b);
return b1 - a1;
}
private int getSpellAbilityPriority(SpellAbility sa) {
int p = 0;
Card source = sa.getHostCard();
final Player ai = source == null ? sa.getActivatingPlayer() : source.getController();
if (ai == null) {
System.err.println("Error: couldn't figure out the activating player and host card for SA: " + sa);
return 0;
}
final boolean noCreatures = ai.getCreaturesInPlay().isEmpty();
if (source != null) {
// puts creatures in front of spells
if (source.isCreature()) {
p += 1;
}
if (source.hasSVar("AIPriorityModifier")) {
p += Integer.parseInt(source.getSVar("AIPriorityModifier"));
}
if (ComputerUtilCard.isCardRemAIDeck(sa.getOriginalHost() != null ? sa.getOriginalHost() : source)) {
p -= 10;
}
// don't play equipments before having any creatures
if (source.isEquipment() && noCreatures) {
p -= 9;
}
// don't equip stuff in main 2 if there's more stuff to cast at the moment
if (sa.getApi() == ApiType.Attach && !sa.isCurse() && source.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
p -= 1;
}
// 1. increase chance of using Surge effects
// 2. non-surged versions are usually inefficient
if (source.getOracleText().contains("surge cost") && !sa.isSurged()) {
p -= 9;
}
// move snap-casted spells to front
if (source.isInZone(ZoneType.Graveyard)) {
if (sa.getMayPlay() != null && source.mayPlay(sa.getMayPlay()) != null) {
p += 50;
}
}
// if the profile specifies it, deprioritize Storm spells in an attempt to build up storm count
if (source.hasKeyword(Keyword.STORM) && ai.getController() instanceof PlayerControllerAi) {
p -= (((PlayerControllerAi) ai.getController()).getAi().getIntProperty(AiProps.PRIORITY_REDUCTION_FOR_STORM_SPELLS));
}
}
// use Surge and Prowl costs when able to
if (sa.isSurged() || sa.isProwl()) {
p += 9;
}
// sort planeswalker abilities with most costly first
if (sa.isPwAbility()) {
final CostPart cost = sa.getPayCosts().getCostParts().get(0);
if (cost instanceof CostRemoveCounter) {
p += cost.convertAmount() == null ? 1 : cost.convertAmount();
} else if (cost instanceof CostPutCounter) {
p -= cost.convertAmount();
}
if (sa.hasParam("Ultimate")) {
p += 9;
}
}
if (ApiType.DestroyAll == sa.getApi()) {
p += 4;
} else if (ApiType.Mana == sa.getApi()) {
p -= 9;
}
// try to cast mana ritual spells before casting spells to maximize potential mana
if ("ManaRitual".equals(sa.getParam("AILogic"))) {
p += 9;
}
return p;
}
};
public CardCollection getCardsToDiscard(final int numDiscard, final String[] uTypes, final SpellAbility sa) {
return getCardsToDiscard(numDiscard, uTypes, sa, CardCollection.EMPTY);
}
@@ -1744,10 +1584,11 @@ public class AiController {
return null;
try {
Collections.sort(all, saComparator); // put best spells first
Collections.sort(all, ComputerUtilAbility.saEvaluator); // put best spells first
ComputerUtilAbility.sortCreatureSpells(all);
} catch (IllegalArgumentException ex) {
System.err.println(ex.getMessage());
String assertex = ComparatorUtil.verifyTransitivity(saComparator, all);
String assertex = ComparatorUtil.verifyTransitivity(ComputerUtilAbility.saEvaluator, all);
Sentry.captureMessage(ex.getMessage() + "\nAssertionError [verifyTransitivity]: " + assertex);
}
@@ -2292,14 +2133,14 @@ public class AiController {
}
// TODO move to more common place
private <T> List<T> filterList(List<T> input, Predicate<? super T> pred) {
private static <T> List<T> filterList(List<T> input, Predicate<? super T> pred) {
List<T> filtered = Lists.newArrayList(Iterables.filter(input, pred));
input.removeAll(filtered);
return filtered;
}
// TODO move to more common place
private List<SpellAbility> filterListByApi(List<SpellAbility> input, ApiType type) {
public static List<SpellAbility> filterListByApi(List<SpellAbility> input, ApiType type) {
return filterList(input, SpellAbilityPredicates.isApi(type));
}

View File

@@ -1,5 +1,7 @@
package forge.ai;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
@@ -15,6 +17,12 @@ import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates.Presets;
import forge.game.cost.CostPart;
import forge.game.cost.CostPayEnergy;
import forge.game.cost.CostPutCounter;
import forge.game.cost.CostRemoveCounter;
import forge.game.keyword.Keyword;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.OptionalCostValue;
import forge.game.spellability.SpellAbility;
@@ -223,4 +231,183 @@ public class ComputerUtilAbility {
}
return true;
}
public final static saComparator saEvaluator = new saComparator();
// not sure "playing biggest spell" matters?
public final static class saComparator implements Comparator<SpellAbility> {
@Override
public int compare(final SpellAbility a, final SpellAbility b) {
return compareEvaluator(a, b, false);
}
public int compareEvaluator(final SpellAbility a, final SpellAbility b, boolean safeToEvaluateCreatures) {
// sort from highest cost to lowest
// we want the highest costs first
int a1 = a.getPayCosts().getTotalMana().getCMC();
int b1 = b.getPayCosts().getTotalMana().getCMC();
// deprioritize SAs explicitly marked as preferred to be activated last compared to all other SAs
if (a.hasParam("AIActivateLast") && !b.hasParam("AIActivateLast")) {
return 1;
} else if (b.hasParam("AIActivateLast") && !a.hasParam("AIActivateLast")) {
return -1;
}
// deprioritize planar die roll marked with AIRollPlanarDieParams:LowPriority$ True
if (ApiType.RollPlanarDice == a.getApi() && a.getHostCard() != null && a.getHostCard().hasSVar("AIRollPlanarDieParams") && a.getHostCard().getSVar("AIRollPlanarDieParams").toLowerCase().matches(".*lowpriority\\$\\s*true.*")) {
return 1;
} else if (ApiType.RollPlanarDice == b.getApi() && b.getHostCard() != null && b.getHostCard().hasSVar("AIRollPlanarDieParams") && b.getHostCard().getSVar("AIRollPlanarDieParams").toLowerCase().matches(".*lowpriority\\$\\s*true.*")) {
return -1;
}
// deprioritize pump spells with pure energy cost (can be activated last,
// since energy is generally scarce, plus can benefit e.g. Electrostatic Pummeler)
int a2 = 0, b2 = 0;
if (a.getApi() == ApiType.Pump && a.getPayCosts().getCostEnergy() != null) {
if (a.getPayCosts().hasOnlySpecificCostType(CostPayEnergy.class)) {
a2 = a.getPayCosts().getCostEnergy().convertAmount();
}
}
if (b.getApi() == ApiType.Pump && b.getPayCosts().getCostEnergy() != null) {
if (b.getPayCosts().hasOnlySpecificCostType(CostPayEnergy.class)) {
b2 = b.getPayCosts().getCostEnergy().convertAmount();
}
}
if (a2 == 0 && b2 > 0) {
return -1;
} else if (b2 == 0 && a2 > 0) {
return 1;
}
// cast 0 mana cost spells first (might be a Mox)
if (a1 == 0 && b1 > 0 && ApiType.Mana != a.getApi()) {
return -1;
} else if (a1 > 0 && b1 == 0 && ApiType.Mana != b.getApi()) {
return 1;
}
if (a.getHostCard() != null && a.getHostCard().hasSVar("FreeSpellAI")) {
return -1;
} else if (b.getHostCard() != null && b.getHostCard().hasSVar("FreeSpellAI")) {
return 1;
}
if (a.getHostCard().equals(b.getHostCard()) && a.getApi() == b.getApi()) {
// Cheaper Spectacle costs should be preferred
// FIXME: Any better way to identify that these are the same ability, one with Spectacle and one not?
// (looks like it's not a full-fledged alternative cost as such, and is not processed with other alt costs)
if (a.isSpectacle() && !b.isSpectacle() && a1 < b1) {
return 1;
} else if (b.isSpectacle() && !a.isSpectacle() && b1 < a1) {
return 1;
}
}
a1 += getSpellAbilityPriority(a);
b1 += getSpellAbilityPriority(b);
// If both are creature spells sort them after
if (safeToEvaluateCreatures) {
a1 += Math.round(ComputerUtilCard.evaluateCreature(a) / 100f);
b1 += Math.round(ComputerUtilCard.evaluateCreature(b) / 100f);
}
return b1 - a1;
}
private static int getSpellAbilityPriority(SpellAbility sa) {
int p = 0;
Card source = sa.getHostCard();
final Player ai = source == null ? sa.getActivatingPlayer() : source.getController();
if (ai == null) {
System.err.println("Error: couldn't figure out the activating player and host card for SA: " + sa);
return 0;
}
final boolean noCreatures = ai.getCreaturesInPlay().isEmpty();
if (source != null) {
// puts creatures in front of spells
if (source.isCreature()) {
p += 1;
}
if (source.hasSVar("AIPriorityModifier")) {
p += Integer.parseInt(source.getSVar("AIPriorityModifier"));
}
if (ComputerUtilCard.isCardRemAIDeck(sa.getOriginalHost() != null ? sa.getOriginalHost() : source)) {
p -= 10;
}
// don't play equipments before having any creatures
if (source.isEquipment() && noCreatures) {
p -= 9;
}
// don't equip stuff in main 2 if there's more stuff to cast at the moment
if (sa.getApi() == ApiType.Attach && !sa.isCurse() && source.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
p -= 1;
}
// 1. increase chance of using Surge effects
// 2. non-surged versions are usually inefficient
if (source.getOracleText().contains("surge cost") && !sa.isSurged()) {
p -= 9;
}
// move snap-casted spells to front
if (source.isInZone(ZoneType.Graveyard)) {
if (sa.getMayPlay() != null && source.mayPlay(sa.getMayPlay()) != null) {
p += 50;
}
}
// if the profile specifies it, deprioritize Storm spells in an attempt to build up storm count
if (source.hasKeyword(Keyword.STORM) && ai.getController() instanceof PlayerControllerAi) {
p -= (((PlayerControllerAi) ai.getController()).getAi().getIntProperty(AiProps.PRIORITY_REDUCTION_FOR_STORM_SPELLS));
}
}
// use Surge and Prowl costs when able to
if (sa.isSurged() || sa.isProwl()) {
p += 9;
}
// sort planeswalker abilities with most costly first
if (sa.isPwAbility()) {
final CostPart cost = sa.getPayCosts().getCostParts().get(0);
if (cost instanceof CostRemoveCounter) {
p += cost.convertAmount() == null ? 1 : cost.convertAmount();
} else if (cost instanceof CostPutCounter) {
p -= cost.convertAmount();
}
if (sa.hasParam("Ultimate")) {
p += 9;
}
}
if (ApiType.DestroyAll == sa.getApi()) {
p += 4;
} else if (ApiType.Mana == sa.getApi()) {
p -= 9;
}
// try to cast mana ritual spells before casting spells to maximize potential mana
if ("ManaRitual".equals(sa.getParam("AILogic"))) {
p += 9;
}
return p;
}
};
public static List<SpellAbility> sortCreatureSpells(final List<SpellAbility> all) {
// try to smoothen power creep by making CMC less of a factor
final List<SpellAbility> creatures = AiController.filterListByApi(Lists.newArrayList(all), ApiType.PermanentCreature);
if (creatures.size() <= 1) {
return all;
}
// TODO this doesn't account for nearly identical creatures where one is a newer but more cost efficient variant
Collections.sort(creatures, ComputerUtilCard.EvaluateCreatureSpellComparator);
int idx = 0;
for (int i = 0; i < all.size(); i++) {
if (all.get(i).getApi() == ApiType.PermanentCreature) {
all.set(i, creatures.get(idx));
idx++;
}
}
return all;
}
}

View File

@@ -567,6 +567,13 @@ public class ComputerUtilCard {
return evaluateCreature(b) - evaluateCreature(a);
}
};
public static final Comparator<SpellAbility> EvaluateCreatureSpellComparator = new Comparator<SpellAbility>() {
@Override
public int compare(final SpellAbility a, final SpellAbility b) {
// TODO ideally we could reuse the value from the previous pass with false
return ComputerUtilAbility.saEvaluator.compareEvaluator(a, b, true);
}
};
private static final CreatureEvaluator creatureEvaluator = new CreatureEvaluator();
private static final LandEvaluator landEvaluator = new LandEvaluator();
@@ -596,7 +603,7 @@ public class ComputerUtilCard {
host.setState(sa.getCardStateName(), false);
}
int eval = creatureEvaluator.evaluateCreature(host);
int eval = evaluateCreature(host);
if (currentState != null) {
host.setState(currentState, false);

View File

@@ -471,6 +471,13 @@ public class CountersPutAi extends CountersAi {
if (sacSelf && c.equals(source)) {
return false;
}
if ("NoCounterOfType".equals(sa.getParam("AILogic"))) {
for (String ctrType : types) {
if (c.getCounters(CounterType.getType(ctrType)) > 0) {
return false;
}
}
}
return sa.canTarget(c) && c.canReceiveCounters(CounterType.getType(type));
}
});

View File

@@ -590,9 +590,7 @@ public class GameAction {
// 400.7g try adding keyword back into card if it doesn't already have it
if (zoneTo.is(ZoneType.Stack) && cause != null && cause.isSpell() && !cause.isIntrinsic() && c.equals(cause.getHostCard())) {
if (cause.getKeyword() != null && !copied.getKeywords().contains(cause.getKeyword())) {
copied.addChangedCardKeywordsInternal(ImmutableList.of(cause.getKeyword()), null, false, game.getNextTimestamp(), 0, false);
// update Keyword Cache
copied.updateKeywords();
copied.addChangedCardKeywordsInternal(ImmutableList.of(cause.getKeyword()), null, false, game.getNextTimestamp(), 0, true);
}
}

View File

@@ -636,10 +636,8 @@ public abstract class SpellAbilityEffect {
combat.initConstraints();
if (sa.hasParam("ChoosePlayerOrPlaneswalker")) {
PlayerCollection defendingPlayers = AbilityUtils.getDefinedPlayers(sa.hasParam("ForEach") ? c : host, attacking, sa);
defs = new FCollection<>();
for (Player p : defendingPlayers) {
defs.addAll(combat.getDefendersControlledBy(p));
}
defs = new FCollection<>(defendingPlayers);
defs.addAll(Iterables.filter(combat.getDefendingPlaneswalkers(), CardPredicates.isControlledByAnyOf(defendingPlayers)));
} else if ("True".equalsIgnoreCase(attacking)) {
defs = (FCollection<GameEntity>) combat.getDefenders();
} else {

View File

@@ -66,7 +66,7 @@ public class ConniveEffect extends SpellAbilityEffect {
for (final Player p : controllers) {
CardCollection connivers = CardLists.filterControlledBy(toConnive, p);
while (connivers.size() > 0) {
while (!connivers.isEmpty()) {
GameEntityCounterTable table = new GameEntityCounterTable();
final CardZoneTable triggerList = new CardZoneTable();
Map<Player, CardCollectionView> discardedMap = Maps.newHashMap();
@@ -76,18 +76,18 @@ public class ConniveEffect extends SpellAbilityEffect {
Card conniver = connivers.size() > 1 ? p.getController().chooseSingleEntityForEffect(connivers, sa,
Localizer.getInstance().getMessage("lblChooseConniver"), null) : connivers.get(0);
connivers.remove(conniver);
p.drawCards(num, sa, moveParams);
CardCollection validDisards =
CardLists.filter(p.getCardsIn(ZoneType.Hand), CardPredicates.Presets.NON_TOKEN);
if (validDisards.isEmpty() || !p.canDiscardBy(sa, true)) { // hand being empty unlikely, just to be safe
CardCollection validDiscards = CardLists.filter(p.getCardsIn(ZoneType.Hand), CardPredicates.Presets.NON_TOKEN);
if (validDiscards.isEmpty() || !p.canDiscardBy(sa, true)) { // hand being empty unlikely, just to be safe
continue;
}
int amt = Math.min(validDisards.size(), num);
int amt = Math.min(validDiscards.size(), num);
CardCollectionView toBeDiscarded = amt == 0 ? CardCollection.EMPTY :
p.getController().chooseCardsToDiscardFrom(p, sa, validDisards, amt, amt);
p.getController().chooseCardsToDiscardFrom(p, sa, validDiscards, amt, amt);
if (toBeDiscarded.size() > 1) {
toBeDiscarded = GameActionUtil.orderCardsByTheirOwners(game, toBeDiscarded, ZoneType.Graveyard, sa);
@@ -101,7 +101,6 @@ public class ConniveEffect extends SpellAbilityEffect {
if (game.getZoneOf(gamec).is(ZoneType.Battlefield) && gamec.equalsWithTimestamp(conniver)) {
conniver.addCounter(CounterEnumType.P1P1, numCntrs, p, table);
}
connivers.remove(conniver);
discardedMap.put(p, CardCollection.getView(toBeDiscarded));
discard(sa, triggerList, true, discardedMap, moveParams);
table.replaceCounterEffect(game, sa, true);

View File

@@ -4721,7 +4721,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
final List<KeywordInterface> keywords, final List<KeywordInterface> removeKeywords,
final boolean removeAllKeywords,
final long timestamp, final long staticId, final boolean updateView) {
final KeywordsChange newCks = new KeywordsChange(keywords, removeKeywords, removeAllKeywords);
changedCardKeywords.put(timestamp, staticId, newCks);

View File

@@ -124,62 +124,49 @@ public class CardFactory {
final Card source = sourceSA.getHostCard();
final Card original = targetSA.getHostCard();
final Game game = source.getGame();
final Card c = new Card(game.nextCardId(), original.getPaperCard(), game);
copyCopiableCharacteristics(original, c, sourceSA, targetSA);
int id = game.nextCardId();
if (sourceSA.hasParam("NonLegendary")) {
c.removeType(CardType.Supertype.Legendary);
}
// need to create a physical card first, i need the original card faces
final Card copy = CardFactory.getCard(original.getPaperCard(), controller, id, game);
if (sourceSA.hasParam("CopySetPower")) {
c.setBasePower(Integer.parseInt(sourceSA.getParam("CopySetPower")));
}
if (sourceSA.hasParam("CopySetToughness")) {
c.setBaseToughness(Integer.parseInt(sourceSA.getParam("CopySetToughness")));
}
if (sourceSA.hasParam("CopySetLoyalty")) {
c.setBaseLoyalty(AbilityUtils.calculateAmount(source, sourceSA.getParam("CopySetLoyalty"), sourceSA));
}
if (sourceSA.hasParam("CopyAddTypes")) {
c.addType(Arrays.asList(sourceSA.getParam("CopyAddTypes").split(" & ")));
}
// change the color of the copy (eg: Fork)
if (sourceSA.hasParam("CopyIsColor")) {
ColorSet finalColors;
final String newColor = sourceSA.getParam("CopyIsColor");
if (newColor.equals("ChosenColor")) {
finalColors = ColorSet.fromNames(source.getChosenColors());
} else {
finalColors = ColorSet.fromNames(newColor.split(","));
if (original.isTransformable()) {
// 707.8a If an effect creates a token that is a copy of a transforming permanent or a transforming double-faced card not on the battlefield,
// the resulting token is a transforming token that has both a front face and a back face.
// The characteristics of each face are determined by the copiable values of the same face of the permanent it is a copy of, as modified by any other copy effects that apply to that permanent.
// If the token is a copy of a transforming permanent with its back face up, the token enters the battlefield with its back face up.
// This rule does not apply to tokens that are created with their own set of characteristics and enter the battlefield as a copy of a transforming permanent due to a replacement effect.
copy.setBackSide(original.isBackSide());
if (original.isTransformed()) {
copy.incrementTransformedTimestamp();
}
c.addColor(finalColors, !sourceSA.hasParam("OverwriteColors"), c.getTimestamp(), 0, false);
}
c.clearControllers();
c.setOwner(controller);
c.setCopiedSpell(true);
c.setCopiedPermanent(original);
copy.setStates(getCloneStates(original, copy, sourceSA));
// force update the now set State
if (original.isTransformable()) {
copy.setState(original.isTransformed() ? CardStateName.Transformed : CardStateName.Original, true, true);
} else {
copy.setState(copy.getCurrentStateName(), true, true);
}
c.setXManaCostPaidByColor(original.getXManaCostPaidByColor());
c.setKickerMagnitude(original.getKickerMagnitude());
copy.setCopiedSpell(true);
copy.setCopiedPermanent(original);
copy.setXManaCostPaidByColor(original.getXManaCostPaidByColor());
copy.setKickerMagnitude(original.getKickerMagnitude());
for (OptionalCost cost : original.getOptionalCostsPaid()) {
c.addOptionalCostPaid(cost);
copy.addOptionalCostPaid(cost);
}
if (targetSA.isBestow()) {
c.animateBestow();
copy.animateBestow();
}
if (sourceSA.hasParam("RememberNewCard")) {
source.addRemembered(c);
source.addRemembered(copy);
}
return c;
return copy;
}
/**
@@ -525,6 +512,7 @@ public class CardFactory {
* @param from the {@link Card} to copy from.
* @param to the {@link Card} to copy to.
*/
@Deprecated
public static void copyCopiableCharacteristics(final Card from, final Card to, SpellAbility sourceSA, SpellAbility targetSA) {
final boolean toIsFaceDown = to.isFaceDown();
if (toIsFaceDown) {
@@ -753,7 +741,10 @@ public class CardFactory {
final CardState ret2 = new CardState(out, CardStateName.Adventure);
ret2.copyFrom(in.getState(CardStateName.Adventure), false, sa);
result.put(CardStateName.Adventure, ret2);
} else if (in.isTransformable() && sa instanceof SpellAbility && ApiType.CopyPermanent.equals(((SpellAbility)sa).getApi())) {
} else if (in.isTransformable() && sa instanceof SpellAbility && (
ApiType.CopyPermanent.equals(((SpellAbility)sa).getApi()) ||
ApiType.CopySpellAbility.equals(((SpellAbility)sa).getApi())
)) {
// CopyPermanent can copy token
final CardState ret1 = new CardState(out, CardStateName.Original);
ret1.copyFrom(in.getState(CardStateName.Original), false, sa);
@@ -820,7 +811,7 @@ public class CardFactory {
}
if (state.getType().isPlaneswalker() && sa.hasParam("SetLoyalty")) {
state.setBaseLoyalty(String.valueOf(sa.getParam("SetLoyalty")));
state.setBaseLoyalty(String.valueOf(AbilityUtils.calculateAmount(host, sa.getParam("SetLoyalty"), sa)));
}
// Planning a Vizier of Many Faces rework; always might come in handy

View File

@@ -239,7 +239,7 @@ public class CombatUtil {
if (!defender.equals(ge) && ge instanceof Player) {
// found a player which does not goad that creature
// and creature can attack this player or planeswalker
if (!attacker.isGoadedBy((Player) ge) && canAttack(attacker, ge)) {
if (!attacker.isGoadedBy((Player) ge) && !ge.hasKeyword("Creatures your opponents control attack a player other than you if able.") && canAttack(attacker, ge)) {
return false;
}
}
@@ -251,7 +251,7 @@ public class CombatUtil {
if (defender != null && defender.hasKeyword("Creatures your opponents control attack a player other than you if able.")) {
for (GameEntity ge : getAllPossibleDefenders(attacker.getController())) {
if (!defender.equals(ge) && ge instanceof Player) {
if (canAttack(attacker, ge)) {
if (!ge.hasKeyword("Creatures your opponents control attack a player other than you if able.") && canAttack(attacker, ge)) {
return false;
}
}

View File

@@ -68,7 +68,7 @@
<object id="55" template="../../obj/gate.tx" x="368" y="640">
<properties>
<property name="dialog">[{
&quot;text&quot;:&quot;A gate is blocking the path. I looks like it is open elsewhere&quot;,
&quot;text&quot;:&quot;A gate is blocking the path. It looks like it is opened elsewhere.&quot;,
&quot;options&quot;:[
{ &quot;name&quot;:&quot;continue&quot; }
]
@@ -84,12 +84,12 @@
<properties>
<property name="dialog">[
{
&quot;text&quot;:&quot;Hmm a big switch is embedded into the wall&quot;,
&quot;text&quot;:&quot;Hmm... A big switch is embedded into the wall.&quot;,
&quot;options&quot;:[
{
&quot;text&quot;:&quot;You hear some loud sounds from the center&quot;,
&quot;text&quot;:&quot;You hear some loud sounds coming from the center.&quot;,
&quot;action&quot;:[{&quot;deleteMapObject&quot;:-1},{&quot;advanceMapFlag&quot;:&quot;gate&quot;}],
&quot;name&quot;:&quot;flip the switch&quot;
&quot;name&quot;:&quot;flip the switch&quot;
&quot;options&quot;:[{
&quot;condition&quot;:[{&quot;getMapFlag&quot;:{&quot;key&quot;:&quot;gate&quot;,&quot;op&quot;:&quot;&gt;=&quot;,&quot;val&quot;:3}}],
&quot;action&quot;:[{&quot;deleteMapObject&quot;:55}],
@@ -106,12 +106,12 @@
<properties>
<property name="dialog">[
{
&quot;text&quot;:&quot;Hmm a big switch is embedded into the wall&quot;,
&quot;text&quot;:&quot;Hmm... A big switch is embedded into the wall.&quot;,
&quot;options&quot;:[
{
&quot;text&quot;:&quot;You hear some loud sounds from the center&quot;,
&quot;text&quot;:&quot;You hear some loud sounds coming from the center.&quot;,
&quot;action&quot;:[{&quot;deleteMapObject&quot;:-1},{&quot;advanceMapFlag&quot;:&quot;gate&quot;}],
&quot;name&quot;:&quot;flip the switch&quot;
&quot;name&quot;:&quot;flip the switch&quot;
&quot;options&quot;:[{
&quot;condition&quot;:[{&quot;getMapFlag&quot;:{&quot;key&quot;:&quot;gate&quot;,&quot;op&quot;:&quot;&gt;=&quot;,&quot;val&quot;:3}}],
&quot;action&quot;:[{&quot;deleteMapObject&quot;:55}],
@@ -128,12 +128,12 @@
<properties>
<property name="dialog">[
{
&quot;text&quot;:&quot;Hmm a big switch is embedded into the wall&quot;,
&quot;text&quot;:&quot;Hmm... A big switch is embedded into the wall.&quot;,
&quot;options&quot;:[
{
&quot;text&quot;:&quot;You hear some loud sounds from the center&quot;,
&quot;text&quot;:&quot;You hear some loud sounds coming from the center.&quot;,
&quot;action&quot;:[{&quot;deleteMapObject&quot;:-1},{&quot;advanceMapFlag&quot;:&quot;gate&quot;}],
&quot;name&quot;:&quot;flip the switch&quot;
&quot;name&quot;:&quot;flip the switch&quot;
&quot;options&quot;:[{
&quot;condition&quot;:[{&quot;getMapFlag&quot;:{&quot;key&quot;:&quot;gate&quot;,&quot;op&quot;:&quot;&gt;=&quot;,&quot;val&quot;:3}}],
&quot;action&quot;:[{&quot;deleteMapObject&quot;:55}],

View File

@@ -68,7 +68,7 @@
<object id="55" template="../../obj/gate.tx" x="368" y="640">
<properties>
<property name="dialog">[{
&quot;text&quot;:&quot;A gate is blocking the path. I looks like it is open elsewhere&quot;,
&quot;text&quot;:&quot;A gate is blocking the path. It looks like it is opened elsewhere.&quot;,
&quot;options&quot;:[
{ &quot;name&quot;:&quot;continue&quot; }
]
@@ -84,12 +84,12 @@
<properties>
<property name="dialog">[
{
&quot;text&quot;:&quot;Hmm a big switch is embedded into the wall&quot;,
&quot;text&quot;:&quot;Hmm... A big switch is embedded into the wall.&quot;,
&quot;options&quot;:[
{
&quot;text&quot;:&quot;You hear some loud sounds from the center&quot;,
&quot;text&quot;:&quot;You hear some loud sounds coming from the center.&quot;,
&quot;action&quot;:[{&quot;deleteMapObject&quot;:-1},{&quot;advanceMapFlag&quot;:&quot;gate&quot;}],
&quot;name&quot;:&quot;flip the switch&quot;
&quot;name&quot;:&quot;flip the switch&quot;
&quot;options&quot;:[{
&quot;condition&quot;:[{&quot;getMapFlag&quot;:{&quot;key&quot;:&quot;gate&quot;,&quot;op&quot;:&quot;&gt;=&quot;,&quot;val&quot;:3}}],
&quot;action&quot;:[{&quot;deleteMapObject&quot;:55}],
@@ -106,12 +106,12 @@
<properties>
<property name="dialog">[
{
&quot;text&quot;:&quot;Hmm a big switch is embedded into the wall&quot;,
&quot;text&quot;:&quot;Hmm... A big switch is embedded into the wall.&quot;,
&quot;options&quot;:[
{
&quot;text&quot;:&quot;You hear some loud sounds from the center&quot;,
&quot;text&quot;:&quot;You hear some loud sounds coming from the center.&quot;,
&quot;action&quot;:[{&quot;deleteMapObject&quot;:-1},{&quot;advanceMapFlag&quot;:&quot;gate&quot;}],
&quot;name&quot;:&quot;flip the switch&quot;
&quot;name&quot;:&quot;flip the switch&quot;
&quot;options&quot;:[{
&quot;condition&quot;:[{&quot;getMapFlag&quot;:{&quot;key&quot;:&quot;gate&quot;,&quot;op&quot;:&quot;&gt;=&quot;,&quot;val&quot;:3}}],
&quot;action&quot;:[{&quot;deleteMapObject&quot;:55}],
@@ -128,12 +128,12 @@
<properties>
<property name="dialog">[
{
&quot;text&quot;:&quot;Hmm a big switch is embedded into the wall&quot;,
&quot;text&quot;:&quot;Hmm... A big switch is embedded into the wall.&quot;,
&quot;options&quot;:[
{
&quot;text&quot;:&quot;You hear some loud sounds from the center&quot;,
&quot;text&quot;:&quot;You hear some loud sounds coming from the center.&quot;,
&quot;action&quot;:[{&quot;deleteMapObject&quot;:-1},{&quot;advanceMapFlag&quot;:&quot;gate&quot;}],
&quot;name&quot;:&quot;flip the switch&quot;
&quot;name&quot;:&quot;flip the switch&quot;
&quot;options&quot;:[{
&quot;condition&quot;:[{&quot;getMapFlag&quot;:{&quot;key&quot;:&quot;gate&quot;,&quot;op&quot;:&quot;&gt;=&quot;,&quot;val&quot;:3}}],
&quot;action&quot;:[{&quot;deleteMapObject&quot;:55}],

View File

@@ -68,7 +68,7 @@
<object id="55" template="../../obj/gate.tx" x="368" y="640">
<properties>
<property name="dialog">[{
&quot;text&quot;:&quot;A gate is blocking the path. I looks like it is open elsewhere&quot;,
&quot;text&quot;:&quot;A gate is blocking the path. It looks like it is opened elsewhere.&quot;,
&quot;options&quot;:[
{ &quot;name&quot;:&quot;continue&quot; }
]
@@ -84,12 +84,12 @@
<properties>
<property name="dialog">[
{
&quot;text&quot;:&quot;Hmm a big switch is embedded into the wall&quot;,
&quot;text&quot;:&quot;Hmm... A big switch is embedded into the wall.&quot;,
&quot;options&quot;:[
{
&quot;text&quot;:&quot;You hear some loud sounds from the center&quot;,
&quot;text&quot;:&quot;You hear some loud sounds coming from the center.&quot;,
&quot;action&quot;:[{&quot;deleteMapObject&quot;:-1},{&quot;advanceMapFlag&quot;:&quot;gate&quot;}],
&quot;name&quot;:&quot;flip the switch&quot;
&quot;name&quot;:&quot;flip the switch&quot;
&quot;options&quot;:[{
&quot;condition&quot;:[{&quot;getMapFlag&quot;:{&quot;key&quot;:&quot;gate&quot;,&quot;op&quot;:&quot;&gt;=&quot;,&quot;val&quot;:3}}],
&quot;action&quot;:[{&quot;deleteMapObject&quot;:55}],
@@ -106,12 +106,12 @@
<properties>
<property name="dialog">[
{
&quot;text&quot;:&quot;Hmm a big switch is embedded into the wall&quot;,
&quot;text&quot;:&quot;Hmm... A big switch is embedded into the wall.&quot;,
&quot;options&quot;:[
{
&quot;text&quot;:&quot;You hear some loud sounds from the center&quot;,
&quot;text&quot;:&quot;You hear some loud sounds coming from the center.&quot;,
&quot;action&quot;:[{&quot;deleteMapObject&quot;:-1},{&quot;advanceMapFlag&quot;:&quot;gate&quot;}],
&quot;name&quot;:&quot;flip the switch&quot;
&quot;name&quot;:&quot;flip the switch&quot;
&quot;options&quot;:[{
&quot;condition&quot;:[{&quot;getMapFlag&quot;:{&quot;key&quot;:&quot;gate&quot;,&quot;op&quot;:&quot;&gt;=&quot;,&quot;val&quot;:3}}],
&quot;action&quot;:[{&quot;deleteMapObject&quot;:55}],
@@ -128,12 +128,12 @@
<properties>
<property name="dialog">[
{
&quot;text&quot;:&quot;Hmm a big switch is embedded into the wall&quot;,
&quot;text&quot;:&quot;Hmm... A big switch is embedded into the wall.&quot;,
&quot;options&quot;:[
{
&quot;text&quot;:&quot;You hear some loud sounds from the center&quot;,
&quot;text&quot;:&quot;You hear some loud sounds coming from the center.&quot;,
&quot;action&quot;:[{&quot;deleteMapObject&quot;:-1},{&quot;advanceMapFlag&quot;:&quot;gate&quot;}],
&quot;name&quot;:&quot;flip the switch&quot;
&quot;name&quot;:&quot;flip the switch&quot;
&quot;options&quot;:[{
&quot;condition&quot;:[{&quot;getMapFlag&quot;:{&quot;key&quot;:&quot;gate&quot;,&quot;op&quot;:&quot;&gt;=&quot;,&quot;val&quot;:3}}],
&quot;action&quot;:[{&quot;deleteMapObject&quot;:55}],

View File

@@ -68,7 +68,7 @@
<object id="55" template="../../obj/gate.tx" x="368" y="640">
<properties>
<property name="dialog">[{
&quot;text&quot;:&quot;A gate is blocking the path. I looks like it is open elsewhere&quot;,
&quot;text&quot;:&quot;A gate is blocking the path. It looks like it is opened elsewhere.&quot;,
&quot;options&quot;:[
{ &quot;name&quot;:&quot;continue&quot; }
]
@@ -84,12 +84,12 @@
<properties>
<property name="dialog">[
{
&quot;text&quot;:&quot;Hmm a big switch is embedded into the wall&quot;,
&quot;text&quot;:&quot;Hmm... A big switch is embedded into the wall.&quot;,
&quot;options&quot;:[
{
&quot;text&quot;:&quot;You hear some loud sounds from the center&quot;,
&quot;text&quot;:&quot;You hear some loud sounds coming from the center.&quot;,
&quot;action&quot;:[{&quot;deleteMapObject&quot;:-1},{&quot;advanceMapFlag&quot;:&quot;gate&quot;}],
&quot;name&quot;:&quot;flip the switch&quot;
&quot;name&quot;:&quot;flip the switch&quot;
&quot;options&quot;:[{
&quot;condition&quot;:[{&quot;getMapFlag&quot;:{&quot;key&quot;:&quot;gate&quot;,&quot;op&quot;:&quot;&gt;=&quot;,&quot;val&quot;:3}}],
&quot;action&quot;:[{&quot;deleteMapObject&quot;:55}],
@@ -106,12 +106,12 @@
<properties>
<property name="dialog">[
{
&quot;text&quot;:&quot;Hmm a big switch is embedded into the wall&quot;,
&quot;text&quot;:&quot;Hmm... A big switch is embedded into the wall.&quot;,
&quot;options&quot;:[
{
&quot;text&quot;:&quot;You hear some loud sounds from the center&quot;,
&quot;text&quot;:&quot;You hear some loud sounds coming from the center.&quot;,
&quot;action&quot;:[{&quot;deleteMapObject&quot;:-1},{&quot;advanceMapFlag&quot;:&quot;gate&quot;}],
&quot;name&quot;:&quot;flip the switch&quot;
&quot;name&quot;:&quot;flip the switch&quot;
&quot;options&quot;:[{
&quot;condition&quot;:[{&quot;getMapFlag&quot;:{&quot;key&quot;:&quot;gate&quot;,&quot;op&quot;:&quot;&gt;=&quot;,&quot;val&quot;:3}}],
&quot;action&quot;:[{&quot;deleteMapObject&quot;:55}],
@@ -128,12 +128,12 @@
<properties>
<property name="dialog">[
{
&quot;text&quot;:&quot;Hmm a big switch is embedded into the wall&quot;,
&quot;text&quot;:&quot;Hmm... A big switch is embedded into the wall.&quot;,
&quot;options&quot;:[
{
&quot;text&quot;:&quot;You hear some loud sounds from the center&quot;,
&quot;text&quot;:&quot;You hear some loud sounds coming from the center.&quot;,
&quot;action&quot;:[{&quot;deleteMapObject&quot;:-1},{&quot;advanceMapFlag&quot;:&quot;gate&quot;}],
&quot;name&quot;:&quot;flip the switch&quot;
&quot;name&quot;:&quot;flip the switch&quot;
&quot;options&quot;:[{
&quot;condition&quot;:[{&quot;getMapFlag&quot;:{&quot;key&quot;:&quot;gate&quot;,&quot;op&quot;:&quot;&gt;=&quot;,&quot;val&quot;:3}}],
&quot;action&quot;:[{&quot;deleteMapObject&quot;:55}],

View File

@@ -357,12 +357,12 @@
<properties>
<property name="dialog">[
{
&quot;text&quot;:&quot;Hmm a big switch is embedded into the wall&quot;,
&quot;text&quot;:&quot;Hmm... A big switch is embedded into the wall.&quot;,
&quot;options&quot;:[
{
&quot;text&quot;:&quot;You hear some loud sounds from the east&quot;,
&quot;text&quot;:&quot;You hear some loud sounds coming from the east&quot;,
&quot;action&quot;:[{&quot;deleteMapObject&quot;:-1},{&quot;advanceMapFlag&quot;:&quot;gate&quot;}],
&quot;name&quot;:&quot;flip the switch&quot;
&quot;name&quot;:&quot;flip the switch&quot;
&quot;options&quot;:[{
&quot;condition&quot;:[{&quot;getMapFlag&quot;:{&quot;key&quot;:&quot;gate&quot;,&quot;op&quot;:&quot;&gt;=&quot;,&quot;val&quot;:2}}],
&quot;action&quot;:[{&quot;deleteMapObject&quot;:18}],
@@ -379,12 +379,12 @@
<properties>
<property name="dialog">[
{
&quot;text&quot;:&quot;Hmm a big switch is embedded into the wall&quot;,
&quot;text&quot;:&quot;Hmm... A big switch is embedded into the wall.&quot;,
&quot;options&quot;:[
{
&quot;text&quot;:&quot;You hear some loud sounds from the east&quot;,
&quot;text&quot;:&quot;You hear some loud sounds coming from the east&quot;,
&quot;action&quot;:[{&quot;deleteMapObject&quot;:-1},{&quot;advanceMapFlag&quot;:&quot;gate&quot;}],
&quot;name&quot;:&quot;flip the switch&quot;
&quot;name&quot;:&quot;flip the switch&quot;
&quot;options&quot;:[{
&quot;condition&quot;:[{&quot;getMapFlag&quot;:{&quot;key&quot;:&quot;gate&quot;,&quot;op&quot;:&quot;&gt;=&quot;,&quot;val&quot;:2}}],
&quot;action&quot;:[{&quot;deleteMapObject&quot;:18}],
@@ -401,11 +401,11 @@
<properties>
<property name="dialog">[
{
&quot;text&quot;:&quot;Hmm a big switch is embedded into the wall&quot;,
&quot;text&quot;:&quot;Hmm... A big switch is embedded into the wall.&quot;,
&quot;options&quot;:[
{
&quot;text&quot;:&quot;You hear some loud sounds from the north&quot;,
&quot;name&quot;:&quot;flip the switch&quot;
&quot;text&quot;:&quot;You hear some loud sounds coming from the north&quot;,
&quot;name&quot;:&quot;flip the switch&quot;
&quot;options&quot;:[{
&quot;action&quot;:[{&quot;deleteMapObject&quot;:17}],
&quot;name&quot;:&quot;ok&quot; }]
@@ -444,11 +444,11 @@
<properties>
<property name="dialog">[
{
&quot;text&quot;:&quot;Hmm a big switch is embedded into the wall&quot;,
&quot;text&quot;:&quot;Hmm... A big switch is embedded into the wall.&quot;,
&quot;options&quot;:[
{
&quot;text&quot;:&quot;You hear some rumbling and commotion from the south. The final gate appears to be open.&quot;,
&quot;name&quot;:&quot;flip the switch&quot;
&quot;name&quot;:&quot;flip the switch&quot;
&quot;options&quot;:[{
&quot;action&quot;:[{&quot;deleteMapObject&quot;:6}],
&quot;name&quot;:&quot;ok&quot; }]
@@ -502,7 +502,7 @@
{
&quot;text&quot;:&quot;The connected machinery whirrs to life, and the gate to your east opens.&quot;,
&quot;action&quot;:[{&quot;deleteMapObject&quot;:-1}],
&quot;name&quot;:&quot;flip the switch&quot;
&quot;name&quot;:&quot;flip the switch&quot;
&quot;options&quot;:[{
&quot;action&quot;:[{&quot;deleteMapObject&quot;:11}],
&quot;name&quot;:&quot;ok&quot; }]

View File

@@ -68,7 +68,7 @@
<object id="55" template="../../obj/gate.tx" x="368" y="640">
<properties>
<property name="dialog">[{
&quot;text&quot;:&quot;A gate is blocking the path. I looks like it is open elsewhere&quot;,
&quot;text&quot;:&quot;A gate is blocking the path. It looks like it is opened elsewhere.&quot;,
&quot;options&quot;:[
{ &quot;name&quot;:&quot;continue&quot; }
]
@@ -84,12 +84,12 @@
<properties>
<property name="dialog">[
{
&quot;text&quot;:&quot;Hmm a big switch is embedded into the wall&quot;,
&quot;text&quot;:&quot;Hmm... A big switch is embedded into the wall.&quot;,
&quot;options&quot;:[
{
&quot;text&quot;:&quot;You hear some loud sounds from the center&quot;,
&quot;text&quot;:&quot;You hear some loud sounds coming from the center.&quot;,
&quot;action&quot;:[{&quot;deleteMapObject&quot;:-1},{&quot;advanceMapFlag&quot;:&quot;gate&quot;}],
&quot;name&quot;:&quot;flip the switch&quot;
&quot;name&quot;:&quot;flip the switch&quot;
&quot;options&quot;:[{
&quot;condition&quot;:[{&quot;getMapFlag&quot;:{&quot;key&quot;:&quot;gate&quot;,&quot;op&quot;:&quot;&gt;=&quot;,&quot;val&quot;:3}}],
&quot;action&quot;:[{&quot;deleteMapObject&quot;:55}],
@@ -106,12 +106,12 @@
<properties>
<property name="dialog">[
{
&quot;text&quot;:&quot;Hmm a big switch is embedded into the wall&quot;,
&quot;text&quot;:&quot;Hmm... A big switch is embedded into the wall.&quot;,
&quot;options&quot;:[
{
&quot;text&quot;:&quot;You hear some loud sounds from the center&quot;,
&quot;text&quot;:&quot;You hear some loud sounds coming from the center.&quot;,
&quot;action&quot;:[{&quot;deleteMapObject&quot;:-1},{&quot;advanceMapFlag&quot;:&quot;gate&quot;}],
&quot;name&quot;:&quot;flip the switch&quot;
&quot;name&quot;:&quot;flip the switch&quot;
&quot;options&quot;:[{
&quot;condition&quot;:[{&quot;getMapFlag&quot;:{&quot;key&quot;:&quot;gate&quot;,&quot;op&quot;:&quot;&gt;=&quot;,&quot;val&quot;:3}}],
&quot;action&quot;:[{&quot;deleteMapObject&quot;:55}],
@@ -128,12 +128,12 @@
<properties>
<property name="dialog">[
{
&quot;text&quot;:&quot;Hmm a big switch is embedded into the wall&quot;,
&quot;text&quot;:&quot;Hmm... A big switch is embedded into the wall.&quot;,
&quot;options&quot;:[
{
&quot;text&quot;:&quot;You hear some loud sounds from the center&quot;,
&quot;text&quot;:&quot;You hear some loud sounds coming from the center.&quot;,
&quot;action&quot;:[{&quot;deleteMapObject&quot;:-1},{&quot;advanceMapFlag&quot;:&quot;gate&quot;}],
&quot;name&quot;:&quot;flip the switch&quot;
&quot;name&quot;:&quot;flip the switch&quot;
&quot;options&quot;:[{
&quot;condition&quot;:[{&quot;getMapFlag&quot;:{&quot;key&quot;:&quot;gate&quot;,&quot;op&quot;:&quot;&gt;=&quot;,&quot;val&quot;:3}}],
&quot;action&quot;:[{&quot;deleteMapObject&quot;:55}],

View File

@@ -3,7 +3,7 @@ ManaCost:2 U U
Types:Legendary Creature Human Wizard
PT:3/3
T:Mode$ SpellCast | TriggerZones$ Battlefield | ValidCard$ Creature.withFlying+nonLegendary | ValidActivatingPlayer$ You | ResolvedLimit$ 1 | Execute$ TrigCopy | OptionalDecider$ You | TriggerDescription$ Whenever you cast a nonlegendary creature spell with flying, you may copy it, except the copy is a 1/1 Spirit in addition to its other types. Do this only once each turn. (The copy becomes a token.)
SVar:TrigCopy:DB$ CopySpellAbility | Defined$ TriggeredSpellAbility | CopySetPower$ 1 | CopySetToughness$ 1 | CopyAddTypes$ Spirit
SVar:TrigCopy:DB$ CopySpellAbility | Defined$ TriggeredSpellAbility | SetPower$ 1 | SetToughness$ 1 | AddTypes$ Spirit
DeckHas:Ability$Token
DeckHints:Keyword$Flying
SVar:BuffedBy:Creature.withFlying

View File

@@ -1,5 +1,5 @@
Name:Fork
ManaCost:R R
Types:Instant
A:SP$ CopySpellAbility | Cost$ R R | ValidTgts$ Instant,Sorcery | TargetType$ Spell | CopyIsColor$ Red | OverwriteColors$ True | MayChooseTarget$ True | SpellDescription$ Copy target instant or sorcery spell, except that the copy is red. You may choose new targets for the copy.
A:SP$ CopySpellAbility | Cost$ R R | ValidTgts$ Instant,Sorcery | TargetType$ Spell | SetColor$ Red | MayChooseTarget$ True | SpellDescription$ Copy target instant or sorcery spell, except that the copy is red. You may choose new targets for the copy.
Oracle:Copy target instant or sorcery spell, except that the copy is red. You may choose new targets for the copy.

View File

@@ -19,7 +19,7 @@ PT:3/6
K:Menace
K:Deathtouch
T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigRemoveCounter | TriggerDescription$ Whenever CARDNAME attacks, remove all counters from up to one target permanent.
SVar:TrigRemoveCounter:DB$ RemoveCounter | ValidTgts$ Permanent | CounterType$ All | CounterNum$ All
SVar:TrigRemoveCounter:DB$ RemoveCounter | ValidTgts$ Permanent | TargetMin$ 0 | TargetMax$ 1 | CounterType$ All | CounterNum$ All
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | CheckSVar$ X | SVarCompare$ EQ0 | Execute$ TrigDraw | TriggerDescription$ At the beginning of your upkeep, if you haven't been dealt combat damage since your last turn, you draw a card and you lose 1 life.
SVar:TrigDraw:DB$ Draw | SubAbility$ DBLoseLife
SVar:DBLoseLife:DB$ LoseLife | LifeAmount$ 1

View File

@@ -3,10 +3,10 @@ ManaCost:3 W R
Types:Instant
A:SP$ Charm | Cost$ 3 W R | Choices$ DBSpirit,DBIndestructible,DBHelix,DBSacrifice | CharmNum$ 2
SVar:DBSpirit:DB$ Token | TokenAmount$ 1 | TokenScript$ rw_3_2_spirit | TokenOwner$ You | SpellDescription$ Create a 3/2 red and white Spirit token.
SVar:DBIndestructible:DB$ PumpAll | ValidCards$ Creature.YouCtrl | NumAtt$ +1 | KW$ Indestructible & haste | SpellDescription$ Creatures you control get +1/+0 and gain indestructible and haste until end of turn.
SVar:DBHelix:DB$ DealDamage | ValidTgts$ Any | NumDmg$ 3 | SubAbility$ DBGainLife | SpellDescription$ CARDNAME deals 3 damage to any target. Target player gains 3 life.
SVar:DBGainLife:DB$ GainLife | ValidTgts$ Player | TgtPrompt$ Select target player (to gain 3 life) | LifeAmount$ 3 | SpellDescription$ Target player gains 3 life.
SVar:DBSacrifice:DB$ Sacrifice | Defined$ You | SacValid$ Permanent | SubAbility$ DBDraw
SVar:DBDraw:DB$ Draw | NumCards$ 2 | SpellDescription$ Sacrifice a permanent,draw two cards.
SVar:DBIndestructible:DB$ PumpAll | ValidCards$ Creature.YouCtrl | NumAtt$ +1 | KW$ Indestructible & Haste | SpellDescription$ Creatures you control get +1/+0 and gain indestructible and haste until end of turn.
SVar:DBHelix:DB$ DealDamage | ValidTgts$ Any | NumDmg$ 3 | SubAbility$ DBGainLife | SpellDescription$ CARDNAME deals 3 damage to any target. Target player gains 3 life.
SVar:DBGainLife:DB$ GainLife | ValidTgts$ Player | TgtPrompt$ Select target player (to gain 3 life) | LifeAmount$ 3
SVar:DBSacrifice:DB$ Sacrifice | Defined$ You | SacValid$ Permanent | SpellDescription$ Sacrifice a permanent, then draw two cards. | SubAbility$ DBDraw
SVar:DBDraw:DB$ Draw | NumCards$ 2
DeckHas:Ability$Token|LifeGain
Oracle:Choose two —\n• Create a 3/2 red and white Spirit creature token.\n• Creatures you control get +1/+0 and gain indestructible and haste until end of turn.\n• Lorehold Command deals 3 damage to any target. Target player gains 3 life.\n• Sacrifice a permanent, then draw two cards.

View File

@@ -4,13 +4,10 @@ Types:Legendary Creature Kor Artificer
PT:5/4
K:Affinity:Artifact.Equipment:equipment
T:Mode$ Attacks | ValidCard$ Creature.equipped+YouCtrl | TriggerZones$ Battlefield | Execute$ TrigExile | TriggerDescription$ Whenever an equipped creature you control attacks, exile the top card of your library. You may play that card this turn. You may cast Equipment spells this way without paying their mana costs.
SVar:TrigExile:DB$ Dig | Defined$ You | DigNum$ 1 | ChangeNum$ All | DestinationZone$ Exile | RememberChanged$ True | SubAbility$ DBBranch
SVar:DBBranch:DB$ Branch | BranchConditionSVar$ X | TrueSubAbility$ DBEffect2 | FalseSubAbility$ DBEffect | SubAbility$ DBCleanup
SVar:DBEffect2:DB$ Effect | StaticAbilities$ STPlay2 | RememberObjects$ Remembered | ForgetOnMoved$ Exile
SVar:STPlay2:Mode$ Continuous | MayPlay$ True | MayPlayWithoutManaCost$ True | EffectZone$ Command | Affected$ Card.IsRemembered | AffectedZone$ Exile | Description$ You may play that card this turn without paying its mana cost.
SVar:DBEffect:DB$ Effect | StaticAbilities$ STPlay | RememberObjects$ Remembered | ForgetOnMoved$ Exile
SVar:TrigExile:DB$ Dig | Defined$ You | DigNum$ 1 | ChangeNum$ All | DestinationZone$ Exile | RememberChanged$ True | SubAbility$ DBEffect
SVar:DBEffect:DB$ Effect | StaticAbilities$ STPlay,STPlay2 | RememberObjects$ Remembered | ForgetOnMoved$ Exile | SubAbility$ DBCleanup
SVar:STPlay:Mode$ Continuous | MayPlay$ True | EffectZone$ Command | Affected$ Card.IsRemembered | AffectedZone$ Exile | Description$ You may play that card this turn.
SVar:STPlay2:Mode$ Continuous | MayPlay$ True | MayPlayWithoutManaCost$ True | EffectZone$ Command | Affected$ Equipment.IsRemembered | ValidAfterStack$ Spell.Equipment | AffectedZone$ Exile | Description$ You may cast Equipment spells this way without paying their mana costs.
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:X:Remembered$Valid Equipment
DeckNeeds:Type$Equipment
Oracle:Affinity for Equipment\nWhenever an equipped creature you control attacks, exile the top card of your library. You may play that card this turn. You may cast Equipment spells this way without paying their mana costs.

View File

@@ -3,7 +3,7 @@ ManaCost:5 G G
Types:Legendary Planeswalker Nissa
Loyalty:5
A:AB$ Untap | Cost$ AddCounter<2/LOYALTY> | ValidTgts$ Creature | TargetMin$ 0 | TargetMax$ 2 | Planeswalker$ True | SubAbility$ DBUntap | TgtPrompt$ Select target creature | SpellDescription$ Untap up to two target creatures and up to two target lands.
SVar:DBUntap:DB$ Untap | ValidTgts$ Land | TargetMin$ 0 | TargetMax$ 2 | TgtPrompt$ Select target Land
SVar:DBUntap:DB$ Untap | ValidTgts$ Land | TargetMin$ 0 | TargetMax$ 2 | AILogic$ Always | TgtPrompt$ Select target Land
A:AB$ Pump | Cost$ SubCounter<3/LOYALTY> | Planeswalker$ True | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumAtt$ +5 | NumDef$ +5 | SpellDescription$ Target creature gets +5/+5 until end of turn.
A:AB$ Dig | Cost$ SubCounter<10/LOYALTY> | DigNum$ 10 | AnyNumber$ True | ChangeValid$ Creature,Land | DestinationZone$ Battlefield | Ultimate$ True | Planeswalker$ True | DestinationZone2$ Library | LibraryPosition$ -1 | RestRandomOrder$ True | SpellDescription$ Look at the top ten cards of your library. You may put any number of creature and/or land cards from among them onto the battlefield. Put the rest on the bottom of your library in a random order.
DeckHints:Name$Nissa's Encouragement|Brambleweft Behemoth|Forest

View File

@@ -4,7 +4,8 @@ Types:Legendary Creature Dragon Avatar
PT:5/5
K:Flying
K:Hexproof:Card.MonoColor:monocolored
S:Mode$ Continuous | Affected$ Instant.YouCtrl+numColorsEQ2,Sorcery.YouCtrl+numColorsEQ2 | AffectedZone$ Graveyard | AddKeyword$ Jump-start | Description$ Each instant and sorcery card in your graveyard that's exactly two colors has jump-start.
# TODO the AffectedZone Stack is needed for now but should be handled by the engine instead
S:Mode$ Continuous | Affected$ Instant.YouCtrl+numColorsEQ2,Sorcery.YouCtrl+numColorsEQ2 | AffectedZone$ Graveyard,Stack | AddKeyword$ Jump-start | Description$ Each instant and sorcery card in your graveyard that's exactly two colors has jump-start.
DeckHints:Type$Instant|Sorcery & Ability$Mill|Graveyard
DeckHas:Ability$Graveyard|Discard
Oracle:Flying, hexproof from monocolored\nEach instant and sorcery card in your graveyard that's exactly two colors has jump-start.

View File

@@ -2,7 +2,7 @@ Name:Ob Nixilis, the Adversary
ManaCost:1 B R
Types:Legendary Planeswalker Nixilis
Loyalty:3
K:Casualty:X:NonLegendary$ True | CopySetLoyalty$ Casualty:The copy isn't legendary and has starting loyalty X.
K:Casualty:X:NonLegendary$ True | SetLoyalty$ Casualty:The copy isn't legendary and has starting loyalty X.
A:AB$ RepeatEach | Cost$ AddCounter<1/LOYALTY> | Planeswalker$ True | RepeatPlayers$ Opponent | RepeatSubAbility$ DBDrain | SubAbility$ DBGainLife | SpellDescription$ Each opponent loses 2 life unless they discard a card. If you control a Demon or Devil, you gain 2 life.
SVar:DBDrain:DB$ LoseLife | Defined$ Player.IsRemembered | LifeAmount$ 2 | UnlessCost$ Discard<1/Card> | UnlessPayer$ Player.IsRemembered
SVar:DBGainLife:DB$ GainLife | LifeAmount$ 2 | ConditionPresent$ Demon.YouCtrl,Devil.YouCtrl | StackDescription$ None

View File

@@ -1,8 +1,8 @@
Name:Outlaws' Merriment
ManaCost:1 R W W
Types:Enchantment
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | Execute$ TrigCharm | TriggerZones$ Battlefield | TriggerDescription$ At the beginning of your upkeep, choose one at random. Create a red and white creature token with those characteristics.
SVar:TrigCharm:DB$ Charm | Random$ True | Choices$ DBToken1,DBToken2,DBToken3
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | Execute$ TrigCharm | TriggerZones$ Battlefield | TriggerDescription$ At the beginning of your upkeep, ABILITY
SVar:TrigCharm:DB$ Charm | Random$ True | Choices$ DBToken1,DBToken2,DBToken3 | AdditionalDescription$ Create a red and white creature token with those characteristics.
SVar:DBToken1:DB$ Token | TokenAmount$ 1 | TokenScript$ rw_3_1_human_warrior_trample_haste | TokenOwner$ You | SpellDescription$ 3/1 Human Warrior with trample and haste.
SVar:DBToken2:DB$ Token | TokenAmount$ 1 | TokenScript$ rw_2_1_human_cleric_lifelink_haste | TokenOwner$ You | SpellDescription$ 2/1 Human Cleric with lifelink and haste.
SVar:DBToken3:DB$ Token | TokenAmount$ 1 | TokenScript$ rw_1_2_human_rogue_haste_damage | TokenOwner$ You | SpellDescription$ 1/2 Human Rogue with haste and "When this creature enters the battlefield, it deals 1 damage to any target."

View File

@@ -3,6 +3,6 @@ ManaCost:R G
Types:Legendary Creature Elf Warrior
PT:2/2
A:AB$ Mana | Cost$ T | Produced$ G | SpellDescription$ Add {G}.
T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigMana | TriggerDescription$ Whenever CARDNAME attacks, add R R.
T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigMana | TriggerDescription$ Whenever CARDNAME attacks, add {R}{R}.
SVar:TrigMana:DB$ Mana | Produced$ R | Amount$ 2 | SpellDescription$ Add {R}{R}.
Oracle:Whenever Radha, Heir to Keld attacks, you may add {R}{R}.\n{T}: Add {G}.

View File

@@ -3,7 +3,7 @@ ManaCost:3 G U
Types:Legendary Creature Human Artificer
PT:3/5
T:Mode$ SpellCast | TriggerZones$ Battlefield | OptionalDecider$ You | ValidCard$ Creature.Bird,Creature.Beast | ValidActivatingPlayer$ You | NoResolvingCheck$ True | Execute$ TrigCopy | TriggerDescription$ Whenever you cast a Beast or Bird creature spell, you may copy it, except it's an artifact in addition to its other types. (The copy becomes a token.)
SVar:TrigCopy:DB$ CopySpellAbility | Defined$ TriggeredSpellAbility | CopyAddTypes$ Artifact
SVar:TrigCopy:DB$ CopySpellAbility | Defined$ TriggeredSpellAbility | AddTypes$ Artifact
DeckNeeds:Type$Beast|Bird
DeckHas:Ability$Token & Type$Artifact
Oracle:Whenever you cast a Beast or Bird creature spell, you may copy it, except it's an artifact in addition to its other types. (The copy becomes a token.)

View File

@@ -3,6 +3,6 @@ ManaCost:1 W
Types:Creature Human Knight
PT:1/2
A:AB$ PutCounter | Cost$ W T | CounterType$ P1P1 | CounterNum$ 1 | ValidTgts$ Creature.counters_GE1_P1P1 | TgtPrompt$ Select target creature with a +1/+1 counter | SpellDescription$ Put a +1/+1 counter on target creature with a +1/+1 counter on it.
A:AB$ PutCounter | Cost$ 4 W W T | CounterType$ P1P1 | CounterNum$ 1 | ValidTgts$ Creature | TgtPrompt$ Select target creature | SpellDescription$ Put a +1/+1 counter on target creature.
A:AB$ PutCounter | Cost$ 4 W W T | CounterType$ P1P1 | CounterNum$ 1 | ValidTgts$ Creature | TgtPrompt$ Select target creature | AILogic$ NoCounterOfType | SpellDescription$ Put a +1/+1 counter on target creature.
DeckHas:Ability$Counters
Oracle:{W}, {T}: Put a +1/+1 counter on target creature with a +1/+1 counter on it.\n{4}{W}{W}, {T}: Put a +1/+1 counter on target creature.

View File

@@ -126,12 +126,14 @@ public class GauntletUtil {
break;
case COMMANDER_DECK:
deck = DeckgenUtil.getCommanderDeck();
eventNames.add(deck.getName());
if (deck != null)
eventNames.add(deck.getName());
break;
default:
continue;
}
decks.add(deck);
if (deck != null)
decks.add(deck);
}
gauntlet.setDecks(decks);

View File

@@ -164,6 +164,8 @@ public final class FModel {
if (new AutoUpdater(true).attemptToUpdate()) {
//
}
// load types before loading cards
loadDynamicGamedata();
//load card database
final CardStorageReader reader = new CardStorageReader(ForgeConstants.CARD_DATA_DIR, progressBarBridge,
@@ -243,8 +245,6 @@ public final class FModel {
Spell.setPerformanceMode(preferences.getPrefBoolean(FPref.PERFORMANCE_MODE));
loadDynamicGamedata();
if (progressBar != null) {
FThreads.invokeInEdtLater(new Runnable() {
@Override