mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-18 11:48:02 +00:00
WrappedAbility: fix TriggeredRemembered being sacrificed
This commit is contained in:
committed by
Michael Kamensky
parent
35ca083650
commit
584b6bd81e
@@ -716,7 +716,6 @@ public class ComputerUtil {
|
|||||||
CardCollection remaining = new CardCollection(cardlist);
|
CardCollection remaining = new CardCollection(cardlist);
|
||||||
final CardCollection sacrificed = new CardCollection();
|
final CardCollection sacrificed = new CardCollection();
|
||||||
final Card host = source.getHostCard();
|
final Card host = source.getHostCard();
|
||||||
final boolean considerSacLogic = "ConsiderSac".equals(source.getParam("AILogic"));
|
|
||||||
final int considerSacThreshold = getAIPreferenceParameter(host, "CreatureEvalThreshold");
|
final int considerSacThreshold = getAIPreferenceParameter(host, "CreatureEvalThreshold");
|
||||||
|
|
||||||
if ("OpponentOnly".equals(source.getParam("AILogic"))) {
|
if ("OpponentOnly".equals(source.getParam("AILogic"))) {
|
||||||
@@ -732,14 +731,14 @@ public class ComputerUtil {
|
|||||||
if (!ai.canLoseLife() || ai.cantLose()) {
|
if (!ai.canLoseLife() || ai.cantLose()) {
|
||||||
return sacrificed; // sacrifice none
|
return sacrificed; // sacrifice none
|
||||||
}
|
}
|
||||||
} else if (!considerSacLogic) {
|
} else {
|
||||||
return sacrificed; // sacrifice none
|
return sacrificed; // sacrifice none
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
boolean exceptSelf = "ExceptSelf".equals(source.getParam("AILogic"));
|
boolean exceptSelf = "ExceptSelf".equals(source.getParam("AILogic"));
|
||||||
boolean removedSelf = false;
|
boolean removedSelf = false;
|
||||||
|
|
||||||
if (isOptional && source.hasParam("Devour") || source.hasParam("Exploit") || considerSacLogic) {
|
if (isOptional && source.hasParam("Devour") || source.hasParam("Exploit")) {
|
||||||
if (source.hasParam("Exploit")) {
|
if (source.hasParam("Exploit")) {
|
||||||
for (Trigger t : host.getTriggers()) {
|
for (Trigger t : host.getTriggers()) {
|
||||||
if (t.getMode() == TriggerType.Exploited) {
|
if (t.getMode() == TriggerType.Exploited) {
|
||||||
@@ -761,17 +760,21 @@ public class ComputerUtil {
|
|||||||
public boolean apply(final Card c) {
|
public boolean apply(final Card c) {
|
||||||
int sacThreshold = 190;
|
int sacThreshold = 190;
|
||||||
|
|
||||||
if ("HeartPiercer".equals(source.getParam("SacrificeParam"))) {
|
String logic = source.getParamOrDefault("AILogic", "");
|
||||||
if (c.getNetPower() == 0) {
|
if (logic.startsWith("SacForDamage")) {
|
||||||
|
if (c.getNetPower() <= 0) {
|
||||||
return false;
|
return false;
|
||||||
} else if (c.getNetPower() >= ai.getOpponentsSmallestLifeTotal()) {
|
} else if (c.getNetPower() >= ai.getOpponentsSmallestLifeTotal()) {
|
||||||
return true;
|
return true;
|
||||||
|
} else if (logic.endsWith(".GiantX2") && c.getType().hasCreatureType("Giant")
|
||||||
|
&& c.getNetPower() * 2 >= ai.getOpponentsSmallestLifeTotal()) {
|
||||||
|
return true; // TODO: generalize this for any type and actually make the AI prefer giants?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("DesecrationDemon".equals(source.getParam("AILogic"))) {
|
if ("DesecrationDemon".equals(source.getParam("AILogic"))) {
|
||||||
sacThreshold = SpecialCardAi.DesecrationDemon.getSacThreshold();
|
sacThreshold = SpecialCardAi.DesecrationDemon.getSacThreshold();
|
||||||
} else if (considerSacLogic && considerSacThreshold != -1) {
|
} else if (considerSacThreshold != -1) {
|
||||||
sacThreshold = considerSacThreshold;
|
sacThreshold = considerSacThreshold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ public enum SpellApiToAi {
|
|||||||
.put(ApiType.GenericChoice, ChooseGenericEffectAi.class)
|
.put(ApiType.GenericChoice, ChooseGenericEffectAi.class)
|
||||||
.put(ApiType.Goad, GoadAi.class)
|
.put(ApiType.Goad, GoadAi.class)
|
||||||
.put(ApiType.Haunt, HauntAi.class)
|
.put(ApiType.Haunt, HauntAi.class)
|
||||||
.put(ApiType.ImmediateTrigger, AlwaysPlayAi.class)
|
.put(ApiType.ImmediateTrigger, DelayedTriggerAi.class)
|
||||||
.put(ApiType.Investigate, InvestigateAi.class)
|
.put(ApiType.Investigate, InvestigateAi.class)
|
||||||
.put(ApiType.LoseLife, LifeLoseAi.class)
|
.put(ApiType.LoseLife, LifeLoseAi.class)
|
||||||
.put(ApiType.LosesGame, GameLossAi.class)
|
.put(ApiType.LosesGame, GameLossAi.class)
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.*;
|
||||||
|
import forge.game.ability.AbilityFactory;
|
||||||
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.AbilitySub;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
|
||||||
|
public class ImmediateTriggerAi extends SpellAbilityAi {
|
||||||
|
// TODO: this class is largely reused from DelayedTriggerAi, consider updating
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||||
|
String logic = sa.getParamOrDefault("AILogic", "");
|
||||||
|
if (logic.equals("Always")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
SpellAbility trigsa = null;
|
||||||
|
if (sa.hasAdditionalAbility("Execute")) {
|
||||||
|
trigsa = sa.getAdditionalAbility("Execute");
|
||||||
|
} else {
|
||||||
|
trigsa = AbilityFactory.getAbility(sa.getHostCard(), sa.getParam("Execute"));
|
||||||
|
}
|
||||||
|
trigsa.setActivatingPlayer(ai);
|
||||||
|
|
||||||
|
if (trigsa instanceof AbilitySub) {
|
||||||
|
return SpellApiToAi.Converter.get(trigsa.getApi()).chkDrawbackWithSubs(ai, (AbilitySub)trigsa);
|
||||||
|
} else {
|
||||||
|
return AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(trigsa);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
|
SpellAbility trigsa = null;
|
||||||
|
if (sa.hasAdditionalAbility("Execute")) {
|
||||||
|
trigsa = sa.getAdditionalAbility("Execute");
|
||||||
|
} else {
|
||||||
|
trigsa = AbilityFactory.getAbility(sa.getHostCard(), sa.getParam("Execute"));
|
||||||
|
}
|
||||||
|
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
|
||||||
|
trigsa.setActivatingPlayer(ai);
|
||||||
|
|
||||||
|
if (!sa.hasParam("OptionalDecider")) {
|
||||||
|
return aic.doTrigger(trigsa, true);
|
||||||
|
} else {
|
||||||
|
return aic.doTrigger(trigsa, !sa.getParam("OptionalDecider").equals("You"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
|
String logic = sa.getParamOrDefault("AILogic", "");
|
||||||
|
if (logic.equals("Always")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
SpellAbility trigsa = null;
|
||||||
|
if (sa.hasAdditionalAbility("Execute")) {
|
||||||
|
trigsa = sa.getAdditionalAbility("Execute");
|
||||||
|
} else {
|
||||||
|
trigsa = AbilityFactory.getAbility(sa.getHostCard(), sa.getParam("Execute"));
|
||||||
|
}
|
||||||
|
trigsa.setActivatingPlayer(ai);
|
||||||
|
return AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(trigsa);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -17,7 +17,6 @@ import java.util.Map.Entry;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.google.common.collect.Lists;
|
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
|
|
||||||
// Wrapper ability that checks the requirements again just before
|
// Wrapper ability that checks the requirements again just before
|
||||||
@@ -487,18 +486,6 @@ public class WrappedAbility extends Ability {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check timestamps of triggered objects
|
|
||||||
final List<Object> original = Lists.newArrayList(sa.getTriggerRemembered());
|
|
||||||
for (Object o : original) {
|
|
||||||
if (o instanceof Card) {
|
|
||||||
Card card = (Card) o;
|
|
||||||
Card current = game.getCardState(card);
|
|
||||||
if (current.getTimestamp() != card.getTimestamp()) {
|
|
||||||
// TODO: figure out if NoTimestampCheck should be the default for ChangesZone triggers
|
|
||||||
sa.getTriggerRemembered().remove(o);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
final Map<AbilityKey, Object> triggerMap = AbilityKey.newMap(sa.getTriggeringObjects());
|
final Map<AbilityKey, Object> triggerMap = AbilityKey.newMap(sa.getTriggeringObjects());
|
||||||
for (Entry<AbilityKey, Object> ev : triggerMap.entrySet()) {
|
for (Entry<AbilityKey, Object> ev : triggerMap.entrySet()) {
|
||||||
if (ev.getValue() instanceof Card) {
|
if (ev.getValue() instanceof Card) {
|
||||||
|
|||||||
@@ -2,13 +2,11 @@ Name:Heart-Piercer Manticore
|
|||||||
ManaCost:2 R R
|
ManaCost:2 R R
|
||||||
Types:Creature Manticore
|
Types:Creature Manticore
|
||||||
PT:4/3
|
PT:4/3
|
||||||
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | TriggerZones$ Battlefield | Execute$ TrigSacrifice | TriggerDescription$ When CARDNAME enters the battlefield, you may sacrifice another creature. When you do, CARDNAME deals damage equal to that creature's power to target creature or player.
|
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | TriggerZones$ Battlefield | Execute$ DBTrigger | TriggerDescription$ When CARDNAME enters the battlefield, you may sacrifice another creature. When you do, CARDNAME deals damage equal to that creature's power to target creature or player.
|
||||||
SVar:TrigSacrifice:DB$ Sacrifice | Optional$ True | SacValid$ Creature.Other | SacMessage$ another Creature | Amount$ 1 | AILogic$ ConsiderSac | RememberSacrificed$ True | SubAbility$ DBTrigger
|
SVar:DBTrigger:AB$ ImmediateTrigger | Cost$ Sac<1/Creature.Other/another creature> | Execute$ TrigDamage | AILogic$ SacForDamage | RememberObjects$ Sacrificed | TriggerDescription$ When you do, CARDNAME deals damage equal to that creature's power to any target.
|
||||||
SVar:DBTrigger:DB$ ImmediateTrigger | Execute$ TrigDamage | RememberObjects$ RememberedCard | ConditionDefined$ Remembered | ConditionPresent$ Card | SubAbility$ DBCleanup | TriggerDescription$ When you do, CARDNAME deals damage equal to that creature's power to any target.
|
|
||||||
SVar:TrigDamage:DB$ DealDamage | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ XPower | References$ XPower
|
SVar:TrigDamage:DB$ DealDamage | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ XPower | References$ XPower
|
||||||
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
|
|
||||||
K:Embalm:5 R
|
K:Embalm:5 R
|
||||||
SVar:XPower:TriggerRemembered$CardPower
|
SVar:XPower:TriggerRemembered$CardPower
|
||||||
SVar:AIPreferenceParams:CreatureEvalThreshold$ 200
|
SVar:AIPreferenceParams:CreatureEvalThreshold$ 200
|
||||||
SVar:DeckHas:Ability$Token
|
DeckHas:Ability$Token & Ability$Sacrifice
|
||||||
Oracle:When Heart-Piercer Manticore enters the battlefield, you may sacrifice another creature. When you do, Heart-Piercer Manticore deals damage equal to that creature's power to any target.\nEmbalm {5} {R} ({5} {R}, Exile this card from your graveyard: Create a token that's a copy of it, except it's a white Zombie Manticore with no mana cost. Embalm only as a sorcery.)
|
Oracle:When Heart-Piercer Manticore enters the battlefield, you may sacrifice another creature. When you do, Heart-Piercer Manticore deals damage equal to that creature's power to any target.\nEmbalm {5} {R} ({5} {R}, Exile this card from your graveyard: Create a token that's a copy of it, except it's a white Zombie Manticore with no mana cost. Embalm only as a sorcery.)
|
||||||
|
|||||||
15
forge-gui/res/cardsfolder/upcoming/surtland_flinger.txt
Normal file
15
forge-gui/res/cardsfolder/upcoming/surtland_flinger.txt
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
Name:Surtland Flinger
|
||||||
|
ManaCost:3 R R
|
||||||
|
Types:Creature Giant Berserker
|
||||||
|
PT:4/6
|
||||||
|
T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigImmediate | TriggerDescription$ Whenever CARDNAME attacks, you may sacrifice another creature. When you do, CARDNAME deals damage equal to the sacrificed creature's power to any target. If the sacrificed creature was a Giant, CARDNAME deals twice that much damage instead.
|
||||||
|
SVar:TrigImmediate:AB$ ImmediateTrigger | Cost$ Sac<1/Creature.Other/another creature> | RememberObjects$ Sacrificed | Execute$ TrigDamage | AILogic$ SacForDamage.GiantX2 | TriggerDescription$ When you do, CARDNAME deals damage equal to the sacrificed creature's power to any target. If the sacrificed creature was a Giant, CARDNAME deals twice that much damage instead.
|
||||||
|
SVar:TrigDamage:DB$ DealDamage | ConditionDefined$ DelayTriggerRememberedLKI | ConditionPresent$ Giant | ConditionCompare$ EQ0 | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ X | References$ X | SubAbility$ DBGiantDamage
|
||||||
|
SVar:DBGiantDamage:DB$ DealDamage | ConditionDefined$ DelayTriggerRememberedLKI | ConditionPresent$ Giant | ConditionCompare$ GE1 | Defined$ Targeted | NumDmg$ Y | References$ Y
|
||||||
|
SVar:X:TriggerRemembered$CardPower
|
||||||
|
SVar:Y:TriggerRemembered$CardPower/Times.2
|
||||||
|
SVar:AIPreferenceParams:CreatureEvalThreshold$ 200
|
||||||
|
SVar:HasAttackEffect:TRUE
|
||||||
|
DeckHas:Ability$Sacrifice
|
||||||
|
DeckHints:Type$Giant
|
||||||
|
Oracle:Whenever Surtland Flinger attacks, you may sacrifice another creature. When you do, Surtland Flinger deals damage equal to the sacrificed creature’s power to any target. If the sacrificed creature was a Giant, Surtland Flinger deals twice that much damage instead.
|
||||||
Reference in New Issue
Block a user