mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-15 18:28:00 +00:00
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:
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
7
forge-gui/res/cardsfolder/upcoming/benalish_marshal.txt
Normal file
7
forge-gui/res/cardsfolder/upcoming/benalish_marshal.txt
Normal 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.
|
||||
6
forge-gui/res/cardsfolder/upcoming/charge.txt
Normal file
6
forge-gui/res/cardsfolder/upcoming/charge.txt
Normal 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.
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user