mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-16 02:38:02 +00:00
Merge remote-tracking branch 'upstream/master' into collector-number-in-card-list-and-card-db-refactoring
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>1.6.43-SNAPSHOT</version>
|
||||
<version>1.6.44-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-game</artifactId>
|
||||
|
||||
@@ -705,17 +705,20 @@ public class GameAction {
|
||||
}
|
||||
}
|
||||
|
||||
if (zoneFrom == null) {
|
||||
c.setCastFrom(null);
|
||||
c.setCastSA(null);
|
||||
} else if (zoneTo.is(ZoneType.Stack)) {
|
||||
c.setCastFrom(zoneFrom.getZoneType());
|
||||
if (zoneTo.is(ZoneType.Stack)) {
|
||||
// zoneFrom maybe null if the spell is cast from "Ouside the game", ex. ability of Garth One-Eye
|
||||
if (zoneFrom == null) {
|
||||
c.setCastFrom(null);
|
||||
} else {
|
||||
c.setCastFrom(zoneFrom.getZoneType());
|
||||
}
|
||||
if (cause != null && cause.isSpell() && c.equals(cause.getHostCard()) && !c.isCopiedSpell()) {
|
||||
c.setCastSA(cause);
|
||||
} else {
|
||||
c.setCastSA(null);
|
||||
}
|
||||
} else if (!(zoneTo.is(ZoneType.Battlefield) && zoneFrom.is(ZoneType.Stack))) {
|
||||
} else if (zoneFrom == null || !(zoneFrom.is(ZoneType.Stack) &&
|
||||
(zoneTo.is(ZoneType.Battlefield) || zoneTo.is(ZoneType.Merged)))) {
|
||||
c.setCastFrom(null);
|
||||
c.setCastSA(null);
|
||||
}
|
||||
|
||||
@@ -153,8 +153,7 @@ public final class GameActionUtil {
|
||||
final StringBuilder sb = new StringBuilder(sa.getDescription());
|
||||
if (!source.equals(host)) {
|
||||
sb.append(" by ");
|
||||
if ((host.isEmblem() || host.getType().hasSubtype("Effect"))
|
||||
&& host.getEffectSource() != null) {
|
||||
if ((host.isImmutable()) && host.getEffectSource() != null) {
|
||||
sb.append(host.getEffectSource());
|
||||
} else {
|
||||
sb.append(host);
|
||||
@@ -542,7 +541,6 @@ public final class GameActionUtil {
|
||||
final Card eff = new Card(game.nextCardId(), game);
|
||||
eff.setTimestamp(game.getNextTimestamp());
|
||||
eff.setName(sourceCard.getName() + "'s Effect");
|
||||
eff.addType("Effect");
|
||||
eff.setOwner(controller);
|
||||
|
||||
eff.setImageKey(sourceCard.getImageKey());
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package forge.game;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
@@ -8,7 +9,7 @@ import forge.game.ability.AbilityKey;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.trigger.TriggerType;
|
||||
|
||||
/**
|
||||
/**
|
||||
* Represents the planar dice for Planechase games.
|
||||
*
|
||||
*/
|
||||
@@ -16,7 +17,7 @@ public enum PlanarDice {
|
||||
Planeswalk,
|
||||
Chaos,
|
||||
Blank;
|
||||
|
||||
|
||||
public static PlanarDice roll(Player roller, PlanarDice riggedResult)
|
||||
{
|
||||
PlanarDice res = Blank;
|
||||
@@ -27,25 +28,37 @@ public enum PlanarDice {
|
||||
res = Planeswalk;
|
||||
else if (i == 1)
|
||||
res = Chaos;
|
||||
|
||||
|
||||
|
||||
|
||||
PlanarDice trigRes = res;
|
||||
|
||||
|
||||
if(roller.getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.blankIsChaos)
|
||||
&& res == Blank)
|
||||
{
|
||||
trigRes = Chaos;
|
||||
}
|
||||
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
|
||||
Map<AbilityKey, Object> runParams = AbilityKey.newMap();
|
||||
runParams.put(AbilityKey.Player, roller);
|
||||
runParams.put(AbilityKey.Result, trigRes);
|
||||
roller.getGame().getTriggerHandler().runTrigger(TriggerType.PlanarDice, runParams,false);
|
||||
|
||||
|
||||
|
||||
// Also run normal RolledDie and RolledDieOnce triggers
|
||||
runParams = AbilityKey.newMap();
|
||||
runParams.put(AbilityKey.Player, roller);
|
||||
runParams.put(AbilityKey.Sides, 6);
|
||||
runParams.put(AbilityKey.Result, 0);
|
||||
roller.getGame().getTriggerHandler().runTrigger(TriggerType.RolledDie, runParams, false);
|
||||
|
||||
runParams = AbilityKey.newMap();
|
||||
runParams.put(AbilityKey.Player, roller);
|
||||
runParams.put(AbilityKey.Sides, 6);
|
||||
runParams.put(AbilityKey.Result, Arrays.asList(0));
|
||||
roller.getGame().getTriggerHandler().runTrigger(TriggerType.RolledDieOnce, runParams, false);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parses a string into an enum member.
|
||||
* @param string to parse
|
||||
|
||||
@@ -31,6 +31,7 @@ import forge.card.mana.ManaCost;
|
||||
import forge.card.mana.ManaCostParser;
|
||||
import forge.card.mana.ManaCostShard;
|
||||
import forge.game.CardTraitBase;
|
||||
import forge.game.Direction;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.GameObject;
|
||||
@@ -111,9 +112,9 @@ public class AbilityUtils {
|
||||
// Probably will move to One function solution sometime in the future
|
||||
public static CardCollection getDefinedCards(final Card hostCard, final String def, final CardTraitBase sa) {
|
||||
CardCollection cards = new CardCollection();
|
||||
String defined = (def == null) ? "Self" : applyAbilityTextChangeEffects(def, sa); // default to Self
|
||||
final String[] incR = defined.split("\\.", 2);
|
||||
defined = incR[0];
|
||||
String changedDef = (def == null) ? "Self" : applyAbilityTextChangeEffects(def, sa); // default to Self
|
||||
final String[] incR = changedDef.split("\\.", 2);
|
||||
String defined = incR[0];
|
||||
final Game game = hostCard.getGame();
|
||||
|
||||
Card c = null;
|
||||
@@ -131,7 +132,7 @@ public class AbilityUtils {
|
||||
}
|
||||
}
|
||||
else if (defined.equals("EffectSource")) {
|
||||
if (hostCard.isEmblem() || hostCard.getType().hasSubtype("Effect")) {
|
||||
if (hostCard.isImmutable()) {
|
||||
c = findEffectRoot(hostCard);
|
||||
}
|
||||
}
|
||||
@@ -313,6 +314,10 @@ public class AbilityUtils {
|
||||
for (final Card imprint : hostCard.getImprintedCards()) {
|
||||
cards.add(game.getCardState(imprint));
|
||||
}
|
||||
} else if (defined.equals("UntilLeavesBattlefield")) {
|
||||
for (final Card ulb : hostCard.getUntilLeavesBattlefield()) {
|
||||
cards.add(game.getCardState(ulb));
|
||||
}
|
||||
} else if (defined.startsWith("ThisTurnEntered")) {
|
||||
final String[] workingCopy = defined.split("_");
|
||||
ZoneType destination, origin;
|
||||
@@ -345,6 +350,23 @@ public class AbilityUtils {
|
||||
cards.add(game.getCardState(cardByID));
|
||||
}
|
||||
}
|
||||
} else if (defined.startsWith("Valid")) {
|
||||
Iterable<Card> candidates;
|
||||
String validDefined;
|
||||
if (defined.startsWith("Valid ")) {
|
||||
candidates = game.getCardsIn(ZoneType.Battlefield);
|
||||
validDefined = changedDef.substring("Valid ".length());
|
||||
} else if (defined.startsWith("ValidAll ")) {
|
||||
candidates = game.getCardsInGame();
|
||||
validDefined = changedDef.substring("ValidAll ".length());
|
||||
} else {
|
||||
String[] s = changedDef.split(" ", 2);
|
||||
String zone = s[0].substring("Valid".length());
|
||||
candidates = game.getCardsIn(ZoneType.smartValueOf(zone));
|
||||
validDefined = s[1];
|
||||
}
|
||||
cards.addAll(CardLists.getValidCards(candidates, validDefined.split(","), hostCard.getController(), hostCard, sa));
|
||||
return cards;
|
||||
} else {
|
||||
CardCollection list = null;
|
||||
if (sa instanceof SpellAbility) {
|
||||
@@ -376,19 +398,6 @@ public class AbilityUtils {
|
||||
}
|
||||
}
|
||||
|
||||
if (defined.startsWith("Valid ")) {
|
||||
String validDefined = defined.substring("Valid ".length());
|
||||
list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), validDefined.split(","), hostCard.getController(), hostCard, sa);
|
||||
} else if (defined.startsWith("ValidAll ")) {
|
||||
String validDefined = defined.substring("ValidAll ".length());
|
||||
list = CardLists.getValidCards(game.getCardsInGame(), validDefined.split(","), hostCard.getController(), hostCard, sa);
|
||||
} else if (defined.startsWith("Valid")) {
|
||||
String[] s = defined.split(" ");
|
||||
String zone = s[0].substring("Valid".length());
|
||||
String validDefined = s[1];
|
||||
list = CardLists.getValidCards(game.getCardsIn(ZoneType.smartValueOf(zone)), validDefined.split(","), hostCard.getController(), hostCard, sa);
|
||||
}
|
||||
|
||||
if (list != null) {
|
||||
cards.addAll(list);
|
||||
}
|
||||
@@ -420,7 +429,7 @@ public class AbilityUtils {
|
||||
private static Card findEffectRoot(Card startCard) {
|
||||
Card cc = startCard.getEffectSource();
|
||||
if (cc != null) {
|
||||
if (cc.isEmblem() || cc.getType().hasSubtype("Effect")) {
|
||||
if (cc.isImmutable()) {
|
||||
return findEffectRoot(cc);
|
||||
}
|
||||
return cc;
|
||||
@@ -1268,6 +1277,9 @@ public class AbilityUtils {
|
||||
}
|
||||
else if (defined.equals("Opponent")) {
|
||||
players.addAll(player.getOpponents());
|
||||
} else if (defined.startsWith("NextPlayerToYour")) {
|
||||
Direction dir = defined.substring(16).equals("Left") ? Direction.Left : Direction.Right;
|
||||
players.add(game.getNextPlayerAfter(player, dir));
|
||||
}
|
||||
else {
|
||||
for (Player p : game.getPlayersInTurnOrder()) {
|
||||
|
||||
@@ -142,6 +142,7 @@ public enum ApiType {
|
||||
ReplaceEffect (ReplaceEffect.class),
|
||||
ReplaceMana (ReplaceManaEffect.class),
|
||||
ReplaceDamage (ReplaceDamageEffect.class),
|
||||
ReplaceToken (ReplaceTokenEffect.class),
|
||||
ReplaceSplitDamage (ReplaceSplitDamageEffect.class),
|
||||
RestartGame (RestartGameEffect.class),
|
||||
Reveal (RevealEffect.class),
|
||||
|
||||
@@ -13,7 +13,6 @@ import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Table;
|
||||
|
||||
import forge.GameCommand;
|
||||
import forge.card.CardType;
|
||||
import forge.card.MagicColor;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameEntity;
|
||||
@@ -457,25 +456,19 @@ public abstract class SpellAbilityEffect {
|
||||
final Card eff = new Card(game.nextCardId(), game);
|
||||
eff.setTimestamp(game.getNextTimestamp());
|
||||
eff.setName(name);
|
||||
eff.setColor(hostCard.determineColor().getColor());
|
||||
// if name includes emblem then it should be one
|
||||
eff.addType(name.startsWith("Emblem") ? "Emblem" : "Effect");
|
||||
// add Planeswalker types into Emblem for fun
|
||||
if (name.startsWith("Emblem") && hostCard.isPlaneswalker()) {
|
||||
for (final String type : hostCard.getType().getSubtypes()) {
|
||||
if (CardType.isAPlaneswalkerType(type)) {
|
||||
eff.addType(type);
|
||||
}
|
||||
}
|
||||
if (name.startsWith("Emblem")) {
|
||||
eff.setEmblem(true);
|
||||
// Emblem needs to be colorless
|
||||
eff.setColor(MagicColor.COLORLESS);
|
||||
}
|
||||
|
||||
eff.setOwner(controller);
|
||||
eff.setSVars(sa.getSVars());
|
||||
|
||||
eff.setImageKey(image);
|
||||
if (eff.getType().hasType(CardType.CoreType.Emblem)) {
|
||||
eff.setColor(MagicColor.COLORLESS);
|
||||
} else {
|
||||
eff.setColor(hostCard.determineColor().getColor());
|
||||
}
|
||||
|
||||
eff.setImmutable(true);
|
||||
eff.setEffectSource(sa);
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ import forge.game.card.CardPredicates;
|
||||
import forge.game.card.CardZoneTable;
|
||||
import forge.game.card.CounterEnumType;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.card.token.TokenInfo;
|
||||
import forge.game.event.GameEventCombatChanged;
|
||||
import forge.game.event.GameEventTokenCreated;
|
||||
import forge.game.player.Player;
|
||||
@@ -53,37 +52,24 @@ public class AmassEffect extends TokenEffectBase {
|
||||
final int amount = AbilityUtils.calculateAmount(card, sa.getParamOrDefault("Num", "1"), sa);
|
||||
final boolean remember = sa.hasParam("RememberAmass");
|
||||
|
||||
boolean useZoneTable = true;
|
||||
CardZoneTable triggerList = sa.getChangeZoneTable();
|
||||
if (triggerList == null) {
|
||||
triggerList = new CardZoneTable();
|
||||
useZoneTable = false;
|
||||
}
|
||||
if (sa.hasParam("ChangeZoneTable")) {
|
||||
sa.setChangeZoneTable(triggerList);
|
||||
useZoneTable = true;
|
||||
}
|
||||
|
||||
MutableBoolean combatChanged = new MutableBoolean(false);
|
||||
// create army token if needed
|
||||
if (CardLists.count(activator.getCardsIn(ZoneType.Battlefield), CardPredicates.isType("Army")) == 0) {
|
||||
final String tokenScript = "b_0_0_zombie_army";
|
||||
CardZoneTable triggerList = new CardZoneTable();
|
||||
MutableBoolean combatChanged = new MutableBoolean(false);
|
||||
|
||||
final Card prototype = TokenInfo.getProtoType(tokenScript, sa, false);
|
||||
makeTokenTable(makeTokenTableInternal(activator, "b_0_0_zombie_army", 1, sa), false, triggerList, combatChanged, sa);
|
||||
|
||||
makeTokens(prototype, activator, sa, 1, true, false, triggerList, combatChanged);
|
||||
triggerList.triggerChangesZoneAll(game, sa);
|
||||
triggerList.clear();
|
||||
|
||||
if (!useZoneTable) {
|
||||
triggerList.triggerChangesZoneAll(game, sa);
|
||||
triggerList.clear();
|
||||
}
|
||||
game.fireEvent(new GameEventTokenCreated());
|
||||
|
||||
if (combatChanged.isTrue()) {
|
||||
game.updateCombatForView();
|
||||
game.fireEvent(new GameEventCombatChanged());
|
||||
}
|
||||
}
|
||||
|
||||
if (combatChanged.isTrue()) {
|
||||
game.updateCombatForView();
|
||||
game.fireEvent(new GameEventCombatChanged());
|
||||
}
|
||||
Map<String, Object> params = Maps.newHashMap();
|
||||
params.put("CounterType", CounterType.get(CounterEnumType.P1P1));
|
||||
params.put("Amount", 1);
|
||||
|
||||
@@ -190,11 +190,6 @@ public class AnimateEffect extends AnimateEffectBase {
|
||||
}
|
||||
}
|
||||
|
||||
// Restore immutable to effect
|
||||
if (sa.hasParam("Immutable")) {
|
||||
c.setImmutable(true);
|
||||
}
|
||||
|
||||
game.fireEvent(new GameEventCardStatsChanged(c));
|
||||
}
|
||||
|
||||
|
||||
@@ -164,14 +164,6 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
|
||||
final Trigger parsedTrigger = TriggerHandler.parseTrigger(AbilityUtils.getSVar(sa, s), c, false, sa);
|
||||
addedTriggers.add(parsedTrigger);
|
||||
}
|
||||
if (sa.hasParam("GainsTriggeredAbilitiesOf")) {
|
||||
final List<Card> cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("GainsTriggeredAbilitiesOf"), sa);
|
||||
for (final Card card : cards) {
|
||||
for (Trigger t : card.getTriggers()) {
|
||||
addedTriggers.add(t.copy(c, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// give replacement effects
|
||||
final List<ReplacementEffect> addedReplacements = Lists.newArrayList();
|
||||
|
||||
@@ -119,6 +119,12 @@ public class ChooseCardEffect extends SpellAbilityEffect {
|
||||
Aggregates.random(choices, validAmount, chosen);
|
||||
} else {
|
||||
String title = sa.hasParam("ChoiceTitle") ? sa.getParam("ChoiceTitle") : Localizer.getInstance().getMessage("lblChooseaCard") + " ";
|
||||
if (sa.hasParam ("ChoiceTitleAppendDefined")) {
|
||||
String defined = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("ChoiceTitleAppendDefined"), sa).toString();
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append(title).append(" ").append(defined);
|
||||
title = sb.toString();
|
||||
}
|
||||
chosen.addAll(p.getController().chooseCardsForEffect(choices, sa, title, minAmount, validAmount, !sa.hasParam("Mandatory"), null));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,8 +72,7 @@ public class ChooseTypeEffect extends SpellAbilityEffect {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
throw new InvalidParameterException(sa.getHostCard() + "'s ability resulted in no types to choose from");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ public class ControlGainEffect extends SpellAbilityEffect {
|
||||
if (tapOnLose) {
|
||||
c.tap();
|
||||
}
|
||||
} // if
|
||||
}
|
||||
host.removeGainControlTargets(c);
|
||||
}
|
||||
|
||||
@@ -91,7 +91,12 @@ public class ControlGainEffect extends SpellAbilityEffect {
|
||||
|
||||
if (sa.hasParam("ControlledByTarget")) {
|
||||
tgtCards = CardLists.filterControlledBy(tgtCards, getTargetPlayers(sa));
|
||||
}
|
||||
}
|
||||
|
||||
// in case source was LKI or still resolving
|
||||
if (source.isLKI() || source.getZone().is(ZoneType.Stack)) {
|
||||
source = game.getCardState(source);
|
||||
}
|
||||
|
||||
// check for lose control criteria right away
|
||||
if (lose != null && lose.contains("LeavesPlay") && !source.isInZone(ZoneType.Battlefield)) {
|
||||
@@ -103,7 +108,6 @@ public class ControlGainEffect extends SpellAbilityEffect {
|
||||
|
||||
boolean combatChanged = false;
|
||||
for (Card tgtC : tgtCards) {
|
||||
|
||||
if (!tgtC.isInPlay() || !tgtC.canBeControlledBy(newController)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import forge.game.card.CardFactory;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.CardZoneTable;
|
||||
import forge.game.card.TokenCreateTable;
|
||||
import forge.game.event.GameEventCombatChanged;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
@@ -177,15 +178,18 @@ public class CopyPermanentEffect extends TokenEffectBase {
|
||||
}
|
||||
|
||||
MutableBoolean combatChanged = new MutableBoolean(false);
|
||||
TokenCreateTable tokenTable = new TokenCreateTable();
|
||||
|
||||
for (final Card c : tgtCards) {
|
||||
// if it only targets player, it already got all needed cards from defined
|
||||
if (sa.usesTargeting() && !sa.getTargetRestrictions().canTgtPlayer() && !c.canBeTargetedBy(sa)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
makeTokens(getProtoType(sa, c), controller, sa, numCopies, true, true, triggerList, combatChanged);
|
||||
tokenTable.put(controller, getProtoType(sa, c, controller), numCopies);
|
||||
} // end foreach Card
|
||||
|
||||
makeTokenTable(tokenTable, true, triggerList, combatChanged, sa);
|
||||
|
||||
if (!useZoneTable) {
|
||||
triggerList.triggerChangesZoneAll(game, sa);
|
||||
triggerList.clear();
|
||||
@@ -196,9 +200,8 @@ public class CopyPermanentEffect extends TokenEffectBase {
|
||||
}
|
||||
} // end resolve
|
||||
|
||||
private Card getProtoType(final SpellAbility sa, final Card original) {
|
||||
private Card getProtoType(final SpellAbility sa, final Card original, final Player newOwner) {
|
||||
final Card host = sa.getHostCard();
|
||||
final Player newOwner = sa.getActivatingPlayer();
|
||||
int id = newOwner == null ? 0 : newOwner.getGame().nextCardId();
|
||||
final Card copy = new Card(id, original.getPaperCard(), host.getGame());
|
||||
copy.setOwner(newOwner);
|
||||
|
||||
@@ -12,7 +12,6 @@ public class DetachedCardEffect extends Card {
|
||||
card = card0;
|
||||
|
||||
setName(name0);
|
||||
addType("Effect");
|
||||
setOwner(card0.getOwner());
|
||||
setImmutable(true);
|
||||
|
||||
|
||||
@@ -140,10 +140,6 @@ public class EffectEffect extends SpellAbilityEffect {
|
||||
final Card eff = createEffect(sa, controller, name, image);
|
||||
eff.setSetCode(sa.getHostCard().getSetCode());
|
||||
eff.setRarity(sa.getHostCard().getRarity());
|
||||
// For Raging River effect to add attacker "left" or "right" pile later
|
||||
if (sa.hasParam("Mutable")) {
|
||||
eff.setImmutable(false);
|
||||
}
|
||||
|
||||
// Abilities and triggers work the same as they do for Token
|
||||
// Grant abilities
|
||||
|
||||
@@ -38,7 +38,6 @@ public class FightEffect extends DamageBaseEffect {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.game.ability.SpellAbilityEffect#resolve(forge.game.spellability.SpellAbility)
|
||||
*/
|
||||
@@ -77,6 +76,7 @@ public class FightEffect extends DamageBaseEffect {
|
||||
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
|
||||
runParams.put(AbilityKey.Fighters, fighters);
|
||||
game.getTriggerHandler().runTrigger(TriggerType.FightOnce, runParams, false);
|
||||
}
|
||||
|
||||
private static List<Card> getFighters(SpellAbility sa) {
|
||||
|
||||
@@ -6,7 +6,6 @@ import forge.game.Game;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardZoneTable;
|
||||
import forge.game.card.token.TokenInfo;
|
||||
import forge.game.event.GameEventCombatChanged;
|
||||
import forge.game.event.GameEventTokenCreated;
|
||||
import forge.game.player.Player;
|
||||
@@ -36,14 +35,13 @@ public class InvestigateEffect extends TokenEffectBase {
|
||||
|
||||
final int amount = AbilityUtils.calculateAmount(card, sa.getParamOrDefault("Num", "1"), sa);
|
||||
|
||||
final String tokenScript = "c_a_clue_draw";
|
||||
final Card prototype = TokenInfo.getProtoType(tokenScript, sa, false);
|
||||
|
||||
// Investigate in Sequence
|
||||
for (final Player p : getTargetPlayers(sa)) {
|
||||
for (int i = 0; i < amount; i++) {
|
||||
CardZoneTable triggerList = new CardZoneTable();
|
||||
MutableBoolean combatChanged = new MutableBoolean(false);
|
||||
makeTokens(prototype, p, sa, 1, true, false, triggerList, combatChanged);
|
||||
|
||||
makeTokenTable(makeTokenTableInternal(p, "c_a_clue_draw", 1, sa), false, triggerList, combatChanged, sa);
|
||||
|
||||
triggerList.triggerChangesZoneAll(game, sa);
|
||||
p.addInvestigatedThisTurn();
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package forge.game.ability.effects;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
@@ -69,6 +72,8 @@ public class PlayEffect extends SpellAbilityEffect {
|
||||
final boolean optional = sa.hasParam("Optional");
|
||||
boolean remember = sa.hasParam("RememberPlayed");
|
||||
int amount = 1;
|
||||
boolean hasTotalCMCLimit = sa.hasParam("WithTotalCMC");
|
||||
int totalCMCLimit = Integer.MAX_VALUE;
|
||||
if (sa.hasParam("Amount") && !sa.getParam("Amount").equals("All")) {
|
||||
amount = AbilityUtils.calculateAmount(source, sa.getParam("Amount"), sa);
|
||||
}
|
||||
@@ -163,13 +168,15 @@ public class PlayEffect extends SpellAbilityEffect {
|
||||
|
||||
if (sa.hasParam("ValidSA")) {
|
||||
final String valid[] = {sa.getParam("ValidSA")};
|
||||
List<Card> toRemove = Lists.newArrayList();
|
||||
for (Card c : tgtCards) {
|
||||
Iterator<Card> it = tgtCards.iterator();
|
||||
while (it.hasNext()) {
|
||||
Card c = it.next();
|
||||
if (!Iterables.any(AbilityUtils.getBasicSpellsFromPlayEffect(c, controller), SpellAbilityPredicates.isValid(valid, controller , c, sa))) {
|
||||
toRemove.add(c);
|
||||
// it.remove will only remove item from the list part of CardCollection
|
||||
tgtCards.asSet().remove(c);
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
tgtCards.removeAll(toRemove);
|
||||
if (tgtCards.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
@@ -179,15 +186,37 @@ public class PlayEffect extends SpellAbilityEffect {
|
||||
amount = tgtCards.size();
|
||||
}
|
||||
|
||||
if (hasTotalCMCLimit) {
|
||||
totalCMCLimit = AbilityUtils.calculateAmount(source, sa.getParam("WithTotalCMC"), sa);
|
||||
}
|
||||
|
||||
if (controlledByPlayer != null) {
|
||||
activator.addController(controlledByTimeStamp, controlledByPlayer);
|
||||
}
|
||||
|
||||
boolean singleOption = tgtCards.size() == 1 && amount == 1 && optional;
|
||||
Map<String, Object> params = hasTotalCMCLimit ? new HashMap<>() : null;
|
||||
|
||||
while (!tgtCards.isEmpty() && amount > 0 && totalCMCLimit >= 0) {
|
||||
if (hasTotalCMCLimit) {
|
||||
// filter out cards with mana value greater than limit
|
||||
Iterator<Card> it = tgtCards.iterator();
|
||||
final String [] valid = {"Spell.cmcLE"+totalCMCLimit};
|
||||
while (it.hasNext()) {
|
||||
Card c = it.next();
|
||||
if (!Iterables.any(AbilityUtils.getBasicSpellsFromPlayEffect(c, controller), SpellAbilityPredicates.isValid(valid, controller , c, sa))) {
|
||||
// it.remove will only remove item from the list part of CardCollection
|
||||
tgtCards.asSet().remove(c);
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
if (tgtCards.isEmpty())
|
||||
break;
|
||||
params.put("CMCLimit", totalCMCLimit);
|
||||
}
|
||||
|
||||
while (!tgtCards.isEmpty() && amount > 0) {
|
||||
activator.getController().tempShowCards(showCards);
|
||||
Card tgtCard = controller.getController().chooseSingleEntityForEffect(tgtCards, sa, Localizer.getInstance().getMessage("lblSelectCardToPlay"), !singleOption && optional, null);
|
||||
Card tgtCard = controller.getController().chooseSingleEntityForEffect(tgtCards, sa, Localizer.getInstance().getMessage("lblSelectCardToPlay"), !singleOption && optional, params);
|
||||
activator.getController().endTempShowCards();
|
||||
if (tgtCard == null) {
|
||||
break;
|
||||
@@ -248,6 +277,14 @@ public class PlayEffect extends SpellAbilityEffect {
|
||||
final String valid[] = {sa.getParam("ValidSA")};
|
||||
sas = Lists.newArrayList(Iterables.filter(sas, SpellAbilityPredicates.isValid(valid, controller , source, sa)));
|
||||
}
|
||||
if (hasTotalCMCLimit) {
|
||||
Iterator<SpellAbility> it = sas.iterator();
|
||||
while (it.hasNext()) {
|
||||
SpellAbility s = it.next();
|
||||
if (s.getPayCosts().getTotalMana().getCMC() > totalCMCLimit)
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
|
||||
if (sas.isEmpty()) {
|
||||
continue;
|
||||
@@ -276,6 +313,8 @@ public class PlayEffect extends SpellAbilityEffect {
|
||||
continue;
|
||||
}
|
||||
|
||||
final int tgtCMC = tgtSA.getPayCosts().getTotalMana().getCMC();
|
||||
|
||||
if (sa.hasParam("WithoutManaCost")) {
|
||||
tgtSA = tgtSA.copyWithNoManaCost();
|
||||
} else if (sa.hasParam("PlayCost")) {
|
||||
@@ -341,6 +380,7 @@ public class PlayEffect extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
amount--;
|
||||
totalCMCLimit -= tgtCMC;
|
||||
}
|
||||
|
||||
// Remove controlled by player if any
|
||||
|
||||
@@ -121,7 +121,7 @@ public class PumpEffect extends SpellAbilityEffect {
|
||||
final Card host = sa.getHostCard();
|
||||
//if host is not on the battlefield don't apply
|
||||
// Suspend should does Affect the Stack
|
||||
if ((sa.hasParam("UntilLoseControlOfHost") || sa.hasParam("UntilHostLeavesPlay"))
|
||||
if (("UntilHostLeavesPlay".equals(sa.getParam("Duration")) || "UntilLoseControlOfHost".equals(sa.getParam("Duration")))
|
||||
&& !(host.isInPlay() || host.isInZone(ZoneType.Stack))) {
|
||||
return;
|
||||
}
|
||||
@@ -158,7 +158,6 @@ public class PumpEffect extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
if (tgts.size() > 0) {
|
||||
|
||||
for (final GameEntity c : tgts) {
|
||||
sb.append(c).append(" ");
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ public class RegenerationEffect extends SpellAbilityEffect {
|
||||
// Play the Regen sound
|
||||
game.fireEvent(new GameEventCardRegenerated());
|
||||
|
||||
if (host.getType().hasStringType("Effect")) {
|
||||
if (host.isImmutable()) {
|
||||
c.subtractShield(host);
|
||||
host.removeRemembered(c);
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ public class RepeatEffect extends SpellAbilityEffect {
|
||||
// TODO Replace Infinite Loop Break with a game draw. Here are the scenarios that can cause this:
|
||||
// Helm of Obedience vs Graveyard to Library replacement effect
|
||||
|
||||
if(source.getName().equals("Helm of Obedience")) {
|
||||
if (source.getName().equals("Helm of Obedience")) {
|
||||
StringBuilder infLoop = new StringBuilder(sa.getHostCard().toString());
|
||||
infLoop.append(" - To avoid an infinite loop, this repeat has been broken ");
|
||||
infLoop.append(" and the game will now continue in the current state, ending the loop early. ");
|
||||
|
||||
@@ -47,7 +47,7 @@ public class ReplaceDamageEffect extends SpellAbilityEffect {
|
||||
prevent -= n;
|
||||
|
||||
if (!StringUtils.isNumeric(varValue) && card.getSVar(varValue).startsWith("Number$")) {
|
||||
if (card.getType().hasStringType("Effect") && prevent <= 0) {
|
||||
if (card.isImmutable() && prevent <= 0) {
|
||||
game.getAction().exile(card, null);
|
||||
} else {
|
||||
card.setSVar(varValue, "Number$" + prevent);
|
||||
|
||||
@@ -3,12 +3,6 @@ package forge.game.ability.effects;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import forge.game.Game;
|
||||
import forge.game.GameLogEntryType;
|
||||
import forge.game.GameObject;
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
@@ -17,25 +11,21 @@ import forge.game.card.Card;
|
||||
import forge.game.card.token.TokenInfo;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.replacement.ReplacementResult;
|
||||
import forge.game.replacement.ReplacementType;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.util.TextUtil;
|
||||
|
||||
|
||||
public class ReplaceEffect extends SpellAbilityEffect {
|
||||
|
||||
@Override
|
||||
public void resolve(SpellAbility sa) {
|
||||
final Card card = sa.getHostCard();
|
||||
final Game game = card.getGame();
|
||||
|
||||
final AbilityKey varName = AbilityKey.fromString(sa.getParam("VarName"));
|
||||
final String varValue = sa.getParam("VarValue");
|
||||
final String type = sa.getParamOrDefault("VarType", "amount");
|
||||
final ReplacementType retype = sa.getReplacementEffect().getMode();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<AbilityKey, Object> originalParams = (Map<AbilityKey, Object>) sa.getReplacingObject(AbilityKey.OriginalParams);
|
||||
Map<AbilityKey, Object> params = Maps.newHashMap(originalParams);
|
||||
Map<AbilityKey, Object> params = (Map<AbilityKey, Object>) sa.getReplacingObject(AbilityKey.OriginalParams);
|
||||
|
||||
if ("Card".equals(type)) {
|
||||
List<Card> list = AbilityUtils.getDefinedCards(card, varValue, sa);
|
||||
@@ -53,7 +43,7 @@ public class ReplaceEffect extends SpellAbilityEffect {
|
||||
params.put(varName, list.get(0));
|
||||
}
|
||||
} else if ("TokenScript".equals(type)) {
|
||||
final Card protoType = TokenInfo.getProtoType(varValue, sa);
|
||||
final Card protoType = TokenInfo.getProtoType(varValue, sa, sa.getActivatingPlayer());
|
||||
if (protoType != null) {
|
||||
params.put(varName, protoType);
|
||||
}
|
||||
@@ -61,42 +51,7 @@ public class ReplaceEffect extends SpellAbilityEffect {
|
||||
params.put(varName, AbilityUtils.calculateAmount(card, varValue, sa));
|
||||
}
|
||||
|
||||
if (params.containsKey(AbilityKey.EffectOnly)) {
|
||||
params.put(AbilityKey.EffectOnly, true);
|
||||
}
|
||||
|
||||
if (retype == ReplacementType.DamageDone) {
|
||||
for (Map.Entry<AbilityKey, Object> e : params.entrySet()) {
|
||||
originalParams.put(e.getKey(), e.getValue());
|
||||
}
|
||||
originalParams.put(AbilityKey.ReplacementResult, ReplacementResult.Updated);
|
||||
return;
|
||||
}
|
||||
|
||||
// need to log Updated events there, or the log is wrong order
|
||||
String message = sa.getReplacementEffect().toString();
|
||||
if ( !StringUtils.isEmpty(message)) {
|
||||
message = TextUtil.fastReplace(message, "CARDNAME", card.getName());
|
||||
game.getGameLog().add(GameLogEntryType.EFFECT_REPLACED, message);
|
||||
}
|
||||
|
||||
//try to call replacementHandler with new Params
|
||||
ReplacementResult result = game.getReplacementHandler().run(retype, params);
|
||||
switch (result) {
|
||||
case NotReplaced:
|
||||
case Updated: {
|
||||
for (Map.Entry<AbilityKey, Object> e : params.entrySet()) {
|
||||
originalParams.put(e.getKey(), e.getValue());
|
||||
}
|
||||
// effect was updated
|
||||
originalParams.put(AbilityKey.ReplacementResult, ReplacementResult.Updated);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// effect was replaced with something else
|
||||
originalParams.put(AbilityKey.ReplacementResult, result);
|
||||
break;
|
||||
}
|
||||
params.put(AbilityKey.ReplacementResult, ReplacementResult.Updated);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,20 +4,15 @@ import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import forge.card.ColorSet;
|
||||
import forge.card.MagicColor;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameLogEntryType;
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.replacement.ReplacementResult;
|
||||
import forge.game.replacement.ReplacementType;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.util.TextUtil;
|
||||
|
||||
public class ReplaceManaEffect extends SpellAbilityEffect {
|
||||
|
||||
@@ -25,17 +20,14 @@ public class ReplaceManaEffect extends SpellAbilityEffect {
|
||||
public void resolve(SpellAbility sa) {
|
||||
final Card card = sa.getHostCard();
|
||||
final Player player = sa.getActivatingPlayer();
|
||||
final Game game = card.getGame();
|
||||
|
||||
// outside of Replacement Effect, unwanted result
|
||||
if (!sa.isReplacementAbility()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final ReplacementType event = sa.getReplacementEffect().getMode();
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<AbilityKey, Object> originalParams = (Map<AbilityKey, Object>) sa.getReplacingObject(AbilityKey.OriginalParams);
|
||||
Map<AbilityKey, Object> params = Maps.newHashMap(originalParams);
|
||||
Map<AbilityKey, Object> params = (Map<AbilityKey, Object>) sa.getReplacingObject(AbilityKey.OriginalParams);
|
||||
|
||||
String replaced = (String)sa.getReplacingObject(AbilityKey.Mana);
|
||||
if (sa.hasParam("ReplaceMana")) {
|
||||
@@ -79,31 +71,8 @@ public class ReplaceManaEffect extends SpellAbilityEffect {
|
||||
replaced = StringUtils.repeat(replaced, " ", Integer.valueOf(sa.getParam("ReplaceAmount")));
|
||||
}
|
||||
params.put(AbilityKey.Mana, replaced);
|
||||
|
||||
// need to log Updated events there, or the log is wrong order
|
||||
String message = sa.getReplacementEffect().toString();
|
||||
if (!StringUtils.isEmpty(message)) {
|
||||
message = TextUtil.fastReplace(message, "CARDNAME", card.getName());
|
||||
game.getGameLog().add(GameLogEntryType.EFFECT_REPLACED, message);
|
||||
}
|
||||
|
||||
//try to call replacementHandler with new Params
|
||||
ReplacementResult result = game.getReplacementHandler().run(event, params);
|
||||
switch (result) {
|
||||
case NotReplaced:
|
||||
case Updated: {
|
||||
for (Map.Entry<AbilityKey, Object> e : params.entrySet()) {
|
||||
originalParams.put(e.getKey(), e.getValue());
|
||||
}
|
||||
// effect was updated
|
||||
originalParams.put(AbilityKey.ReplacementResult, ReplacementResult.Updated);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// effect was replaced with something else
|
||||
originalParams.put(AbilityKey.ReplacementResult, result);
|
||||
break;
|
||||
}
|
||||
// effect was updated
|
||||
params.put(AbilityKey.ReplacementResult, ReplacementResult.Updated);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ public class ReplaceSplitDamageEffect extends SpellAbilityEffect {
|
||||
dmg -= n;
|
||||
prevent -= n;
|
||||
|
||||
if (card.getType().hasStringType("Effect") && prevent <= 0) {
|
||||
if (card.isImmutable() && prevent <= 0) {
|
||||
game.getAction().exile(card, null);
|
||||
} else if (!StringUtils.isNumeric(varValue)) {
|
||||
sa.setSVar(varValue, "Number$" + prevent);
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
package forge.game.ability.effects;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.TokenCreateTable;
|
||||
import forge.game.card.token.TokenInfo;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.replacement.ReplacementResult;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
public class ReplaceTokenEffect extends SpellAbilityEffect {
|
||||
|
||||
@Override
|
||||
public void resolve(SpellAbility sa) {
|
||||
final Card card = sa.getHostCard();
|
||||
final Player p = sa.getActivatingPlayer();
|
||||
final Game game = card.getGame();
|
||||
|
||||
// ReplaceToken Effect only applies to one Player
|
||||
Player affected = (Player) sa.getReplacingObject(AbilityKey.Player);
|
||||
TokenCreateTable table = (TokenCreateTable) sa.getReplacingObject(AbilityKey.Token);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<AbilityKey, Object> originalParams = (Map<AbilityKey, Object>) sa
|
||||
.getReplacingObject(AbilityKey.OriginalParams);
|
||||
|
||||
// currently the only ones that changes the amount does double it
|
||||
if ("Amount".equals(sa.getParam("Type"))) {
|
||||
for (Map.Entry<Card, Integer> e : table.row(affected).entrySet()) {
|
||||
if (!sa.matchesValidParam("ValidCard", e.getKey())) {
|
||||
continue;
|
||||
}
|
||||
// currently the amount is only doubled
|
||||
table.put(affected, e.getKey(), e.getValue() * 2);
|
||||
}
|
||||
} else if ("AddToken".equals(sa.getParam("Type"))) {
|
||||
long timestamp = game.getNextTimestamp();
|
||||
|
||||
Map<Player, Integer> byController = Maps.newHashMap();
|
||||
for (Map.Entry<Card, Integer> e : table.row(affected).entrySet()) {
|
||||
if (!sa.matchesValidParam("ValidCard", e.getKey())) {
|
||||
continue;
|
||||
}
|
||||
Player contoller = e.getKey().getController();
|
||||
int old = ObjectUtils.defaultIfNull(byController.get(contoller), 0);
|
||||
byController.put(contoller, old + e.getValue());
|
||||
}
|
||||
|
||||
if (!byController.isEmpty()) {
|
||||
// for Xorn, might matter if you could somehow create Treasure under multiple players control
|
||||
if (sa.hasParam("Amount")) {
|
||||
int i = AbilityUtils.calculateAmount(card, sa.getParam("Amount"), sa);
|
||||
for (Map.Entry<Player, Integer> e : byController.entrySet()) {
|
||||
e.setValue(i);
|
||||
}
|
||||
}
|
||||
for (Map.Entry<Player, Integer> e : byController.entrySet()) {
|
||||
for (String script : sa.getParam("TokenScript").split(",")) {
|
||||
final Card token = TokenInfo.getProtoType(script, sa, p);
|
||||
|
||||
if (token == null) {
|
||||
throw new RuntimeException("don't find Token for TokenScript: " + script);
|
||||
}
|
||||
token.setController(e.getKey(), timestamp);
|
||||
table.put(p, token, e.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if ("ReplaceToken".equals(sa.getParam("Type"))) {
|
||||
long timestamp = game.getNextTimestamp();
|
||||
|
||||
Map<Player, Integer> toInsertMap = Maps.newHashMap();
|
||||
Set<Card> toRemoveSet = Sets.newHashSet();
|
||||
for (Map.Entry<Card, Integer> e : table.row(affected).entrySet()) {
|
||||
if (!sa.matchesValidParam("ValidCard", e.getKey())) {
|
||||
continue;
|
||||
}
|
||||
Player controller = e.getKey().getController();
|
||||
int old = ObjectUtils.defaultIfNull(toInsertMap.get(controller), 0);
|
||||
toInsertMap.put(controller, old + e.getValue());
|
||||
toRemoveSet.add(e.getKey());
|
||||
}
|
||||
// remove replaced tokens
|
||||
table.row(affected).keySet().removeAll(toRemoveSet);
|
||||
|
||||
// insert new tokens
|
||||
for (Map.Entry<Player, Integer> pe : toInsertMap.entrySet()) {
|
||||
if (pe.getValue() <= 0) {
|
||||
continue;
|
||||
}
|
||||
for (String script : sa.getParam("TokenScript").split(",")) {
|
||||
final Card token = TokenInfo.getProtoType(script, sa, pe.getKey());
|
||||
|
||||
if (token == null) {
|
||||
throw new RuntimeException("don't find Token for TokenScript: " + script);
|
||||
}
|
||||
|
||||
token.setController(pe.getKey(), timestamp);
|
||||
table.put(affected, token, pe.getValue());
|
||||
}
|
||||
}
|
||||
} else if ("ReplaceController".equals(sa.getParam("Type"))) {
|
||||
long timestamp = game.getNextTimestamp();
|
||||
Player newController = sa.getActivatingPlayer();
|
||||
if (sa.hasParam("NewController")) {
|
||||
newController = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("NewController"), sa).get(0);
|
||||
}
|
||||
for (Map.Entry<Card, Integer> c : table.row(affected).entrySet()) {
|
||||
if (!sa.matchesValidParam("ValidCard", c.getKey())) {
|
||||
continue;
|
||||
}
|
||||
c.getKey().setController(newController, timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
// effect was updated
|
||||
originalParams.put(AbilityKey.ReplacementResult, ReplacementResult.Updated);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -23,10 +23,8 @@ import forge.game.Game;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardZoneTable;
|
||||
import forge.game.card.token.TokenInfo;
|
||||
import forge.game.event.GameEventCombatChanged;
|
||||
import forge.game.event.GameEventTokenCreated;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
public class TokenEffect extends TokenEffectBase {
|
||||
@@ -36,20 +34,6 @@ public class TokenEffect extends TokenEffectBase {
|
||||
return sa.getDescription();
|
||||
}
|
||||
|
||||
public Card loadTokenPrototype(SpellAbility sa) {
|
||||
if (!sa.hasParam("TokenScript")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final Card result = TokenInfo.getProtoType(sa.getParam("TokenScript"), sa);
|
||||
|
||||
if (result == null) {
|
||||
throw new RuntimeException("don't find Token for TokenScript: " + sa.getParam("TokenScript"));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resolve(SpellAbility sa) {
|
||||
final Card host = sa.getHostCard();
|
||||
@@ -67,9 +51,8 @@ public class TokenEffect extends TokenEffectBase {
|
||||
}
|
||||
}
|
||||
|
||||
Card prototype = loadTokenPrototype(sa);
|
||||
|
||||
final int finalAmount = AbilityUtils.calculateAmount(host, sa.getParamOrDefault("TokenAmount", "1"), sa);
|
||||
MutableBoolean combatChanged = new MutableBoolean(false);
|
||||
|
||||
boolean useZoneTable = true;
|
||||
CardZoneTable triggerList = sa.getChangeZoneTable();
|
||||
@@ -82,10 +65,8 @@ public class TokenEffect extends TokenEffectBase {
|
||||
useZoneTable = true;
|
||||
}
|
||||
|
||||
MutableBoolean combatChanged = new MutableBoolean(false);
|
||||
for (final Player owner : AbilityUtils.getDefinedPlayers(host, sa.getParamOrDefault("TokenOwner", "You"), sa)) {
|
||||
makeTokens(prototype, owner, sa, finalAmount, true, false, triggerList, combatChanged);
|
||||
}
|
||||
makeTokenTable(AbilityUtils.getDefinedPlayers(host, sa.getParamOrDefault("TokenOwner", "You"), sa),
|
||||
sa.getParam("TokenScript").split(","), finalAmount, false, triggerList, combatChanged, sa);
|
||||
|
||||
if (!useZoneTable) {
|
||||
triggerList.triggerChangesZoneAll(game, sa);
|
||||
|
||||
@@ -2,123 +2,212 @@ package forge.game.ability.effects;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang3.mutable.MutableBoolean;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.common.collect.Table;
|
||||
|
||||
import forge.GameCommand;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.GameObject;
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardFactory;
|
||||
import forge.game.card.CardUtil;
|
||||
import forge.game.card.CardZoneTable;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.card.TokenCreateTable;
|
||||
import forge.game.card.token.TokenInfo;
|
||||
import forge.game.event.GameEventCardStatsChanged;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.replacement.ReplacementType;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.trigger.Trigger;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
public abstract class TokenEffectBase extends SpellAbilityEffect {
|
||||
|
||||
protected List<Card> makeTokens(final Card prototype, final Player creator, final SpellAbility sa, int finalAmount,
|
||||
boolean applyMultiplier, boolean clone, CardZoneTable triggerList, MutableBoolean combatChanged) {
|
||||
protected TokenCreateTable createTokenTable(Iterable<Player> players, String[] tokenScripts, final int finalAmount, final SpellAbility sa) {
|
||||
|
||||
TokenCreateTable tokenTable = new TokenCreateTable();
|
||||
for (final Player owner : players) {
|
||||
for (String script : tokenScripts) {
|
||||
final Card result = TokenInfo.getProtoType(script, sa, owner);
|
||||
|
||||
if (result == null) {
|
||||
throw new RuntimeException("don't find Token for TokenScript: " + script);
|
||||
}
|
||||
// set owner
|
||||
result.setOwner(owner);
|
||||
tokenTable.put(owner, result, finalAmount);
|
||||
}
|
||||
}
|
||||
return tokenTable;
|
||||
}
|
||||
|
||||
protected TokenCreateTable makeTokenTableInternal(Player owner, String script, final int finalAmount, final SpellAbility sa) {
|
||||
TokenCreateTable tokenTable = new TokenCreateTable();
|
||||
final Card result = TokenInfo.getProtoType(script, sa, owner, false);
|
||||
|
||||
if (result == null) {
|
||||
throw new RuntimeException("don't find Token for TokenScript: " + script);
|
||||
}
|
||||
// set owner
|
||||
result.setOwner(owner);
|
||||
tokenTable.put(owner, result, finalAmount);
|
||||
|
||||
return tokenTable;
|
||||
}
|
||||
|
||||
protected TokenCreateTable makeTokenTable(Iterable<Player> players, String[] tokenScripts, final int finalAmount, final boolean clone,
|
||||
CardZoneTable triggerList, MutableBoolean combatChanged, final SpellAbility sa) {
|
||||
return makeTokenTable(createTokenTable(players, tokenScripts, finalAmount, sa), clone, triggerList, combatChanged, sa);
|
||||
}
|
||||
|
||||
protected TokenCreateTable makeTokenTable(TokenCreateTable tokenTable, final boolean clone, CardZoneTable triggerList, MutableBoolean combatChanged, final SpellAbility sa) {
|
||||
final Card host = sa.getHostCard();
|
||||
final Game game = host.getGame();
|
||||
final long timestamp = game.getNextTimestamp();
|
||||
long timestamp = game.getNextTimestamp();
|
||||
|
||||
// support PlayerCollection for affected
|
||||
Set<Player> toRemove = Sets.newHashSet();
|
||||
for (Player p : tokenTable.rowKeySet()) {
|
||||
|
||||
final Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(p);
|
||||
repParams.put(AbilityKey.Token, tokenTable);
|
||||
repParams.put(AbilityKey.EffectOnly, true); // currently only effects can create tokens?
|
||||
|
||||
switch (game.getReplacementHandler().run(ReplacementType.CreateToken, repParams)) {
|
||||
case NotReplaced:
|
||||
break;
|
||||
case Updated: {
|
||||
tokenTable = (TokenCreateTable) repParams.get(AbilityKey.Token);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
toRemove.add(p);
|
||||
}
|
||||
}
|
||||
tokenTable.rowKeySet().removeAll(toRemove);
|
||||
|
||||
final List<String> pumpKeywords = Lists.newArrayList();
|
||||
if (sa.hasParam("PumpKeywords")) {
|
||||
pumpKeywords.addAll(Arrays.asList(sa.getParam("PumpKeywords").split(" & ")));
|
||||
}
|
||||
|
||||
List<Card> allTokens = Lists.newArrayList();
|
||||
for (Card tok : TokenInfo.makeTokensFromPrototype(prototype, creator, finalAmount, applyMultiplier)) {
|
||||
if (sa.hasParam("TokenTapped")) {
|
||||
tok.setTapped(true);
|
||||
}
|
||||
|
||||
if (!sa.hasParam("AttachAfter") && sa.hasParam("AttachedTo") && !attachTokenTo(tok, sa)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sa.hasParam("WithCounters")) {
|
||||
String[] parse = sa.getParam("WithCounters").split("_");
|
||||
tok.addEtbCounter(CounterType.getType(parse[0]), Integer.parseInt(parse[1]), creator);
|
||||
}
|
||||
|
||||
if (sa.hasParam("WithCountersType")) {
|
||||
CounterType cType = CounterType.getType(sa.getParam("WithCountersType"));
|
||||
int cAmount = AbilityUtils.calculateAmount(host, sa.getParamOrDefault("WithCountersAmount", "1"), sa);
|
||||
tok.addEtbCounter(cType, cAmount, creator);
|
||||
}
|
||||
|
||||
if (clone) {
|
||||
tok.setCopiedPermanent(prototype);
|
||||
}
|
||||
|
||||
// Should this be catching the Card that's returned?
|
||||
Card c = game.getAction().moveToPlay(tok, sa);
|
||||
if (c == null || c.getZone() == null) {
|
||||
// in case token can't enter the battlefield, it isn't created
|
||||
triggerList.put(ZoneType.None, ZoneType.None, c);
|
||||
continue;
|
||||
}
|
||||
triggerList.put(ZoneType.None, c.getZone().getZoneType(), c);
|
||||
|
||||
creator.addTokensCreatedThisTurn();
|
||||
|
||||
if (clone) {
|
||||
c.setCloneOrigin(host);
|
||||
}
|
||||
if (!pumpKeywords.isEmpty()) {
|
||||
c.addChangedCardKeywords(pumpKeywords, Lists.newArrayList(), false, false, timestamp);
|
||||
addPumpUntil(sa, c, timestamp);
|
||||
}
|
||||
|
||||
if (sa.hasParam("AtEOTTrig")) {
|
||||
addSelfTrigger(sa, sa.getParam("AtEOTTrig"), c);
|
||||
}
|
||||
|
||||
if (addToCombat(c, tok.getController(), sa, "TokenAttacking", "TokenBlocking")) {
|
||||
combatChanged.setTrue();
|
||||
}
|
||||
|
||||
if (sa.hasParam("AttachAfter") && sa.hasParam("AttachedTo")) {
|
||||
attachTokenTo(tok, sa);
|
||||
}
|
||||
|
||||
c.updateStateForView();
|
||||
|
||||
if (sa.hasParam("RememberTokens")) {
|
||||
host.addRemembered(c);
|
||||
}
|
||||
if (sa.hasParam("ImprintTokens")) {
|
||||
host.addImprintedCard(c);
|
||||
}
|
||||
if (sa.hasParam("RememberSource")) {
|
||||
c.addRemembered(host);
|
||||
}
|
||||
if (sa.hasParam("TokenRemembered")) {
|
||||
final String remembered = sa.getParam("TokenRemembered");
|
||||
for (final Object o : AbilityUtils.getDefinedObjects(host, remembered, sa)) {
|
||||
c.addRemembered(o);
|
||||
for (final Table.Cell<Player, Card, Integer> c : tokenTable.cellSet()) {
|
||||
Card prototype = c.getColumnKey();
|
||||
Player creator = c.getRowKey();
|
||||
Player controller = prototype.getController();
|
||||
int cellAmount = c.getValue();
|
||||
for (int i = 0; i < cellAmount; i++) {
|
||||
Card tok = CardFactory.copyCard(prototype, true);
|
||||
// Crafty Cutpurse would change under which control it does enter,
|
||||
// but it shouldn't change who creates the token
|
||||
tok.setOwner(creator);
|
||||
if (creator != controller) {
|
||||
tok.setController(controller, timestamp);
|
||||
}
|
||||
tok.setTimestamp(timestamp);
|
||||
tok.setToken(true);
|
||||
|
||||
// do effect stuff with the token
|
||||
if (sa.hasParam("TokenTapped")) {
|
||||
tok.setTapped(true);
|
||||
}
|
||||
|
||||
if (!sa.hasParam("AttachAfter") && sa.hasParam("AttachedTo") && !attachTokenTo(tok, sa)) {
|
||||
continue;
|
||||
}
|
||||
if (sa.hasParam("WithCounters")) {
|
||||
String[] parse = sa.getParam("WithCounters").split("_");
|
||||
tok.addEtbCounter(CounterType.getType(parse[0]), Integer.parseInt(parse[1]), creator);
|
||||
}
|
||||
|
||||
if (sa.hasParam("WithCountersType")) {
|
||||
CounterType cType = CounterType.getType(sa.getParam("WithCountersType"));
|
||||
int cAmount = AbilityUtils.calculateAmount(host, sa.getParamOrDefault("WithCountersAmount", "1"), sa);
|
||||
tok.addEtbCounter(cType, cAmount, creator);
|
||||
}
|
||||
|
||||
if (sa.hasParam("AddTriggersFrom")) {
|
||||
final List<Card> cards = AbilityUtils.getDefinedCards(host, sa.getParam("AddTriggersFrom"), sa);
|
||||
for (final Card card : cards) {
|
||||
for (final Trigger trig : card.getTriggers()) {
|
||||
tok.addTrigger(trig.copy(tok, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (clone) {
|
||||
tok.setCopiedPermanent(prototype);
|
||||
}
|
||||
|
||||
// Should this be catching the Card that's returned?
|
||||
Card moved = game.getAction().moveToPlay(tok, sa);
|
||||
if (moved == null || moved.getZone() == null) {
|
||||
// in case token can't enter the battlefield, it isn't created
|
||||
triggerList.put(ZoneType.None, ZoneType.None, moved);
|
||||
continue;
|
||||
}
|
||||
triggerList.put(ZoneType.None, moved.getZone().getZoneType(), moved);
|
||||
|
||||
creator.addTokensCreatedThisTurn();
|
||||
|
||||
if (clone) {
|
||||
moved.setCloneOrigin(host);
|
||||
}
|
||||
if (!pumpKeywords.isEmpty()) {
|
||||
moved.addChangedCardKeywords(pumpKeywords, Lists.newArrayList(), false, false, timestamp);
|
||||
addPumpUntil(sa, moved, timestamp);
|
||||
}
|
||||
|
||||
if (sa.hasParam("AtEOTTrig")) {
|
||||
addSelfTrigger(sa, sa.getParam("AtEOTTrig"), moved);
|
||||
}
|
||||
|
||||
if (addToCombat(moved, tok.getController(), sa, "TokenAttacking", "TokenBlocking")) {
|
||||
combatChanged.setTrue();
|
||||
}
|
||||
|
||||
if (sa.hasParam("AttachAfter") && sa.hasParam("AttachedTo")) {
|
||||
attachTokenTo(tok, sa);
|
||||
}
|
||||
|
||||
moved.updateStateForView();
|
||||
|
||||
if (sa.hasParam("RememberTokens")) {
|
||||
host.addRemembered(moved);
|
||||
}
|
||||
if (sa.hasParam("ImprintTokens")) {
|
||||
host.addImprintedCard(moved);
|
||||
}
|
||||
if (sa.hasParam("RememberSource")) {
|
||||
moved.addRemembered(host);
|
||||
}
|
||||
if (sa.hasParam("TokenRemembered")) {
|
||||
final String remembered = sa.getParam("TokenRemembered");
|
||||
for (final Object o : AbilityUtils.getDefinedObjects(host, remembered, sa)) {
|
||||
moved.addRemembered(o);
|
||||
}
|
||||
}
|
||||
allTokens.add(moved);
|
||||
}
|
||||
allTokens.add(c);
|
||||
}
|
||||
|
||||
if (sa.hasParam("AtEOT")) {
|
||||
registerDelayedTrigger(sa, sa.getParam("AtEOT"), allTokens);
|
||||
}
|
||||
return allTokens;
|
||||
return tokenTable;
|
||||
}
|
||||
|
||||
private boolean attachTokenTo(Card tok, SpellAbility sa) {
|
||||
|
||||
@@ -206,6 +206,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
|
||||
// for Vanguard / Manapool / Emblems etc.
|
||||
private boolean isImmutable = false;
|
||||
private boolean isEmblem = false;
|
||||
|
||||
private int exertThisTurn = 0;
|
||||
private PlayerCollection exertedByPlayer = new PlayerCollection();
|
||||
@@ -1629,7 +1630,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
return exiledWith;
|
||||
}
|
||||
public final void setExiledWith(final Card e) {
|
||||
exiledWith = e;
|
||||
exiledWith = view.setCard(exiledWith, e, TrackableProperty.ExiledWith);
|
||||
}
|
||||
|
||||
public final void cleanupExiledWith() {
|
||||
@@ -1738,6 +1739,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
public void setCurrentRoom(String room) {
|
||||
currentRoom = room;
|
||||
view.updateCurrentRoom(this);
|
||||
view.getCurrentState().updateAbilityText(this, getCurrentState());
|
||||
}
|
||||
public boolean isInLastRoom() {
|
||||
for (final Trigger t : getTriggers()) {
|
||||
@@ -2154,6 +2156,9 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
}
|
||||
|
||||
public String getAbilityText(final CardState state) {
|
||||
final String linebreak = "\r\n\r\n";
|
||||
final String grayTag = "<span style=\"color:gray;\">";
|
||||
final String endTag = "</span>";
|
||||
final CardTypeView type = state.getType();
|
||||
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
@@ -2199,9 +2204,9 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
// Get original description since text might be translated
|
||||
if (replacementEffect.hasParam("Description") &&
|
||||
replacementEffect.getParam("Description").contains("enters the battlefield")) {
|
||||
sb.append(text).append("\r\n");
|
||||
sb.append(text).append(linebreak);
|
||||
} else {
|
||||
replacementEffects.append(text).append("\r\n");
|
||||
replacementEffects.append(text).append(linebreak);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2213,7 +2218,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
Cost cost = first.getPayCosts();
|
||||
if (cost != null && !cost.isOnlyManaCost()) {
|
||||
sb.append(cost.toString());
|
||||
sb.append("\r\n");
|
||||
sb.append(linebreak);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2233,15 +2238,22 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
}
|
||||
|
||||
// Give spellText line breaks for easier reading
|
||||
sb.append("\r\n");
|
||||
sb.append(text.replaceAll("\\\\r\\\\n", "\r\n"));
|
||||
sb.append("\r\n");
|
||||
sb.append(linebreak);
|
||||
|
||||
// Triggered abilities
|
||||
for (final Trigger trig : state.getTriggers()) {
|
||||
if (!trig.isSecondary() && !trig.isClassAbility()) {
|
||||
boolean disabled = false;
|
||||
// Disable text of other rooms
|
||||
if (type.isDungeon() && !trig.getOverridingAbility().getParam("RoomName").equals(getCurrentRoom())) {
|
||||
disabled = true;
|
||||
}
|
||||
String trigStr = trig.replaceAbilityText(trig.toString(), state);
|
||||
sb.append(trigStr.replaceAll("\\\\r\\\\n", "\r\n")).append("\r\n");
|
||||
if (disabled) sb.append(grayTag);
|
||||
sb.append(trigStr.replaceAll("\\\\r\\\\n", "\r\n"));
|
||||
if (disabled) sb.append(endTag);
|
||||
sb.append(linebreak);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2253,7 +2265,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
if (!stAb.isSecondary() && !stAb.isClassAbility()) {
|
||||
final String stAbD = stAb.toString();
|
||||
if (!stAbD.equals("")) {
|
||||
sb.append(stAbD).append("\r\n");
|
||||
sb.append(stAbD).append(linebreak);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2326,7 +2338,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
desc = TextUtil.fastReplace(desc, "EFFECTSOURCE", host.getEffectSource().getName());
|
||||
}
|
||||
sb.append(desc);
|
||||
sb.append("\r\n");
|
||||
sb.append(linebreak);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2334,13 +2346,10 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
|
||||
// Class Abilities
|
||||
if (isClassCard()) {
|
||||
String linebreak = "\r\n\r\n";
|
||||
sb.append(linebreak);
|
||||
// Currently the maximum levels of all Class cards are all 3
|
||||
for (int level = 1; level <= 3; ++level) {
|
||||
boolean disabled = level > getClassLevel() && isInZone(ZoneType.Battlefield);
|
||||
final String grayTag = "<span style=\"color:gray;\">";
|
||||
final String endTag = "</span>";
|
||||
for (final Trigger trig : state.getTriggers()) {
|
||||
if (trig.isClassLevelNAbility(level) && !trig.isSecondary()) {
|
||||
if (disabled) sb.append(grayTag);
|
||||
@@ -4532,15 +4541,13 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
}
|
||||
|
||||
public final boolean isPermanent() {
|
||||
return !isImmutable && (isInZone(ZoneType.Battlefield) || getType().isPermanent());
|
||||
return !isImmutable() && (isInZone(ZoneType.Battlefield) || getType().isPermanent());
|
||||
}
|
||||
|
||||
public final boolean isSpell() {
|
||||
return (isInstant() || isSorcery() || (isAura() && !isInZone((ZoneType.Battlefield))));
|
||||
}
|
||||
|
||||
public final boolean isEmblem() { return getType().isEmblem(); }
|
||||
|
||||
public final boolean isLand() { return getType().isLand(); }
|
||||
public final boolean isBasicLand() { return getType().isBasicLand(); }
|
||||
public final boolean isSnow() { return getType().isSnow(); }
|
||||
@@ -4799,11 +4806,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
// Takes one argument like Permanent.Blue+withFlying
|
||||
@Override
|
||||
public final boolean isValid(final String restriction, final Player sourceController, final Card source, CardTraitBase spellAbility) {
|
||||
if (isImmutable() && source != null && !source.isRemembered(this) &&
|
||||
!(restriction.startsWith("Emblem") || restriction.startsWith("Effect"))) { // special case exclusion
|
||||
return false;
|
||||
}
|
||||
|
||||
// Inclusive restrictions are Card types
|
||||
final String[] incR = restriction.split("\\.", 2);
|
||||
|
||||
@@ -4813,14 +4815,27 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
incR[0] = incR[0].substring(1); // consume negation sign
|
||||
}
|
||||
|
||||
if (incR[0].equals("Spell") && !isSpell()) {
|
||||
return testFailed;
|
||||
}
|
||||
if (incR[0].equals("Permanent") && !isPermanent()) {
|
||||
return testFailed;
|
||||
}
|
||||
if (!incR[0].equals("card") && !incR[0].equals("Card") && !incR[0].equals("Spell")
|
||||
&& !incR[0].equals("Permanent") && !getType().hasStringType(incR[0])) {
|
||||
if (incR[0].equals("Spell")) {
|
||||
if (!isSpell()) {
|
||||
return testFailed;
|
||||
}
|
||||
} else if (incR[0].equals("Permanent")) {
|
||||
if (!isPermanent()) {
|
||||
return testFailed;
|
||||
}
|
||||
} else if (incR[0].equals("Effect")) {
|
||||
if (!isImmutable()) {
|
||||
return testFailed;
|
||||
}
|
||||
} else if (incR[0].equals("Emblem")) {
|
||||
if (!isEmblem()) {
|
||||
return testFailed;
|
||||
}
|
||||
} else if (incR[0].equals("card") || incR[0].equals("Card")) {
|
||||
if (isImmutable()) {
|
||||
return testFailed;
|
||||
}
|
||||
} else if (!getType().hasStringType(incR[0])) {
|
||||
return testFailed; // Check for wrong type
|
||||
}
|
||||
|
||||
@@ -4828,15 +4843,8 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
final String excR = incR[1];
|
||||
final String[] exRs = excR.split("\\+"); // Exclusive Restrictions are ...
|
||||
for (String exR : exRs) {
|
||||
if (exR.startsWith("!")) {
|
||||
exR = exR.substring(1);
|
||||
if (hasProperty(exR, sourceController, source, spellAbility)) {
|
||||
return testFailed;
|
||||
}
|
||||
} else {
|
||||
if (!hasProperty(exR, sourceController, source, spellAbility)) {
|
||||
return testFailed;
|
||||
}
|
||||
if (!hasProperty(exR, sourceController, source, spellAbility)) {
|
||||
return testFailed;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4846,6 +4854,9 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
// Takes arguments like Blue or withFlying
|
||||
@Override
|
||||
public boolean hasProperty(final String property, final Player sourceController, final Card source, CardTraitBase spellAbility) {
|
||||
if (property.startsWith("!")) {
|
||||
return !CardProperty.cardHasProperty(this, property.substring(1), sourceController, source, spellAbility);
|
||||
}
|
||||
return CardProperty.cardHasProperty(this, property, sourceController, source, spellAbility);
|
||||
}
|
||||
|
||||
@@ -4854,6 +4865,15 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
}
|
||||
public final void setImmutable(final boolean isImmutable0) {
|
||||
isImmutable = isImmutable0;
|
||||
view.updateImmutable(this);
|
||||
}
|
||||
|
||||
public final boolean isEmblem() {
|
||||
return isEmblem;
|
||||
}
|
||||
public final void setEmblem(final boolean isEmblem0) {
|
||||
isEmblem = isEmblem0;
|
||||
view.updateEmblem(this);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -5969,6 +5989,19 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
public void setCastFrom(final ZoneType castFrom0) {
|
||||
castFrom = castFrom0;
|
||||
}
|
||||
public boolean wasCast() {
|
||||
if (hasMergedCard()) {
|
||||
boolean wasCast = false;
|
||||
for (Card c : getMergedCards()) {
|
||||
if (null != c.getCastFrom()) {
|
||||
wasCast = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return wasCast;
|
||||
}
|
||||
return getCastFrom() != null;
|
||||
}
|
||||
|
||||
public SpellAbility getCastSA() {
|
||||
return castSA;
|
||||
|
||||
@@ -350,7 +350,7 @@ public class CardProperty {
|
||||
return false;
|
||||
}
|
||||
} else if (property.equals("EffectSource")) {
|
||||
if (!source.isEmblem() && !source.getType().hasSubtype("Effect")) {
|
||||
if (!source.isImmutable()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -446,6 +446,9 @@ public class CardProperty {
|
||||
}
|
||||
} else if (property.startsWith("CanEnchant")) {
|
||||
final String restriction = property.substring(10);
|
||||
if (restriction.equals("EquippedBy")) {
|
||||
if (!source.getEquipping().canBeAttached(card)) return false;
|
||||
}
|
||||
if (restriction.equals("Remembered")) {
|
||||
for (final Object rem : source.getRemembered()) {
|
||||
if (!(rem instanceof Card) || !((Card) rem).canBeAttached(card))
|
||||
@@ -1658,6 +1661,10 @@ public class CardProperty {
|
||||
if (source.hasImprintedCard(card)) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.equals("IsGoaded")) {
|
||||
if (!card.isGoaded()) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.equals("NoAbilities")) {
|
||||
if (!card.hasNoAbilities()) {
|
||||
return false;
|
||||
@@ -1680,16 +1687,15 @@ public class CardProperty {
|
||||
return false;
|
||||
}
|
||||
} else if (property.equals("wasCast")) {
|
||||
if (null == card.getCastFrom()) {
|
||||
if (!card.wasCast()) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.equals("wasNotCast")) {
|
||||
if (null != card.getCastFrom()) {
|
||||
if (card.wasCast()) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.startsWith("wasCastFrom")) {
|
||||
// How are we getting in here with a comma?
|
||||
final String strZone = property.split(",")[0].substring(11);
|
||||
final String strZone = property.substring(11);
|
||||
final ZoneType realZone = ZoneType.smartValueOf(strZone);
|
||||
if (realZone != card.getCastFrom()) {
|
||||
return false;
|
||||
|
||||
@@ -120,7 +120,7 @@ public final class CardUtil {
|
||||
List<Card> res = Lists.newArrayList();
|
||||
final Game game = src.getGame();
|
||||
if (to != ZoneType.Stack) {
|
||||
for (Player p : game.getPlayers()) {
|
||||
for (Player p : game.getRegisteredPlayers()) {
|
||||
res.addAll(p.getZone(to).getCardsAddedThisTurn(from));
|
||||
}
|
||||
}
|
||||
@@ -242,6 +242,7 @@ public final class CardUtil {
|
||||
newCopy.setToken(in.isToken());
|
||||
newCopy.setCopiedSpell(in.isCopiedSpell());
|
||||
newCopy.setImmutable(in.isImmutable());
|
||||
newCopy.setEmblem(in.isEmblem());
|
||||
|
||||
// lock in the current P/T
|
||||
newCopy.setBasePower(in.getCurrentPower());
|
||||
@@ -530,6 +531,7 @@ public final class CardUtil {
|
||||
// however, due to the changes necessary for SA_Requirements this is much
|
||||
// different than the original
|
||||
public static List<Card> getValidCardsToTarget(TargetRestrictions tgt, SpellAbility ability) {
|
||||
Card activatingCard = ability.getHostCard();
|
||||
final Game game = ability.getActivatingPlayer().getGame();
|
||||
final List<ZoneType> zone = tgt.getZone();
|
||||
|
||||
@@ -539,7 +541,6 @@ public final class CardUtil {
|
||||
if (canTgtStack) {
|
||||
// Since getTargetableCards doesn't have additional checks if one of the Zones is stack
|
||||
// Remove the activating card from targeting itself if its on the Stack
|
||||
Card activatingCard = ability.getHostCard();
|
||||
if (activatingCard.isInZone(ZoneType.Stack)) {
|
||||
choices.remove(ability.getHostCard());
|
||||
}
|
||||
@@ -561,7 +562,7 @@ public final class CardUtil {
|
||||
|
||||
final List<Card> choicesCopy = Lists.newArrayList(choices);
|
||||
for (final Card c : choicesCopy) {
|
||||
if (c.getCMC() > tgt.getMaxTotalCMC(c, ability) - totalCMCTargeted) {
|
||||
if (c.getCMC() > tgt.getMaxTotalCMC(activatingCard, ability) - totalCMCTargeted) {
|
||||
choices.remove(c);
|
||||
}
|
||||
}
|
||||
@@ -576,7 +577,7 @@ public final class CardUtil {
|
||||
|
||||
final List<Card> choicesCopy = Lists.newArrayList(choices);
|
||||
for (final Card c : choicesCopy) {
|
||||
if (c.getNetPower() > tgt.getMaxTotalPower(c, ability) - totalPowerTargeted) {
|
||||
if (c.getNetPower() > tgt.getMaxTotalPower(activatingCard, ability) - totalPowerTargeted) {
|
||||
choices.remove(c);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,6 +233,20 @@ public class CardView extends GameEntityView {
|
||||
set(TrackableProperty.Token, c.isToken());
|
||||
}
|
||||
|
||||
public boolean isImmutable() {
|
||||
return get(TrackableProperty.IsImmutable);
|
||||
}
|
||||
public void updateImmutable(Card c) {
|
||||
set(TrackableProperty.IsImmutable, c.isImmutable());
|
||||
}
|
||||
|
||||
public boolean isEmblem() {
|
||||
return get(TrackableProperty.IsEmblem);
|
||||
}
|
||||
public void updateEmblem(Card c) {
|
||||
set(TrackableProperty.IsEmblem, c.isEmblem());
|
||||
}
|
||||
|
||||
public boolean isTokenCard() { return get(TrackableProperty.TokenCard); }
|
||||
void updateTokenCard(Card c) { set(TrackableProperty.TokenCard, c.isTokenCard()); }
|
||||
|
||||
@@ -585,6 +599,10 @@ public class CardView extends GameEntityView {
|
||||
return get(TrackableProperty.CloneOrigin);
|
||||
}
|
||||
|
||||
public CardView getExiledWith() {
|
||||
return get(TrackableProperty.ExiledWith);
|
||||
}
|
||||
|
||||
public FCollectionView<CardView> getImprintedCards() {
|
||||
return get(TrackableProperty.ImprintedCards);
|
||||
}
|
||||
|
||||
@@ -65,6 +65,8 @@ public enum CounterEnumType {
|
||||
|
||||
COIN("COIN",255,215,0),
|
||||
|
||||
COMPONENT("COMPN", 224, 160, 48),
|
||||
|
||||
CORPSE("CRPSE", 230, 186, 209),
|
||||
|
||||
CORRUPTION("CRPTN", 210, 121, 210),
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
package forge.game.card;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
|
||||
import com.google.common.collect.ForwardingTable;
|
||||
import com.google.common.collect.HashBasedTable;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Table;
|
||||
|
||||
import forge.game.CardTraitBase;
|
||||
import forge.game.GameObjectPredicates;
|
||||
import forge.game.player.Player;
|
||||
|
||||
public class TokenCreateTable extends ForwardingTable<Player, Card, Integer> {
|
||||
|
||||
Table<Player, Card, Integer> dataMap = HashBasedTable.create();
|
||||
|
||||
public TokenCreateTable() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Table<Player, Card, Integer> delegate() {
|
||||
return dataMap;
|
||||
}
|
||||
|
||||
public int add(Player p, Card c, int i) {
|
||||
int old = ObjectUtils.defaultIfNull(this.get(p, c), 0);
|
||||
int newValue = old + i;
|
||||
this.put(p, c, newValue);
|
||||
return newValue;
|
||||
}
|
||||
|
||||
public int getFilterAmount(String validOwner, String validToken, final CardTraitBase ctb) {
|
||||
final Card host = ctb.getHostCard();
|
||||
int result = 0;
|
||||
List<Card> filteredCards = null;
|
||||
List<Player> filteredPlayer = null;
|
||||
|
||||
if (validOwner == null && validToken == null) {
|
||||
for (Integer i : values()) {
|
||||
result += i;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
if (validOwner != null) {
|
||||
filteredPlayer = Lists.newArrayList(Iterables.filter(rowKeySet(),
|
||||
GameObjectPredicates.restriction(validOwner.split(","), host.getController(), host, ctb)));
|
||||
if (filteredPlayer.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if (validToken != null) {
|
||||
filteredCards = CardLists.getValidCardsAsList(columnKeySet(), validToken, host.getController(), host, ctb);
|
||||
if (filteredCards.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (filteredPlayer == null) {
|
||||
for (Map.Entry<Card, Map<Player, Integer>> e : columnMap().entrySet()) {
|
||||
for (Integer i : e.getValue().values()) {
|
||||
result += i;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
if (filteredCards == null) {
|
||||
for (Map.Entry<Player, Map<Card, Integer>> e : rowMap().entrySet()) {
|
||||
for (Integer i : e.getValue().values()) {
|
||||
result += i;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
for (Table.Cell<Player, Card, Integer> c : this.cellSet()) {
|
||||
if (!filteredPlayer.contains(c.getRowKey())) {
|
||||
continue;
|
||||
}
|
||||
if (!filteredCards.contains(c.getColumnKey())) {
|
||||
continue;
|
||||
}
|
||||
result += c.getValue();
|
||||
}
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -15,15 +15,12 @@ import forge.StaticData;
|
||||
import forge.card.CardType;
|
||||
import forge.card.MagicColor;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardFactory;
|
||||
import forge.game.card.CardFactoryUtil;
|
||||
import forge.game.card.CardUtil;
|
||||
import forge.game.keyword.KeywordInterface;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.replacement.ReplacementType;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.item.PaperToken;
|
||||
|
||||
@@ -140,59 +137,6 @@ public class TokenInfo {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static List<Card> makeToken(final Card prototype, final Player owner,
|
||||
final boolean applyMultiplier, final int num) {
|
||||
final List<Card> list = Lists.newArrayList();
|
||||
|
||||
final Game game = owner.getGame();
|
||||
int multiplier = num;
|
||||
Player player = owner;
|
||||
Card proto = prototype;
|
||||
|
||||
final Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(player);
|
||||
repParams.put(AbilityKey.Token, prototype);
|
||||
repParams.put(AbilityKey.TokenNum, multiplier);
|
||||
repParams.put(AbilityKey.EffectOnly, applyMultiplier);
|
||||
|
||||
switch (game.getReplacementHandler().run(ReplacementType.CreateToken, repParams)) {
|
||||
case NotReplaced:
|
||||
break;
|
||||
case Updated: {
|
||||
multiplier = (int) repParams.get(AbilityKey.TokenNum);
|
||||
player = (Player) repParams.get(AbilityKey.Affected);
|
||||
proto = (Card) repParams.get(AbilityKey.Token);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
multiplier = 0;
|
||||
break;
|
||||
}
|
||||
if (multiplier <= 0) {
|
||||
return list;
|
||||
}
|
||||
|
||||
long timestamp = game.getNextTimestamp();
|
||||
|
||||
for (int i = 0; i < multiplier; i++) {
|
||||
// need to set owner or copyCard will fail with assign new ID
|
||||
proto.setOwner(owner);
|
||||
Card copy = CardFactory.copyCard(proto, true);
|
||||
// need to assign player after token is copied
|
||||
if (player != owner) {
|
||||
copy.setController(player, timestamp);
|
||||
}
|
||||
copy.setTimestamp(timestamp);
|
||||
copy.setToken(true);
|
||||
list.add(copy);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
static public List<Card> makeTokensFromPrototype(Card prototype, final Player owner, int amount, final boolean applyMultiplier) {
|
||||
return makeToken(prototype, owner, applyMultiplier, amount);
|
||||
}
|
||||
|
||||
public Card makeOneToken(final Player controller) {
|
||||
final Game game = controller.getGame();
|
||||
final Card c = toCard(game);
|
||||
@@ -321,10 +265,10 @@ public class TokenInfo {
|
||||
result.getCurrentState().changeTextIntrinsic(colorMap, typeMap);
|
||||
}
|
||||
|
||||
static public Card getProtoType(final String script, final SpellAbility sa) {
|
||||
return getProtoType(script, sa, true);
|
||||
static public Card getProtoType(final String script, final SpellAbility sa, final Player owner) {
|
||||
return getProtoType(script, sa, owner, true);
|
||||
}
|
||||
static public Card getProtoType(final String script, final SpellAbility sa, boolean applyTextChange) {
|
||||
static public Card getProtoType(final String script, final SpellAbility sa, final Player owner, boolean applyTextChange) {
|
||||
// script might be null, or sa might be null
|
||||
if (script == null || sa == null) {
|
||||
return null;
|
||||
@@ -338,7 +282,7 @@ public class TokenInfo {
|
||||
if (token == null) {
|
||||
return null;
|
||||
}
|
||||
final Card result = Card.fromPaperCard(token, null, game);
|
||||
final Card result = Card.fromPaperCard(token, owner, game);
|
||||
|
||||
if (sa.hasParam("TokenPower")) {
|
||||
String str = sa.getParam("TokenPower");
|
||||
|
||||
@@ -1080,7 +1080,7 @@ public class CombatUtil {
|
||||
return canAttackerBeBlockedWithAmount(attacker, amount, combat != null ? combat.getDefenderPlayerByAttacker(attacker) : null);
|
||||
}
|
||||
public static boolean canAttackerBeBlockedWithAmount(Card attacker, int amount, Player defender) {
|
||||
if(amount == 0 )
|
||||
if (amount == 0)
|
||||
return false; // no block
|
||||
|
||||
List<String> restrictions = Lists.newArrayList();
|
||||
@@ -1093,7 +1093,7 @@ public class CombatUtil {
|
||||
restrictions.add("LT2");
|
||||
}
|
||||
}
|
||||
for ( String res : restrictions ) {
|
||||
for (String res : restrictions) {
|
||||
int operand = Integer.parseInt(res.substring(2));
|
||||
String operator = res.substring(0,2);
|
||||
if (Expressions.compare(amount, operator, operand) )
|
||||
@@ -1118,7 +1118,7 @@ public class CombatUtil {
|
||||
}
|
||||
}
|
||||
int minBlockers = 1;
|
||||
for ( String res : restrictions ) {
|
||||
for (String res : restrictions) {
|
||||
int operand = Integer.parseInt(res.substring(2));
|
||||
String operator = res.substring(0, 2);
|
||||
if (operator.equals("LT") || operator.equals("GE")) {
|
||||
|
||||
@@ -363,6 +363,13 @@ public class Cost implements Serializable {
|
||||
return new CostFlipCoin(splitStr[0]);
|
||||
}
|
||||
|
||||
if (parse.startsWith("RollDice<")) {
|
||||
// RollDice<NumDice/Sides/ResultSVar>
|
||||
final String[] splitStr = abCostParse(parse, 4);
|
||||
final String description = splitStr.length > 3 ? splitStr[3] : null;
|
||||
return new CostRollDice(splitStr[0], splitStr[1], splitStr[2], description);
|
||||
}
|
||||
|
||||
if (parse.startsWith("Discard<")) {
|
||||
// Discard<NumCards/Type>
|
||||
final String[] splitStr = abCostParse(parse, 3);
|
||||
|
||||
@@ -211,7 +211,7 @@ public class CostPayment extends ManaConversionMatrix {
|
||||
return false;
|
||||
}
|
||||
// abilities care what was used to pay for them
|
||||
if( part instanceof CostPartWithList ) {
|
||||
if (part instanceof CostPartWithList) {
|
||||
((CostPartWithList) part).resetLists();
|
||||
}
|
||||
|
||||
|
||||
68
forge-game/src/main/java/forge/game/cost/CostRollDice.java
Normal file
68
forge-game/src/main/java/forge/game/cost/CostRollDice.java
Normal file
@@ -0,0 +1,68 @@
|
||||
package forge.game.cost;
|
||||
|
||||
import forge.game.ability.effects.RollDiceEffect;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
/**
|
||||
* This is for the "RollDice" Cost
|
||||
*/
|
||||
public class CostRollDice extends CostPart {
|
||||
|
||||
/**
|
||||
* Serializables need a version ID.
|
||||
*/
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final String resultSVar;
|
||||
|
||||
/**
|
||||
* Instantiates a new cost RollDice.
|
||||
*
|
||||
* @param amount
|
||||
* the amount
|
||||
*/
|
||||
public CostRollDice(final String amount, final String sides, final String resultSVar, final String description) {
|
||||
super(amount, sides, description);
|
||||
this.resultSVar = resultSVar;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* forge.card.cost.CostPart#canPay(forge.card.spellability.SpellAbility,
|
||||
* forge.Card, forge.Player, forge.card.cost.Cost)
|
||||
*/
|
||||
@Override
|
||||
public final boolean canPay(final SpellAbility ability, final Player payer) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String toString() {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append("Roll ").append(getAmount());
|
||||
|
||||
if (this.getTypeDescription() == null) {
|
||||
sb.append("d").append(getType());
|
||||
} else {
|
||||
sb.append(" ").append(this.getTypeDescription());
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean payAsDecided(Player payer, PaymentDecision pd, SpellAbility sa) {
|
||||
int sides = Integer.parseInt(getType());
|
||||
int result = RollDiceEffect.rollDiceForPlayer(sa, payer, pd.c, sides);
|
||||
sa.setSVar(resultSVar, Integer.toString(result));
|
||||
return true;
|
||||
}
|
||||
|
||||
public <T> T accept(ICostVisitor<T> visitor) {
|
||||
return visitor.visit(this);
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ public interface ICostVisitor<T> {
|
||||
T visit(CostExiledMoveToGrave cost);
|
||||
T visit(CostExert cost);
|
||||
T visit(CostFlipCoin cost);
|
||||
T visit(CostRollDice cost);
|
||||
T visit(CostMill cost);
|
||||
T visit(CostAddMana cost);
|
||||
T visit(CostPayLife cost);
|
||||
@@ -84,6 +85,11 @@ public interface ICostVisitor<T> {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T visit(CostRollDice cost) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T visit(CostMill cost) {
|
||||
return null;
|
||||
@@ -173,7 +179,7 @@ public interface ICostVisitor<T> {
|
||||
public T visit(CostUnattach cost) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public T visit(CostTapType cost) {
|
||||
return null;
|
||||
|
||||
@@ -2217,6 +2217,9 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
|
||||
@Override
|
||||
public final boolean hasProperty(final String property, final Player sourceController, final Card source, CardTraitBase spellAbility) {
|
||||
if (property.startsWith("!")) {
|
||||
return !PlayerProperty.playerHasProperty(this, property.substring(1), sourceController, source, spellAbility);
|
||||
}
|
||||
return PlayerProperty.playerHasProperty(this, property, sourceController, source, spellAbility);
|
||||
}
|
||||
|
||||
@@ -3173,6 +3176,7 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
if (monarchEffect == null) {
|
||||
monarchEffect = new Card(game.nextCardId(), null, game);
|
||||
monarchEffect.setOwner(this);
|
||||
monarchEffect.setImmutable(true);
|
||||
if (set != null) {
|
||||
monarchEffect.setImageKey("t:monarch_" + set.toLowerCase());
|
||||
monarchEffect.setSetCode(set);
|
||||
@@ -3180,7 +3184,6 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
monarchEffect.setImageKey("t:monarch");
|
||||
}
|
||||
monarchEffect.setName("The Monarch");
|
||||
monarchEffect.addType("Effect");
|
||||
|
||||
{
|
||||
final String drawTrig = "Mode$ Phase | Phase$ End of Turn | TriggerZones$ Command | " +
|
||||
@@ -3277,8 +3280,7 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
blessingEffect.setOwner(this);
|
||||
blessingEffect.setImageKey("t:blessing");
|
||||
blessingEffect.setName("City's Blessing");
|
||||
blessingEffect.addType("Effect");
|
||||
|
||||
blessingEffect.setImmutable(true);
|
||||
|
||||
blessingEffect.updateStateForView();
|
||||
|
||||
@@ -3350,7 +3352,6 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
keywordEffect.setOwner(this);
|
||||
keywordEffect.setName("Keyword Effects");
|
||||
keywordEffect.setImageKey(ImageKeys.HIDDEN_CARD);
|
||||
keywordEffect.addType("Effect");
|
||||
|
||||
keywordEffect.updateStateForView();
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import java.util.Map;
|
||||
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.TokenCreateTable;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
/**
|
||||
@@ -27,9 +28,11 @@ public class ReplaceToken extends ReplacementEffect {
|
||||
*/
|
||||
@Override
|
||||
public boolean canReplace(Map<AbilityKey, Object> runParams) {
|
||||
/*
|
||||
if (((int) runParams.get(AbilityKey.TokenNum)) <= 0) {
|
||||
return false;
|
||||
}
|
||||
//*/
|
||||
|
||||
if (hasParam("EffectOnly")) {
|
||||
final Boolean effectOnly = (Boolean) runParams.get(AbilityKey.EffectOnly);
|
||||
@@ -41,10 +44,16 @@ public class ReplaceToken extends ReplacementEffect {
|
||||
if (!matchesValidParam("ValidPlayer", runParams.get(AbilityKey.Affected))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*/
|
||||
if (!matchesValidParam("ValidToken", runParams.get(AbilityKey.Token))) {
|
||||
return false;
|
||||
}
|
||||
//*/
|
||||
|
||||
if (filterAmount((TokenCreateTable) runParams.get(AbilityKey.Token)) <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -54,8 +63,13 @@ public class ReplaceToken extends ReplacementEffect {
|
||||
*/
|
||||
@Override
|
||||
public void setReplacingObjects(Map<AbilityKey, Object> runParams, SpellAbility sa) {
|
||||
sa.setReplacingObject(AbilityKey.TokenNum, runParams.get(AbilityKey.TokenNum));
|
||||
sa.setReplacingObject(AbilityKey.TokenNum, filterAmount((TokenCreateTable) runParams.get(AbilityKey.Token)));
|
||||
sa.setReplacingObject(AbilityKey.Token, runParams.get(AbilityKey.Token));
|
||||
sa.setReplacingObject(AbilityKey.Player, runParams.get(AbilityKey.Affected));
|
||||
}
|
||||
|
||||
|
||||
public int filterAmount(final TokenCreateTable table) {
|
||||
return table.getFilterAmount(getParamOrDefault("ValidPlayer", null), getParamOrDefault("ValidToken", null), this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,7 +220,7 @@ public abstract class ReplacementEffect extends TriggerReplacementBase {
|
||||
if (hasParam("Description") && !this.isSuppressed()) {
|
||||
String desc = AbilityUtils.applyDescriptionTextChangeEffects(getParam("Description"), this);
|
||||
String currentName;
|
||||
if (this.isIntrinsic() && !this.getHostCard().isMutated() && cardState != null) {
|
||||
if (this.isIntrinsic() && cardState != null && cardState.getCard() == getHostCard()) {
|
||||
currentName = cardState.getName();
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -28,6 +28,7 @@ import java.util.Set;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
import forge.game.CardTraitBase;
|
||||
@@ -274,20 +275,45 @@ public class ReplacementHandler {
|
||||
chosenRE.setOtherChoices(null);
|
||||
return res;
|
||||
}
|
||||
|
||||
// Log there
|
||||
String message = chosenRE.getDescription();
|
||||
if (!StringUtils.isEmpty(message)) {
|
||||
if (chosenRE.getHostCard() != null) {
|
||||
message = TextUtil.fastReplace(message, "CARDNAME", chosenRE.getHostCard().getName());
|
||||
}
|
||||
game.getGameLog().add(GameLogEntryType.EFFECT_REPLACED, message);
|
||||
}
|
||||
|
||||
// if its updated, try to call event again
|
||||
if (res == ReplacementResult.Updated) {
|
||||
Map<AbilityKey, Object> params = Maps.newHashMap(runParams);
|
||||
|
||||
if (params.containsKey(AbilityKey.EffectOnly)) {
|
||||
params.put(AbilityKey.EffectOnly, true);
|
||||
}
|
||||
ReplacementResult result = run(event, params);
|
||||
switch (result) {
|
||||
case NotReplaced:
|
||||
case Updated: {
|
||||
for (Map.Entry<AbilityKey, Object> e : params.entrySet()) {
|
||||
runParams.put(e.getKey(), e.getValue());
|
||||
}
|
||||
// effect was updated
|
||||
runParams.put(AbilityKey.ReplacementResult, ReplacementResult.Updated);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// effect was replaced with something else
|
||||
runParams.put(AbilityKey.ReplacementResult, result);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
chosenRE.setHasRun(false);
|
||||
hasRun.remove(chosenRE);
|
||||
chosenRE.setOtherChoices(null);
|
||||
|
||||
// Updated Replacements need to be logged elsewhere because its otherwise in the wrong order
|
||||
if (res != ReplacementResult.Updated) {
|
||||
String message = chosenRE.getDescription();
|
||||
if (!StringUtils.isEmpty(message))
|
||||
if (chosenRE.getHostCard() != null) {
|
||||
message = TextUtil.fastReplace(message, "CARDNAME", chosenRE.getHostCard().getName());
|
||||
}
|
||||
game.getGameLog().add(GameLogEntryType.EFFECT_REPLACED, message);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -300,7 +326,6 @@ public class ReplacementHandler {
|
||||
*/
|
||||
private ReplacementResult executeReplacement(final Map<AbilityKey, Object> runParams,
|
||||
final ReplacementEffect replacementEffect, final Player decider, final Game game) {
|
||||
final Map<String, String> mapParams = replacementEffect.getMapParams();
|
||||
|
||||
SpellAbility effectSA = null;
|
||||
|
||||
@@ -311,10 +336,9 @@ public class ReplacementHandler {
|
||||
host = game.getCardState(host);
|
||||
}
|
||||
|
||||
if (replacementEffect.getOverridingAbility() == null && mapParams.containsKey("ReplaceWith")) {
|
||||
final String effectSVar = mapParams.get("ReplaceWith");
|
||||
if (replacementEffect.getOverridingAbility() == null && replacementEffect.hasParam("ReplaceWith")) {
|
||||
// TODO: the source of replacement effect should be the source of the original effect
|
||||
effectSA = AbilityFactory.getAbility(host, effectSVar, replacementEffect);
|
||||
effectSA = AbilityFactory.getAbility(host, replacementEffect.getParam("ReplaceWith"), replacementEffect);
|
||||
//replacementEffect.setOverridingAbility(effectSA);
|
||||
//effectSA.setTrigger(true);
|
||||
} else if (replacementEffect.getOverridingAbility() != null) {
|
||||
@@ -342,10 +366,10 @@ public class ReplacementHandler {
|
||||
// Decider gets to choose whether or not to apply the replacement.
|
||||
if (replacementEffect.hasParam("Optional")) {
|
||||
Player optDecider = decider;
|
||||
if (mapParams.containsKey("OptionalDecider") && (effectSA != null)) {
|
||||
if (replacementEffect.hasParam("OptionalDecider") && (effectSA != null)) {
|
||||
effectSA.setActivatingPlayer(host.getController());
|
||||
optDecider = AbilityUtils.getDefinedPlayers(host,
|
||||
mapParams.get("OptionalDecider"), effectSA).get(0);
|
||||
replacementEffect.getParam("OptionalDecider"), effectSA).get(0);
|
||||
}
|
||||
|
||||
Card cardForUi = host.getCardForUi();
|
||||
@@ -360,12 +384,12 @@ public class ReplacementHandler {
|
||||
}
|
||||
}
|
||||
|
||||
boolean isPrevent = mapParams.containsKey("Prevent") && mapParams.get("Prevent").equals("True");
|
||||
if (isPrevent || mapParams.containsKey("PreventionEffect")) {
|
||||
boolean isPrevent = "True".equals(replacementEffect.getParam("Prevent"));
|
||||
if (isPrevent || replacementEffect.hasParam("PreventionEffect")) {
|
||||
if (Boolean.TRUE.equals(runParams.get(AbilityKey.NoPreventDamage))) {
|
||||
// If can't prevent damage, result is not replaced
|
||||
// But still put "prevented" amount for buffered SA
|
||||
if (mapParams.containsKey("AlwaysReplace")) {
|
||||
if (replacementEffect.hasParam("AlwaysReplace")) {
|
||||
runParams.put(AbilityKey.PreventedAmount, runParams.get(AbilityKey.DamageAmount));
|
||||
} else {
|
||||
runParams.put(AbilityKey.PreventedAmount, 0);
|
||||
@@ -377,10 +401,8 @@ public class ReplacementHandler {
|
||||
}
|
||||
}
|
||||
|
||||
if (mapParams.containsKey("Skip")) {
|
||||
if (mapParams.get("Skip").equals("True")) {
|
||||
return ReplacementResult.Skipped; // Event is skipped.
|
||||
}
|
||||
if ("True".equals(replacementEffect.getParam("Skip"))) {
|
||||
return ReplacementResult.Skipped; // Event is skipped.
|
||||
}
|
||||
|
||||
Player player = host.getController();
|
||||
@@ -394,6 +416,11 @@ public class ReplacementHandler {
|
||||
// The SA if buffered, but replacement result should be set to Replaced
|
||||
runParams.put(AbilityKey.ReplacementResult, ReplacementResult.Replaced);
|
||||
}
|
||||
|
||||
// these ones are special for updating
|
||||
if (apiType == ApiType.ReplaceToken || apiType == ApiType.ReplaceEffect || apiType == ApiType.ReplaceMana) {
|
||||
runParams.put(AbilityKey.ReplacementResult, ReplacementResult.Updated);
|
||||
}
|
||||
}
|
||||
|
||||
// if the spellability is a replace effect then its some new logic
|
||||
|
||||
@@ -95,8 +95,7 @@ public class LandAbility extends Ability {
|
||||
Card source = sta.getHostCard();
|
||||
if (!source.equals(getHostCard())) {
|
||||
sb.append(" by ");
|
||||
if ((source.isEmblem() || source.getType().hasSubtype("Effect"))
|
||||
&& source.getEffectSource() != null) {
|
||||
if (source.isImmutable() && source.getEffectSource() != null) {
|
||||
sb.append(source.getEffectSource());
|
||||
} else {
|
||||
sb.append(source);
|
||||
|
||||
@@ -854,7 +854,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
||||
if (node.getHostCard() != null) {
|
||||
String currentName;
|
||||
// if alternate state is viewed while card uses original
|
||||
if (node.isIntrinsic() && !node.getHostCard().isMutated() && node.cardState != null) {
|
||||
if (node.isIntrinsic() && node.cardState != null && node.cardState.getCard() == node.getHostCard()) {
|
||||
currentName = node.cardState.getName();
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -209,7 +209,7 @@ public class StaticAbility extends CardTraitBase implements IIdentifiable, Clone
|
||||
public final String toString() {
|
||||
if (hasParam("Description") && !this.isSuppressed()) {
|
||||
String currentName;
|
||||
if (this.isIntrinsic() && !this.getHostCard().isMutated() && cardState != null) {
|
||||
if (this.isIntrinsic() && cardState != null && cardState.getCard() == getHostCard()) {
|
||||
currentName = cardState.getName();
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -92,7 +92,7 @@ public class StaticAbilityCantAttackBlock {
|
||||
}
|
||||
|
||||
/**
|
||||
* returns true if attacker can be blocked by blocker
|
||||
* returns true if attacker can't be blocked by blocker
|
||||
* @param stAb
|
||||
* @param attacker
|
||||
* @param blocker
|
||||
@@ -104,9 +104,10 @@ public class StaticAbilityCantAttackBlock {
|
||||
return false;
|
||||
}
|
||||
if (stAb.hasParam("ValidBlocker")) {
|
||||
boolean stillblock = true;
|
||||
for (final String v : stAb.getParam("ValidBlocker").split(",")) {
|
||||
if (blocker.isValid(v, host.getController(), host, stAb)) {
|
||||
boolean stillblock = false;
|
||||
stillblock = false;
|
||||
//Dragon Hunter check
|
||||
if (v.contains("withoutReach") && blocker.hasStartOfKeyword("IfReach")) {
|
||||
for (KeywordInterface inst : blocker.getKeywords()) {
|
||||
@@ -120,13 +121,14 @@ public class StaticAbilityCantAttackBlock {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (stillblock) {
|
||||
return false;
|
||||
if (!stillblock) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (stillblock) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// relative valid relative to each other
|
||||
if (!stAb.matchesValidParam("ValidAttackerRelative", attacker, blocker)) {
|
||||
|
||||
@@ -132,7 +132,7 @@ public abstract class Trigger extends TriggerReplacementBase {
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String currentName;
|
||||
if (this.isIntrinsic() && !this.getHostCard().isMutated() && cardState != null) {
|
||||
if (this.isIntrinsic() && cardState != null && cardState.getCard() == getHostCard()) {
|
||||
currentName = cardState.getName();
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -31,8 +31,6 @@ import forge.game.IHasSVars;
|
||||
import forge.game.ability.AbilityFactory;
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.ability.effects.CharmEffect;
|
||||
import forge.game.card.*;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
@@ -346,8 +344,7 @@ public class TriggerHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean runNonStaticTriggersForPlayer(final Player player, final TriggerWaiting wt, final List<Trigger> delayedTriggersWorkingCopy ) {
|
||||
|
||||
private boolean runNonStaticTriggersForPlayer(final Player player, final TriggerWaiting wt, final List<Trigger> delayedTriggersWorkingCopy) {
|
||||
final TriggerType mode = wt.getMode();
|
||||
final Map<AbilityKey, Object> runParams = wt.getParams();
|
||||
final List<Trigger> triggers = wt.getTriggers() != null ? wt.getTriggers() : activeTriggers;
|
||||
@@ -503,7 +500,6 @@ public class TriggerHandler {
|
||||
// runs it if so.
|
||||
// Return true if the trigger went off, false otherwise.
|
||||
private void runSingleTriggerInternal(final Trigger regtrig, final Map<AbilityKey, Object> runParams) {
|
||||
|
||||
// All tests passed, execute ability.
|
||||
if (regtrig instanceof TriggerTapsForMana) {
|
||||
final SpellAbility abMana = (SpellAbility) runParams.get(AbilityKey.AbilityMana);
|
||||
@@ -519,8 +515,7 @@ public class TriggerHandler {
|
||||
if (sa == null) {
|
||||
if (!regtrig.hasParam("Execute")) {
|
||||
sa = new SpellAbility.EmptySa(host);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
String name = regtrig.getParam("Execute");
|
||||
if (!host.getCurrentState().hasSVar(name)) {
|
||||
System.err.println("Warning: tried to run a trigger for card " + host + " referencing a SVar " + name + " not present on the current state " + host.getCurrentState() + ". Aborting trigger execution to prevent a crash.");
|
||||
@@ -577,12 +572,6 @@ public class TriggerHandler {
|
||||
}
|
||||
|
||||
sa.setStackDescription(sa.toString());
|
||||
if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) {
|
||||
if (!CharmEffect.makeChoices(sa)) {
|
||||
// 603.3c If no mode is chosen, the ability is removed from the stack.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Player decider = null;
|
||||
boolean isMandatory = false;
|
||||
@@ -592,8 +581,7 @@ public class TriggerHandler {
|
||||
}
|
||||
else if (sa instanceof AbilitySub || !sa.hasParam("Cost") || sa.getParam("Cost").equals("0")) {
|
||||
isMandatory = true;
|
||||
}
|
||||
else { // triggers with a cost can't be mandatory
|
||||
} else { // triggers with a cost can't be mandatory
|
||||
sa.setOptionalTrigger(true);
|
||||
decider = sa.getActivatingPlayer();
|
||||
}
|
||||
@@ -605,8 +593,7 @@ public class TriggerHandler {
|
||||
wrapperAbility.setLastStateBattlefield(game.getLastStateBattlefield());
|
||||
if (regtrig.isStatic()) {
|
||||
wrapperAbility.getActivatingPlayer().getController().playTrigger(host, wrapperAbility, isMandatory);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
game.getStack().addSimultaneousStackEntry(wrapperAbility);
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,11 @@ public class TriggerRolledDie extends Trigger {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (hasParam("ValidSides")) {
|
||||
final int validSides = Integer.parseInt(getParam("ValidSides"));
|
||||
final int sides = (int) runParams.get(AbilityKey.Sides);
|
||||
if (sides == validSides) return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -232,8 +232,8 @@ public class WrappedAbility extends Ability {
|
||||
if (regtrig == null) return "";
|
||||
final StringBuilder sb = new StringBuilder(regtrig.replaceAbilityText(regtrig.toString(true), this));
|
||||
List<TargetChoices> allTargets = sa.getAllTargetChoices();
|
||||
if (!allTargets.isEmpty()) {
|
||||
sb.append(" (Targeting ");
|
||||
if (!allTargets.isEmpty() && !ApiType.Charm.equals(sa.getApi())) {
|
||||
sb.append(" (Targeting: ");
|
||||
sb.append(allTargets);
|
||||
sb.append(")");
|
||||
}
|
||||
@@ -549,7 +549,6 @@ public class WrappedAbility extends Ability {
|
||||
sa.setXManaCostPaid(n);
|
||||
}
|
||||
|
||||
|
||||
public CardState getCardState() {
|
||||
return sa.getCardState();
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ import forge.game.GameObject;
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.ability.effects.CharmEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardUtil;
|
||||
@@ -224,8 +225,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
|
||||
undoStackOwner = activator;
|
||||
}
|
||||
undoStack.push(sp);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
clearUndoStack();
|
||||
}
|
||||
|
||||
@@ -628,8 +628,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
|
||||
if (sa.usesTargeting()) {
|
||||
if (sa.isZeroTargets()) {
|
||||
// Nothing targeted, and nothing needs to be targeted.
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// Some targets were chosen, fizzling for this subability is now possible
|
||||
//fizzle = true;
|
||||
// With multi-targets, as long as one target is still legal,
|
||||
@@ -675,8 +674,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
|
||||
}
|
||||
else if (sa.getTargetCard() != null) {
|
||||
fizzle = !sa.canTarget(sa.getTargetCard());
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// Set fizzle to the same as the parent if there's no target info
|
||||
fizzle = parentFizzled;
|
||||
}
|
||||
@@ -799,7 +797,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
|
||||
result |= chooseOrderOfSimultaneousStackEntry(whoAddsToStack);
|
||||
// 2014-08-10 Fix infinite loop when a player dies during a multiplayer game during their turn
|
||||
whoAddsToStack = game.getNextPlayerAfter(whoAddsToStack);
|
||||
} while( whoAddsToStack != null && whoAddsToStack != playerTurn);
|
||||
} while (whoAddsToStack != null && whoAddsToStack != playerTurn);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -809,9 +807,19 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
|
||||
}
|
||||
|
||||
final List<SpellAbility> activePlayerSAs = Lists.newArrayList();
|
||||
final List<SpellAbility> failedSAs = Lists.newArrayList();
|
||||
for (int i = 0; i < simultaneousStackEntryList.size(); i++) {
|
||||
SpellAbility sa = simultaneousStackEntryList.get(i);
|
||||
Player activator = sa.getActivatingPlayer();
|
||||
|
||||
if (sa.getApi() == ApiType.Charm) {
|
||||
if (!CharmEffect.makeChoices(sa)) {
|
||||
// 603.3c If no mode is chosen, the ability is removed from the stack.
|
||||
failedSAs.add(sa);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (activator == null) {
|
||||
if (sa.getHostCard().getController().equals(activePlayer)) {
|
||||
activePlayerSAs.add(sa);
|
||||
@@ -823,6 +831,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
|
||||
}
|
||||
}
|
||||
simultaneousStackEntryList.removeAll(activePlayerSAs);
|
||||
simultaneousStackEntryList.removeAll(failedSAs);
|
||||
|
||||
if (activePlayerSAs.isEmpty()) {
|
||||
return false;
|
||||
@@ -875,8 +884,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
|
||||
public final void addCastCommand(final String valid, final GameCommand c) {
|
||||
if (commandList.containsKey(valid)) {
|
||||
commandList.get(valid).add(0, c);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
commandList.put(valid, Lists.newArrayList(c));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,9 @@ public enum TrackableProperty {
|
||||
Controller(TrackableTypes.PlayerViewType),
|
||||
Zone(TrackableTypes.EnumType(ZoneType.class)),
|
||||
|
||||
IsImmutable(TrackableTypes.BooleanType),
|
||||
IsEmblem(TrackableTypes.BooleanType),
|
||||
|
||||
Flipped(TrackableTypes.BooleanType),
|
||||
Facedown(TrackableTypes.BooleanType),
|
||||
Foretold(TrackableTypes.BooleanType),
|
||||
@@ -74,6 +77,7 @@ public enum TrackableProperty {
|
||||
UntilLeavesBattlefield(TrackableTypes.CardViewCollectionType),
|
||||
GainControlTargets(TrackableTypes.CardViewCollectionType),
|
||||
CloneOrigin(TrackableTypes.CardViewType),
|
||||
ExiledWith(TrackableTypes.CardViewType),
|
||||
|
||||
ImprintedCards(TrackableTypes.CardViewCollectionType),
|
||||
HauntedBy(TrackableTypes.CardViewCollectionType),
|
||||
|
||||
@@ -53,6 +53,7 @@ public class MessageUtil {
|
||||
case Protection:
|
||||
return Localizer.getInstance().getMessage("lblPlayerChooseValue", choser, value);
|
||||
case RollDice:
|
||||
case PutCounter:// For Clay Golem cost text
|
||||
return value;
|
||||
case Vote:
|
||||
String chooser = StringUtils.capitalize(mayBeYou(player, target));
|
||||
|
||||
Reference in New Issue
Block a user