This commit is contained in:
Alessandro Coli
2019-06-16 08:54:25 +02:00
406 changed files with 661 additions and 350 deletions

View File

@@ -6,7 +6,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.27-SNAPSHOT</version>
</parent>
<artifactId>forge-ai</artifactId>

View File

@@ -1778,7 +1778,7 @@ public class AiController {
+ MyRandom.getRandom().nextInt(3);
return Math.max(remaining, min) / 2;
} else if ("LowestLoseLife".equals(logic)) {
return MyRandom.getRandom().nextInt(Math.min(player.getLife() / 3, ComputerUtil.getOpponentFor(player).getLife())) + 1;
return MyRandom.getRandom().nextInt(Math.min(player.getLife() / 3, player.getWeakestOpponent().getLife())) + 1;
} else if ("HighestGetCounter".equals(logic)) {
return MyRandom.getRandom().nextInt(3);
} else if (source.hasSVar("EnergyToPay")) {

View File

@@ -521,7 +521,7 @@ public class ComputerUtilCard {
*/
public static CardCollectionView getLikelyBlockers(final Player ai, final CardCollectionView blockers) {
AiBlockController aiBlk = new AiBlockController(ai);
final Player opp = ComputerUtil.getOpponentFor(ai);
final Player opp = ai.getWeakestOpponent();
Combat combat = new Combat(opp);
//Use actual attackers if available, else consider all possible attackers
Combat currentCombat = ai.getGame().getCombat();
@@ -884,7 +884,7 @@ public class ComputerUtilCard {
List<String> chosen = new ArrayList<String>();
Player ai = sa.getActivatingPlayer();
final Game game = ai.getGame();
Player opp = ComputerUtil.getOpponentFor(ai);
Player opp = ai.getWeakestOpponent();
if (sa.hasParam("AILogic")) {
final String logic = sa.getParam("AILogic");
@@ -974,7 +974,7 @@ public class ComputerUtilCard {
public static boolean useRemovalNow(final SpellAbility sa, final Card c, final int dmg, ZoneType destination) {
final Player ai = sa.getActivatingPlayer();
final AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
final Player opp = ComputerUtil.getOpponentFor(ai);
final Player opp = ai.getWeakestOpponent();
final Game game = ai.getGame();
final PhaseHandler ph = game.getPhaseHandler();
final PhaseType phaseType = ph.getPhase();
@@ -1269,7 +1269,7 @@ public class ComputerUtilCard {
}
}
final Player opp = ComputerUtil.getOpponentFor(ai);
final Player opp = ai.getWeakestOpponent();
Card pumped = getPumpedCreature(ai, sa, c, toughness, power, keywords);
List<Card> oppCreatures = opp.getCreaturesInPlay();
float chance = 0;
@@ -1842,17 +1842,45 @@ public class ComputerUtilCard {
String needsToPlayName = isRightSplit ? "SplitNeedsToPlay" : "NeedsToPlay";
String needsToPlayVarName = isRightSplit ? "SplitNeedsToPlayVar" : "NeedsToPlayVar";
if (sa != null && sa.isEvoke()) {
if (card.hasSVar("NeedsToPlayEvoked")) {
needsToPlayName = "NeedsToPlayEvoked";
}
if (card.hasSVar("NeedsToPlayEvokedVar")) {
needsToPlayVarName = "NeedsToPlayEvokedVar";
// TODO: if there are ever split cards with Evoke or Kicker, factor in the right split option above
if (sa != null) {
if (sa.isEvoke()) {
// if the spell is evoked, will use NeedsToPlayEvoked if available (otherwise falls back to NeedsToPlay)
if (card.hasSVar("NeedsToPlayEvoked")) {
needsToPlayName = "NeedsToPlayEvoked";
}
if (card.hasSVar("NeedsToPlayEvokedVar")) {
needsToPlayVarName = "NeedsToPlayEvokedVar";
}
} else if (sa.isKicked()) {
// if the spell is kicked, uses NeedsToPlayKicked if able and locks out the regular NeedsToPlay check
// for unkicked spells, uses NeedsToPlay
if (card.hasSVar("NeedsToPlayKicked")) {
needsToPlayName = "NeedsToPlayKicked";
} else {
needsToPlayName = "UNUSED";
}
if (card.hasSVar("NeedsToPlayKickedVar")) {
needsToPlayVarName = "NeedsToPlayKickedVar";
} else {
needsToPlayVarName = "UNUSED";
}
}
}
if (card.hasSVar(needsToPlayName)) {
final String needsToPlay = card.getSVar(needsToPlayName);
// A special case which checks that this creature will attack if it's the AI's turn
if (needsToPlay.equalsIgnoreCase("WillAttack")) {
if (game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())) {
return ComputerUtilCard.doesSpecifiedCreatureAttackAI(sa.getActivatingPlayer(), card) ?
AiPlayDecision.WillPlay : AiPlayDecision.BadEtbEffects;
} else {
return AiPlayDecision.WillPlay; // not our turn, skip this check for the possible Flash use etc.
}
}
CardCollectionView list = game.getCardsIn(ZoneType.Battlefield);
list = CardLists.getValidCards(list, needsToPlay.split(","), card.getController(), card, null);

View File

@@ -635,7 +635,7 @@ public class ComputerUtilCost {
&& (!source.getName().equals("Tyrannize") || payer.getCardsIn(ZoneType.Hand).size() > 2)
&& (!source.getName().equals("Perplex") || payer.getCardsIn(ZoneType.Hand).size() < 2)
&& (!source.getName().equals("Breaking Point") || payer.getCreaturesInPlay().size() > 1)
&& (!source.getName().equals("Chain of Vapor") || (ComputerUtil.getOpponentFor(payer).getCreaturesInPlay().size() > 0 && payer.getLandsInPlay().size() > 3));
&& (!source.getName().equals("Chain of Vapor") || (payer.getWeakestOpponent().getCreaturesInPlay().size() > 0 && payer.getLandsInPlay().size() > 3));
}
public static Set<String> getAvailableManaColors(Player ai, Card additionalLand) {

View File

@@ -1105,7 +1105,7 @@ public class PlayerControllerAi extends PlayerController {
public String chooseCardName(SpellAbility sa, Predicate<ICardFace> cpp, String valid, String message) {
if (sa.hasParam("AILogic")) {
CardCollectionView aiLibrary = player.getCardsIn(ZoneType.Library);
CardCollectionView oppLibrary = ComputerUtil.getOpponentFor(player).getCardsIn(ZoneType.Library);
CardCollectionView oppLibrary = player.getWeakestOpponent().getCardsIn(ZoneType.Library);
final Card source = sa.getHostCard();
final String logic = sa.getParam("AILogic");
@@ -1227,6 +1227,18 @@ public class PlayerControllerAi extends PlayerController {
// Choose the optional cost if it can be paid (to be improved later, check for playability and other conditions perhaps)
Cost fullCost = opt.getCost().copy().add(costSoFar);
SpellAbility fullCostSa = chosen.copyWithDefinedCost(fullCost);
// Playability check for Kicker
if (opt.getType() == OptionalCost.Kicker1 || opt.getType() == OptionalCost.Kicker2) {
SpellAbility kickedSaCopy = fullCostSa.copy();
kickedSaCopy.addOptionalCost(opt.getType());
Card copy = CardUtil.getLKICopy(chosen.getHostCard());
copy.addOptionalCostPaid(opt.getType());
if (ComputerUtilCard.checkNeedsToPlayReqs(copy, kickedSaCopy) != AiPlayDecision.WillPlay) {
continue; // don't choose kickers we don't want to play
}
}
if (ComputerUtilCost.canPayCost(fullCostSa, player)) {
chosenOptCosts.add(opt);
costSoFar.add(opt.getCost());

View File

@@ -1,6 +1,5 @@
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
@@ -22,7 +21,7 @@ public class ActivateAbilityAi extends SpellAbilityAi {
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard();
final Player opp = ComputerUtil.getOpponentFor(ai);
final Player opp = ai.getWeakestOpponent();
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
List<Card> list = CardLists.getType(opp.getCardsIn(ZoneType.Battlefield), sa.getParamOrDefault("Type", "Card"));
@@ -46,7 +45,7 @@ public class ActivateAbilityAi extends SpellAbilityAi {
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final Player opp = ComputerUtil.getOpponentFor(ai);
final Player opp = ai.getWeakestOpponent();
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard();
@@ -87,7 +86,7 @@ public class ActivateAbilityAi extends SpellAbilityAi {
}
} else {
sa.resetTargets();
sa.getTargets().add(ComputerUtil.getOpponentFor(ai));
sa.getTargets().add(ai.getWeakestOpponent());
}
return randomReturn;

View File

@@ -78,13 +78,13 @@ public class AnimateAi extends SpellAbilityAi {
num = (num == null) ? "1" : num;
final int nToSac = AbilityUtils.calculateAmount(topStack.getHostCard(), num, topStack);
CardCollection list = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","),
ComputerUtil.getOpponentFor(ai), topStack.getHostCard(), topStack);
ai.getWeakestOpponent(), topStack.getHostCard(), topStack);
list = CardLists.filter(list, CardPredicates.canBeSacrificedBy(topStack));
ComputerUtilCard.sortByEvaluateCreature(list);
if (!list.isEmpty() && list.size() == nToSac && ComputerUtilCost.canPayCost(sa, ai)) {
Card animatedCopy = becomeAnimated(source, sa);
list.add(animatedCopy);
list = CardLists.getValidCards(list, valid.split(","), ComputerUtil.getOpponentFor(ai), topStack.getHostCard(),
list = CardLists.getValidCards(list, valid.split(","), ai.getWeakestOpponent(), topStack.getHostCard(),
topStack);
list = CardLists.filter(list, CardPredicates.canBeSacrificedBy(topStack));
if (ComputerUtilCard.evaluateCreature(animatedCopy) < ComputerUtilCard.evaluateCreature(list.get(0))

View File

@@ -1,6 +1,5 @@
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
@@ -17,7 +16,7 @@ public class BalanceAi extends SpellAbilityAi {
int diff = 0;
// TODO Add support for multiplayer logic
final Player opp = ComputerUtil.getOpponentFor(aiPlayer);
final Player opp = aiPlayer.getWeakestOpponent();
final CardCollectionView humPerms = opp.getCardsIn(ZoneType.Battlefield);
final CardCollectionView compPerms = aiPlayer.getCardsIn(ZoneType.Battlefield);

View File

@@ -2,7 +2,6 @@ package forge.ai.ability;
import java.util.List;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
@@ -25,7 +24,7 @@ public class BidLifeAi extends SpellAbilityAi {
if (tgt != null) {
sa.resetTargets();
if (tgt.canTgtCreature()) {
List<Card> list = CardLists.getTargetableCards(ComputerUtil.getOpponentFor(aiPlayer).getCardsIn(ZoneType.Battlefield), sa);
List<Card> list = CardLists.getTargetableCards(aiPlayer.getWeakestOpponent().getCardsIn(ZoneType.Battlefield), sa);
list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, sa);
if (list.isEmpty()) {
return false;

View File

@@ -229,7 +229,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
final Card source = sa.getHostCard();
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
ZoneType origin = null;
final Player opponent = ComputerUtil.getOpponentFor(ai);
final Player opponent = ai.getWeakestOpponent();
boolean activateForCost = ComputerUtil.activateForCost(sa, ai);
if (sa.hasParam("Origin")) {
@@ -439,7 +439,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
// if putting cards from hand to library and parent is drawing cards
// make sure this will actually do something:
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Player opp = ComputerUtil.getOpponentFor(aiPlayer);
final Player opp = aiPlayer.getWeakestOpponent();
if (tgt != null && tgt.canTgtPlayer()) {
boolean isCurse = sa.isCurse();
if (isCurse && sa.canTarget(opp)) {
@@ -500,7 +500,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
Iterable<Player> pDefined;
final TargetRestrictions tgt = sa.getTargetRestrictions();
if ((tgt != null) && tgt.canTgtPlayer()) {
final Player opp = ComputerUtil.getOpponentFor(ai);
final Player opp = ai.getWeakestOpponent();
if (sa.isCurse()) {
if (sa.canTarget(opp)) {
sa.getTargets().add(opp);
@@ -619,7 +619,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
*/
private static Card chooseCreature(final Player ai, CardCollection list) {
// Creating a new combat for testing purposes.
final Player opponent = ComputerUtil.getOpponentFor(ai);
final Player opponent = ai.getWeakestOpponent();
Combat combat = new Combat(opponent);
for (Card att : opponent.getCreaturesInPlay()) {
combat.addAttacker(att, ai);
@@ -935,7 +935,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
&& !currCombat.getBlockers(attacker).isEmpty()) {
ComputerUtilCard.sortByEvaluateCreature(blockers);
Combat combat = new Combat(ai);
combat.addAttacker(attacker, ComputerUtil.getOpponentFor(ai));
combat.addAttacker(attacker, ai.getWeakestOpponent());
for (Card blocker : blockers) {
combat.addBlocker(attacker, blocker);
}

View File

@@ -8,7 +8,6 @@ import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilCard;
@@ -127,7 +126,7 @@ public class ChooseCardAi extends SpellAbilityAi {
}
} else if (aiLogic.equals("Duneblast")) {
CardCollection aiCreatures = ai.getCreaturesInPlay();
CardCollection oppCreatures = ComputerUtil.getOpponentFor(ai).getCreaturesInPlay();
CardCollection oppCreatures = ai.getWeakestOpponent().getCreaturesInPlay();
aiCreatures = CardLists.getNotKeyword(aiCreatures, Keyword.INDESTRUCTIBLE);
oppCreatures = CardLists.getNotKeyword(oppCreatures, Keyword.INDESTRUCTIBLE);

View File

@@ -41,7 +41,7 @@ public class ChooseCardNameAi extends SpellAbilityAi {
if (tgt != null) {
sa.resetTargets();
if (tgt.canOnlyTgtOpponent()) {
sa.getTargets().add(ComputerUtil.getOpponentFor(ai));
sa.getTargets().add(ai.getWeakestOpponent());
} else {
sa.getTargets().add(ai);
}

View File

@@ -52,7 +52,7 @@ public class ChooseColorAi extends SpellAbilityAi {
}
if ("Addle".equals(sourceName)) {
if (ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS) || ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Hand).isEmpty()) {
if (ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS) || ai.getWeakestOpponent().getCardsIn(ZoneType.Hand).isEmpty()) {
return false;
}
return true;
@@ -61,7 +61,7 @@ public class ChooseColorAi extends SpellAbilityAi {
if (logic.equals("MostExcessOpponentControls")) {
for (byte color : MagicColor.WUBRG) {
CardCollectionView ailist = ai.getCardsIn(ZoneType.Battlefield);
CardCollectionView opplist = ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield);
CardCollectionView opplist = ai.getWeakestOpponent().getCardsIn(ZoneType.Battlefield);
ailist = CardLists.filter(ailist, CardPredicates.isColor(color));
opplist = CardLists.filter(opplist, CardPredicates.isColor(color));

View File

@@ -1,6 +1,5 @@
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
@@ -17,7 +16,7 @@ public class ChooseNumberAi extends SpellAbilityAi {
TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
sa.resetTargets();
Player opp = ComputerUtil.getOpponentFor(aiPlayer);
Player opp = aiPlayer.getWeakestOpponent();
if (sa.canTarget(opp)) {
sa.getTargets().add(opp);
} else {

View File

@@ -7,7 +7,6 @@ import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat;
import forge.ai.ComputerUtilCost;
@@ -68,7 +67,7 @@ public class ChooseSourceAi extends SpellAbilityAi {
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
sa.resetTargets();
Player opp = ComputerUtil.getOpponentFor(ai);
Player opp = ai.getWeakestOpponent();
if (sa.canTarget(opp)) {
sa.getTargets().add(opp);
} else {

View File

@@ -3,7 +3,6 @@ package forge.ai.ability;
import com.google.common.base.Predicate;
import com.google.common.collect.Lists;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
@@ -30,7 +29,7 @@ public class ControlExchangeAi extends SpellAbilityAi {
sa.resetTargets();
CardCollection list =
CardLists.getValidCards(ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa);
CardLists.getValidCards(ai.getWeakestOpponent().getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa);
// AI won't try to grab cards that are filtered out of AI decks on
// purpose
list = CardLists.filter(list, new Predicate<Card>() {

View File

@@ -2,7 +2,6 @@ package forge.ai.ability;
import com.google.common.base.Predicate;
import com.google.common.collect.Lists;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCost;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
@@ -39,7 +38,7 @@ public class CountersPutAllAi extends SpellAbilityAi {
final boolean curse = sa.isCurse();
final TargetRestrictions tgt = sa.getTargetRestrictions();
hList = CardLists.getValidCards(ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield), valid, source.getController(), source);
hList = CardLists.getValidCards(ai.getWeakestOpponent().getCardsIn(ZoneType.Battlefield), valid, source.getController(), source);
cList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid, source.getController(), source);
if (abCost != null) {
@@ -68,7 +67,7 @@ public class CountersPutAllAi extends SpellAbilityAi {
}
if (tgt != null) {
Player pl = curse ? ComputerUtil.getOpponentFor(ai) : ai;
Player pl = curse ? ai.getWeakestOpponent() : ai;
sa.getTargets().add(pl);
hList = CardLists.filterControlledBy(hList, pl);
@@ -149,7 +148,7 @@ public class CountersPutAllAi extends SpellAbilityAi {
*/
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
return player.getCreaturesInPlay().size() >= ComputerUtil.getOpponentFor(player).getCreaturesInPlay().size();
return player.getCreaturesInPlay().size() >= player.getWeakestOpponent().getCreaturesInPlay().size();
}
@Override

View File

@@ -1,7 +1,6 @@
package forge.ai.ability;
import com.google.common.collect.Iterables;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCombat;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
@@ -20,7 +19,7 @@ import forge.util.MyRandom;
public abstract class DamageAiBase extends SpellAbilityAi {
protected boolean avoidTargetP(final Player comp, final SpellAbility sa) {
Player enemy = ComputerUtil.getOpponentFor(comp);
Player enemy = comp.getWeakestOpponent();
// Logic for cards that damage owner, like Fireslinger
// Do not target a player if they aren't below 75% of our health.
// Unless Lifelink will cancel the damage to us
@@ -54,7 +53,7 @@ public abstract class DamageAiBase extends SpellAbilityAi {
protected boolean shouldTgtP(final Player comp, final SpellAbility sa, final int d, final boolean noPrevention, final boolean noPlaneswalkerRedirection) {
int restDamage = d;
final Game game = comp.getGame();
Player enemy = ComputerUtil.getOpponentFor(comp);
Player enemy = comp.getWeakestOpponent();
boolean dmgByCardsInHand = false;
if ("X".equals(sa.getParam("NumDmg")) && sa.getHostCard() != null && sa.hasSVar(sa.getParam("NumDmg")) &&

View File

@@ -212,7 +212,7 @@ public class DamageAllAi extends SpellAbilityAi {
}
// Evaluate creatures getting killed
Player enemy = ComputerUtil.getOpponentFor(ai);
Player enemy = ai.getWeakestOpponent();
final CardCollection humanList = getKillableCreatures(sa, enemy, dmg);
CardCollection computerList = getKillableCreatures(sa, ai, dmg);
final TargetRestrictions tgt = sa.getTargetRestrictions();
@@ -294,7 +294,7 @@ public class DamageAllAi extends SpellAbilityAi {
}
// Evaluate creatures getting killed
Player enemy = ComputerUtil.getOpponentFor(ai);
Player enemy = ai.getWeakestOpponent();
final CardCollection humanList = getKillableCreatures(sa, enemy, dmg);
CardCollection computerList = getKillableCreatures(sa, ai, dmg);
final TargetRestrictions tgt = sa.getTargetRestrictions();

View File

@@ -552,7 +552,7 @@ public class DamageDealAi extends DamageAiBase {
final boolean oppTargetsChoice = sa.hasParam("TargetingPlayer");
final String logic = sa.getParamOrDefault("AILogic", "");
Player enemy = ComputerUtil.getOpponentFor(ai);
Player enemy = ai.getWeakestOpponent();
if ("PowerDmg".equals(logic)) {
// check if it is better to target the player instead, the original target is already set in PumpAi.pumpTgtAI()
@@ -876,7 +876,7 @@ public class DamageDealAi extends DamageAiBase {
// this is for Triggered targets that are mandatory
final boolean noPrevention = sa.hasParam("NoPrevention");
final boolean divided = sa.hasParam("DividedAsYouChoose");
final Player opp = ComputerUtil.getOpponentFor(ai);
final Player opp = ai.getWeakestOpponent();
while (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
if (tgt.canTgtPlaneswalker()) {

View File

@@ -4,7 +4,6 @@ import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCost;
import forge.ai.SpellAbilityAi;
@@ -177,7 +176,7 @@ public class DebuffAi extends SpellAbilityAi {
* @return a CardCollection.
*/
private CardCollection getCurseCreatures(final Player ai, final SpellAbility sa, final List<String> kws) {
final Player opp = ComputerUtil.getOpponentFor(ai);
final Player opp = ai.getWeakestOpponent();
CardCollection list = CardLists.getTargetableCards(opp.getCreaturesInPlay(), sa);
if (!list.isEmpty()) {
list = CardLists.filter(list, new Predicate<Card>() {
@@ -217,7 +216,7 @@ public class DebuffAi extends SpellAbilityAi {
list.remove(c);
}
final CardCollection pref = CardLists.filterControlledBy(list, ComputerUtil.getOpponentFor(ai));
final CardCollection pref = CardLists.filterControlledBy(list, ai.getWeakestOpponent());
final CardCollection forced = CardLists.filterControlledBy(list, ai);
final Card source = sa.getHostCard();

View File

@@ -288,7 +288,7 @@ public class DestroyAi extends SpellAbilityAi {
} else if (sa.hasParam("Defined")) {
list = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
if ("WillSkipTurn".equals(logic) && (sa.getHostCard().getController().equals(ai)
|| ai.getCreaturesInPlay().size() < ComputerUtil.getOpponentFor(ai).getCreaturesInPlay().size()
|| ai.getCreaturesInPlay().size() < ai.getWeakestOpponent().getCreaturesInPlay().size()
|| !source.getGame().getPhaseHandler().isPlayerTurn(ai)
|| ai.getLife() <= 5)) {
// Basic ai logic for Lethal Vapors

View File

@@ -66,7 +66,7 @@ public class DestroyAllAi extends SpellAbilityAi {
public boolean doMassRemovalLogic(Player ai, SpellAbility sa) {
final Card source = sa.getHostCard();
final String logic = sa.getParamOrDefault("AILogic", "");
Player opponent = ComputerUtil.getOpponentFor(ai); // TODO: how should this AI logic work for multiplayer and getOpponents()?
Player opponent = ai.getWeakestOpponent(); // TODO: how should this AI logic work for multiplayer and getOpponents()?
final int CREATURE_EVAL_THRESHOLD = 200;

View File

@@ -24,7 +24,7 @@ public class DigAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Game game = ai.getGame();
Player opp = ComputerUtil.getOpponentFor(ai);
Player opp = ai.getWeakestOpponent();
final Card host = sa.getHostCard();
Player libraryOwner = ai;
@@ -106,7 +106,7 @@ public class DigAi extends SpellAbilityAi {
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final Player opp = ComputerUtil.getOpponentFor(ai);
final Player opp = ai.getWeakestOpponent();
if (sa.usesTargeting()) {
sa.resetTargets();
if (mandatory && sa.canTarget(opp)) {

View File

@@ -1,6 +1,5 @@
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
@@ -36,7 +35,7 @@ public class DigUntilAi extends SpellAbilityAi {
final boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(chance, sa.getActivationsThisTurn() + 1);
Player libraryOwner = ai;
Player opp = ComputerUtil.getOpponentFor(ai);
Player opp = ai.getWeakestOpponent();
if ("DontMillSelf".equals(logic)) {
// A card that digs for specific things and puts everything revealed before it into graveyard

View File

@@ -56,7 +56,7 @@ public class DiscardAi extends SpellAbilityAi {
return SpecialCardAi.VolrathsShapeshifter.consider(ai, sa);
}
final boolean humanHasHand = ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Hand).size() > 0;
final boolean humanHasHand = ai.getWeakestOpponent().getCardsIn(ZoneType.Hand).size() > 0;
if (tgt != null) {
if (!discardTargetAI(ai, sa)) {
@@ -87,7 +87,7 @@ public class DiscardAi extends SpellAbilityAi {
if (sa.hasParam("NumCards")) {
if (sa.getParam("NumCards").equals("X") && source.getSVar("X").equals("Count$xPaid")) {
// Set PayX here to maximum value.
final int cardsToDiscard = Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), ComputerUtil.getOpponentFor(ai)
final int cardsToDiscard = Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), ai.getWeakestOpponent()
.getCardsIn(ZoneType.Hand).size());
if (cardsToDiscard < 1) {
return false;
@@ -172,7 +172,7 @@ public class DiscardAi extends SpellAbilityAi {
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
Player opp = ComputerUtil.getOpponentFor(ai);
Player opp = ai.getWeakestOpponent();
if (!discardTargetAI(ai, sa)) {
if (mandatory && sa.canTarget(opp)) {
sa.getTargets().add(opp);
@@ -193,7 +193,7 @@ public class DiscardAi extends SpellAbilityAi {
}
if ("X".equals(sa.getParam("RevealNumber")) && sa.getHostCard().getSVar("X").equals("Count$xPaid")) {
// Set PayX here to maximum value.
final int cardsToDiscard = Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), ComputerUtil.getOpponentFor(ai)
final int cardsToDiscard = Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), ai.getWeakestOpponent()
.getCardsIn(ZoneType.Hand).size());
sa.getHostCard().setSVar("PayX", Integer.toString(cardsToDiscard));
}

View File

@@ -1,6 +1,5 @@
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
@@ -19,7 +18,7 @@ public class DrainManaAi extends SpellAbilityAi {
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard();
final Player opp = ComputerUtil.getOpponentFor(ai);
final Player opp = ai.getWeakestOpponent();
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
if (tgt == null) {
@@ -41,7 +40,7 @@ public class DrainManaAi extends SpellAbilityAi {
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final Player opp = ComputerUtil.getOpponentFor(ai);
final Player opp = ai.getWeakestOpponent();
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard();
@@ -82,7 +81,7 @@ public class DrainManaAi extends SpellAbilityAi {
}
} else {
sa.resetTargets();
sa.getTargets().add(ComputerUtil.getOpponentFor(ai));
sa.getTargets().add(ai.getWeakestOpponent());
}
return randomReturn;

View File

@@ -107,7 +107,7 @@ public class FogAi extends SpellAbilityAi {
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
final Game game = aiPlayer.getGame();
boolean chance;
if (game.getPhaseHandler().isPlayerTurn(ComputerUtil.getOpponentFor(sa.getActivatingPlayer()))) {
if (game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer().getWeakestOpponent())) {
chance = game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_FIRST_STRIKE_DAMAGE);
} else {
chance = game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DAMAGE);

View File

@@ -1,6 +1,5 @@
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
@@ -9,7 +8,7 @@ import forge.game.spellability.TargetRestrictions;
public class GameLossAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Player opp = ComputerUtil.getOpponentFor(ai);
final Player opp = ai.getWeakestOpponent();
if (opp.cantLose()) {
return false;
}
@@ -34,15 +33,16 @@ public class GameLossAi extends SpellAbilityAi {
// Phage the Untouchable
// (Final Fortune would need to attach it's delayed trigger to a
// specific turn, which can't be done yet)
Player opp = ai.getWeakestOpponent();
if (!mandatory && ComputerUtil.getOpponentFor(ai).cantLose()) {
if (!mandatory && opp.cantLose()) {
return false;
}
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
sa.resetTargets();
sa.getTargets().add(ComputerUtil.getOpponentFor(ai));
sa.getTargets().add(opp);
}
return true;

View File

@@ -1,6 +1,5 @@
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
@@ -20,7 +19,7 @@ public class LifeExchangeAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
final int myLife = aiPlayer.getLife();
Player opponent = ComputerUtil.getOpponentFor(aiPlayer);
Player opponent = aiPlayer.getWeakestOpponent();
final int hLife = opponent.getLife();
if (!aiPlayer.canGainLife()) {
@@ -76,7 +75,7 @@ public class LifeExchangeAi extends SpellAbilityAi {
final boolean mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
Player opp = ComputerUtil.getOpponentFor(ai);
Player opp = ai.getWeakestOpponent();
if (tgt != null) {
sa.resetTargets();
if (sa.canTarget(opp) && (mandatory || ai.getLife() < opp.getLife())) {

View File

@@ -149,7 +149,7 @@ public class LifeExchangeVariantAi extends SpellAbilityAi {
final boolean mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
Player opp = ComputerUtil.getOpponentFor(ai);
Player opp = ai.getWeakestOpponent();
if (tgt != null) {
sa.resetTargets();
if (sa.canTarget(opp) && (mandatory || ai.getLife() < opp.getLife())) {

View File

@@ -1,6 +1,5 @@
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
@@ -20,7 +19,7 @@ public class LifeSetAi extends SpellAbilityAi {
// Ability_Cost abCost = sa.getPayCosts();
final Card source = sa.getHostCard();
final int myLife = ai.getLife();
final Player opponent = ComputerUtil.getOpponentFor(ai);
final Player opponent = ai.getWeakestOpponent();
final int hlife = opponent.getLife();
final String amountStr = sa.getParam("LifeAmount");
@@ -107,7 +106,7 @@ public class LifeSetAi extends SpellAbilityAi {
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final int myLife = ai.getLife();
final Player opponent = ComputerUtil.getOpponentFor(ai);
final Player opponent = ai.getWeakestOpponent();
final int hlife = opponent.getLife();
final Card source = sa.getHostCard();
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);

View File

@@ -95,7 +95,7 @@ public class MustBlockAi extends SpellAbilityAi {
boolean chance = false;
if (abTgt != null) {
final List<Card> list = determineGoodBlockers(definedAttacker, ai, ComputerUtil.getOpponentFor(ai), sa, true,true);
final List<Card> list = determineGoodBlockers(definedAttacker, ai, ai.getWeakestOpponent(), sa, true,true);
if (list.isEmpty()) {
return false;
}

View File

@@ -2,7 +2,6 @@ package forge.ai.ability;
import com.google.common.base.Predicate;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
@@ -31,7 +30,7 @@ public class PowerExchangeAi extends SpellAbilityAi {
sa.resetTargets();
List<Card> list =
CardLists.getValidCards(ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa);
CardLists.getValidCards(ai.getWeakestOpponent().getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa);
// AI won't try to grab cards that are filtered out of AI decks on
// purpose
list = CardLists.filter(list, new Predicate<Card>() {

View File

@@ -146,7 +146,7 @@ public class ProtectAi extends SpellAbilityAi {
if (s==null) {
return false;
} else {
Player opponent = ComputerUtil.getOpponentFor(ai);
Player opponent = ai.getWeakestOpponent();
Combat combat = ai.getGame().getCombat();
int dmg = ComputerUtilCombat.damageIfUnblocked(c, opponent, combat, true);
float ratio = 1.0f * dmg / opponent.getLife();

View File

@@ -177,7 +177,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
final Game game = ai.getGame();
final Combat combat = game.getCombat();
final PhaseHandler ph = game.getPhaseHandler();
final Player opp = ComputerUtil.getOpponentFor(ai);
final Player opp = ai.getWeakestOpponent();
final int newPower = card.getNetCombatDamage() + attack;
//int defense = getNumDefense(sa);
if (!CardUtil.isStackingKeyword(keyword) && card.hasKeyword(keyword)) {

View File

@@ -68,7 +68,7 @@ public class PumpAllAi extends PumpAiBase {
valid = sa.getParam("ValidCards");
}
final Player opp = ComputerUtil.getOpponentFor(ai);
final Player opp = ai.getWeakestOpponent();
CardCollection comp = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid, source.getController(), source);
CardCollection human = CardLists.getValidCards(opp.getCardsIn(ZoneType.Battlefield), valid, source.getController(), source);

View File

@@ -46,10 +46,10 @@ public class RearrangeTopOfLibraryAi extends SpellAbilityAi {
// ability is targeted
sa.resetTargets();
Player opp = ComputerUtil.getOpponentFor(aiPlayer);
Player opp = aiPlayer.getWeakestOpponent();
final boolean canTgtAI = sa.canTarget(aiPlayer);
final boolean canTgtHuman = sa.canTarget(opp);
if (canTgtHuman && canTgtAI) {
// TODO: maybe some other consideration rather than random?
Player preferredTarget = MyRandom.percentTrue(50) ? aiPlayer : opp;

View File

@@ -15,7 +15,7 @@ public class RepeatAi extends SpellAbilityAi {
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Card source = sa.getHostCard();
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Player opp = ComputerUtil.getOpponentFor(ai);
final Player opp = ai.getWeakestOpponent();
if (tgt != null) {
if (!opp.canBeTargetedBy(sa)) {
@@ -49,7 +49,7 @@ public class RepeatAi extends SpellAbilityAi {
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
if (sa.usesTargeting()) {
final Player opp = ComputerUtil.getOpponentFor(ai);
final Player opp = ai.getWeakestOpponent();
if (sa.canTarget(opp)) {
sa.resetTargets();
sa.getTargets().add(opp);

View File

@@ -1,6 +1,5 @@
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
@@ -63,7 +62,7 @@ public class SacrificeAi extends SpellAbilityAi {
final TargetRestrictions tgt = sa.getTargetRestrictions();
final boolean destroy = sa.hasParam("Destroy");
Player opp = ComputerUtil.getOpponentFor(ai);
Player opp = ai.getWeakestOpponent();
if (tgt != null) {
sa.resetTargets();
if (!opp.canBeTargetedBy(sa)) {

View File

@@ -1,6 +1,5 @@
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCost;
import forge.ai.ComputerUtilMana;
@@ -37,7 +36,7 @@ public class SacrificeAllAi extends SpellAbilityAi {
}
CardCollection humanlist =
CardLists.getValidCards(ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield), valid.split(","), source.getController(), source, sa);
CardLists.getValidCards(ai.getWeakestOpponent().getCardsIn(ZoneType.Battlefield), valid.split(","), source.getController(), source, sa);
CardCollection computerlist =
CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","), source.getController(), source, sa);

View File

@@ -112,7 +112,7 @@ public abstract class TapAiBase extends SpellAbilityAi {
* @return a boolean.
*/
protected boolean tapPrefTargeting(final Player ai, final Card source, final TargetRestrictions tgt, final SpellAbility sa, final boolean mandatory) {
final Player opp = ComputerUtil.getOpponentFor(ai);
final Player opp = ai.getWeakestOpponent();
final Game game = ai.getGame();
CardCollection tapList = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents());
tapList = CardLists.getValidCards(tapList, tgt.getValidTgts(), source.getController(), source, sa);

View File

@@ -3,7 +3,6 @@ package forge.ai.ability;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCombat;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
@@ -30,7 +29,7 @@ public class TapAllAi extends SpellAbilityAi {
// or during upkeep/begin combat?
final Card source = sa.getHostCard();
final Player opp = ComputerUtil.getOpponentFor(ai);
final Player opp = ai.getWeakestOpponent();
final Game game = ai.getGame();
if (game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_BEGIN)) {
@@ -125,8 +124,9 @@ public class TapAllAi extends SpellAbilityAi {
if (tgt != null) {
sa.resetTargets();
sa.getTargets().add(ComputerUtil.getOpponentFor(ai));
validTappables = ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield);
Player opp = ai.getWeakestOpponent();
sa.getTargets().add(opp);
validTappables = opp.getCardsIn(ZoneType.Battlefield);
}
if (mandatory) {

View File

@@ -167,7 +167,7 @@ public class TokenAi extends SpellAbilityAi {
*/
final Card source = sa.getHostCard();
final Game game = ai.getGame();
final Player opp = ComputerUtil.getOpponentFor(ai);
final Player opp = ai.getWeakestOpponent();
if (ComputerUtil.preventRunAwayActivations(sa)) {
return false; // prevent infinite tokens?
@@ -261,13 +261,13 @@ public class TokenAi extends SpellAbilityAi {
num = (num == null) ? "1" : num;
final int nToSac = AbilityUtils.calculateAmount(topStack.getHostCard(), num, topStack);
CardCollection list = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","),
ComputerUtil.getOpponentFor(ai), topStack.getHostCard(), sa);
ai.getWeakestOpponent(), topStack.getHostCard(), sa);
list = CardLists.filter(list, CardPredicates.canBeSacrificedBy(topStack));
// only care about saving single creature for now
if (!list.isEmpty() && nTokens > 0 && list.size() == nToSac) {
ComputerUtilCard.sortByEvaluateCreature(list);
list.add(token);
list = CardLists.getValidCards(list, valid.split(","), ComputerUtil.getOpponentFor(ai), topStack.getHostCard(), sa);
list = CardLists.getValidCards(list, valid.split(","), ai.getWeakestOpponent(), topStack.getHostCard(), sa);
list = CardLists.filter(list, CardPredicates.canBeSacrificedBy(topStack));
if (ComputerUtilCard.evaluateCreature(token) < ComputerUtilCard.evaluateCreature(list.get(0))
&& list.contains(token)) {
@@ -285,7 +285,7 @@ public class TokenAi extends SpellAbilityAi {
if (tgt != null) {
sa.resetTargets();
if (tgt.canOnlyTgtOpponent()) {
sa.getTargets().add(ComputerUtil.getOpponentFor(ai));
sa.getTargets().add(ai.getWeakestOpponent());
} else {
sa.getTargets().add(ai);
}

View File

@@ -2,7 +2,6 @@ package forge.ai.ability;
import java.util.List;
import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
@@ -29,7 +28,7 @@ public class TwoPilesAi extends SpellAbilityAi {
valid = sa.getParam("ValidCards");
}
final Player opp = ComputerUtil.getOpponentFor(ai);
final Player opp = ai.getWeakestOpponent();
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {

View File

@@ -1,6 +1,5 @@
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
@@ -65,7 +64,7 @@ public class UnattachAllAi extends SpellAbilityAi {
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final Card card = sa.getHostCard();
final Player opp = ComputerUtil.getOpponentFor(ai);
final Player opp = ai.getWeakestOpponent();
// Check if there are any valid targets
List<GameObject> targets = new ArrayList<GameObject>();
final TargetRestrictions tgt = sa.getTargetRestrictions();

View File

@@ -139,7 +139,7 @@ public class UntapAi extends SpellAbilityAi {
Player targetController = ai;
if (sa.isCurse()) {
targetController = ComputerUtil.getOpponentFor(ai);
targetController = ai.getWeakestOpponent();
}
CardCollection list = CardLists.getTargetableCards(targetController.getCardsIn(ZoneType.Battlefield), sa);

View File

@@ -1,24 +1,13 @@
package forge.ai.simulation;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import forge.LobbyPlayer;
import forge.ai.LobbyPlayerAi;
import forge.card.CardStateName;
import forge.game.Game;
import forge.game.GameEntity;
import forge.game.GameObject;
import forge.game.GameObjectMap;
import forge.game.GameRules;
import forge.game.Match;
import forge.game.StaticEffect;
import forge.game.*;
import forge.game.card.*;
import forge.game.card.token.TokenInfo;
import forge.game.combat.Combat;
@@ -28,13 +17,16 @@ import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.RegisteredPlayer;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityRestriction;
import forge.game.spellability.SpellAbilityStackInstance;
import forge.game.staticability.StaticAbility;
import forge.game.trigger.TriggerType;
import forge.game.zone.PlayerZoneBattlefield;
import forge.game.zone.ZoneType;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class GameCopier {
private static final ZoneType[] ZONES = new ZoneType[] {
ZoneType.Battlefield,
@@ -323,7 +315,6 @@ public class GameCopier {
}
if (c.isPlaneswalker()) {
for (SpellAbility sa : c.getAllSpellAbilities()) {
SpellAbilityRestriction restrict = sa.getRestrictions();
int active = sa.getActivationsThisTurn();
if (sa.isPwAbility() && active > 0) {
SpellAbility newSa = findSAInCard(sa, newCard);

View File

@@ -207,7 +207,7 @@ public class GameSimulator {
}
// TODO: Support multiple opponents.
Player opponent = ComputerUtil.getOpponentFor(aiPlayer);
Player opponent = aiPlayer.getWeakestOpponent();
resolveStack(simGame, opponent);
// TODO: If this is during combat, before blockers are declared,

View File

@@ -6,7 +6,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.27-SNAPSHOT</version>
</parent>
<artifactId>forge-core</artifactId>

View File

@@ -7,8 +7,6 @@ import forge.card.CardRules;
import forge.card.CardSplitType;
import forge.item.PaperCard;
import org.apache.commons.lang3.StringUtils;
public class ImageUtil {
public static float getNearestHQSize(float baseSize, float actualSize) {
//get nearest power of actualSize to baseSize so that the image renders good
@@ -46,9 +44,7 @@ public class ImageUtil {
cntPictures = db.getPrintCount(card.getName(), edition);
hasManyPictures = cntPictures > 1;
} else {
// without set number of pictures equals number of urls provided in Svar:Picture
String urls = card.getPictureUrl(backFace);
cntPictures = StringUtils.countMatches(urls, "\\") + 1;
cntPictures = 1;
// raise the art index limit to the maximum of the sets this card was printed in
int maxCntPictures = db.getMaxPrintCount(card.getName());

View File

@@ -6,7 +6,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.27-SNAPSHOT</version>
</parent>
<artifactId>forge-game</artifactId>

View File

@@ -17,18 +17,7 @@
*/
package forge.game.card;
import java.util.List;
import java.util.Set;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.collect.Table;
import io.sentry.Sentry;
import io.sentry.event.BreadcrumbBuilder;
import com.google.common.collect.*;
import forge.ImageKeys;
import forge.card.CardStateName;
import forge.card.CardType;
@@ -40,14 +29,16 @@ import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.player.Player;
import forge.game.replacement.ReplacementEffect;
import forge.game.spellability.AbilityManaPart;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.spellability.*;
import forge.game.trigger.Trigger;
import forge.game.zone.ZoneType;
import forge.util.TextUtil;
import forge.util.collect.FCollection;
import io.sentry.Sentry;
import io.sentry.event.BreadcrumbBuilder;
import java.util.List;
import java.util.Set;
public final class CardUtil {
// disable instantiation
@@ -308,6 +299,15 @@ public final class CardUtil {
newCopy.updateKeywordsCache(newCopy.getState(s));
}
newCopy.setKickerMagnitude(in.getKickerMagnitude());
for (OptionalCost ocost : in.getOptionalCostsPaid()) {
newCopy.addOptionalCostPaid(ocost);
}
newCopy.setCastSA(in.getCastSA());
newCopy.setCastFrom(in.getCastFrom());
return newCopy;
}

View File

@@ -6,7 +6,7 @@
<packaging.type>jar</packaging.type>
<build.min.memory>-Xms1024m</build.min.memory>
<build.max.memory>-Xmx1536m</build.max.memory>
<alpha-version>1.6.25.001</alpha-version>
<alpha-version>1.6.26.001</alpha-version>
<sign.keystore>keystore</sign.keystore>
<sign.alias>alias</sign.alias>
<sign.storepass>storepass</sign.storepass>
@@ -19,7 +19,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.27-SNAPSHOT</version>
</parent>
<artifactId>forge-gui-android</artifactId>

View File

@@ -4,7 +4,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.27-SNAPSHOT</version>
</parent>
<artifactId>forge-gui-desktop</artifactId>

View File

@@ -386,7 +386,7 @@ public enum FControl implements KeyEventDispatcher {
return true;
}
}
else if (e.getID() == KeyEvent.KEY_PRESSED && e.getModifiers() == InputEvent.ALT_MASK) {
else if (e.getID() == KeyEvent.KEY_PRESSED && e.getModifiersEx() == InputEvent.ALT_DOWN_MASK) {
altKeyLastDown = true;
}
}

View File

@@ -20,7 +20,6 @@ package forge.gui;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import forge.card.CardEdition;
import forge.card.CardRules;
import forge.item.IPaperCard;
import forge.item.PaperCard;
import forge.model.FModel;
@@ -295,30 +294,10 @@ public class ImportSourceAnalyzer {
// character mangling on some system locales, but we want to replicate the old code here exactly
return out.toString().toLowerCase();
}
@Deprecated
private void addDefaultPicNames(final PaperCard c, final boolean backFace) {
final CardRules card = c.getRules();
final String urls = card.getPictureUrl(backFace);
if (StringUtils.isEmpty(urls)) { return; }
final int numPics = 1 + StringUtils.countMatches(urls, "\\");
if (c.getArtIndex() > numPics) {
return;
}
final String filenameBase = ImageUtil.getImageKey(c, backFace, false);
final String filename = filenameBase + ".jpg";
final boolean alreadyHadIt = null != defaultPicNames.put(filename, filename);
if ( alreadyHadIt ) {
return;
}
// Do you shift artIndex by one here?
final String newLastSymbol = 0 == c.getArtIndex() ? "" : String.valueOf(c.getArtIndex() /* + 1 */);
final String oldFilename = oldCleanString(filenameBase.replaceAll("[0-9]?(\\.full)?$", "")) + newLastSymbol + ".jpg";
//if ( numPics > 1 )
//System.out.printf("Will move %s -> %s%n", oldFilename, filename);
defaultPicOldNameToCurrentName.put(oldFilename, filename);
return;
}

View File

@@ -79,7 +79,7 @@ public class TextSearchFilter<T extends InventoryItem> extends ItemFilter<T> {
itemManager.focus();
break;
case KeyEvent.VK_ENTER:
if (e.getModifiers() == 0) {
if (e.getModifiersEx() == 0) {
if (changeTimer.isRunning()) {
applyChange(); //apply change now if currently delayed
}

View File

@@ -327,7 +327,7 @@ public abstract class ItemView<T extends InventoryItem> {
private boolean popupShowing = false;
private Popup popup;
private Timer popupTimer;
private static final int okModifiers = InputEvent.SHIFT_MASK | InputEvent.ALT_GRAPH_MASK;
private static final int okModifiers = InputEvent.SHIFT_DOWN_MASK | InputEvent.ALT_GRAPH_DOWN_MASK;
public IncrementalSearch() {
}
@@ -481,7 +481,7 @@ public abstract class ItemView<T extends InventoryItem> {
//$FALL-THROUGH$
default:
// shift and/or alt-graph down is ok. anything else is a hotkey (e.g. ctrl-f)
if (okModifiers != (e.getModifiers() | okModifiers)
if (okModifiers != (e.getModifiersEx() | okModifiers)
|| !CharUtils.isAsciiPrintable(e.getKeyChar())) { // escape sneaks in here on Windows
return;
}

View File

@@ -585,7 +585,7 @@ public abstract class ACEditorBase<TItem extends InventoryItem, TModel extends D
}
public void addMoveItems(final String verb, final String dest) {
addItems(verb, dest, false, 0, InputEvent.SHIFT_DOWN_MASK, InputEvent.ALT_MASK);
addItems(verb, dest, false, 0, InputEvent.SHIFT_DOWN_MASK, InputEvent.ALT_DOWN_MASK);
}
public void addMoveAlternateItems(final String verb, final String dest) {
@@ -596,7 +596,7 @@ public abstract class ACEditorBase<TItem extends InventoryItem, TModel extends D
addItems(verb, dest, true, InputEvent.CTRL_DOWN_MASK,
//getMenuShortcutKeyMask() instead of CTRL_DOWN_MASK since on OSX, ctrl-shift-space brings up the window manager
InputEvent.SHIFT_DOWN_MASK | Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(),
InputEvent.ALT_MASK | Toolkit.getDefaultToolkit().getMenuShortcutKeyMask());
InputEvent.ALT_DOWN_MASK | Toolkit.getDefaultToolkit().getMenuShortcutKeyMask());
}
}
}

View File

@@ -42,7 +42,7 @@ public enum VCardScript implements IVDoc<CCardScript> {
txtScript.setDocument(doc);
error = doc.addStyle("error", null);
error.addAttribute(StyleConstants.Background, Color.red);
error.addAttribute(StyleConstants.Bold, new Boolean(true));
error.addAttribute(StyleConstants.Bold, Boolean.valueOf(true));
}
public JTextPane getTxtScript() {

View File

@@ -200,8 +200,8 @@ public class FUndoManager extends UndoManager implements DocumentListener {
public UndoAction() {
putValue(Action.NAME, "Undo");
putValue(Action.SHORT_DESCRIPTION, getValue(Action.NAME));
putValue(Action.MNEMONIC_KEY, new Integer(KeyEvent.VK_U));
putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_Z, InputEvent.CTRL_MASK));
putValue(Action.MNEMONIC_KEY, Integer.valueOf(KeyEvent.VK_U));
putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_Z, InputEvent.CTRL_DOWN_MASK));
setEnabled(false);
}
@@ -229,8 +229,8 @@ public class FUndoManager extends UndoManager implements DocumentListener {
public RedoAction() {
putValue(Action.NAME, "Redo");
putValue(Action.SHORT_DESCRIPTION, getValue(Action.NAME));
putValue(Action.MNEMONIC_KEY, new Integer(KeyEvent.VK_R));
putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_Y, InputEvent.CTRL_MASK));
putValue(Action.MNEMONIC_KEY, Integer.valueOf(KeyEvent.VK_R));
putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_Y, InputEvent.CTRL_DOWN_MASK));
setEnabled(false);
}

View File

@@ -1849,7 +1849,7 @@ public class GameSimulatorTest extends SimulationTestCase {
Card simSpark = (Card)sim.getGameCopier().find(sparkDouble);
assertTrue(simSpark != null);
assertNotNull(simSpark);
assertTrue(simSpark.getZone().is(ZoneType.Battlefield));
assertTrue(simSpark.getCounters(CounterType.P1P1) == 1);
assertTrue(simSpark.getCounters(CounterType.LOYALTY) == 5);
@@ -1882,13 +1882,59 @@ public class GameSimulatorTest extends SimulationTestCase {
Card awakened = findCardWithName(sim.getSimulatedGameState(), "Vitu-Ghazi");
assertTrue(awakened != null);
assertNotNull(awakened);
assertTrue(awakened.getName().equals("Vitu-Ghazi"));
assertTrue(awakened.getCounters(CounterType.P1P1) == 9);
assertTrue(awakened.hasKeyword(Keyword.HASTE));
assertTrue(awakened.getType().hasSubtype("Goblin"));
}
public void testNecroticOozeActivateOnce() {
Game game = initAndCreateGame();
Player p = game.getPlayers().get(0);
game.getPhaseHandler().devModeSet(PhaseType.MAIN1, p);
for (int i=0; i<7; i++) { addCardToZone("Swamp", p, ZoneType.Battlefield); }
for (int i=0; i<7; i++) { addCardToZone("Forest", p, ZoneType.Battlefield); }
addCardToZone("Basking Rootwalla", p, ZoneType.Graveyard);
Card ooze = addCardToZone("Necrotic Ooze", p, ZoneType.Hand);
SpellAbility oozeSA = ooze.getFirstSpellAbility();
GameSimulator sim = createSimulator(game, p);
sim.simulateSpellAbility(oozeSA);
Card oozeOTB = findCardWithName(sim.getSimulatedGameState(), "Necrotic Ooze");
assertNotNull(oozeOTB);
SpellAbility copiedSA = findSAWithPrefix(oozeOTB, "{1}{G}:");
assertNotNull(copiedSA);
assertTrue(copiedSA.getRestrictions().getLimitToCheck().equals("1"));
}
public void testEpochrasite() {
Game game = initAndCreateGame();
Player p = game.getPlayers().get(0);
game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p);
for (int i=0; i<7; i++) { addCardToZone("Swamp", p, ZoneType.Battlefield); }
Card epo = addCardToZone("Epochrasite", p, ZoneType.Graveyard);
Card animate = addCardToZone("Animate Dead", p, ZoneType.Hand);
SpellAbility saAnimate = animate.getFirstSpellAbility();
saAnimate.getTargets().add(epo);
GameSimulator sim = createSimulator(game, p);
sim.simulateSpellAbility(saAnimate);
Card epoOTB = findCardWithName(sim.getSimulatedGameState(), "Epochrasite");
assertNotNull(epoOTB);
assertTrue(epoOTB.getCounters(CounterType.P1P1) == 3);
}
@SuppressWarnings("unused")
public void broken_testCloneDimir() {
Game game = initAndCreateGame();

View File

@@ -6,13 +6,13 @@
<packaging.type>jar</packaging.type>
<build.min.memory>-Xms128m</build.min.memory>
<build.max.memory>-Xmx2048m</build.max.memory>
<alpha-version>1.6.25.001</alpha-version>
<alpha-version>1.6.26.001</alpha-version>
</properties>
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.27-SNAPSHOT</version>
</parent>
<artifactId>forge-gui-ios</artifactId>

View File

@@ -4,7 +4,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.27-SNAPSHOT</version>
</parent>
<artifactId>forge-gui-mobile-dev</artifactId>

View File

@@ -4,7 +4,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.27-SNAPSHOT</version>
</parent>
<artifactId>forge-gui-mobile</artifactId>

View File

@@ -34,7 +34,7 @@ import java.util.List;
import java.util.Stack;
public class Forge implements ApplicationListener {
public static final String CURRENT_VERSION = "1.6.25.001";
public static final String CURRENT_VERSION = "1.6.26.001";
private static final ApplicationListener app = new Forge();
private static Clipboard clipboard;

View File

@@ -4,7 +4,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.27-SNAPSHOT</version>
</parent>
<artifactId>forge-gui</artifactId>

View File

@@ -1,5 +1,2 @@
- New Net Deck Category -
The Genetic Algorithm AI Decks category has been added to net decks. This contains decks generated using a genetic algorithm to try to find decks that the AI plays best in each format.
- Bug fixes -
As always, this release of Forge features an assortment of bug fixes and improvements based on user feedback during the previous release run.

View File

@@ -2,9 +2,9 @@ Name:Ajani's Pridemate
ManaCost:1 W
Types:Creature Cat Soldier
PT:2/2
T:Mode$ LifeGained | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigPutCounter | OptionalDecider$ You | TriggerDescription$ Whenever you gain life, you may put a +1/+1 counter on CARDNAME.
T:Mode$ LifeGained | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ Whenever you gain life, put a +1/+1 counter on CARDNAME.
SVar:TrigPutCounter:DB$PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1
SVar:Picture:http://www.wizards.com/global/images/magic/general/ajanis_pridemate.jpg
DeckHints:Ability$LifeGain
DeckHas:Ability$Counters
Oracle:Whenever you gain life, you may put a +1/+1 counter on Ajani's Pridemate.
Oracle:Whenever you gain life, put a +1/+1 counter on Ajani's Pridemate.

View File

@@ -3,7 +3,7 @@ ManaCost:6 W
Types:Sorcery
T:Mode$ SpellCast | ValidCard$ Card.Self | Static$ True | Execute$ ApproachingSuns
SVar:ApproachingSuns:DB$ Effect | Name$ ApproachingSuns | Duration$ Permanent
A:SP$ Branch | Cost$ 6 W | References$ X,Y,Z | BranchConditionSVar$ X | BranchConditionSVarCompare$ EQ3 | TrueSubAbility$ WinGame | FalseSubAbility$ GainLife | SpellDescription$ If CARDNAME was cast from your hand and you've cast another spell named Approach of the Second Sun this game, you win the game. Otherwise, put CARDNAME into its owner's library seventh from the top and you gain 7 life.
A:SP$ Branch | Cost$ 6 W | References$ X,Y,Z | BranchConditionSVar$ X | BranchConditionSVarCompare$ EQ3 | TrueSubAbility$ WinGame | FalseSubAbility$ GainLife | SpellDescription$ If this spell was cast from your hand and you've cast another spell named Approach of the Second Sun this game, you win the game. Otherwise, put CARDNAME into its owner's library seventh from the top and you gain 7 life.
SVar:WinGame:DB$ WinsGame | Defined$ You
SVar:GainLife:DB$ GainLife | LifeAmount$ 7 | Defined$ You | SubAbility$ Reapproach
SVar:Reapproach:DB$ ChangeZone | Origin$ Stack | Destination$ Library | LibraryPosition$ 6 | Defined$ Self
@@ -11,4 +11,4 @@ SVar:Y:Count$ValidCommand Effect.YouCtrl+namedApproachingSuns/LimitMax.2
SVar:Z:Count$ValidStack Card.wasCastFromHand+Self/LimitMax.1
SVar:X:SVar$Y/Plus.Z
DeckNeeds:Name$Approach of the Second Sun
Oracle:If Approach of the Second Sun was cast from your hand and you've cast another spell named Approach of the Second Sun this game, you win the game. Otherwise, put Approach of the Second Sun into its owner's library seventh from the top and you gain 7 life.
Oracle:If this spell was cast from your hand and you've cast another spell named Approach of the Second Sun this game, you win the game. Otherwise, put Approach of the Second Sun into its owner's library seventh from the top and you gain 7 life.

View File

@@ -2,7 +2,7 @@ Name:Artisan of Forms
ManaCost:1 U
Types:Creature Human Wizard
PT:1/1
T:Mode$ SpellCast | ValidActivatingPlayer$ You | TargetsValid$ Card.Self | TriggerZones$ Battlefield | Execute$ TrigArtisanCopy | TriggerDescription$ Heroic — Whenever you cast a spell that targets CARDNAME, you may have CARDNAME become a copy of target creature and gain this ability.
T:Mode$ SpellCast | ValidActivatingPlayer$ You | TargetsValid$ Card.Self | TriggerZones$ Battlefield | Execute$ TrigArtisanCopy | TriggerDescription$ Heroic — Whenever you cast a spell that targets CARDNAME, you may have CARDNAME become a copy of target creature, except it has this ability.
SVar:TrigArtisanCopy:DB$ Clone | ValidTgts$ Creature | TgtPrompt$ Select target creature to copy | Optional$ True | GainThisAbility$ True | AddSVars$ TrigArtisanCopy | AILogic$ CloneBestCreature
SVar:Picture:http://www.wizards.com/global/images/magic/general/artisan_of_forms.jpg
Oracle:Heroic — Whenever you cast a spell that targets Artisan of Forms, you may have Artisan of Forms become a copy of target creature and gain this ability.
Oracle:Heroic — Whenever you cast a spell that targets Artisan of Forms, you may have Artisan of Forms become a copy of target creature, except it has this ability.

View File

@@ -6,5 +6,6 @@ K:Kicker:1 G
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self+kicked | Execute$ TrigKicker | TriggerDescription$ When CARDNAME enters the battlefield, if it was kicked, destroy target land.
SVar:TrigKicker:DB$Destroy | ValidTgts$ Land | TgtPrompt$ Select target land
DeckHints:Color$Green
SVar:NeedsToPlayKicked:Land.OppCtrl
SVar:Picture:http://www.wizards.com/global/images/magic/general/benalish_emissary.jpg
Oracle:Kicker {1}{G} (You may pay an additional {1}{G} as you cast this spell.)\nWhen Benalish Emissary enters the battlefield, if it was kicked, destroy target land.

View File

@@ -4,5 +4,8 @@ Types:Sorcery
K:Kicker:Sac<2/Land>
A:SP$ Discard | Cost$ 2 B | ValidTgts$ Player | TgtPrompt$ Choose a player | NumCards$ WasKicked | References$ WasKicked | Mode$ TgtChoose | SpellDescription$ Target player discards two cards. If CARDNAME was kicked, that player discards three cards instead.
SVar:WasKicked:Count$Kicked.3.2
SVar:NeedsToPlayKickedVar:Z GE3
SVar:Z:Count$ValidHand Card.OppCtrl
SVar:AIPreference:SacCost$Land.basic+YouCtrl
SVar:Picture:http://www.wizards.com/global/images/magic/general/bog_down.jpg
Oracle:Kicker—Sacrifice two lands. (You may sacrifice two lands in addition to any other costs as you cast this spell.)\nTarget player discards two cards. If this spell was kicked, that player discards three cards instead.

View File

@@ -5,4 +5,6 @@ PT:1/3
K:Kicker:3 B
T:Mode$ ChangesZone | ValidCard$ Card.Self+kicked | Origin$ Any | Destination$ Battlefield | Execute$ TrigDiscard | TriggerDescription$ When CARDNAME enters the battlefield, if it was kicked, each opponent discards two cards.
SVar:TrigDiscard:DB$ Discard | Defined$ Player.Opponent | NumCards$ 2 | Mode$ TgtChoose
SVar:NeedsToPlayKickedVar:Z GE1
SVar:Z:Count$ValidHand Card.OppCtrl
Oracle:Kicker {3}{B} (You may pay an additional {3}{B} as you cast this spell.)\nWhen Caligo Skin-Witch enters the battlefield, if it was kicked, each opponent discards two cards.

View File

@@ -3,8 +3,8 @@ ManaCost:1 UB UB
Types:Creature Shapeshifter
PT:1/2
# Make Svars for granting abilities and triggers on clones distinct to avoid SVars getting overwritten when cloning a clone
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Creature | TriggerZones$ Battlefield | Execute$ CemeteryPucaCopy | TriggerDescription$ Whenever a creature dies, you may pay {1}. If you do, CARDNAME becomes a copy of that creature and gains this ability.
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Creature | TriggerZones$ Battlefield | Execute$ CemeteryPucaCopy | TriggerDescription$ Whenever a creature dies, you may pay {1}. If you do, CARDNAME becomes a copy of that creature, except it has this ability.
SVar:CemeteryPucaCopy:AB$ Clone | Cost$ 1 | Defined$ TriggeredCardLKICopy | GainThisAbility$ True
AI:RemoveDeck:All
SVar:Picture:http://www.wizards.com/global/images/magic/general/cemetery_puca.jpg
Oracle:Whenever a creature dies, you may pay {1}. If you do, Cemetery Puca becomes a copy of that creature and gains this ability.
Oracle:Whenever a creature dies, you may pay {1}. If you do, Cemetery Puca becomes a copy of that creature, except it has this ability.

View File

@@ -3,8 +3,8 @@ ManaCost:2 W
Types:Creature Human Soldier
PT:1/2
K:Protection from black
S:Mode$ Continuous | Affected$ Card.Self | EffectZone$ All | AddKeyword$ Alternative Cost:0 | CheckSVar$ X | CheckSecondSVar$ Y | Description$ If an opponent controls a Swamp and you control a Plains, you may cast CARDNAME without paying its mana cost.
S:Mode$ Continuous | Affected$ Card.Self | EffectZone$ All | AddKeyword$ Alternative Cost:0 | CheckSVar$ X | CheckSecondSVar$ Y | Description$ If an opponent controls a Swamp and you control a Plains, you may cast this spell without paying its mana cost.
SVar:X:Count$Valid Swamp.OppCtrl
SVar:Y:Count$Valid Plains.YouCtrl
SVar:Picture:http://www.wizards.com/global/images/magic/general/cho_arrim_legate.jpg
Oracle:Protection from black\nIf an opponent controls a Swamp and you control a Plains, you may cast Cho-Arrim Legate without paying its mana cost.
Oracle:Protection from black\nIf an opponent controls a Swamp and you control a Plains, you may cast this spell without paying its mana cost.

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