WrappedAbility: fix TriggeredRemembered being sacrificed

This commit is contained in:
Hans Mackowiak
2021-01-09 06:15:05 +00:00
committed by Michael Kamensky
parent 35ca083650
commit 584b6bd81e
6 changed files with 97 additions and 25 deletions

View File

@@ -716,7 +716,6 @@ public class ComputerUtil {
CardCollection remaining = new CardCollection(cardlist);
final CardCollection sacrificed = new CardCollection();
final Card host = source.getHostCard();
final boolean considerSacLogic = "ConsiderSac".equals(source.getParam("AILogic"));
final int considerSacThreshold = getAIPreferenceParameter(host, "CreatureEvalThreshold");
if ("OpponentOnly".equals(source.getParam("AILogic"))) {
@@ -732,14 +731,14 @@ public class ComputerUtil {
if (!ai.canLoseLife() || ai.cantLose()) {
return sacrificed; // sacrifice none
}
} else if (!considerSacLogic) {
} else {
return sacrificed; // sacrifice none
}
}
boolean exceptSelf = "ExceptSelf".equals(source.getParam("AILogic"));
boolean removedSelf = false;
if (isOptional && source.hasParam("Devour") || source.hasParam("Exploit") || considerSacLogic) {
if (isOptional && source.hasParam("Devour") || source.hasParam("Exploit")) {
if (source.hasParam("Exploit")) {
for (Trigger t : host.getTriggers()) {
if (t.getMode() == TriggerType.Exploited) {
@@ -761,17 +760,21 @@ public class ComputerUtil {
public boolean apply(final Card c) {
int sacThreshold = 190;
if ("HeartPiercer".equals(source.getParam("SacrificeParam"))) {
if (c.getNetPower() == 0) {
String logic = source.getParamOrDefault("AILogic", "");
if (logic.startsWith("SacForDamage")) {
if (c.getNetPower() <= 0) {
return false;
} else if (c.getNetPower() >= ai.getOpponentsSmallestLifeTotal()) {
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"))) {
sacThreshold = SpecialCardAi.DesecrationDemon.getSacThreshold();
} else if (considerSacLogic && considerSacThreshold != -1) {
} else if (considerSacThreshold != -1) {
sacThreshold = considerSacThreshold;
}

View File

@@ -91,7 +91,7 @@ public enum SpellApiToAi {
.put(ApiType.GenericChoice, ChooseGenericEffectAi.class)
.put(ApiType.Goad, GoadAi.class)
.put(ApiType.Haunt, HauntAi.class)
.put(ApiType.ImmediateTrigger, AlwaysPlayAi.class)
.put(ApiType.ImmediateTrigger, DelayedTriggerAi.class)
.put(ApiType.Investigate, InvestigateAi.class)
.put(ApiType.LoseLife, LifeLoseAi.class)
.put(ApiType.LosesGame, GameLossAi.class)

View File

@@ -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);
}
}

View File

@@ -17,7 +17,6 @@ import java.util.Map.Entry;
import java.util.Set;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
// Wrapper ability that checks the requirements again just before
@@ -487,18 +486,6 @@ public class WrappedAbility extends Ability {
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());
for (Entry<AbilityKey, Object> ev : triggerMap.entrySet()) {
if (ev.getValue() instanceof Card) {

View File

@@ -2,13 +2,11 @@ Name:Heart-Piercer Manticore
ManaCost:2 R R
Types:Creature Manticore
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.
SVar:TrigSacrifice:DB$ Sacrifice | Optional$ True | SacValid$ Creature.Other | SacMessage$ another Creature | Amount$ 1 | AILogic$ ConsiderSac | RememberSacrificed$ True | SubAbility$ DBTrigger
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.
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: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: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
SVar:XPower:TriggerRemembered$CardPower
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.)

View 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 creatures power to any target. If the sacrificed creature was a Giant, Surtland Flinger deals twice that much damage instead.