diff --git a/forge-ai/src/main/java/forge/ai/SpellApiToAi.java b/forge-ai/src/main/java/forge/ai/SpellApiToAi.java index 22340548028..9da94706493 100644 --- a/forge-ai/src/main/java/forge/ai/SpellApiToAi.java +++ b/forge-ai/src/main/java/forge/ai/SpellApiToAi.java @@ -68,6 +68,7 @@ public enum SpellApiToAi { .put(ApiType.Encode, EncodeAi.class) .put(ApiType.EndTurn, EndTurnAi.class) .put(ApiType.ExchangeLife, LifeExchangeAi.class) + .put(ApiType.ExchangeLifeVariant, LifeExchangeVariantAi.class) .put(ApiType.ExchangeControl, ControlExchangeAi.class) .put(ApiType.ExchangeControlVariant, CannotPlayAi.class) .put(ApiType.ExchangePower, PowerExchangeAi.class) diff --git a/forge-ai/src/main/java/forge/ai/ability/LifeExchangeVariantAi.java b/forge-ai/src/main/java/forge/ai/ability/LifeExchangeVariantAi.java new file mode 100644 index 00000000000..a34f5e0d801 --- /dev/null +++ b/forge-ai/src/main/java/forge/ai/ability/LifeExchangeVariantAi.java @@ -0,0 +1,122 @@ +package forge.ai.ability; + +import forge.ai.ComputerUtil; +import forge.ai.ComputerUtilAbility; +import forge.ai.ComputerUtilCombat; +import forge.ai.SpellAbilityAi; +import forge.game.Game; +import forge.game.card.Card; +import forge.game.player.Player; +import forge.game.spellability.SpellAbility; +import forge.game.spellability.TargetRestrictions; + +public class LifeExchangeVariantAi extends SpellAbilityAi { + + /* + * (non-Javadoc) + * + * @see + * forge.card.abilityfactory.AbilityFactoryAlterLife.SpellAiLogic#canPlayAI + * (forge.game.player.Player, java.util.Map, + * forge.card.spellability.SpellAbility) + */ + @Override + protected boolean canPlayAI(Player ai, SpellAbility sa) { + final Card source = sa.getHostCard(); + final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa); + final Game game = ai.getGame(); + + if ("Tree of Redemption".equals(sourceName)) { + if (!ai.canGainLife()) + return false; + + // someone controls "Rain of Gore" or "Sulfuric Vortex", lifegain is bad in that case + if (game.isCardInPlay("Rain of Gore") || game.isCardInPlay("Sulfuric Vortex")) + return false; + + // an opponent controls "Tainted Remedy", lifegain is bad in that case + for (Player op : ai.getOpponents()) { + if (op.isCardInPlay("Tainted Remedy")) + return false; + } + + if (ComputerUtil.waitForBlocking(sa) || ai.getLife() + 1 >= source.getNetToughness() + || (ai.getLife() > 5 && !ComputerUtilCombat.lifeInSeriousDanger(ai, ai.getGame().getCombat()))) { + return false; + } + } + else if ("Tree of Perdition".equals(sourceName)) { + boolean shouldDo = false; + + if (ComputerUtil.waitForBlocking(sa)) + return false; + + for (Player op : ai.getOpponents()) { + // if oppoent can't be targeted, or it can't lose life, try another one + if (!op.canBeTargetedBy(sa) || !op.canLoseLife()) + continue; + // an opponent has more live than this toughness + if (op.getLife() + 1 >= source.getNetToughness()) { + shouldDo = true; + } else { + // opponent can't gain life, so "Tainted Remedy" should not work. + if (!op.canGainLife()) { + continue; + } else if (ai.isCardInPlay("Tainted Remedy")) { // or AI has Tainted Remedy + shouldDo = true; + } else { + for (Player ally : ai.getAllies()) { + // if an Ally has Tainted Remedy and opponent is also opponent of ally + if (ally.isCardInPlay("Tainted Remedy") && op.isOpponentOf(ally)) + shouldDo = true; + } + } + + } + + if (shouldDo) { + sa.getTargets().add(op); + break; + } + } + + return shouldDo; + } + else if ("Evra, Halcyon Witness".equals(sourceName)) { + // TODO add logic + } + return false; + + } + + /** + *

+ * exchangeLifeDoTriggerAINoCost. + *

+ * @param sa + * a {@link forge.game.spellability.SpellAbility} object. + * @param mandatory + * a boolean. + * @param af + * a {@link forge.game.ability.AbilityFactory} object. + * + * @return a boolean. + */ + @Override + protected boolean doTriggerAINoCost(final Player ai, final SpellAbility sa, + final boolean mandatory) { + + final TargetRestrictions tgt = sa.getTargetRestrictions(); + Player opp = ComputerUtil.getOpponentFor(ai); + if (tgt != null) { + sa.resetTargets(); + if (sa.canTarget(opp) && (mandatory || ai.getLife() < opp.getLife())) { + sa.getTargets().add(opp); + } else { + return false; + } + } + return true; + } + +} diff --git a/forge-ai/src/main/java/forge/ai/ability/StoreSVarAi.java b/forge-ai/src/main/java/forge/ai/ability/StoreSVarAi.java index 6df57af7807..f5dc2c0282e 100644 --- a/forge-ai/src/main/java/forge/ai/ability/StoreSVarAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/StoreSVarAi.java @@ -1,6 +1,5 @@ package forge.ai.ability; -import forge.ai.ComputerUtil; import forge.ai.ComputerUtilAbility; import forge.ai.ComputerUtilCombat; import forge.ai.ComputerUtilMana; @@ -25,7 +24,6 @@ public class StoreSVarAi extends SpellAbilityAi { @Override protected boolean canPlayAI(Player ai, SpellAbility sa) { final Card source = sa.getHostCard(); - final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa); final Game game = ai.getGame(); final Combat combat = game.getCombat(); final PhaseHandler ph = game.getPhaseHandler(); @@ -69,62 +67,7 @@ public class StoreSVarAi extends SpellAbilityAi { } return false; } - else if ("Tree of Redemption".equals(sourceName)) { - if (!ai.canGainLife()) - return false; - // someone controls "Rain of Gore" or "Sulfuric Vortex", lifegain is bad in that case - if (game.isCardInPlay("Rain of Gore") || game.isCardInPlay("Sulfuric Vortex")) - return false; - - // an opponent controls "Tainted Remedy", lifegain is bad in that case - for (Player op : ai.getOpponents()) { - if (op.isCardInPlay("Tainted Remedy")) - return false; - } - - if (ComputerUtil.waitForBlocking(sa) || ai.getLife() + 1 >= source.getNetToughness() - || (ai.getLife() > 5 && !ComputerUtilCombat.lifeInSeriousDanger(ai, ai.getGame().getCombat()))) { - return false; - } - } - else if ("Tree of Perdition".equals(sourceName)) { - boolean shouldDo = false; - - if (ComputerUtil.waitForBlocking(sa)) - return false; - - for (Player op : ai.getOpponents()) { - // if oppoent can't be targeted, or it can't lose life, try another one - if (!op.canBeTargetedBy(sa) || !op.canLoseLife()) - continue; - // an opponent has more live than this toughness - if (op.getLife() + 1 >= source.getNetToughness()) { - shouldDo = true; - } else { - // opponent can't gain life, so "Tainted Remedy" should not work. - if (!op.canGainLife()) { - continue; - } else if (ai.isCardInPlay("Tainted Remedy")) { // or AI has Tainted Remedy - shouldDo = true; - } else { - for (Player ally : ai.getAllies()) { - // if an Ally has Tainted Remedy and opponent is also opponent of ally - if (ally.isCardInPlay("Tainted Remedy") && op.isOpponentOf(ally)) - shouldDo = true; - } - } - - } - - if (shouldDo) { - sa.getTargets().add(op); - break; - } - } - - return shouldDo; - } return true; } diff --git a/forge-game/src/main/java/forge/game/ability/ApiType.java b/forge-game/src/main/java/forge/game/ability/ApiType.java index 7c0a63d1ceb..8605e2453fd 100644 --- a/forge-game/src/main/java/forge/game/ability/ApiType.java +++ b/forge-game/src/main/java/forge/game/ability/ApiType.java @@ -65,6 +65,7 @@ public enum ApiType { Encode (EncodeEffect.class), EndTurn (EndTurnEffect.class), ExchangeLife (LifeExchangeEffect.class), + ExchangeLifeVariant (LifeExchangeVariantEffect.class), ExchangeControl (ControlExchangeEffect.class), ExchangeControlVariant (ControlExchangeVariantEffect.class), ExchangePower (PowerExchangeEffect.class), diff --git a/forge-game/src/main/java/forge/game/ability/effects/LifeExchangeVariantEffect.java b/forge-game/src/main/java/forge/game/ability/effects/LifeExchangeVariantEffect.java new file mode 100644 index 00000000000..c0836c66c06 --- /dev/null +++ b/forge-game/src/main/java/forge/game/ability/effects/LifeExchangeVariantEffect.java @@ -0,0 +1,95 @@ +package forge.game.ability.effects; + +import forge.game.Game; +import forge.game.ability.SpellAbilityEffect; +import forge.game.card.Card; +import forge.game.event.GameEventCardStatsChanged; +import forge.game.player.Player; +import forge.game.spellability.SpellAbility; +import forge.game.zone.ZoneType; + +import java.util.List; + +public class LifeExchangeVariantEffect extends SpellAbilityEffect { + + // ************************************************************************* + // ************************ EXCHANGE LIFE ********************************** + // ************************************************************************* + + + + // ************************************************************************* + // ************************* LOSE LIFE ************************************* + // ************************************************************************* + + /* (non-Javadoc) + * @see forge.card.abilityfactory.AbilityFactoryAlterLife.SpellEffect#getStackDescription(java.util.Map, forge.card.spellability.SpellAbility) + */ + @Override + protected String getStackDescription(SpellAbility sa) { + final StringBuilder sb = new StringBuilder(); + final Player activatingPlayer = sa.getActivatingPlayer(); + final String mode = sa.getParam("Mode"); + + sb.append(activatingPlayer).append(" exchanges life totals with "); + sb.append(sa.getHostCard()); + sb.append("'s "); + sb.append(mode.toLowerCase()); + + return sb.toString(); + } + + /* (non-Javadoc) + * @see forge.card.abilityfactory.AbilityFactoryAlterLife.SpellEffect#resolve(java.util.Map, forge.card.spellability.SpellAbility) + */ + @Override + public void resolve(SpellAbility sa) { + final Card source = sa.getHostCard(); + final String mode = sa.getParam("Mode"); + final List tgtPlayers = getTargetPlayers(sa); + + if (tgtPlayers.isEmpty()) { + return; + } + + Player p = tgtPlayers.get(0); + + Integer power = null; + Integer toughness = null; + + final Game game = p.getGame(); + final long timestamp = game.getNextTimestamp(); + + final int pLife = p.getLife(); + int num = 0; + + if ("Power".equals(mode)) { + num = source.getNetPower(); + power = pLife; + } else if ("Toughness".equals(mode)) { + num = source.getNetToughness(); + toughness = pLife; + } else { + return; + } + + if (!source.isInZone(ZoneType.Battlefield)) { + return; + } + + if ((pLife > num) && p.canLoseLife()) { + final int diff = pLife - num; + p.loseLife(diff); + source.addNewPT(power, toughness, timestamp); + game.fireEvent(new GameEventCardStatsChanged(source)); + } else if ((num > pLife) && p.canGainLife()) { + final int diff = num - pLife; + p.gainLife(diff, source); + source.addNewPT(power, toughness, timestamp); + game.fireEvent(new GameEventCardStatsChanged(source)); + } else { + // do nothing if they are equal + } + } + +} diff --git a/forge-gui/res/cardsfolder/t/tree_of_perdition.txt b/forge-gui/res/cardsfolder/t/tree_of_perdition.txt index b73d1548c41..c14e374727d 100644 --- a/forge-gui/res/cardsfolder/t/tree_of_perdition.txt +++ b/forge-gui/res/cardsfolder/t/tree_of_perdition.txt @@ -3,10 +3,6 @@ ManaCost:3 B Types:Creature Plant PT:0/13 K:Defender -A:AB$ StoreSVar | Cost$ T | ValidTgts$ Opponent | TgtPrompt$ Select target opponent | SVar$ OldToughness | Type$ Count | Expression$ CardToughness | SubAbility$ TreeRedemption | ConditionPresent$ Creature.StrictlySelf | StackDescription$ SpellDescription | SpellDescription$ Exchange target opponent's life total with CARDNAME's toughness. -SVar:TreeRedemption:DB$ Animate | Toughness$ X | References$ X | Permanent$ True | SubAbility$ SetLife | ConditionPresent$ Creature.StrictlySelf -SVar:SetLife:DB$ SetLife | Defined$ ParentTarget | LifeAmount$ OldToughness | References$ OldToughness | ConditionPresent$ Creature.StrictlySelf -SVar:OldToughness:Number$13 -SVar:X:TargetedPlayer$LifeTotal +A:AB$ ExchangeLifeVariant | Cost$ T | ConditionPresent$ Creature.StrictlySelf | ValidTgts$ Opponent | Mode$ Toughness | SpellDescription$ Exchange your life total with CARDNAME's toughness. SVar:Picture:http://www.wizards.com/global/images/magic/general/tree_of_perdition.jpg Oracle:Defender\n{T}: Exchange target opponent's life total with Tree of Perdition's toughness. diff --git a/forge-gui/res/cardsfolder/t/tree_of_redemption.txt b/forge-gui/res/cardsfolder/t/tree_of_redemption.txt index c49f1fab574..b9a38bd5394 100644 --- a/forge-gui/res/cardsfolder/t/tree_of_redemption.txt +++ b/forge-gui/res/cardsfolder/t/tree_of_redemption.txt @@ -3,10 +3,6 @@ ManaCost:3 G Types:Creature Plant PT:0/13 K:Defender -A:AB$ StoreSVar | Cost$ T | SVar$ OldToughness | Type$ Count | Expression$ CardToughness | SubAbility$ TreeRedemption | ConditionPresent$ Creature.StrictlySelf | StackDescription$ SpellDescription | SpellDescription$ Exchange your life total with CARDNAME's toughness. -SVar:TreeRedemption:DB$ Animate | Toughness$ X | References$ X | Permanent$ True | SubAbility$ SetLife | ConditionPresent$ Creature.StrictlySelf -SVar:SetLife:DB$ SetLife | Defined$ You | LifeAmount$ OldToughness | References$ OldToughness | ConditionPresent$ Creature.StrictlySelf -SVar:OldToughness:Number$13 -SVar:X:Count$YourLifeTotal +A:AB$ ExchangeLifeVariant | Cost$ T | ConditionPresent$ Creature.StrictlySelf | Defined$ You | Mode$ Toughness | SpellDescription$ Exchange your life total with CARDNAME's toughness. SVar:Picture:http://www.wizards.com/global/images/magic/general/tree_of_redemption.jpg Oracle:Defender\n{T}: Exchange your life total with Tree of Redemption's toughness. diff --git a/forge-gui/res/cardsfolder/upcoming/evra_halcyon_witness.txt b/forge-gui/res/cardsfolder/upcoming/evra_halcyon_witness.txt new file mode 100644 index 00000000000..9be129a3ca9 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/evra_halcyon_witness.txt @@ -0,0 +1,8 @@ +Name:Evra, Halcyon Witness +ManaCost:4 W W +Types:Creature Creature Avatar +PT:4/4 +K:Lifelink +A:AB$ ExchangeLifeVariant | Cost$ 4 | ConditionPresent$ Creature.StrictlySelf | Defined$ You | Mode$ Power | SpellDescription$ Exchange your life total with CARDNAME's power. +SVar:Picture:http://www.wizards.com/global/images/magic/general/evra_halcyon_witness.jpg +Oracle:Lifelink\n{4}: Exchange your life total with Evra, Halcyon Witness's power.