mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-19 12:18:00 +00:00
Merge branch 'master' into 'typo'
# Conflicts: # forge-gui/res/cardsfolder/e/eternal_dominion.txt
This commit is contained in:
@@ -1318,7 +1318,6 @@ public class AiBlockController {
|
||||
}
|
||||
|
||||
int evalAtk = ComputerUtilCard.evaluateCreature(attacker, true, false);
|
||||
int evalBlk = ComputerUtilCard.evaluateCreature(blocker, true, false);
|
||||
boolean atkEmbalm = (attacker.hasStartOfKeyword("Embalm") || attacker.hasStartOfKeyword("Eternalize")) && !attacker.isToken();
|
||||
boolean blkEmbalm = (blocker.hasStartOfKeyword("Embalm") || blocker.hasStartOfKeyword("Eternalize")) && !blocker.isToken();
|
||||
|
||||
@@ -1327,10 +1326,13 @@ public class AiBlockController {
|
||||
chance = Math.max(0, chance - chanceModForEmbalm);
|
||||
}
|
||||
|
||||
if (blocker.isFaceDown() && !checkingOther && blocker.getState(CardStateName.Original).getType().isCreature()) {
|
||||
int evalBlk;
|
||||
if (blocker.isFaceDown() && blocker.getView().canFaceDownBeShownTo(ai.getView(), false) && blocker.getState(CardStateName.Original).getType().isCreature()) {
|
||||
// if the blocker is a face-down creature (e.g. cast via Morph, Manifest), evaluate it
|
||||
// in relation to the original state, not to the Morph state
|
||||
evalBlk = ComputerUtilCard.evaluateCreature(Card.fromPaperCard(blocker.getPaperCard(), ai), false, true);
|
||||
} else {
|
||||
evalBlk = ComputerUtilCard.evaluateCreature(blocker, true, false);
|
||||
}
|
||||
int chanceToSavePW = chanceToTradeDownToSaveWalker > 0 && evalAtk + 1 < evalBlk ? chanceToTradeDownToSaveWalker : chanceToTradeToSaveWalker;
|
||||
boolean powerParityOrHigher = blocker.getNetPower() <= attacker.getNetPower();
|
||||
|
||||
@@ -125,15 +125,8 @@ public class AttachAi extends SpellAbilityAi {
|
||||
|
||||
if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Chained to the Rocks")) {
|
||||
final SpellAbility effectExile = AbilityFactory.getAbility(source.getSVar("TrigExile"), source);
|
||||
final ZoneType origin = ZoneType.listValueOf(effectExile.getParam("Origin")).get(0);
|
||||
final TargetRestrictions exile_tgt = effectExile.getTargetRestrictions();
|
||||
final CardCollection list = CardLists.getValidCards(ai.getGame().getCardsIn(origin), exile_tgt.getValidTgts(), ai, source, effectExile);
|
||||
final CardCollection targets = CardLists.filter(list, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return !(c.hasProtectionFrom(source) || c.hasKeyword(Keyword.SHROUD) || c.hasKeyword(Keyword.HEXPROOF));
|
||||
}
|
||||
});
|
||||
final CardCollection targets = CardLists.filter(CardUtil.getValidCardsToTarget(exile_tgt, effectExile), CardPredicates.canBeAttached(source));
|
||||
return !targets.isEmpty();
|
||||
}
|
||||
|
||||
@@ -1325,8 +1318,7 @@ public class AttachAi extends SpellAbilityAi {
|
||||
|
||||
// Is a SA that moves target attachment
|
||||
if ("MoveTgtAura".equals(sa.getParam("AILogic"))) {
|
||||
CardCollection list = CardLists.getValidCards(aiPlayer.getGame().getCardsIn(tgt.getZone()), tgt.getValidTgts(), sa.getActivatingPlayer(), attachSource, sa);
|
||||
list = CardLists.filter(list, Predicates.not(CardPredicates.isProtectedFrom(attachSource)));
|
||||
CardCollection list = new CardCollection(CardUtil.getValidCardsToTarget(tgt, sa));
|
||||
list = CardLists.filter(list, Predicates.or(CardPredicates.isControlledByAnyOf(aiPlayer.getOpponents()), new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card card) {
|
||||
@@ -1336,7 +1328,7 @@ public class AttachAi extends SpellAbilityAi {
|
||||
|
||||
return !list.isEmpty() ? ComputerUtilCard.getBestAI(list) : null;
|
||||
} else if ("Unenchanted".equals(sa.getParam("AILogic"))) {
|
||||
CardCollection list = CardLists.getValidCards(aiPlayer.getGame().getCardsIn(tgt.getZone()), tgt.getValidTgts(), sa.getActivatingPlayer(), attachSource, sa);
|
||||
CardCollection list = new CardCollection(CardUtil.getValidCardsToTarget(tgt, sa));
|
||||
CardCollection preferred = CardLists.filter(list, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card card) {
|
||||
@@ -1361,18 +1353,7 @@ public class AttachAi extends SpellAbilityAi {
|
||||
if (tgt == null) {
|
||||
list = AbilityUtils.getDefinedCards(attachSource, sa.getParam("Defined"), sa);
|
||||
} else {
|
||||
list = CardLists.getValidCards(aiPlayer.getGame().getCardsIn(tgt.getZone()), tgt.getValidTgts(), sa.getActivatingPlayer(), attachSource, sa);
|
||||
list = CardLists.filter(list, CardPredicates.canBeAttached(attachSource));
|
||||
|
||||
// TODO If Attaching without casting, don't need to actually target.
|
||||
// I believe this is the only case where mandatory will be true, so just
|
||||
// check that when starting that work
|
||||
// But we shouldn't attach to things with Protection
|
||||
if (!mandatory) {
|
||||
list = CardLists.getTargetableCards(list, sa);
|
||||
} else {
|
||||
list = CardLists.filter(list, Predicates.not(CardPredicates.isProtectedFrom(attachSource)));
|
||||
}
|
||||
list = CardLists.filter(CardUtil.getValidCardsToTarget(tgt, sa), CardPredicates.canBeAttached(attachSource));
|
||||
}
|
||||
|
||||
if (list.isEmpty()) {
|
||||
|
||||
@@ -187,7 +187,7 @@ public class CopyPermanentAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
if (choice == null) { // can't find anything left
|
||||
if (!sa.isTargetNumberValid() || (sa.getTargets().size() == 0)) {
|
||||
if (!sa.isTargetNumberValid() || sa.getTargets().size() == 0) {
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
} else {
|
||||
|
||||
@@ -598,9 +598,9 @@ public class DamageDealAi extends DamageAiBase {
|
||||
lastTgt = humanCreature;
|
||||
dmg -= assignedDamage;
|
||||
}
|
||||
if (!source.hasProtectionFrom(humanCreature)) {
|
||||
// protection is already checked by target above
|
||||
dmgTaken += humanCreature.getNetPower();
|
||||
}
|
||||
|
||||
if (dmg == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,21 +1,18 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilAbility;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCombat;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.AbilityFactory;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
@@ -263,15 +260,13 @@ public class FightAi extends SpellAbilityAi {
|
||||
for (Trigger t : aiCreature.getTriggers()) {
|
||||
if (t.getMode() == TriggerType.SpellCast) {
|
||||
SpellAbility sa = t.ensureAbility();
|
||||
final Map<String, String> params = t.getMapParams();
|
||||
if (sa == null) {
|
||||
continue;
|
||||
}
|
||||
if (ApiType.PutCounter.equals(sa.getApi())) {
|
||||
if ("Card.Self".equals(params.get("TargetsValid")) && "You".equals(params.get("ValidActivatingPlayer"))) {
|
||||
SpellAbility heroic = AbilityFactory.getAbility(aiCreature.getSVar(params.get("Execute")),aiCreature);
|
||||
if ("Self".equals(heroic.getParam("Defined")) && "P1P1".equals(heroic.getParam("CounterType"))) {
|
||||
return AbilityUtils.calculateAmount(aiCreature, heroic.getParam("CounterNum"), heroic);
|
||||
if ("Card.Self".equals(t.getParam("TargetsValid")) && "You".equals(t.getParam("ValidActivatingPlayer"))) {
|
||||
if ("Self".equals(sa.getParam("Defined")) && "P1P1".equals(sa.getParam("CounterType"))) {
|
||||
return AbilityUtils.calculateAmount(aiCreature, sa.getParam("CounterNum"), sa);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -299,12 +294,14 @@ public class FightAi extends SpellAbilityAi {
|
||||
if (opponent.getSVar("Targeting").equals("Dies")) {
|
||||
return true;
|
||||
}
|
||||
if (opponent.hasProtectionFrom(fighter) || !opponent.canBeDestroyed() || opponent.getShieldCount() > 0
|
||||
|| ComputerUtil.canRegenerate(opponent.getController(), opponent)) {
|
||||
// the damage prediction is later
|
||||
int damage = fighter.getNetPower() + pumpAttack;
|
||||
if (damage <= 0 || opponent.getShieldCount() > 0 || ComputerUtil.canRegenerate(opponent.getController(), opponent)) {
|
||||
return false;
|
||||
}
|
||||
if (fighter.hasKeyword(Keyword.DEATHTOUCH)
|
||||
|| ComputerUtilCombat.getDamageToKill(opponent, true) <= fighter.getNetPower() + pumpAttack) {
|
||||
// try to predict the damage that fighter would deal to opponent
|
||||
// this should also handle if the opponents creature can be destroyed or not
|
||||
if (ComputerUtilCombat.getEnoughDamageToKill(opponent, damage, fighter, false) <= damage) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -30,7 +30,7 @@ public class GoadAi extends SpellAbilityAi {
|
||||
if (list.isEmpty())
|
||||
return false;
|
||||
|
||||
if (game.getPlayers().size() >= 2) {
|
||||
if (game.getPlayers().size() > 2) {
|
||||
// use this part only in multiplayer
|
||||
CardCollection betterList = CardLists.filter(list, new Predicate<Card>() {
|
||||
@Override
|
||||
@@ -79,10 +79,43 @@ public class GoadAi extends SpellAbilityAi {
|
||||
|
||||
// AI does not find a good creature to goad.
|
||||
// because if it would goad a creature it would attack AI.
|
||||
// AI might not have enough infomation to block it
|
||||
// AI might not have enough information to block it
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
if (checkApiLogic(ai, sa)) {
|
||||
return true;
|
||||
}
|
||||
if (!mandatory) {
|
||||
return false;
|
||||
}
|
||||
if (sa.usesTargeting()) {
|
||||
if (sa.getTargetRestrictions().canTgtPlayer()) {
|
||||
for (Player opp : ai.getOpponents()) {
|
||||
if (sa.canTarget(opp)) {
|
||||
sa.getTargets().add(opp);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (sa.canTarget(ai)) {
|
||||
sa.getTargets().add(ai);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
List<Card> list = CardLists.getTargetableCards(ai.getGame().getCardsIn(ZoneType.Battlefield), sa);
|
||||
|
||||
if (list.isEmpty())
|
||||
return false;
|
||||
|
||||
sa.getTargets().add(ComputerUtilCard.getWorstCreatureAI(list));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ public class ReplaceDamageAi extends SpellAbilityAi {
|
||||
if (c.hasSVar("MustBeBlocked")) {
|
||||
return c;
|
||||
}
|
||||
// TODO check if target can receive counters
|
||||
// TODO check if target can receive counters + sort these to the front if that can prevent loss
|
||||
if (c.hasKeyword(Keyword.INFECT)) {
|
||||
return c;
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ public class RevealAi extends RevealAiBase {
|
||||
@Override
|
||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||
// we can reuse this function here...
|
||||
final boolean bFlag = revealHandTargetAI(ai, sa/* , true, false */);
|
||||
final boolean bFlag = revealHandTargetAI(ai, sa, false);
|
||||
|
||||
if (!bFlag) {
|
||||
return false;
|
||||
@@ -80,7 +80,7 @@ public class RevealAi extends RevealAiBase {
|
||||
|
||||
}
|
||||
|
||||
if (!revealHandTargetAI(ai, sa/*, false, mandatory*/)) {
|
||||
if (!revealHandTargetAI(ai, sa, mandatory)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import forge.game.zone.ZoneType;
|
||||
|
||||
public abstract class RevealAiBase extends SpellAbilityAi {
|
||||
|
||||
protected boolean revealHandTargetAI(final Player ai, final SpellAbility sa) {
|
||||
protected boolean revealHandTargetAI(final Player ai, final SpellAbility sa, boolean mandatory) {
|
||||
if (sa.usesTargeting()) {
|
||||
// ability is targeted
|
||||
sa.resetTargets();
|
||||
@@ -29,7 +29,7 @@ public abstract class RevealAiBase extends SpellAbilityAi {
|
||||
|
||||
Player p = Collections.max(opps, PlayerPredicates.compareByZoneSize(ZoneType.Hand));
|
||||
|
||||
if (p.getCardsIn(ZoneType.Hand).isEmpty()) {
|
||||
if (!mandatory && p.getCardsIn(ZoneType.Hand).isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
sa.getTargets().add(p);
|
||||
@@ -45,7 +45,7 @@ public abstract class RevealAiBase extends SpellAbilityAi {
|
||||
*/
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
revealHandTargetAI(ai, sa);
|
||||
revealHandTargetAI(ai, sa, false);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ public class RevealHandAi extends RevealAiBase {
|
||||
*/
|
||||
@Override
|
||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||
final boolean bFlag = revealHandTargetAI(ai, sa/*, true, false*/);
|
||||
final boolean bFlag = revealHandTargetAI(ai, sa, false);
|
||||
|
||||
if (!bFlag) {
|
||||
return false;
|
||||
@@ -29,8 +29,7 @@ public class RevealHandAi extends RevealAiBase {
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
|
||||
return revealHandTargetAI(ai, sa/*, false, mandatory*/);
|
||||
return revealHandTargetAI(ai, sa, mandatory);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ import forge.game.replacement.ReplacementEffect;
|
||||
import forge.game.replacement.ReplacementType;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.staticability.StaticAbility;
|
||||
import forge.game.staticability.StaticAbilityCantAttach;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
public abstract class GameEntity extends GameObject implements IIdentifiable {
|
||||
@@ -222,15 +222,7 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
|
||||
return false;
|
||||
}
|
||||
|
||||
// CantTarget static abilities
|
||||
for (final Card ca : getGame().getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {
|
||||
for (final StaticAbility stAb : ca.getStaticAbilities()) {
|
||||
if (stAb.applyAbility("CantAttach", attach, this)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check for rules
|
||||
if (attach.isAura() && !canBeEnchantedBy(attach)) {
|
||||
return false;
|
||||
}
|
||||
@@ -241,8 +233,13 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
|
||||
return false;
|
||||
}
|
||||
|
||||
// check for can't attach static
|
||||
if (StaticAbilityCantAttach.cantAttach(this, attach, checkSBA)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// true for all
|
||||
return !hasProtectionFrom(attach, checkSBA);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean canBeEquippedBy(final Card aura) {
|
||||
@@ -260,6 +257,8 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
|
||||
}
|
||||
|
||||
protected boolean canBeEnchantedBy(final Card aura) {
|
||||
// TODO need to check for multiple Enchant Keywords
|
||||
|
||||
SpellAbility sa = aura.getFirstAttachSpell();
|
||||
TargetRestrictions tgt = null;
|
||||
if (sa != null) {
|
||||
@@ -269,11 +268,6 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
|
||||
return !((tgt != null) && !isValid(tgt.getValidTgts(), aura.getController(), aura, sa));
|
||||
}
|
||||
|
||||
public boolean hasProtectionFrom(final Card source) {
|
||||
return hasProtectionFrom(source, false);
|
||||
}
|
||||
public abstract boolean hasProtectionFrom(final Card source, final boolean checkSBA);
|
||||
|
||||
// Counters!
|
||||
public boolean hasCounters() {
|
||||
return !counters.isEmpty();
|
||||
|
||||
@@ -519,7 +519,7 @@ public abstract class SpellAbilityEffect {
|
||||
|
||||
String valid = sa.getParamOrDefault("ReplaceDyingValid", "Card.IsRemembered");
|
||||
|
||||
String repeffstr = "Event$ Moved | ValidCard$ " + valid +
|
||||
String repeffstr = "Event$ Moved | ValidLKI$ " + valid +
|
||||
"| Origin$ Battlefield | Destination$ Graveyard " +
|
||||
"| Description$ If that permanent would die this turn, exile it instead.";
|
||||
String effect = "DB$ ChangeZone | Defined$ ReplacedCard | Origin$ Battlefield | Destination$ " + zone;
|
||||
|
||||
@@ -127,7 +127,7 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
|
||||
// itself a static ability)
|
||||
final List<StaticAbility> addedStaticAbilities = Lists.newArrayList();
|
||||
for (final String s : stAbs) {
|
||||
addedStaticAbilities.add(new StaticAbility(AbilityUtils.getSVar(sa, s), c, sa.getCardState()));
|
||||
addedStaticAbilities.add(StaticAbility.create(AbilityUtils.getSVar(sa, s), c, sa.getCardState(), false));
|
||||
}
|
||||
|
||||
final GameCommand unanimate = new GameCommand() {
|
||||
|
||||
@@ -298,7 +298,8 @@ public class CounterEffect extends SpellAbilityEffect {
|
||||
private static void removeFromStack(final SpellAbility tgtSA, final SpellAbility srcSA, final SpellAbilityStackInstance si, CardZoneTable triggerList) {
|
||||
final Game game = tgtSA.getActivatingPlayer().getGame();
|
||||
Card movedCard = null;
|
||||
final Zone originZone = tgtSA.getHostCard().getZone();
|
||||
final Card c = tgtSA.getHostCard();
|
||||
final Zone originZone = c.getZone();
|
||||
|
||||
// Run any applicable replacement effects.
|
||||
final Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(tgtSA.getHostCard());
|
||||
@@ -310,8 +311,8 @@ public class CounterEffect extends SpellAbilityEffect {
|
||||
game.getStack().remove(si);
|
||||
|
||||
// if the target card on stack was a spell with Bestow, then unbestow it
|
||||
if (tgtSA.getHostCard() != null && tgtSA.getHostCard().isBestowed()) {
|
||||
tgtSA.getHostCard().unanimateBestow(true);
|
||||
if (c.isBestowed()) {
|
||||
c.unanimateBestow(true);
|
||||
}
|
||||
|
||||
Map<AbilityKey, Object> params = AbilityKey.newMap();
|
||||
@@ -327,33 +328,32 @@ public class CounterEffect extends SpellAbilityEffect {
|
||||
// For Ability-targeted counterspells - do not move it anywhere,
|
||||
// even if Destination$ is specified.
|
||||
} else if (destination.equals("Graveyard")) {
|
||||
movedCard = game.getAction().moveToGraveyard(tgtSA.getHostCard(), srcSA, params);
|
||||
movedCard = game.getAction().moveToGraveyard(c, srcSA, params);
|
||||
} else if (destination.equals("Exile")) {
|
||||
movedCard = game.getAction().exile(tgtSA.getHostCard(), srcSA, params);
|
||||
movedCard = game.getAction().exile(c, srcSA, params);
|
||||
} else if (destination.equals("TopOfLibrary")) {
|
||||
movedCard = game.getAction().moveToLibrary(tgtSA.getHostCard(), srcSA, params);
|
||||
movedCard = game.getAction().moveToLibrary(c, srcSA, params);
|
||||
} else if (destination.equals("Hand")) {
|
||||
movedCard = game.getAction().moveToHand(tgtSA.getHostCard(), srcSA, params);
|
||||
movedCard = game.getAction().moveToHand(c, srcSA, params);
|
||||
} else if (destination.equals("Battlefield")) {
|
||||
if (tgtSA instanceof SpellPermanent) {
|
||||
Card c = tgtSA.getHostCard();
|
||||
c.setController(srcSA.getActivatingPlayer(), 0);
|
||||
movedCard = game.getAction().moveToPlay(c, srcSA.getActivatingPlayer(), srcSA, params);
|
||||
} else {
|
||||
movedCard = game.getAction().moveToPlay(tgtSA.getHostCard(), srcSA.getActivatingPlayer(), srcSA, params);
|
||||
movedCard = game.getAction().moveToPlay(c, srcSA.getActivatingPlayer(), srcSA, params);
|
||||
movedCard.setController(srcSA.getActivatingPlayer(), 0);
|
||||
}
|
||||
} else if (destination.equals("BottomOfLibrary")) {
|
||||
movedCard = game.getAction().moveToBottomOfLibrary(tgtSA.getHostCard(), srcSA, params);
|
||||
movedCard = game.getAction().moveToBottomOfLibrary(c, srcSA, params);
|
||||
} else if (destination.equals("ShuffleIntoLibrary")) {
|
||||
movedCard = game.getAction().moveToBottomOfLibrary(tgtSA.getHostCard(), srcSA, params);
|
||||
tgtSA.getHostCard().getController().shuffle(srcSA);
|
||||
movedCard = game.getAction().moveToBottomOfLibrary(c, srcSA, params);
|
||||
c.getController().shuffle(srcSA);
|
||||
} else {
|
||||
throw new IllegalArgumentException("AbilityFactory_CounterMagic: Invalid Destination argument for card "
|
||||
+ srcSA.getHostCard().getName());
|
||||
}
|
||||
// Run triggers
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromCard(tgtSA.getHostCard());
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromCard(c);
|
||||
runParams.put(AbilityKey.Player, tgtSA.getActivatingPlayer());
|
||||
runParams.put(AbilityKey.Cause, srcSA.getHostCard());
|
||||
runParams.put(AbilityKey.CounteredSA, tgtSA);
|
||||
|
||||
@@ -9,13 +9,13 @@ public class HauntEffect extends SpellAbilityEffect {
|
||||
|
||||
@Override
|
||||
public void resolve(SpellAbility sa) {
|
||||
Card card = sa.getHostCard();
|
||||
final Game game = card.getGame();
|
||||
card = game.getCardState(card, null);
|
||||
Card host = sa.getHostCard();
|
||||
final Game game = host.getGame();
|
||||
Card card = game.getCardState(host, null);
|
||||
if (card == null) {
|
||||
return;
|
||||
} else if (sa.usesTargeting() && !card.isToken()) {
|
||||
// haunt target but only if card is no token
|
||||
} else if (sa.usesTargeting() && !card.isToken() && host.equalsWithTimestamp(card)) {
|
||||
// haunt target but only if card is no token and still in grave
|
||||
final Card copy = game.getAction().exile(card, sa);
|
||||
sa.getTargets().getFirstTargetedCard().addHauntedBy(copy);
|
||||
} else if (!sa.usesTargeting() && card.getHaunting() != null) {
|
||||
|
||||
@@ -24,7 +24,6 @@ public class ReplaceCounterEffect extends SpellAbilityEffect {
|
||||
|
||||
@Override
|
||||
public void resolve(SpellAbility sa) {
|
||||
|
||||
final Card card = sa.getHostCard();
|
||||
|
||||
// outside of Replacement Effect, unwanted result
|
||||
@@ -64,8 +63,6 @@ public class ReplaceCounterEffect extends SpellAbilityEffect {
|
||||
counterTable.get(p).put(e.getKey(), value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} else {
|
||||
for (Map.Entry<Optional<Player>, Map<CounterType, Integer>> e : counterTable.entrySet()) {
|
||||
if (!sa.matchesValidParam("ValidSource", e.getKey().orNull())) {
|
||||
|
||||
@@ -1912,9 +1912,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
for (KeywordInterface inst : keywords) {
|
||||
String keyword = inst.getOriginal();
|
||||
try {
|
||||
if (keyword.startsWith("SpellCantTarget")) {
|
||||
continue;
|
||||
}
|
||||
if (keyword.startsWith("CantBeCounteredBy")) {
|
||||
final String[] p = keyword.split(":");
|
||||
sbLong.append(p[2]).append("\r\n");
|
||||
@@ -4596,8 +4593,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
}
|
||||
public final StaticAbility addStaticAbility(final String s) {
|
||||
if (!s.trim().isEmpty()) {
|
||||
final StaticAbility stAb = new StaticAbility(s, this, currentState);
|
||||
stAb.setIntrinsic(true);
|
||||
final StaticAbility stAb = StaticAbility.create(s, this, currentState, true);
|
||||
currentState.addStaticAbility(stAb);
|
||||
return stAb;
|
||||
}
|
||||
@@ -5693,103 +5689,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
public Card getMeldedWith() { return meldedWith; }
|
||||
public void setMeldedWith(Card meldedWith) { this.meldedWith = meldedWith; }
|
||||
|
||||
public boolean hasProtectionFrom(final Card source) {
|
||||
return hasProtectionFrom(source, false, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasProtectionFrom(final Card source, final boolean checkSBA) {
|
||||
return hasProtectionFrom(source, checkSBA, false);
|
||||
}
|
||||
public boolean hasProtectionFrom(final Card source, final boolean checkSBA, final boolean damageSource) {
|
||||
if (source == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isImmutable()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Protection only works on the Battlefield
|
||||
if (!isInPlay()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final boolean colorlessDamage = damageSource && source.hasKeyword("Colorless Damage Source");
|
||||
|
||||
for (final KeywordInterface inst : getKeywords(Keyword.PROTECTION)) {
|
||||
String kw = inst.getOriginal();
|
||||
if (kw.equals("Protection from white")) {
|
||||
if (source.isWhite() && !colorlessDamage) {
|
||||
return true;
|
||||
}
|
||||
} else if (kw.equals("Protection from blue")) {
|
||||
if (source.isBlue() && !colorlessDamage) {
|
||||
return true;
|
||||
}
|
||||
} else if (kw.equals("Protection from black")) {
|
||||
if (source.isBlack() && !colorlessDamage) {
|
||||
return true;
|
||||
}
|
||||
} else if (kw.equals("Protection from red")) {
|
||||
if (source.isRed() && !colorlessDamage) {
|
||||
return true;
|
||||
}
|
||||
} else if (kw.equals("Protection from green")) {
|
||||
if (source.isGreen() && !colorlessDamage) {
|
||||
return true;
|
||||
}
|
||||
} else if (kw.equals("Protection from all colors")) {
|
||||
if (!source.isColorless() && !colorlessDamage) {
|
||||
return true;
|
||||
}
|
||||
} else if (kw.equals("Protection from colorless")) {
|
||||
if (source.isColorless() || colorlessDamage) {
|
||||
return true;
|
||||
}
|
||||
} else if (kw.equals("Protection from everything")) {
|
||||
return true;
|
||||
} else if (kw.startsWith("Protection:")) { // uses isValid; Protection:characteristic:desc:exception
|
||||
final String[] kws = kw.split(":");
|
||||
String characteristic = kws[1];
|
||||
|
||||
if (characteristic.startsWith("Player")) {
|
||||
// TODO need to handle that better in CardProperty
|
||||
if (source.getController().isValid(characteristic.split(","), getController(), this, null)) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// if damageSource then it does only check damage color..
|
||||
if (damageSource) {
|
||||
if (characteristic.endsWith("White") || characteristic.endsWith("Blue")
|
||||
|| characteristic.endsWith("Black") || characteristic.endsWith("Red")
|
||||
|| characteristic.endsWith("Green") || characteristic.endsWith("Colorless")
|
||||
|| characteristic.endsWith("MonoColor") || characteristic.endsWith("MultiColor")) {
|
||||
characteristic += "Source";
|
||||
}
|
||||
}
|
||||
|
||||
final String[] characteristics = characteristic.split(",");
|
||||
final String[] exceptions = kws.length > 3 ? kws[3].split(",") : null; // check "This effect cannot remove sth"
|
||||
if (source.isValid(characteristics, getController(), this, null)
|
||||
&& (!checkSBA || exceptions == null || !source.isValid(exceptions, getController(), this, null))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else if (kw.startsWith("Protection from opponent of ")) {
|
||||
final String playerName = kw.substring("Protection from opponent of ".length());
|
||||
if (source.getController().isOpponentOf(playerName)) {
|
||||
return true;
|
||||
}
|
||||
} else if (kw.startsWith("Protection from ")) {
|
||||
final String protectType = CardType.getSingularType(kw.substring("Protection from ".length()));
|
||||
if (source.getType().hasStringType(protectType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public String getProtectionKey() {
|
||||
String protectKey = "";
|
||||
boolean pR = false; boolean pG = false; boolean pB = false; boolean pU = false; boolean pW = false;
|
||||
@@ -5933,30 +5832,10 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (hasProtectionFrom(sa.getHostCard())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isPhasedOut()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
if (sa.isSpell()) {
|
||||
// TODO replace with Static Ability
|
||||
for (KeywordInterface inst : source.getKeywords()) {
|
||||
String kw = inst.getOriginal();
|
||||
if (!kw.startsWith("SpellCantTarget")) {
|
||||
continue;
|
||||
}
|
||||
final String[] k = kw.split(":");
|
||||
final String[] restrictions = k[1].split(",");
|
||||
if (isValid(restrictions, source.getController(), source, null)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -745,9 +745,7 @@ public class CardFactory {
|
||||
for (final String s : str.split(",")) {
|
||||
if (origSVars.containsKey(s)) {
|
||||
final String actualStatic = origSVars.get(s);
|
||||
final StaticAbility grantedStatic = new StaticAbility(actualStatic, out, sa.getCardState());
|
||||
grantedStatic.setIntrinsic(true);
|
||||
state.addStaticAbility(grantedStatic);
|
||||
state.addStaticAbility(StaticAbility.create(actualStatic, out, sa.getCardState(), true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -677,13 +677,13 @@ public class CardFactoryUtil {
|
||||
}
|
||||
|
||||
public static String getProtectionValid(final String kw, final boolean damage) {
|
||||
String validSource = "Card."; // TODO extend for Emblem too
|
||||
String validSource = "";
|
||||
|
||||
if (kw.startsWith("Protection:")) {
|
||||
final String[] kws = kw.split(":");
|
||||
String characteristic = kws[1];
|
||||
if (characteristic.startsWith("Player")) {
|
||||
validSource += "ControlledBy " + characteristic;
|
||||
validSource = "ControlledBy " + characteristic;
|
||||
} else {
|
||||
if (damage && (characteristic.endsWith("White") || characteristic.endsWith("Blue")
|
||||
|| characteristic.endsWith("Black") || characteristic.endsWith("Red")
|
||||
@@ -691,35 +691,38 @@ public class CardFactoryUtil {
|
||||
|| characteristic.endsWith("MonoColor") || characteristic.endsWith("MultiColor"))) {
|
||||
characteristic += "Source";
|
||||
}
|
||||
validSource = characteristic;
|
||||
return characteristic;
|
||||
}
|
||||
} else if (kw.startsWith("Protection from ")) {
|
||||
String protectType = kw.substring("Protection from ".length());
|
||||
if (protectType.equals("white")) {
|
||||
validSource += "White" + (damage ? "Source" : "");
|
||||
validSource = "White" + (damage ? "Source" : "");
|
||||
} else if (protectType.equals("blue")) {
|
||||
validSource += "Blue" + (damage ? "Source" : "");
|
||||
validSource = "Blue" + (damage ? "Source" : "");
|
||||
} else if (protectType.equals("black")) {
|
||||
validSource += "Black" + (damage ? "Source" : "");
|
||||
validSource = "Black" + (damage ? "Source" : "");
|
||||
} else if (protectType.equals("red")) {
|
||||
validSource += "Red" + (damage ? "Source" : "");
|
||||
validSource = "Red" + (damage ? "Source" : "");
|
||||
} else if (protectType.equals("green")) {
|
||||
validSource += "Green" + (damage ? "Source" : "");
|
||||
validSource = "Green" + (damage ? "Source" : "");
|
||||
} else if (protectType.equals("colorless")) {
|
||||
validSource += "Colorless" + (damage ? "Source" : "");
|
||||
validSource = "Colorless" + (damage ? "Source" : "");
|
||||
} else if (protectType.equals("all colors")) {
|
||||
validSource += "nonColorless" + (damage ? "Source" : "");
|
||||
validSource = "nonColorless" + (damage ? "Source" : "");
|
||||
} else if (protectType.equals("everything")) {
|
||||
validSource = "";
|
||||
return "";
|
||||
} else if (protectType.startsWith("opponent of ")) {
|
||||
final String playerName = protectType.substring("opponent of ".length());
|
||||
validSource += "ControlledBy Player.OpponentOf PlayerNamed_" + playerName;
|
||||
validSource = "ControlledBy Player.OpponentOf PlayerNamed_" + playerName;
|
||||
} else {
|
||||
validSource = CardType.getSingularType(protectType);
|
||||
}
|
||||
}
|
||||
if (validSource.isEmpty()) {
|
||||
return validSource;
|
||||
}
|
||||
return "Card." + validSource + ",Emblem." + validSource;
|
||||
}
|
||||
|
||||
public static ReplacementEffect makeEtbCounter(final String kw, final CardState card, final boolean intrinsic) {
|
||||
String parse = kw;
|
||||
@@ -1290,13 +1293,12 @@ public class CardFactoryUtil {
|
||||
triggers.add(haunterETB);
|
||||
}
|
||||
|
||||
// First, create trigger that runs when the haunter goes to the
|
||||
// graveyard
|
||||
// First, create trigger that runs when the haunter goes to the graveyard
|
||||
final StringBuilder sbHaunter = new StringBuilder();
|
||||
sbHaunter.append("Mode$ ChangesZone | Origin$ ");
|
||||
sbHaunter.append(card.isCreature() ? "Battlefield" : "Stack");
|
||||
sbHaunter.append(card.isCreature() ? "Battlefield" : "Stack | ResolvedCard$ True");
|
||||
sbHaunter.append(" | Destination$ Graveyard | ValidCard$ Card.Self");
|
||||
sbHaunter.append(" | Static$ True | Secondary$ True | TriggerDescription$ Blank");
|
||||
sbHaunter.append(" | Secondary$ True | TriggerDescription$ " + inst.getReminderText());
|
||||
|
||||
final Trigger haunterDies = TriggerHandler.parseTrigger(sbHaunter.toString(), card, intrinsic);
|
||||
|
||||
@@ -2965,7 +2967,7 @@ public class CardFactoryUtil {
|
||||
final String manacost = k[1];
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("AB$ PutCounter| Cost$ ").append(manacost);
|
||||
sb.append("AB$ PutCounter | Cost$ ").append(manacost);
|
||||
sb.append(" | PrecostDesc$ Level Up | CostDesc$ ").append(ManaCostParser.parse(manacost));
|
||||
sb.append(" | SorcerySpeed$ True | LevelUp$ True | CounterNum$ 1 | CounterType$ LEVEL");
|
||||
if (card.hasSVar("maxLevel")) {
|
||||
@@ -3398,8 +3400,6 @@ public class CardFactoryUtil {
|
||||
|
||||
public static void addStaticAbility(final KeywordInterface inst, final CardState state, final boolean intrinsic) {
|
||||
String keyword = inst.getOriginal();
|
||||
String effect = null;
|
||||
Map<String, String> svars = Maps.newHashMap();
|
||||
|
||||
if (keyword.startsWith("Affinity")) {
|
||||
final String[] k = keyword.split(":");
|
||||
@@ -3419,20 +3419,24 @@ public class CardFactoryUtil {
|
||||
sb.append("Mode$ ReduceCost | ValidCard$ Card.Self | Type$ Spell | Amount$ AffinityX | EffectZone$ All");
|
||||
sb.append("| Description$ Affinity for ").append(desc);
|
||||
sb.append(" (").append(inst.getReminderText()).append(")");
|
||||
effect = sb.toString();
|
||||
String effect = sb.toString();
|
||||
|
||||
svars.put("AffinityX", "Count$Valid " + t + ".YouCtrl");
|
||||
StaticAbility st = StaticAbility.create(effect, state.getCard(), state, intrinsic);
|
||||
|
||||
st.setSVar("AffinityX", "Count$Valid " + t + ".YouCtrl");
|
||||
inst.addStaticAbility(st);
|
||||
} else if (keyword.equals("Changeling")) {
|
||||
effect = "Mode$ Continuous | EffectZone$ All | Affected$ Card.Self" +
|
||||
String effect = "Mode$ Continuous | EffectZone$ All | Affected$ Card.Self" +
|
||||
" | CharacteristicDefining$ True | AddAllCreatureTypes$ True | Secondary$ True" +
|
||||
" | Description$ Changeling (" + inst.getReminderText() + ")";
|
||||
inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic));
|
||||
} else if (keyword.equals("Cipher")) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Mode$ Continuous | EffectZone$ Exile | Affected$ Card.EncodedWithSource");
|
||||
sb.append(" | AddTrigger$ CipherTrigger");
|
||||
sb.append(" | Description$ Cipher (").append(inst.getReminderText()).append(")");
|
||||
|
||||
effect = sb.toString();
|
||||
String effect = sb.toString();
|
||||
|
||||
sb = new StringBuilder();
|
||||
|
||||
@@ -3445,8 +3449,12 @@ public class CardFactoryUtil {
|
||||
|
||||
String ab = "DB$ Play | Defined$ OriginalHost | WithoutManaCost$ True | CopyCard$ True";
|
||||
|
||||
svars.put("CipherTrigger", trig);
|
||||
svars.put("PlayEncoded", ab);
|
||||
StaticAbility st = StaticAbility.create(effect, state.getCard(), state, intrinsic);
|
||||
|
||||
st.setSVar("CipherTrigger", trig);
|
||||
st.setSVar("PlayEncoded", ab);
|
||||
|
||||
inst.addStaticAbility(st);
|
||||
} else if (keyword.startsWith("Class")) {
|
||||
final String[] k = keyword.split(":");
|
||||
final String level = k[1];
|
||||
@@ -3475,24 +3483,32 @@ public class CardFactoryUtil {
|
||||
}
|
||||
}
|
||||
|
||||
effect = "Mode$ Continuous | Affected$ Card.Self | ClassLevel$ " + level + " | " + params;
|
||||
String effect = "Mode$ Continuous | Affected$ Card.Self | ClassLevel$ " + level + " | " + params;
|
||||
if (descAdded) {
|
||||
effect += " | Description$ " + desc.toString();
|
||||
}
|
||||
|
||||
inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic));
|
||||
} else if (keyword.startsWith("Dash")) {
|
||||
effect = "Mode$ Continuous | Affected$ Card.Self+dashed | AddKeyword$ Haste";
|
||||
String effect = "Mode$ Continuous | Affected$ Card.Self+dashed | AddKeyword$ Haste";
|
||||
inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic));
|
||||
} else if (keyword.equals("Daybound")) {
|
||||
effect = "Mode$ CantTransform | ValidCard$ Creature.Self | ExceptCause$ SpellAbility.Daybound | Secondary$ True | Description$ This permanent can't be transformed except by its daybound ability.";
|
||||
String effect = "Mode$ CantTransform | ValidCard$ Creature.Self | ExceptCause$ SpellAbility.Daybound | Secondary$ True | Description$ This permanent can't be transformed except by its daybound ability.";
|
||||
inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic));
|
||||
} else if (keyword.equals("Decayed")) {
|
||||
effect = "Mode$ Continuous | Affected$ Card.Self | AddHiddenKeyword$ CARDNAME can't block. | " +
|
||||
String effect = "Mode$ Continuous | Affected$ Card.Self | AddHiddenKeyword$ CARDNAME can't block. | " +
|
||||
"Secondary$ True";
|
||||
svars.put("SacrificeEndCombat", "True");
|
||||
StaticAbility st = StaticAbility.create(effect, state.getCard(), state, intrinsic);
|
||||
st.setSVar("SacrificeEndCombat", "True");
|
||||
inst.addStaticAbility(st);
|
||||
} else if (keyword.equals("Defender")) {
|
||||
effect = "Mode$ CantAttack | ValidCard$ Card.Self | DefenderKeyword$ True | Secondary$ True";
|
||||
String effect = "Mode$ CantAttack | ValidCard$ Card.Self | DefenderKeyword$ True | Secondary$ True";
|
||||
inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic));
|
||||
} else if (keyword.equals("Devoid")) {
|
||||
effect = "Mode$ Continuous | EffectZone$ All | Affected$ Card.Self" +
|
||||
String effect = "Mode$ Continuous | EffectZone$ All | Affected$ Card.Self" +
|
||||
" | CharacteristicDefining$ True | SetColor$ Colorless | Secondary$ True" +
|
||||
" | Description$ Devoid (" + inst.getReminderText() + ")";
|
||||
inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic));
|
||||
} else if (keyword.startsWith("Escalate")) {
|
||||
final String[] k = keyword.split(":");
|
||||
final String manacost = k[1];
|
||||
@@ -3506,15 +3522,18 @@ public class CardFactoryUtil {
|
||||
}
|
||||
sb.append(cost.toSimpleString());
|
||||
|
||||
effect = "Mode$ RaiseCost | ValidCard$ Card.Self | Type$ Spell | Secondary$ True"
|
||||
String effect = "Mode$ RaiseCost | ValidCard$ Card.Self | Type$ Spell | Secondary$ True"
|
||||
+ " | Amount$ Escalate | Cost$ "+ manacost +" | EffectZone$ All"
|
||||
+ " | Description$ " + sb.toString() + " (" + inst.getReminderText() + ")";
|
||||
inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic));
|
||||
} else if (keyword.equals("Fear")) {
|
||||
effect = "Mode$ CantBlockBy | ValidAttacker$ Creature.Self | ValidBlocker$ Creature.nonArtifact+nonBlack | Secondary$ True " +
|
||||
String effect = "Mode$ CantBlockBy | ValidAttacker$ Creature.Self | ValidBlocker$ Creature.nonArtifact+nonBlack | Secondary$ True " +
|
||||
" | Description$ Fear ( " + inst.getReminderText() + ")";
|
||||
inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic));
|
||||
} else if (keyword.equals("Flying")) {
|
||||
effect = "Mode$ CantBlockBy | ValidAttacker$ Creature.Self | ValidBlocker$ Creature.withoutFlying+withoutReach | Secondary$ True " +
|
||||
String effect = "Mode$ CantBlockBy | ValidAttacker$ Creature.Self | ValidBlocker$ Creature.withoutFlying+withoutReach | Secondary$ True " +
|
||||
" | Description$ Flying ( " + inst.getReminderText() + ")";
|
||||
inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic));
|
||||
} else if (keyword.startsWith("Hexproof")) {
|
||||
final StringBuilder sbDesc = new StringBuilder("Hexproof");
|
||||
final StringBuilder sbValid = new StringBuilder();
|
||||
@@ -3526,55 +3545,84 @@ public class CardFactoryUtil {
|
||||
sbValid.append("| ValidSource$ ").append(k[1]);
|
||||
}
|
||||
|
||||
effect = "Mode$ CantTarget | Hexproof$ True | ValidCard$ Card.Self | Secondary$ True"
|
||||
String effect = "Mode$ CantTarget | Hexproof$ True | ValidCard$ Card.Self | Secondary$ True"
|
||||
+ sbValid.toString() + " | Activator$ Opponent | Description$ "
|
||||
+ sbDesc.toString() + " (" + inst.getReminderText() + ")";
|
||||
inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic));
|
||||
} else if (keyword.equals("Horsemanship")) {
|
||||
effect = "Mode$ CantBlockBy | ValidAttacker$ Creature.Self | ValidBlocker$ Creature.withoutHorsemanship | Secondary$ True " +
|
||||
String effect = "Mode$ CantBlockBy | ValidAttacker$ Creature.Self | ValidBlocker$ Creature.withoutHorsemanship | Secondary$ True " +
|
||||
" | Description$ Horsemanship ( " + inst.getReminderText() + ")";
|
||||
inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic));
|
||||
} else if (keyword.equals("Intimidate")) {
|
||||
effect = "Mode$ CantBlockBy | ValidAttacker$ Creature.Self | ValidBlocker$ Creature.nonArtifact+notSharesColorWith | Secondary$ True " +
|
||||
String effect = "Mode$ CantBlockBy | ValidAttacker$ Creature.Self | ValidBlocker$ Creature.nonArtifact+notSharesColorWith | Secondary$ True " +
|
||||
" | Description$ Intimidate ( " + inst.getReminderText() + ")";
|
||||
inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic));
|
||||
} else if (keyword.equals("Nightbound")) {
|
||||
effect = "Mode$ CantTransform | ValidCard$ Creature.Self | ExceptCause$ SpellAbility.Nightbound | Secondary$ True | Description$ This permanent can't be transformed except by its nightbound ability.";
|
||||
String effect = "Mode$ CantTransform | ValidCard$ Creature.Self | ExceptCause$ SpellAbility.Nightbound | Secondary$ True | Description$ This permanent can't be transformed except by its nightbound ability.";
|
||||
inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic));
|
||||
} else if (keyword.startsWith("Protection")) {
|
||||
String valid = getProtectionValid(keyword, false);
|
||||
effect = "Mode$ CantBlockBy | ValidAttacker$ Creature.Self ";
|
||||
|
||||
// Block
|
||||
String effect = "Mode$ CantBlockBy | ValidAttacker$ Creature.Self | Secondary$ True ";
|
||||
String desc = "Protection ( " + inst.getReminderText() + ")";
|
||||
if (!valid.isEmpty()) {
|
||||
effect += "| ValidBlocker$ " + valid;
|
||||
}
|
||||
effect += " | Secondary$ True | Description$ Protection ( " + inst.getReminderText() + ")";
|
||||
effect += " | Description$ " + desc;
|
||||
inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic));
|
||||
|
||||
// Target
|
||||
effect = "Mode$ CantTarget | Protection$ True | ValidCard$ Card.Self | Secondary$ True ";
|
||||
if (!valid.isEmpty()) {
|
||||
effect += "| ValidSource$ " + valid;
|
||||
}
|
||||
effect += " | Description$ " + desc;
|
||||
inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic));
|
||||
|
||||
// Attach
|
||||
effect = "Mode$ CantAttach | Protection$ True | Target$ Card.Self | Secondary$ True ";
|
||||
if (!valid.isEmpty()) {
|
||||
effect += "| ValidCard$ " + valid;
|
||||
}
|
||||
// This effect doesn't remove something
|
||||
if (keyword.startsWith("Protection:")) {
|
||||
final String[] kws = keyword.split(":");
|
||||
if (kws.length > 3) {
|
||||
effect += "| Exceptions$ " + kws[3];
|
||||
}
|
||||
}
|
||||
effect += " | Description$ " + desc;
|
||||
inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic));
|
||||
} else if (keyword.equals("Shroud")) {
|
||||
effect = "Mode$ CantTarget | Shroud$ True | ValidCard$ Card.Self | Secondary$ True"
|
||||
String effect = "Mode$ CantTarget | Shroud$ True | ValidCard$ Card.Self | Secondary$ True"
|
||||
+ " | Description$ Shroud (" + inst.getReminderText() + ")";
|
||||
inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic));
|
||||
} else if (keyword.equals("Skulk")) {
|
||||
effect = "Mode$ CantBlockBy | ValidAttacker$ Creature.Self | ValidBlocker$ Creature.powerGTX | Secondary$ True " +
|
||||
String effect = "Mode$ CantBlockBy | ValidAttacker$ Creature.Self | ValidBlocker$ Creature.powerGTX | Secondary$ True " +
|
||||
" | Description$ Skulk ( " + inst.getReminderText() + ")";
|
||||
svars.put("X", "Count$CardPower");
|
||||
StaticAbility st = StaticAbility.create(effect, state.getCard(), state, intrinsic);
|
||||
st.setSVar("X", "Count$CardPower");
|
||||
inst.addStaticAbility(st);
|
||||
} else if (keyword.startsWith("Strive")) {
|
||||
final String[] k = keyword.split(":");
|
||||
final String manacost = k[1];
|
||||
|
||||
effect = "Mode$ RaiseCost | ValidCard$ Card.Self | Type$ Spell | Amount$ Strive | Cost$ "+ manacost +" | EffectZone$ All" +
|
||||
String effect = "Mode$ RaiseCost | ValidCard$ Card.Self | Type$ Spell | Amount$ Strive | Cost$ "+ manacost +" | EffectZone$ All" +
|
||||
" | Description$ Strive - " + inst.getReminderText();
|
||||
inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic));
|
||||
} else if (keyword.equals("Unleash")) {
|
||||
effect = "Mode$ Continuous | Affected$ Card.Self+counters_GE1_P1P1 | AddHiddenKeyword$ CARDNAME can't block.";
|
||||
String effect = "Mode$ Continuous | Affected$ Card.Self+counters_GE1_P1P1 | AddHiddenKeyword$ CARDNAME can't block.";
|
||||
inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic));
|
||||
} else if (keyword.equals("Undaunted")) {
|
||||
effect = "Mode$ ReduceCost | ValidCard$ Card.Self | Type$ Spell | Secondary$ True"
|
||||
String effect = "Mode$ ReduceCost | ValidCard$ Card.Self | Type$ Spell | Secondary$ True"
|
||||
+ "| Amount$ Undaunted | EffectZone$ All | Description$ Undaunted (" + inst.getReminderText() + ")";
|
||||
inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic));
|
||||
} else if (keyword.equals("MayFlashSac")) {
|
||||
effect = "Mode$ Continuous | EffectZone$ All | Affected$ Card.Self | Secondary$ True | MayPlay$ True"
|
||||
String effect = "Mode$ Continuous | EffectZone$ All | Affected$ Card.Self | Secondary$ True | MayPlay$ True"
|
||||
+ " | MayPlayNotSorcerySpeed$ True | MayPlayWithFlash$ True | MayPlayText$ Sacrifice at the next cleanup step"
|
||||
+ " | AffectedZone$ Exile,Graveyard,Hand,Library,Stack | Description$ " + inst.getReminderText();
|
||||
}
|
||||
|
||||
if (effect != null) {
|
||||
StaticAbility st = new StaticAbility(effect, state.getCard(), state);
|
||||
st.setIntrinsic(intrinsic);
|
||||
for (Map.Entry<String, String> e : svars.entrySet()) {
|
||||
st.setSVar(e.getKey(), e.getValue());
|
||||
}
|
||||
inst.addStaticAbility(st);
|
||||
inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -224,15 +224,6 @@ public final class CardPredicates {
|
||||
};
|
||||
}
|
||||
|
||||
public static final Predicate<Card> isProtectedFrom(final Card source) {
|
||||
return new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return c.hasProtectionFrom(source);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static final Predicate<Card> restriction(final String[] restrictions, final Player sourceController, final Card source, final CardTraitBase spellAbility) {
|
||||
return new Predicate<Card>() {
|
||||
@Override
|
||||
|
||||
@@ -562,7 +562,7 @@ public class CardView extends GameEntityView {
|
||||
});
|
||||
}
|
||||
|
||||
private boolean canFaceDownBeShownTo(final PlayerView viewer, boolean skip) {
|
||||
public boolean canFaceDownBeShownTo(final PlayerView viewer, boolean skip) {
|
||||
if (!isFaceDown()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -529,8 +529,7 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
}
|
||||
|
||||
// Run triggers
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
|
||||
runParams.put(AbilityKey.Player, this);
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPlayer(this);
|
||||
runParams.put(AbilityKey.LifeAmount, lifeGain);
|
||||
runParams.put(AbilityKey.Source, source);
|
||||
runParams.put(AbilityKey.SourceSA, sa);
|
||||
@@ -602,8 +601,7 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
lifeLostThisTurn += toLose;
|
||||
|
||||
// Run triggers
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
|
||||
runParams.put(AbilityKey.Player, this);
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPlayer(this);
|
||||
runParams.put(AbilityKey.LifeAmount, toLose);
|
||||
runParams.put(AbilityKey.FirstTime, firstLost);
|
||||
game.getTriggerHandler().runTrigger(TriggerType.LifeLost, runParams, false);
|
||||
@@ -630,8 +628,7 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
loseLife(lifePayment, false, false);
|
||||
|
||||
// Run triggers
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
|
||||
runParams.put(AbilityKey.Player, this);
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPlayer(this);
|
||||
runParams.put(AbilityKey.LifeAmount, lifePayment);
|
||||
game.getTriggerHandler().runTrigger(TriggerType.PayLife, runParams, false);
|
||||
|
||||
@@ -1097,71 +1094,8 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !hasProtectionFrom(sa.getHostCard());
|
||||
}
|
||||
|
||||
public boolean hasProtectionFromDamage(final Card source) {
|
||||
return hasProtectionFrom(source, false, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasProtectionFrom(final Card source, final boolean checkSBA) {
|
||||
return hasProtectionFrom(source, checkSBA, false);
|
||||
}
|
||||
public boolean hasProtectionFrom(final Card source, final boolean checkSBA, final boolean damageSource) {
|
||||
final boolean colorlessDamage = damageSource && source.hasKeyword("Colorless Damage Source");
|
||||
for (KeywordInterface ki : keywords) {
|
||||
String kw = ki.getOriginal();
|
||||
if (kw.startsWith("Protection")) {
|
||||
if (kw.startsWith("Protection:")) { // uses isValid
|
||||
final String characteristic = kw.split(":")[1];
|
||||
if (characteristic.startsWith("Player")) {
|
||||
// Protection:PlayerUID
|
||||
if (source.getController().isValid(characteristic, this, null, null)) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
final String[] characteristics = characteristic.split(",");
|
||||
if (source.isValid(characteristics, this, null, null)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else if (kw.equals("Protection from white")) {
|
||||
if (source.isWhite() && !colorlessDamage) {
|
||||
return true;
|
||||
}
|
||||
} else if (kw.equals("Protection from blue")) {
|
||||
if (source.isBlue() && !colorlessDamage) {
|
||||
return true;
|
||||
}
|
||||
} else if (kw.equals("Protection from black")) {
|
||||
if (source.isBlack() && !colorlessDamage) {
|
||||
return true;
|
||||
}
|
||||
} else if (kw.equals("Protection from red")) {
|
||||
if (source.isRed() && !colorlessDamage) {
|
||||
return true;
|
||||
}
|
||||
} else if (kw.equals("Protection from green")) {
|
||||
if (source.isGreen() && !colorlessDamage) {
|
||||
return true;
|
||||
}
|
||||
} else if (kw.equals("Protection from all colors")) {
|
||||
if (!source.isColorless() && !colorlessDamage) {
|
||||
return true;
|
||||
}
|
||||
} else if (kw.equals("Protection from everything")) {
|
||||
return true;
|
||||
} else if (kw.startsWith("Protection from ")) {
|
||||
final String protectType = CardType.getSingularType(kw.substring("Protection from ".length()));
|
||||
if (source.getType().hasStringType(protectType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void surveil(int num, SpellAbility cause, CardZoneTable table) {
|
||||
final Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(this);
|
||||
@@ -1212,8 +1146,7 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
getGame().fireEvent(new GameEventSurveil(this, numToTop, numToGrave));
|
||||
|
||||
surveilThisTurn++;
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
|
||||
runParams.put(AbilityKey.Player, this);
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPlayer(this);
|
||||
runParams.put(AbilityKey.NumThisTurn, surveilThisTurn);
|
||||
getGame().getTriggerHandler().runTrigger(TriggerType.Surveil, runParams, false);
|
||||
}
|
||||
@@ -1329,7 +1262,7 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
}
|
||||
view.updateNumDrawnThisTurn(this);
|
||||
|
||||
final Map<AbilityKey, Object> runParams = Maps.newHashMap();
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPlayer(this);
|
||||
|
||||
// CR 121.8 card was drawn as part of another sa (e.g. paying with Chromantic Sphere), hide it temporarily
|
||||
if (game.getTopLibForPlayer(this) != null && getPaidForSA() != null && cause != null && getPaidForSA() != cause.getRootAbility()) {
|
||||
@@ -1341,7 +1274,6 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
// Run triggers
|
||||
runParams.put(AbilityKey.Card, c);
|
||||
runParams.put(AbilityKey.Number, numDrawnThisTurn);
|
||||
runParams.put(AbilityKey.Player, this);
|
||||
game.getTriggerHandler().runTrigger(TriggerType.Drawn, runParams, false);
|
||||
}
|
||||
}
|
||||
@@ -1545,8 +1477,7 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
}
|
||||
}
|
||||
}
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
|
||||
runParams.put(AbilityKey.Player, this);
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPlayer(this);
|
||||
runParams.put(AbilityKey.Card, c);
|
||||
runParams.put(AbilityKey.Cause, cause);
|
||||
runParams.put(AbilityKey.IsMadness, discardMadness);
|
||||
@@ -1564,8 +1495,7 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
|
||||
public final void addTokensCreatedThisTurn(Card token) {
|
||||
numTokenCreatedThisTurn++;
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
|
||||
runParams.put(AbilityKey.Player, this);
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPlayer(this);
|
||||
runParams.put(AbilityKey.Num, numTokenCreatedThisTurn);
|
||||
runParams.put(AbilityKey.Card, token);
|
||||
game.getTriggerHandler().runTrigger(TriggerType.TokenCreated, runParams, false);
|
||||
@@ -1581,8 +1511,7 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
|
||||
public final void addForetoldThisTurn() {
|
||||
numForetoldThisTurn++;
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
|
||||
runParams.put(AbilityKey.Player, this);
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPlayer(this);
|
||||
runParams.put(AbilityKey.Num, numForetoldThisTurn);
|
||||
game.getTriggerHandler().runTrigger(TriggerType.Foretell, runParams, false);
|
||||
}
|
||||
@@ -1717,8 +1646,7 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
getZone(ZoneType.Library).setCards(getController().cheatShuffle(list));
|
||||
|
||||
// Run triggers
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
|
||||
runParams.put(AbilityKey.Player, this);
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPlayer(this);
|
||||
runParams.put(AbilityKey.Source, sa);
|
||||
game.getTriggerHandler().runTrigger(TriggerType.Shuffled, runParams, false);
|
||||
|
||||
@@ -2007,12 +1935,7 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
return getOutcome().lossState != null;
|
||||
}
|
||||
|
||||
// Rule 704.5a - If a player has 0 or less life, he or she loses the game.
|
||||
final boolean hasNoLife = getLife() <= 0;
|
||||
if (hasNoLife && !cantLoseForZeroOrLessLife()) {
|
||||
return loseConditionMet(GameLossReason.LifeReachedZero, null);
|
||||
}
|
||||
|
||||
// check this first because of Lich's Mirror (704.7)
|
||||
// Rule 704.5b - If a player attempted to draw a card from a library with no cards in it
|
||||
// since the last time state-based actions were checked, he or she loses the game.
|
||||
if (triedToDrawFromEmptyLibrary) {
|
||||
@@ -2023,6 +1946,12 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
}
|
||||
}
|
||||
|
||||
// Rule 704.5a - If a player has 0 or less life, he or she loses the game.
|
||||
final boolean hasNoLife = getLife() <= 0;
|
||||
if (hasNoLife && !cantLoseForZeroOrLessLife()) {
|
||||
return loseConditionMet(GameLossReason.LifeReachedZero, null);
|
||||
}
|
||||
|
||||
// Rule 704.5c - If a player has ten or more poison counters, he or she loses the game.
|
||||
if (getCounters(CounterEnumType.POISON) >= 10) {
|
||||
return loseConditionMet(GameLossReason.Poisoned, null);
|
||||
@@ -2244,8 +2173,7 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
}
|
||||
public final void addInvestigatedThisTurn() {
|
||||
investigatedThisTurn++;
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
|
||||
runParams.put(AbilityKey.Player, this);
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPlayer(this);
|
||||
runParams.put(AbilityKey.Num, investigatedThisTurn);
|
||||
game.getTriggerHandler().runTrigger(TriggerType.Investigated, runParams, false);
|
||||
}
|
||||
@@ -2265,10 +2193,9 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
sacrificedThisTurn.add(cpy);
|
||||
|
||||
// Run triggers
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPlayer(this);
|
||||
// use a copy that preserves last known information about the card (e.g. for Savra, Queen of the Golgari + Painter's Servant)
|
||||
runParams.put(AbilityKey.Card, cpy);
|
||||
runParams.put(AbilityKey.Player, this);
|
||||
runParams.put(AbilityKey.Cause, source);
|
||||
runParams.put(AbilityKey.CostStack, game.costPaymentStack);
|
||||
runParams.put(AbilityKey.IndividualCostPaymentInstance, game.costPaymentStack.peek());
|
||||
@@ -3513,8 +3440,7 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
// Change this if something would make multiple player learn at the same time
|
||||
|
||||
// Discard Trigger outside Effect
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
|
||||
runParams.put(AbilityKey.Player, this);
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPlayer(this);
|
||||
runParams.put(AbilityKey.Cards, new CardCollection(c));
|
||||
runParams.put(AbilityKey.Cause, sa);
|
||||
runParams.put(AbilityKey.FirstTime, firstDiscard);
|
||||
|
||||
@@ -11,7 +11,7 @@ public class PlayerFactoryUtil {
|
||||
|
||||
public static void addStaticAbility(final KeywordInterface inst, final Player player) {
|
||||
String keyword = inst.getOriginal();
|
||||
String effect = null;
|
||||
|
||||
if (keyword.startsWith("Hexproof")) {
|
||||
final StringBuilder sbDesc = new StringBuilder("Hexproof");
|
||||
final StringBuilder sbValid = new StringBuilder();
|
||||
@@ -23,18 +23,40 @@ public class PlayerFactoryUtil {
|
||||
sbValid.append("| ValidSource$ ").append(k[1]);
|
||||
}
|
||||
|
||||
effect = "Mode$ CantTarget | Hexproof$ True | ValidPlayer$ Player.You | Secondary$ True "
|
||||
String effect = "Mode$ CantTarget | Hexproof$ True | ValidPlayer$ Player.You | Secondary$ True "
|
||||
+ sbValid.toString() + " | Activator$ Opponent | EffectZone$ Command | Description$ "
|
||||
+ sbDesc.toString() + " (" + inst.getReminderText() + ")";
|
||||
} else if (keyword.equals("Shroud")) {
|
||||
effect = "Mode$ CantTarget | Shroud$ True | ValidPlayer$ Player.You | Secondary$ True "
|
||||
+ "| EffectZone$ Command | Description$ Shroud (" + inst.getReminderText() + ")";
|
||||
}
|
||||
if (effect != null) {
|
||||
|
||||
final Card card = player.getKeywordCard();
|
||||
StaticAbility st = new StaticAbility(effect, card, card.getCurrentState());
|
||||
st.setIntrinsic(false);
|
||||
inst.addStaticAbility(st);
|
||||
inst.addStaticAbility(StaticAbility.create(effect, card, card.getCurrentState(), false));
|
||||
} else if (keyword.equals("Shroud")) {
|
||||
String effect = "Mode$ CantTarget | Shroud$ True | ValidPlayer$ Player.You | Secondary$ True "
|
||||
+ "| EffectZone$ Command | Description$ Shroud (" + inst.getReminderText() + ")";
|
||||
|
||||
final Card card = player.getKeywordCard();
|
||||
inst.addStaticAbility(StaticAbility.create(effect, card, card.getCurrentState(), false));
|
||||
} else if (keyword.startsWith("Protection")) {
|
||||
String valid = CardFactoryUtil.getProtectionValid(keyword, false);
|
||||
String effect = "Mode$ CantTarget | Protection$ True | ValidCard$ Player.You | Secondary$ True ";
|
||||
if (!valid.isEmpty()) {
|
||||
effect += "| ValidSource$ " + valid;
|
||||
}
|
||||
final Card card = player.getKeywordCard();
|
||||
inst.addStaticAbility(StaticAbility.create(effect, card, card.getCurrentState(), false));
|
||||
|
||||
// Attach
|
||||
effect = "Mode$ CantAttach | Protection$ True | Target$ Player.You | Secondary$ True ";
|
||||
if (!valid.isEmpty()) {
|
||||
effect += "| ValidCard$ " + valid;
|
||||
}
|
||||
// This effect doesn't remove something
|
||||
if (keyword.startsWith("Protection:")) {
|
||||
final String[] kws = keyword.split(":");
|
||||
if (kws.length > 3) {
|
||||
effect += "| Exceptions$ " + kws[3];
|
||||
}
|
||||
}
|
||||
inst.addStaticAbility(StaticAbility.create(effect, card, card.getCurrentState(), false));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -504,7 +504,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
||||
}
|
||||
|
||||
public boolean isCycling() {
|
||||
return this.isAlternativeCost(AlternativeCost.Cycling);
|
||||
return isAlternativeCost(AlternativeCost.Cycling);
|
||||
}
|
||||
|
||||
public boolean isBoast() {
|
||||
@@ -515,6 +515,14 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
||||
return this.hasParam("Ninjutsu");
|
||||
}
|
||||
|
||||
public boolean isEpic() {
|
||||
AbilitySub sub = this.getSubAbility();
|
||||
while (sub != null && !sub.hasParam("Epic")) {
|
||||
sub = sub.getSubAbility();
|
||||
}
|
||||
return sub != null && sub.hasParam("Epic");
|
||||
}
|
||||
|
||||
// If this is not null, then ability was made in a factory
|
||||
public ApiType getApi() {
|
||||
return api;
|
||||
@@ -777,7 +785,10 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
||||
public void resetOnceResolved() {
|
||||
//resetPaidHash(); // FIXME: if uncommented, breaks Dragon Presence, e.g. Orator of Ojutai + revealing a Dragon from hand.
|
||||
// Is it truly necessary at this point? The paid hash seems to be reset on all SA instance operations.
|
||||
// Epic spell keeps original targets
|
||||
if (!isEpic()) {
|
||||
resetTargets();
|
||||
}
|
||||
resetTriggeringObjects();
|
||||
resetTriggerRemembered();
|
||||
|
||||
|
||||
@@ -234,6 +234,12 @@ public class StaticAbility extends CardTraitBase implements IIdentifiable, Clone
|
||||
this(parseParams(params, host), host, state);
|
||||
}
|
||||
|
||||
public static StaticAbility create(final String params, final Card host, CardState state, boolean intrinsic) {
|
||||
StaticAbility st = new StaticAbility(params, state.getCard(), state);
|
||||
st.setIntrinsic(intrinsic);
|
||||
return st;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a new static ability.
|
||||
*
|
||||
@@ -325,8 +331,6 @@ public class StaticAbility extends CardTraitBase implements IIdentifiable, Clone
|
||||
return StaticAbilityCantAttackBlock.applyCantAttackAbility(this, card, target);
|
||||
} else if (mode.equals("CantBlockBy") && target instanceof Card) {
|
||||
return StaticAbilityCantAttackBlock.applyCantBlockByAbility(this, card, (Card)target);
|
||||
} else if (mode.equals("CantAttach")) {
|
||||
return StaticAbilityCantAttach.applyCantAttachAbility(this, card, target);
|
||||
} else if (mode.equals("CanAttackIfHaste")) {
|
||||
return StaticAbilityCantAttackBlock.applyCanAttackHasteAbility(this, card, target);
|
||||
}
|
||||
|
||||
@@ -2,10 +2,29 @@ package forge.game.staticability;
|
||||
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
public class StaticAbilityCantAttach {
|
||||
|
||||
public static boolean applyCantAttachAbility(final StaticAbility stAb, final Card card, final GameEntity target) {
|
||||
static String MODE = "CantAttach";
|
||||
|
||||
public static boolean cantAttach(final GameEntity target, final Card card, boolean checkSBA) {
|
||||
// CantTarget static abilities
|
||||
for (final Card ca : target.getGame().getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {
|
||||
for (final StaticAbility stAb : ca.getStaticAbilities()) {
|
||||
if (!stAb.getParam("Mode").equals(MODE) || stAb.isSuppressed() || !stAb.checkConditions()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (applyCantAttachAbility(stAb, card, target, checkSBA)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean applyCantAttachAbility(final StaticAbility stAb, final Card card, final GameEntity target, boolean checkSBA) {
|
||||
if (!stAb.matchesValidParam("ValidCard", card)) {
|
||||
return false;
|
||||
}
|
||||
@@ -25,6 +44,10 @@ public class StaticAbilityCantAttach {
|
||||
}
|
||||
}
|
||||
|
||||
if (checkSBA && stAb.matchesValidParam("Exceptions", card)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -875,9 +875,7 @@ public final class StaticAbilityContinuous {
|
||||
s = TextUtil.fastReplace(s, "ConvertedManaCost", costcmc);
|
||||
}
|
||||
|
||||
StaticAbility stat = new StaticAbility(s, affectedCard, stAb.getCardState());
|
||||
stat.setIntrinsic(false);
|
||||
addedStaticAbility.add(stat);
|
||||
addedStaticAbility.add(StaticAbility.create(s, affectedCard, stAb.getCardState(), false));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -130,6 +130,12 @@ public class TriggerChangesZone extends Trigger {
|
||||
}
|
||||
}
|
||||
|
||||
if (hasParam("ResolvedCard")) {
|
||||
if (!runParams.containsKey(AbilityKey.Fizzle)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check number of lands ETB this turn on triggered card's controller
|
||||
if (hasParam("CheckOnTriggeredCard")) {
|
||||
final String[] condition = getParam("CheckOnTriggeredCard").split(" ", 2);
|
||||
|
||||
@@ -253,7 +253,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
|
||||
}
|
||||
}
|
||||
|
||||
if (!sp.isCopied() && !hasLegalTargeting(sp, source)) {
|
||||
if (!sp.isCopied() && !hasLegalTargeting(sp)) {
|
||||
String str = source + " - [Couldn't add to stack, failed to target] - " + sp.getDescription();
|
||||
System.err.println(str + sp.getAllTargetChoices());
|
||||
game.getGameLog().add(GameLogEntryType.STACK_ADD, str);
|
||||
@@ -604,14 +604,14 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
|
||||
}
|
||||
}
|
||||
|
||||
public final boolean hasLegalTargeting(final SpellAbility sa, final Card source) {
|
||||
public final boolean hasLegalTargeting(final SpellAbility sa) {
|
||||
if (sa == null) {
|
||||
return true;
|
||||
}
|
||||
if (!sa.isTargetNumberValid()) {
|
||||
return false;
|
||||
}
|
||||
return hasLegalTargeting(sa.getSubAbility(), source);
|
||||
return hasLegalTargeting(sa.getSubAbility());
|
||||
}
|
||||
|
||||
private final boolean hasFizzled(final SpellAbility sa, final Card source, final Boolean parentFizzled) {
|
||||
|
||||
@@ -28,7 +28,7 @@ public enum ZoneType {
|
||||
Subgame(true, "lblSubgameZone"),
|
||||
None(true, "lblNoneZone");
|
||||
|
||||
public static final List<ZoneType> STATIC_ABILITIES_SOURCE_ZONES = Arrays.asList(Battlefield, Graveyard, Exile, Command/*, Hand*/);
|
||||
public static final List<ZoneType> STATIC_ABILITIES_SOURCE_ZONES = Arrays.asList(Battlefield, Graveyard, Exile, Command, Stack/*, Hand*/);
|
||||
|
||||
private final boolean holdsHiddenInfo;
|
||||
private final String zoneName;
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
Name:Dream Leash
|
||||
ManaCost:3 U U
|
||||
Types:Enchantment Aura
|
||||
Text:You can't choose an untapped creature as this spell's target as you cast it.
|
||||
K:Enchant permanent
|
||||
K:SpellCantTarget:Permanent.untapped
|
||||
S:Mode$ CantTarget | EffectZone$ Stack | ValidSource$ Spell.Self | ValidCard$ Permanent.untapped | Description$ You can't choose an untapped permanent as this spell's target as you cast it.
|
||||
A:SP$ Attach | Cost$ 3 U U | ValidTgts$ Permanent | AILogic$ GainControl
|
||||
S:Mode$ Continuous | Affected$ Card.EnchantedBy | GainControl$ You | Description$ You control enchanted permanent.
|
||||
Oracle:Enchant permanent\nYou can't choose an untapped permanent as this spell's target as you cast it.\nYou control enchanted permanent.
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
Name:Enthralling Hold
|
||||
ManaCost:3 U U
|
||||
Types:Enchantment Aura
|
||||
Text:You can't choose an untapped creature as this spell's target as you cast it.
|
||||
K:Enchant creature
|
||||
K:SpellCantTarget:Creature.untapped
|
||||
S:Mode$ CantTarget | EffectZone$ Stack | ValidSource$ Spell.Self | ValidCard$ Creature.untapped | Description$ You can't choose an untapped creature as this spell's target as you cast it.
|
||||
A:SP$ Attach | Cost$ 3 U U | ValidTgts$ Creature | AILogic$ GainControl
|
||||
S:Mode$ Continuous | Affected$ Card.EnchantedBy | GainControl$ You | Description$ You control enchanted creature.
|
||||
Oracle:Enchant creature\nYou can't choose an untapped creature as this spell's target as you cast it.\nYou control enchanted creature.
|
||||
|
||||
@@ -2,7 +2,7 @@ Name:Eternal Dominion
|
||||
ManaCost:7 U U U
|
||||
Types:Sorcery
|
||||
K:Epic
|
||||
A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ValidTgts$ Opponent | TgtPrompt$ Select target opponent | ChangeType$ Artifact,Creature,Enchantment,Land | ChangeNum$ 1 | GainControl$ True | IsCurse$ True | StackDescription$ SpellDescription | SpellDescription$ Search target opponent's library for an artifact, creature, enchantment, or land card and put that card onto the battlefield under your control. Then that player shuffles their library.
|
||||
A:SP$ ChangeZone | Cost$ 7 U U U | Origin$ Library | Destination$ Battlefield | ValidTgts$ Opponent | ChangeType$ Artifact,Creature,Enchantment,Land | ChangeNum$ 1 | GainControl$ True | IsCurse$ True | StackDescription$ SpellDescription | SpellDescription$ Search target opponent's library for an artifact, creature, enchantment, or land card. Put that card onto the battlefield under your control. Then that player shuffles.
|
||||
#TODO: Tutoring in general can be improved to make this card work better for the AI. Currently the AI will grab the most expensive targets in the opponent's library (which is not necessarily a bad thing in itself, but not always optimal).
|
||||
AI:RemoveDeck:Random
|
||||
Oracle:Search target opponent's library for an artifact, creature, enchantment, or land card. Put that card onto the battlefield under your control. Then that player shuffles.\nEpic (For the rest of the game, you can't cast spells. At the beginning of each of your upkeeps, copy this spell except for its epic ability. You may choose a new target for the copy.)
|
||||
|
||||
@@ -2,7 +2,7 @@ Name:Gisa, Glorious Resurrector
|
||||
ManaCost:2 B B
|
||||
Types:Legendary Creature Human Wizard
|
||||
PT:4/4
|
||||
R:Event$ Moved | ActiveZones$ Battlefield | CheckSelfLKIZone$ True | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Creature.OppCtrl | ReplaceWith$ Exile | Description$ If a creature an opponent controls would die, exile it instead.
|
||||
R:Event$ Moved | ActiveZones$ Battlefield | CheckSelfLKIZone$ True | Origin$ Battlefield | Destination$ Graveyard | ValidLKI$ Creature.OppCtrl | ReplaceWith$ Exile | Description$ If a creature an opponent controls would die, exile it instead.
|
||||
SVar:Exile:DB$ ChangeZone | Hidden$ True | Origin$ All | Destination$ Exile | Defined$ ReplacedCard
|
||||
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | IsPresent$ Card.ExiledWithSource | PresentZone$ Exile | Execute$ TrigChange | TriggerDescription$ At the beginning of your upkeep, put all creature cards exiled with CARDNAME onto the battlefield under your control. They gain decayed. (A creature with decayed can't block. When it attacks, sacrifice it at end of combat.)
|
||||
SVar:TrigChange:DB$ ChangeZoneAll | ChangeType$ Card.ExiledWithSource | Origin$ Exile | Destination$ Battlefield | GainControl$ True | RememberChanged$ True | SubAbility$ DBPump
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Name:Hardened Scales
|
||||
ManaCost:G
|
||||
Types:Enchantment
|
||||
R:Event$ AddCounter | ActiveZones$ Battlefield | ValidCard$ Creature.YouCtrl+InZoneBattlefield | ValidCounterType$ P1P1 | ReplaceWith$ AddOneMoreCounters | Description$ If one or more +1/+1 counters would be put on a creature you control, that many plus one +1/+1 counters are put on it instead.
|
||||
R:Event$ AddCounter | ActiveZones$ Battlefield | ValidCard$ Creature.YouCtrl+inZoneBattlefield | ValidCounterType$ P1P1 | ReplaceWith$ AddOneMoreCounters | Description$ If one or more +1/+1 counters would be put on a creature you control, that many plus one +1/+1 counters are put on it instead.
|
||||
SVar:AddOneMoreCounters:DB$ ReplaceCounter | ValidCounterType$ P1P1 | ChooseCounter$ True | Amount$ X
|
||||
SVar:X:ReplaceCount$CounterNum/Plus.1
|
||||
AI:RemoveDeck:Random
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Name:Rite of Flame
|
||||
ManaCost:R
|
||||
Types:Sorcery
|
||||
A:SP$ Mana | Produced$ R | Amount$ 2 | AILogic$ ManaRitual | AINoRecursiveCheck$ True | SpellDescription$ Add {R}{R}, then add {R} for each card named CARDNAME in each graveyard.
|
||||
A:SP$ Mana | Produced$ R | Amount$ 2 | AILogic$ ManaRitual | AINoRecursiveCheck$ True | SubAbility$ SubMana | SpellDescription$ Add {R}{R}, then add {R} for each card named CARDNAME in each graveyard.
|
||||
SVar:SubMana:DB$ Mana | Produced$ R | Amount$ X
|
||||
SVar:X:Count$NamedInAllYards.Rite of Flame
|
||||
DeckHints:Name$Rite of Flame
|
||||
|
||||
@@ -2,5 +2,5 @@ Name:Submerged Boneyard
|
||||
ManaCost:no cost
|
||||
Types:Land
|
||||
K:CARDNAME enters the battlefield tapped.
|
||||
A:AB$ Mana | Cost$ T | Produced$ Combo U | SpellDescription$ Add {U} or {B}.
|
||||
A:AB$ Mana | Cost$ T | Produced$ Combo U B | SpellDescription$ Add {U} or {B}.
|
||||
Oracle:Submerged Boneyard enters the battlefield tapped.\n{T}: Add {U} or {B}.
|
||||
|
||||
@@ -4,7 +4,7 @@ Types:Legendary Creature Vampire Warlock
|
||||
PT:1/1
|
||||
K:Menace
|
||||
K:Lifelink
|
||||
R:Event$ Moved | ActiveZones$ Battlefield | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Creature+nonToken+OppOwn | ReplaceWith$ Exile | Description$ If a nontoken creature an opponent controls would die, exile it instead. When you do, you may pay {2}. If you do, create a 1/1 black and green Pest creature token with "When this creature dies, you gain 1 life."
|
||||
R:Event$ Moved | ActiveZones$ Battlefield | Origin$ Battlefield | Destination$ Graveyard | ValidLKI$ Card.Creature+nonToken+OppOwn | ReplaceWith$ Exile | Description$ If a nontoken creature an opponent controls would die, exile it instead. When you do, you may pay {2}. If you do, create a 1/1 black and green Pest creature token with "When this creature dies, you gain 1 life."
|
||||
SVar:Exile:DB$ ChangeZone | Hidden$ True | Origin$ All | Destination$ Exile | Defined$ ReplacedCard | SubAbility$ DBImmediateTrigger
|
||||
SVar:DBImmediateTrigger:DB$ ImmediateTrigger | Execute$ TrigToken | TriggerDescription$ If a nontoken creature an opponent controls would die, exile it instead. When you do, you may pay {2}. If you do, create a 1/1 black and green Pest creature token with "When this creature dies, you gain 1 life."
|
||||
SVar:TrigToken:AB$ Token | Cost$ 2 | TokenAmount$ 1 | TokenScript$ bg_1_1_pest_lifegain | TokenOwner$ You
|
||||
|
||||
@@ -3,7 +3,7 @@ ManaCost:4 B B
|
||||
Types:Creature Horror
|
||||
PT:4/5
|
||||
K:Trample
|
||||
R:Event$ Moved | ActiveZones$ Battlefield | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Creature.Other | ReplaceWith$ Exile | Description$ If another creature would die, exile it instead.
|
||||
R:Event$ Moved | ActiveZones$ Battlefield | Origin$ Battlefield | Destination$ Graveyard | ValidLKI$ Creature.Other | ReplaceWith$ Exile | Description$ If another creature would die, exile it instead.
|
||||
SVar:Exile:DB$ ChangeZone | Hidden$ True | Origin$ All | Destination$ Exile | Defined$ ReplacedCard | SubAbility$ DBRemember | RememberChanged$ True
|
||||
SVar:DBRemember:DB$ Pump | ConditionDefined$ Remembered | ConditionPresent$ Card.inZoneExile | ConditionCompare$ GE1
|
||||
T:Mode$ ChangesZone | Origin$ Exile | Destination$ Any | Static$ True | ValidCard$ Card.IsRemembered+ExiledWithSource | Execute$ DBForget
|
||||
|
||||
@@ -3,7 +3,7 @@ ManaCost:1
|
||||
Types:Legendary Artifact Creature Insect
|
||||
PT:0/0
|
||||
K:Modular:1
|
||||
R:Event$ AddCounter | ActiveZones$ Battlefield | ValidCause$ Triggered.Modular | ValidCard$ Creature.YouCtrl+InZoneBattlefield | ValidCounterType$ P1P1 | ReplaceWith$ AddOneMoreCounter | Description$ If a modular triggered ability would put one or more +1/+1 counters on a creature you control, that many plus one +1/+1 counters are put on it instead.
|
||||
R:Event$ AddCounter | ActiveZones$ Battlefield | ValidCause$ Triggered.Modular | ValidCard$ Creature.YouCtrl+inZoneBattlefield | ValidCounterType$ P1P1 | ReplaceWith$ AddOneMoreCounter | Description$ If a modular triggered ability would put one or more +1/+1 counters on a creature you control, that many plus one +1/+1 counters are put on it instead.
|
||||
SVar:AddOneMoreCounter:DB$ ReplaceCounter | ValidCounterType$ P1P1 | ChooseCounter$ True | Amount$ X
|
||||
SVar:X:ReplaceCount$CounterNum/Plus.1
|
||||
A:AB$ Destroy | Cost$ R | ValidTgts$ Artifact.YouCtrl | TgtPrompt$ Choose target artifact you control | SpellDescription$ Destroy target artifact you control.
|
||||
|
||||
Reference in New Issue
Block a user