Kicker Linked Lite (#2401)

* Kicker Linked Lite

* Fix incomplete LKI for Metallic Mimic and possibly others
This commit is contained in:
tool4ever
2023-02-10 20:43:03 +01:00
committed by GitHub
parent d5839d7a35
commit 7e3f8ad9e4
11 changed files with 67 additions and 27 deletions

View File

@@ -232,7 +232,7 @@ public class AiController {
if (!validCard.contains("Self")) {
continue;
}
if (validCard.contains("notkicked")) {
if (validCard.contains("!kicked")) {
if (sa.isKicked()) {
continue;
}
@@ -288,7 +288,7 @@ public class AiController {
if (!validCard.contains("Self")) {
continue;
}
if (validCard.contains("notkicked")) {
if (validCard.contains("!kicked")) {
if (sa.isKicked()) {
continue;
}

View File

@@ -1766,7 +1766,7 @@ public class AbilityUtils {
// Count$Kicked.<numHB>.<numNotHB>
if (sq[0].startsWith("Kicked")) {
boolean kicked = sa.isKicked() || c.getKickerMagnitude() > 0;
boolean kicked = sa.isKicked() || (!isUnlinkedFromCastSA(ctb, c) && c.getKickerMagnitude() > 0);
return doXMath(Integer.parseInt(kicked ? sq[1] : sq[2]), expr, c, ctb);
}
@@ -2004,7 +2004,7 @@ public class AbilityUtils {
}
if (sq[0].startsWith("Kicked")) { // fallback for not spellAbility
return doXMath(calculateAmount(c, sq[c.getKickerMagnitude() > 0 ? 1 : 2], ctb), expr, c, ctb);
return doXMath(calculateAmount(c, sq[!isUnlinkedFromCastSA(ctb, c) && c.getKickerMagnitude() > 0 ? 1 : 2], ctb), expr, c, ctb);
}
if (sq[0].startsWith("Escaped")) {
return doXMath(calculateAmount(c, sq[c.getCastSA() != null && c.getCastSA().isEscape() ? 1 : 2], ctb), expr, c, ctb);
@@ -2073,7 +2073,7 @@ public class AbilityUtils {
return doXMath(c.getKeywordMagnitude(Keyword.smartValueOf(l[0].split(" ")[1])), expr, c, ctb);
}
if (sq[0].contains("TimesKicked")) {
return doXMath(c.getKickerMagnitude(), expr, c, ctb);
return doXMath(isUnlinkedFromCastSA(ctb, c) ? 0 : c.getKickerMagnitude(), expr, c, ctb);
}
if (sq[0].contains("TimesPseudokicked")) {
return doXMath(c.getPseudoKickerMagnitude(), expr, c, ctb);
@@ -3894,4 +3894,35 @@ public class AbilityUtils {
}
return types.size();
}
/**
* Checks if an ability source can be considered a "broken link" on a specific host
* (which usually means it won't have its normal effect).
* <br>
* Because castSA gets used to compare it can only make a safe conclusion for
* links that depend on stack decisions and can't be gained by other means
* e.g. Kicker costs.
*
* @param ctb the source of the ability
* @param card the host that it should be linked to
* @return true if the ability can't be linked
*/
public static boolean isUnlinkedFromCastSA(final CardTraitBase ctb, final Card card) {
// check if it should come from same host
if (ctb != null && ctb.isIntrinsic() && ctb.getHostCard().equals(card)) {
Card host = ctb.getOriginalHost();
SpellAbility castSA = card.getCastSA();
if (host != null && castSA != null) {
Card castHost = castSA.getOriginalHost();
if (castHost == null) {
castHost = castSA.getHostCard();
}
// impossible to match with the other part when not even from same host
if (!host.equals(castHost)) {
return true;
}
}
}
return false;
}
}

View File

@@ -1708,6 +1708,10 @@ public class CardProperty {
if (!game.getPhaseHandler().isPlayerTurn(controller)) return false;
return CombatUtil.couldAttackButNotAttacking(combat, card);
} else if (property.startsWith("kicked")) {
// CR 607.2i check cost is linked
if (AbilityUtils.isUnlinkedFromCastSA(spellAbility, card)) {
return false;
}
if (property.equals("kicked")) {
if (card.getKickerMagnitude() == 0) {
return false;
@@ -1717,10 +1721,6 @@ public class CardProperty {
if ("1".equals(s) && !card.isOptionalCostPaid(OptionalCost.Kicker1)) return false;
if ("2".equals(s) && !card.isOptionalCostPaid(OptionalCost.Kicker2)) return false;
}
} else if (property.startsWith("notkicked")) {
if (card.getKickerMagnitude() > 0) {
return false;
}
} else if (property.startsWith("pseudokicked")) {
if (property.equals("pseudokicked")) {
if (!card.isOptionalCostPaid(OptionalCost.Generic)) return false;

View File

@@ -238,6 +238,10 @@ public final class CardUtil {
newCopy.setCounters(Maps.newHashMap(in.getCounters()));
newCopy.setTributed(in.isTributed());
newCopy.setMonstrous(in.isMonstrous());
newCopy.setRenowned(in.isRenowned());
newCopy.setColor(in.getColor().getColor());
newCopy.setPhasedOut(in.getPhasedOut());
@@ -266,6 +270,15 @@ public final class CardUtil {
newCopy.addImprintedCards(in.getImprintedCards());
newCopy.setChosenCards(new CardCollection(in.getChosenCards()));
newCopy.setChosenType(in.getChosenType());
newCopy.setChosenType2(in.getChosenType2());
newCopy.setChosenName(in.getChosenName());
newCopy.setChosenName2(in.getChosenName2());
newCopy.setChosenColors(Lists.newArrayList(in.getChosenColors()));
if (in.hasChosenNumber()) {
newCopy.setChosenNumber(in.getChosenNumber());
}
for (Table.Cell<Player, CounterType, Integer> cl : in.getEtbCounters()) {
newCopy.addEtbCounter(cl.getColumnKey(), cl.getValue(), cl.getRowKey());
}

View File

@@ -4,10 +4,9 @@ Types:Creature Angel
PT:5/4
K:Flying
K:Kicker:W W
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self+notkicked | Execute$ TrigDestroyYourLand | TriggerDescription$ When CARDNAME enters the battlefield, destroy all lands you control. If it was kicked, destroy all lands instead.
SVar:TrigDestroyYourLand:DB$ DestroyAll | ValidCards$ Land.YouCtrl | SpellDescription$ CARDNAME destroys all land you control.
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self+kicked | Secondary$ True | Execute$ TrigKicker | TriggerDescription$ Kicker: If you paid the kicker cost, destroy all lands instead.
SVar:TrigKicker:DB$ DestroyAll | ValidCards$ Land | SpellDescription$ CARDNAME destroys all land.
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDestroyYourLand | TriggerDescription$ When CARDNAME enters the battlefield, destroy all lands you control. If it was kicked, destroy all lands instead.
SVar:TrigDestroyYourLand:DB$ DestroyAll | ValidCards$ Land.YouCtrl | ConditionDefined$ Self | ConditionPresent$ Card.!kicked | SubAbility$ TrigKicker
SVar:TrigKicker:DB$ DestroyAll | ValidCards$ Land | Condition$ Kicked
AI:RemoveDeck:Random
DeckNeeds:Color$White
Oracle:Kicker {W}{W} (You may pay an additional {W}{W} as you cast this spell.)\nFlying\nWhen Desolation Angel enters the battlefield, destroy all lands you control. If it was kicked, destroy all lands instead.

View File

@@ -3,10 +3,9 @@ ManaCost:2 R R
Types:Creature Giant
PT:3/3
K:Kicker:W W
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self+notkicked | Execute$ TrigDestroy | TriggerDescription$ When CARDNAME enters the battlefield, destroy all other creatures you control. If it was kicked, destroy all other creatures instead.
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self+kicked | Secondary$ True | Execute$ TrigKicker | TriggerDescription$ When CARDNAME enters the battlefield, if it was kicked, destroy all other creatures.
SVar:TrigDestroy:DB$ DestroyAll | ValidCards$ Creature.Other+YouCtrl
SVar:TrigKicker:DB$ DestroyAll | ValidCards$ Creature.Other
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDestroy | TriggerDescription$ When CARDNAME enters the battlefield, destroy all other creatures you control. If it was kicked, destroy all other creatures instead.
SVar:TrigDestroy:DB$ DestroyAll | ValidCards$ Creature.Other+YouCtrl | ConditionDefined$ Self | ConditionPresent$ Card.!kicked | SubAbility$ TrigKicker
SVar:TrigKicker:DB$ DestroyAll | ValidCards$ Creature.Other | Condition$ Kicked
AI:RemoveDeck:All
AI:RemoveDeck:Random
DeckNeeds:Color$White

View File

@@ -4,7 +4,7 @@ Types:Creature Elemental
PT:0/0
K:etbCounter:P1P1:X
SVar:X:Count$xPaid
R:Event$ DamageDone | ActiveZones$ Battlefield | ValidTarget$ Card.Self+counters_GE1_P1P1 | ReplaceWith$ Counters | PreventionEffect$ True | AlwaysReplace$ True | Description$ If damage would be dealt to CARDNAME while it has a +1/+1 counter on it, prevent that damage and remove that many +1/+1 counters from it. When one or more counters are removed from Magma Pummeler this way, it deals that much damage to any target.
R:Event$ DamageDone | ActiveZones$ Battlefield | ValidTarget$ Card.Self+counters_GE1_P1P1 | ReplaceWith$ Counters | PreventionEffect$ True | AlwaysReplace$ True | Description$ If damage would be dealt to CARDNAME while it has a +1/+1 counter on it, prevent that damage and remove that many +1/+1 counters from it. When one or more counters are removed from CARDNAME this way, it deals that much damage to any target.
SVar:Counters:DB$ RemoveCounter | Defined$ ReplacedTarget | CounterType$ P1P1 | CounterNum$ Y | RememberRemoved$ True | SubAbility$ DBImmediateTrigger
SVar:Y:ReplaceCount$DamageAmount
SVar:DBImmediateTrigger:DB$ ImmediateTrigger | Execute$ TrigDamage | RememberSVarAmount$ Z | SubAbility$ DBCleanup | TriggerDescription$ When one or more counters are removed from CARDNAME this way, it deals that much damage to any target.

View File

@@ -5,10 +5,10 @@ K:MayFlashSac
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ RaiseDead | TriggerDescription$ When CARDNAME enters the battlefield, if it's on the battlefield, it becomes an Aura with "enchant creature put onto the battlefield with CARDNAME." Put target creature card from a graveyard onto the battlefield under your control and attach CARDNAME to it. When CARDNAME leaves the battlefield, that creature's controller sacrifices it. When CARDNAME leaves the battlefield, that creature's controller sacrifices it.
SVar:RaiseDead:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | GainControl$ True | RememberChanged$ True | TgtPrompt$ Select target creature card in a graveyard | ValidTgts$ Creature | ChangeNum$ 1 | SubAbility$ Aurify
SVar:Aurify:DB$ Animate | IsPresent$ Card.Self | Types$ Aura | OverwriteSpells$ True | Abilities$ NewAttach | Keywords$ Enchant creature put onto the battlefield with CARDNAME | Duration$ Permanent | SubAbility$ NecromAttach
SVar:NewAttach:SP$ Attach | Cost$ 2 B | ValidTgts$ Creature.IsRemembered | AILogic$ Pump | SubAbility$ DBDelay
SVar:NewAttach:SP$ Attach | Cost$ 2 B | ValidTgts$ Creature.IsRemembered | AILogic$ Pump
SVar:DBDelay:DB$ DelayedTrigger | Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Battlefield | Execute$ TrigSacrifice | RememberObjects$ RememberedLKI | TriggerDescription$ When CARDNAME leaves the battlefield, that creature's controller sacrifices it.
SVar:NecromAttach:DB$ Attach | Defined$ Remembered
SVar:TrigSacrifice:DB$ Destroy | Sacrifice$ True | Defined$ DirectRemembered
SVar:NecromAttach:DB$ Attach | Defined$ Remembered | SubAbility$ DBDelay
SVar:TrigSacrifice:DB$ Destroy | Sacrifice$ True | Defined$ DelayTriggerRememberedLKI
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ DBCleanup | Static$ True
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:NeedsToPlayVar:Y GE1

View File

@@ -5,7 +5,7 @@ PT:5/3
K:Haste
K:Trample
K:Kicker:R
T:Mode$ Phase | Phase$ End of Turn | IsPresent$ Card.Self+notkicked | Execute$ TrigNotKicked | TriggerZones$ Battlefield | TriggerDescription$ At the beginning of the end step, if CARDNAME wasn't kicked, sacrifice it.
T:Mode$ Phase | Phase$ End of Turn | IsPresent$ Card.Self+!kicked | Execute$ TrigNotKicked | TriggerZones$ Battlefield | TriggerDescription$ At the beginning of the end step, if CARDNAME wasn't kicked, sacrifice it.
SVar:TrigNotKicked:DB$ Sacrifice
# The following construct specifies that the AI always plays the spell kicked, and plays it unkicked on its own turn only if it will attack
SVar:NeedsToPlay:WillAttack

View File

@@ -4,9 +4,7 @@ Types:Creature Sphinx
PT:3/5
K:Flying
K:Kicker:1 U
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self+notkicked | Execute$ TrigDraw | TriggerDescription$ When CARDNAME enters the battlefield, draw three cards. Then if it wasn't kicked, discard three cards.
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self+kicked | Secondary$ True | Execute$ TrigKicker | TriggerDescription$ When CARDNAME enters the battlefield, if it was kicked, draw three cards.
SVar:TrigKicker:DB$ Draw | NumCards$ 3
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDraw | TriggerDescription$ When CARDNAME enters the battlefield, draw three cards. Then if it wasn't kicked, discard three cards.
SVar:TrigDraw:DB$ Draw | NumCards$ 3 | SubAbility$ DBDiscard
SVar:DBDiscard:DB$ Discard | Defined$ You | Mode$ TgtChoose | NumCards$ 3
SVar:DBDiscard:DB$ Discard | Defined$ You | Mode$ TgtChoose | NumCards$ 3 | ConditionDefined$ Self | ConditionPresent$ Card.!kicked
Oracle:Kicker {1}{U} (You may pay an additional {1}{U} as you cast this spell.)\nFlying\nWhen Sphinx of Lost Truths enters the battlefield, draw three cards. Then if it wasn't kicked, discard three cards.

View File

@@ -489,7 +489,7 @@ public final class CardScriptParser {
"blockedBySourceThisTurn", "blockedSource",
"isBlockedByRemembered", "blockedRemembered",
"blockedByRemembered", "unblocked", "attackersBandedWith",
"kicked", "kicked1", "kicked2", "notkicked", "evoked",
"kicked", "kicked1", "kicked2", "evoked",
"HasDevoured", "HasNotDevoured", "IsMonstrous", "IsNotMonstrous",
"CostsPhyrexianMana", "IsRemembered", "IsNotRemembered",
"IsImprinted", "IsNotImprinted", "hasActivatedAbilityWithTapCost",