Merge branch 'patch' into 'master'

ExchangeLife Variant for Evra, Halcyon Witness, Tree of Redemption, and Tree of Perdition

See merge request core-developers/forge!280
This commit is contained in:
swordshine
2018-03-11 01:55:52 +00:00
11 changed files with 250 additions and 67 deletions

View File

@@ -68,6 +68,7 @@ public enum SpellApiToAi {
.put(ApiType.Encode, EncodeAi.class) .put(ApiType.Encode, EncodeAi.class)
.put(ApiType.EndTurn, EndTurnAi.class) .put(ApiType.EndTurn, EndTurnAi.class)
.put(ApiType.ExchangeLife, LifeExchangeAi.class) .put(ApiType.ExchangeLife, LifeExchangeAi.class)
.put(ApiType.ExchangeLifeVariant, LifeExchangeVariantAi.class)
.put(ApiType.ExchangeControl, ControlExchangeAi.class) .put(ApiType.ExchangeControl, ControlExchangeAi.class)
.put(ApiType.ExchangeControlVariant, CannotPlayAi.class) .put(ApiType.ExchangeControlVariant, CannotPlayAi.class)
.put(ApiType.ExchangePower, PowerExchangeAi.class) .put(ApiType.ExchangePower, PowerExchangeAi.class)

View File

@@ -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;
}
/**
* <p>
* exchangeLifeDoTriggerAINoCost.
* </p>
* @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;
}
}

View File

@@ -1,6 +1,5 @@
package forge.ai.ability; package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilAbility; import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilCombat; import forge.ai.ComputerUtilCombat;
import forge.ai.ComputerUtilMana; import forge.ai.ComputerUtilMana;
@@ -25,7 +24,6 @@ public class StoreSVarAi extends SpellAbilityAi {
@Override @Override
protected boolean canPlayAI(Player ai, SpellAbility sa) { protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
final Game game = ai.getGame(); final Game game = ai.getGame();
final Combat combat = game.getCombat(); final Combat combat = game.getCombat();
final PhaseHandler ph = game.getPhaseHandler(); final PhaseHandler ph = game.getPhaseHandler();
@@ -69,62 +67,7 @@ public class StoreSVarAi extends SpellAbilityAi {
} }
return false; 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; return true;
} }

View File

@@ -65,6 +65,7 @@ public enum ApiType {
Encode (EncodeEffect.class), Encode (EncodeEffect.class),
EndTurn (EndTurnEffect.class), EndTurn (EndTurnEffect.class),
ExchangeLife (LifeExchangeEffect.class), ExchangeLife (LifeExchangeEffect.class),
ExchangeLifeVariant (LifeExchangeVariantEffect.class),
ExchangeControl (ControlExchangeEffect.class), ExchangeControl (ControlExchangeEffect.class),
ExchangeControlVariant (ControlExchangeVariantEffect.class), ExchangeControlVariant (ControlExchangeVariantEffect.class),
ExchangePower (PowerExchangeEffect.class), ExchangePower (PowerExchangeEffect.class),

View File

@@ -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<Player> 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
}
}
}

View File

@@ -3,10 +3,6 @@ ManaCost:3 B
Types:Creature Plant Types:Creature Plant
PT:0/13 PT:0/13
K:Defender 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. A:AB$ ExchangeLifeVariant | Cost$ T | ConditionPresent$ Creature.StrictlySelf | ValidTgts$ Opponent | Mode$ Toughness | 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$ ParentTarget | LifeAmount$ OldToughness | References$ OldToughness | ConditionPresent$ Creature.StrictlySelf
SVar:OldToughness:Number$13
SVar:X:TargetedPlayer$LifeTotal
SVar:Picture:http://www.wizards.com/global/images/magic/general/tree_of_perdition.jpg 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. Oracle:Defender\n{T}: Exchange target opponent's life total with Tree of Perdition's toughness.

View File

@@ -3,10 +3,6 @@ ManaCost:3 G
Types:Creature Plant Types:Creature Plant
PT:0/13 PT:0/13
K:Defender 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. A:AB$ ExchangeLifeVariant | Cost$ T | ConditionPresent$ Creature.StrictlySelf | Defined$ You | Mode$ Toughness | 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
SVar:Picture:http://www.wizards.com/global/images/magic/general/tree_of_redemption.jpg 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. Oracle:Defender\n{T}: Exchange your life total with Tree of Redemption's toughness.

View File

@@ -0,0 +1,8 @@
Name:Baird, Steward of Argive
ManaCost:2 W W
Types:Legendary Creature Human Soldier
PT:2/4
K:Vigilance
S:Mode$ CantAttackUnless | ValidCard$ Creature | Target$ You,Planeswalker.YouCtrl | Cost$ 1 | Description$ Creatures can't attack you or a planeswalker you control unless their controller pays {1} for each of those creatures.
SVar:Picture:http://www.wizards.com/global/images/magic/general/baird_steward_of_argive.jpg
Oracle:Vigilance\nCreatures can't attack you or a planeswalker you control unless their controller pays {1} for each of those creatures.

View File

@@ -0,0 +1,7 @@
Name:Benalish Marshal
ManaCost:W W W
Types:Creature Human Knight
PT:3/3
S:Mode$ Continuous | Affected$ Creature.Other+YouCtrl | AddPower$ 1 | AddToughness$ 1 | Description$ Other creatures you control get +1/+1.
SVar:Picture:http://www.wizards.com/global/images/magic/general/benalish_marshal.jpg
Oracle:Other creatures you control get +1/+1.

View File

@@ -0,0 +1,6 @@
Name:Charge
ManaCost:W
Types:Instant
A:SP$ PumpAll | Cost$ W | ValidCards$ Creature.YouCtrl | NumAtt$ +1 | SpellDescription$ Creatures you control get +1/+1 until end of turn.
SVar:Picture:http://www.wizards.com/global/images/magic/general/charge.jpg
Oracle:Creatures you control get +1/+1 until end of turn.

View File

@@ -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.