Fix getTotalPreventionShieldAmount (#3654)

* Fix scripts

* Fix damage prevention display

* Optimize attachment tracking for netplay

* Phasing fix

* Fix NPE

* Tweak logic order

* Clean up

* Fix scripts

* Clean up

* Fix FailedToTarget

---------

Co-authored-by: TRT <>
This commit is contained in:
tool4ever
2023-08-23 10:34:50 +02:00
committed by GitHub
parent de72773634
commit 2025487c34
22 changed files with 74 additions and 56 deletions

View File

@@ -398,10 +398,8 @@ public class ComputerUtilCost {
return false; return false;
} }
for (final CostPart part : cost.getCostParts()) { for (final CostPart part : cost.getCostParts()) {
if (part instanceof CostSacrifice) { if (part instanceof CostSacrifice && part.payCostFromSource()) {
if ("CARDNAME".equals(part.getType())) { return true;
return true;
}
} }
} }
return false; return false;

View File

@@ -85,7 +85,7 @@ public class DamagePreventAi extends SpellAbilityAi {
// check stack for something on the stack will kill anything i control // check stack for something on the stack will kill anything i control
final List<GameObject> objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa); final List<GameObject> objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa);
if (objects.contains(ai)) { if (objects.contains(ai) && sa.canTarget(ai)) {
tcs.add(ai); tcs.add(ai);
chance = true; chance = true;
} }

View File

@@ -216,6 +216,7 @@ public class EffectAi extends SpellAbilityAi {
} else if (logic.equals("Fight")) { } else if (logic.equals("Fight")) {
return FightAi.canFightAi(ai, sa, 0, 0); return FightAi.canFightAi(ai, sa, 0, 0);
} else if (logic.equals("Pump")) { } else if (logic.equals("Pump")) {
sa.resetTargets();
List<Card> options = CardUtil.getValidCardsToTarget(sa); List<Card> options = CardUtil.getValidCardsToTarget(sa);
options = CardLists.filterControlledBy(options, ai); options = CardLists.filterControlledBy(options, ai);
if (sa.getPayCosts().hasTapCost()) { if (sa.getPayCosts().hasTapCost()) {

View File

@@ -89,7 +89,7 @@ public class ManaEffectAi extends SpellAbilityAi {
if (logic.startsWith("ManaRitual")) { if (logic.startsWith("ManaRitual")) {
return ph.is(PhaseType.MAIN2, ai) || ph.is(PhaseType.MAIN1, ai); return ph.is(PhaseType.MAIN2, ai) || ph.is(PhaseType.MAIN1, ai);
} else if ("AtOppEOT".equals(logic)) { } else if ("AtOppEOT".equals(logic)) {
return !ai.getManaPool().hasBurn() && ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn() == ai; return (!ai.getManaPool().hasBurn() || !ai.canLoseLife() || ai.cantLoseForZeroOrLessLife()) && ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn() == ai;
} }
return super.checkPhaseRestrictions(ai, sa, ph, logic); return super.checkPhaseRestrictions(ai, sa, ph, logic);
} }
@@ -261,7 +261,8 @@ public class ManaEffectAi extends SpellAbilityAi {
} }
private boolean improvesPosition(Player ai, SpellAbility sa) { private boolean improvesPosition(Player ai, SpellAbility sa) {
boolean activateForTrigger = Iterables.any(Iterables.filter(sa.getHostCard().getTriggers(), CardTraitPredicates.hasParam("AILogic", "ActivateOnce")), boolean activateForTrigger = (!ai.getManaPool().hasBurn() || !ai.canLoseLife() || ai.cantLoseForZeroOrLessLife()) &&
Iterables.any(Iterables.filter(sa.getHostCard().getTriggers(), CardTraitPredicates.hasParam("AILogic", "ActivateOnce")),
t -> sa.getHostCard().getAbilityActivatedThisTurn(t.getOverridingAbility()) == 0); t -> sa.getHostCard().getAbilityActivatedThisTurn(t.getOverridingAbility()) == 0);
PhaseHandler ph = ai.getGame().getPhaseHandler(); PhaseHandler ph = ai.getGame().getPhaseHandler();

View File

@@ -1,6 +1,7 @@
package forge.game; package forge.game;
import forge.game.card.CardCollectionView; import com.google.common.collect.Iterables;
import forge.game.card.CardView; import forge.game.card.CardView;
import forge.trackable.TrackableCollection; import forge.trackable.TrackableCollection;
import forge.trackable.TrackableObject; import forge.trackable.TrackableObject;
@@ -45,35 +46,34 @@ public abstract class GameEntityView extends TrackableObject {
public int getPreventNextDamage() { public int getPreventNextDamage() {
return get(TrackableProperty.PreventNextDamage); return get(TrackableProperty.PreventNextDamage);
} }
protected void updatePreventNextDamage(GameEntity e) { public void updatePreventNextDamage(GameEntity e) {
set(TrackableProperty.PreventNextDamage, e.getPreventNextDamageTotalShields()); set(TrackableProperty.PreventNextDamage, e.getPreventNextDamageTotalShields());
} }
public Iterable<CardView> getAttachedCards() { public Iterable<CardView> getAttachedCards() {
return get(TrackableProperty.AttachedCards); if (hasAnyCardAttachments()) {
Iterable<CardView> active = Iterables.filter(get(TrackableProperty.AttachedCards), c -> !c.isPhasedOut());
if (!Iterables.isEmpty(active)) {
return active;
}
}
return null;
} }
public boolean hasCardAttachments() { public boolean hasCardAttachments() {
return getAttachedCards() != null; return getAttachedCards() != null;
} }
public Iterable<CardView> getAllAttachedCards() { public Iterable<CardView> getAllAttachedCards() {
return get(TrackableProperty.AllAttachedCards); return get(TrackableProperty.AttachedCards);
} }
public boolean hasAnyCardAttachments() { public boolean hasAnyCardAttachments() {
return getAllAttachedCards() != null; return getAllAttachedCards() != null;
} }
protected void updateAttachedCards(GameEntity e) { protected void updateAttachedCards(GameEntity e) {
if (e.hasCardAttachments()) { if (!e.getAllAttachedCards().isEmpty()) {
set(TrackableProperty.AttachedCards, CardView.getCollection(e.getAttachedCards())); set(TrackableProperty.AttachedCards, CardView.getCollection(e.getAllAttachedCards()));
}
else {
set(TrackableProperty.AttachedCards, null);
}
CardCollectionView all = e.getAllAttachedCards();
if (all.isEmpty()) {
set(TrackableProperty.AllAttachedCards, null);
} else { } else {
set(TrackableProperty.AllAttachedCards, CardView.getCollection(all)); set(TrackableProperty.AttachedCards, null);
} }
} }
} }

View File

@@ -1999,13 +1999,6 @@ public class AbilityUtils {
return doXMath(calculateAmount(c, sq[c.isOptionalCostPaid(OptionalCost.Generic) ? 1 : 2], ctb), expr, c, ctb); return doXMath(calculateAmount(c, sq[c.isOptionalCostPaid(OptionalCost.Generic) ? 1 : 2], ctb), expr, c, ctb);
} }
if (sq[0].equals("TotalDamageDoneByThisTurn")) {
return doXMath(c.getTotalDamageDoneBy(), expr, c, ctb);
}
if (sq[0].equals("TotalDamageReceivedThisTurn")) {
return doXMath(c.getAssignedDamage(), expr, c, ctb);
}
if (sq[0].contains("CardPower")) { if (sq[0].contains("CardPower")) {
return doXMath(c.getNetPower(), expr, c, ctb); return doXMath(c.getNetPower(), expr, c, ctb);
} }
@@ -2335,6 +2328,13 @@ public class AbilityUtils {
return doXMath(player.getOpponentsTotalPoisonCounters(), expr, c, ctb); return doXMath(player.getOpponentsTotalPoisonCounters(), expr, c, ctb);
} }
if (sq[0].equals("TotalDamageDoneByThisTurn")) {
return doXMath(c.getTotalDamageDoneBy(), expr, c, ctb);
}
if (sq[0].equals("TotalDamageReceivedThisTurn")) {
return doXMath(c.getAssignedDamage(), expr, c, ctb);
}
if (sq[0].equals("MaxOppDamageThisTurn")) { if (sq[0].equals("MaxOppDamageThisTurn")) {
return doXMath(player.getMaxOpponentAssignedDamage(), expr, c, ctb); return doXMath(player.getMaxOpponentAssignedDamage(), expr, c, ctb);
} }
@@ -3493,6 +3493,7 @@ public class AbilityUtils {
if (value.equals("RingTemptedYou")) { if (value.equals("RingTemptedYou")) {
return doXMath(player.getNumRingTemptedYou(), m, source, ctb); return doXMath(player.getNumRingTemptedYou(), m, source, ctb);
} }
if (value.startsWith("DungeonCompletedNamed")) { if (value.startsWith("DungeonCompletedNamed")) {
String [] full = value.split("_"); String [] full = value.split("_");
String name = full[1]; String name = full[1];

View File

@@ -4,12 +4,13 @@ import java.util.List;
import forge.GameCommand; import forge.GameCommand;
import forge.game.Game; import forge.game.Game;
import forge.game.GameObject; import forge.game.GameEntity;
import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityKey; import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.event.GameEventPlayerStatsChanged;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.replacement.ReplacementEffect; import forge.game.replacement.ReplacementEffect;
import forge.game.replacement.ReplacementHandler; import forge.game.replacement.ReplacementHandler;
@@ -20,7 +21,7 @@ import forge.game.zone.ZoneType;
import forge.util.TextUtil; import forge.util.TextUtil;
public abstract class DamagePreventEffectBase extends SpellAbilityEffect { public abstract class DamagePreventEffectBase extends SpellAbilityEffect {
public static void addPreventNextDamage(SpellAbility sa, GameObject o, int numDam) { public static void addPreventNextDamage(SpellAbility sa, GameEntity o, int numDam) {
final Card hostCard = sa.getHostCard(); final Card hostCard = sa.getHostCard();
final Game game = hostCard.getGame(); final Game game = hostCard.getGame();
final Player player = hostCard.getController(); final Player player = hostCard.getController();
@@ -40,9 +41,9 @@ public abstract class DamagePreventEffectBase extends SpellAbilityEffect {
if (sa.hasParam("PreventionSubAbility")) { if (sa.hasParam("PreventionSubAbility")) {
String subAbString = sa.getSVar(sa.getParam("PreventionSubAbility")); String subAbString = sa.getSVar(sa.getParam("PreventionSubAbility"));
if (sa.hasParam("ShieldEffectTarget")) { if (sa.hasParam("ShieldEffectTarget")) {
List<GameObject> effTgts = AbilityUtils.getDefinedObjects(hostCard, sa.getParam("ShieldEffectTarget"), sa); List<GameEntity> effTgts = AbilityUtils.getDefinedEntities(hostCard, sa.getParam("ShieldEffectTarget"), sa);
String effTgtString = ""; String effTgtString = "";
for (final GameObject effTgt : effTgts) { for (final GameEntity effTgt : effTgts) {
if (effTgt instanceof Card) { if (effTgt instanceof Card) {
effTgtString = "CardUID_" + String.valueOf(((Card) effTgt).getId()); effTgtString = "CardUID_" + String.valueOf(((Card) effTgt).getId());
} else if (effTgt instanceof Player) { } else if (effTgt instanceof Player) {
@@ -70,12 +71,21 @@ public abstract class DamagePreventEffectBase extends SpellAbilityEffect {
eff.updateStateForView(); eff.updateStateForView();
game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone);
o.getView().updatePreventNextDamage(o);
if (o instanceof Player) {
game.fireEvent(new GameEventPlayerStatsChanged((Player) o, false));
}
game.getEndOfTurn().addUntil(new GameCommand() { game.getEndOfTurn().addUntil(new GameCommand() {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@Override @Override
public void run() { public void run() {
game.getAction().exile(eff, null); game.getAction().exile(eff, null);
o.getView().updatePreventNextDamage(o);
if (o instanceof Player) {
game.fireEvent(new GameEventPlayerStatsChanged((Player) o, false));
}
} }
}); });
} }

View File

@@ -5636,7 +5636,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
/** /**
* Gets the total damage done by card this turn (after prevention and redirects). * Gets the total damage done by card this turn (after prevention and redirects).
* *
* @return the damage done to player p this turn * @return the damage done by the card this turn
*/ */
public final int getTotalDamageDoneBy() { public final int getTotalDamageDoneBy() {
return getDamageHistory().getDamageDoneThisTurn(null, false, null, null, this, getController(), null); return getDamageHistory().getDamageDoneThisTurn(null, false, null, null, this, getController(), null);

View File

@@ -19,6 +19,7 @@ import forge.game.Game;
import forge.game.GameEntity; import forge.game.GameEntity;
import forge.game.GameObjectPredicates; import forge.game.GameObjectPredicates;
import forge.game.ability.AbilityKey; import forge.game.ability.AbilityKey;
import forge.game.event.GameEventPlayerStatsChanged;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.player.PlayerCollection; import forge.game.player.PlayerCollection;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
@@ -48,6 +49,11 @@ public class CardDamageMap extends ForwardingTable<Card, GameEntity, Integer> {
runParams.put(AbilityKey.IsCombatDamage, isCombat); runParams.put(AbilityKey.IsCombatDamage, isCombat);
ge.getGame().getTriggerHandler().runTrigger(TriggerType.DamagePreventedOnce, runParams, false); ge.getGame().getTriggerHandler().runTrigger(TriggerType.DamagePreventedOnce, runParams, false);
ge.getView().updatePreventNextDamage(ge);
if (ge instanceof Player) {
ge.getGame().fireEvent(new GameEventPlayerStatsChanged((Player) ge, false));
}
} }
} }
} }

View File

@@ -362,7 +362,6 @@ public class AttackConstraints {
MapToAmount<GameEntity> sortedPlayerReqs = new LinkedHashMapToAmount<>(); MapToAmount<GameEntity> sortedPlayerReqs = new LinkedHashMapToAmount<>();
sortedPlayerReqs.addAll(Iterables.concat(playerReqs)); sortedPlayerReqs.addAll(Iterables.concat(playerReqs));
while (!sortedPlayerReqs.isEmpty()) { while (!sortedPlayerReqs.isEmpty()) {
sortedPlayerReqs.keySet().removeAll(excludedDefenders);
Pair<GameEntity, Integer> playerReq = MapToAmountUtil.max(sortedPlayerReqs); Pair<GameEntity, Integer> playerReq = MapToAmountUtil.max(sortedPlayerReqs);
// find best attack to also fulfill the additional requirements // find best attack to also fulfill the additional requirements
Attack bestMatch = Iterables.getLast(Iterables.filter(result, att -> !usedAttackers.contains(att.attacker) && att.defender.equals(playerReq.getLeft())), null); Attack bestMatch = Iterables.getLast(Iterables.filter(result, att -> !usedAttackers.contains(att.attacker) && att.defender.equals(playerReq.getLeft())), null);
@@ -376,6 +375,7 @@ public class AttackConstraints {
} else { } else {
excludedDefenders.add(playerReq.getLeft()); excludedDefenders.add(playerReq.getLeft());
} }
sortedPlayerReqs.keySet().removeAll(excludedDefenders);
} }
if (!usedAttackers.isEmpty()) { if (!usedAttackers.isEmpty()) {
// order could have changed // order could have changed

View File

@@ -42,6 +42,7 @@ import forge.game.keyword.KeywordInterface;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.player.PlayerController.BinaryChoiceType; import forge.game.player.PlayerController.BinaryChoiceType;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.staticability.StaticAbilityCantPhaseOut;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
/** /**
@@ -266,14 +267,14 @@ public class Untap extends Phase {
// If c is attached to something, it will phase out on its own, and try // If c is attached to something, it will phase out on its own, and try
// to attach back to that thing when it comes back // to attach back to that thing when it comes back
for (final Card c : list) { for (final Card c : list) {
if (c.isPhasedOut()) { if (c.isPhasedOut() && c.isDirectlyPhasedOut()) {
c.phase(true); c.phase(true);
} else if (c.hasKeyword(Keyword.PHASING)) { } else if (c.hasKeyword(Keyword.PHASING)) {
// CR 702.26h If an object would simultaneously phase out directly // CR 702.26h If an object would simultaneously phase out directly
// and indirectly, it just phases out indirectly. // and indirectly, it just phases out indirectly.
if (c.isAttachment()) { if (c.isAttachment()) {
final Card ent = c.getAttachedTo(); final Card ent = c.getAttachedTo();
if (ent != null && list.contains(ent)) { if (ent != null && list.contains(ent) && !StaticAbilityCantPhaseOut.cantPhaseOut(ent)) {
continue; continue;
} }
} }

View File

@@ -145,7 +145,6 @@ public abstract class ReplacementEffect extends TriggerReplacementBase {
public boolean requirementsCheck(Game game) { public boolean requirementsCheck(Game game) {
return this.requirementsCheck(game, this.getMapParams()); return this.requirementsCheck(game, this.getMapParams());
} }
public boolean requirementsCheck(Game game, Map<String,String> params) { public boolean requirementsCheck(Game game, Map<String,String> params) {
if (this.isSuppressed()) { if (this.isSuppressed()) {
return false; // Effect removed by effect return false; // Effect removed by effect

View File

@@ -911,7 +911,8 @@ public class ReplacementHandler {
&& re.hasParam("PreventionEffect") && re.hasParam("PreventionEffect")
&& re.zonesCheck(game.getZoneOf(c)) && re.zonesCheck(game.getZoneOf(c))
&& re.getOverridingAbility() != null && re.getOverridingAbility() != null
&& re.getOverridingAbility().getApi() == ApiType.ReplaceDamage) { && re.getOverridingAbility().getApi() == ApiType.ReplaceDamage
&& re.matchesValidParam("ValidTarget", o)) {
list.add(re); list.add(re);
} }
} }

View File

@@ -14,7 +14,6 @@ public enum TrackableProperty {
Text(TrackableTypes.StringType), Text(TrackableTypes.StringType),
PreventNextDamage(TrackableTypes.IntegerType), PreventNextDamage(TrackableTypes.IntegerType),
AttachedCards(TrackableTypes.CardViewCollectionType), AttachedCards(TrackableTypes.CardViewCollectionType),
AllAttachedCards(TrackableTypes.CardViewCollectionType),
Counters(TrackableTypes.CounterMapType), Counters(TrackableTypes.CounterMapType),
CurrentPlane(TrackableTypes.StringType), CurrentPlane(TrackableTypes.StringType),
PlanarPlayer(TrackableTypes.PlayerViewType), PlanarPlayer(TrackableTypes.PlayerViewType),

View File

@@ -595,8 +595,7 @@ public class PlayArea extends CardPanelContainer implements CardPanelMouseListen
Iterable<CardView> cards = model.getCards(zone); Iterable<CardView> cards = model.getCards(zone);
if (cards != null) { if (cards != null) {
modelCopy = Lists.newArrayList(cards); modelCopy = Lists.newArrayList(cards);
} } else {
else {
modelCopy = Lists.newArrayList(); modelCopy = Lists.newArrayList();
} }
} }
@@ -648,17 +647,17 @@ public class PlayArea extends CardPanelContainer implements CardPanelMouseListen
doLayout(); doLayout();
} }
invalidate(); //pfps do the extra invalidate before any scrolling invalidate(); //pfps do the extra invalidate before any scrolling
if (!newPanels.isEmpty()) { if (!newPanels.isEmpty()) {
int i = newPanels.size(); int i = newPanels.size();
for (final CardPanel toPanel : newPanels) { for (final CardPanel toPanel : newPanels) {
if ( --i == 0 ) { // only scroll to last panel to be added if ( --i == 0 ) { // only scroll to last panel to be added
scrollRectToVisible(new Rectangle(toPanel.getCardX(), toPanel.getCardY(), toPanel.getCardWidth(), toPanel.getCardHeight())); scrollRectToVisible(new Rectangle(toPanel.getCardX(), toPanel.getCardY(), toPanel.getCardWidth(), toPanel.getCardHeight()));
} }
Animation.moveCard(toPanel); Animation.moveCard(toPanel);
} }
} }
repaint(); repaint();
} }
public boolean updateCard(final CardView card, boolean fromRefresh) { public boolean updateCard(final CardView card, boolean fromRefresh) {

View File

@@ -2,5 +2,6 @@ Name:Break of Day
ManaCost:1 W ManaCost:1 W
Types:Instant Types:Instant
A:SP$ PumpAll | Cost$ 1 W | ValidCards$ Creature.YouCtrl | NumAtt$ +1 | NumDef$ +1 | SubAbility$ FatefulHourPump | SpellDescription$ Creatures you control get +1/+1 until end of turn. A:SP$ PumpAll | Cost$ 1 W | ValidCards$ Creature.YouCtrl | NumAtt$ +1 | NumDef$ +1 | SubAbility$ FatefulHourPump | SpellDescription$ Creatures you control get +1/+1 until end of turn.
SVar:FatefulHourPump:DB$ PumpAll | ValidCards$ Creature.YouCtrl | KW$ Indestructible | FatefulHour$ True | SpellDescription$ Fateful hour — If you have 5 or less life, those creatures gain indestructible until end of turn. SVar:FatefulHourPump:DB$ PumpAll | ValidCards$ Creature.YouCtrl | KW$ Indestructible | ConditionCheckSVar$ FatefulHour | ConditionSVarCompare$ LE5 | SpellDescription$ Fateful hour — If you have 5 or less life, those creatures gain indestructible until end of turn.
SVar:FatefulHour:Count$YourLifeTotal
Oracle:Creatures you control get +1/+1 until end of turn.\nFateful hour — If you have 5 or less life, those creatures gain indestructible until end of turn. (Damage and effects that say "destroy" don't destroy them.) Oracle:Creatures you control get +1/+1 until end of turn.\nFateful hour — If you have 5 or less life, those creatures gain indestructible until end of turn. (Damage and effects that say "destroy" don't destroy them.)

View File

@@ -2,7 +2,7 @@ Name:Ogre Slumlord
ManaCost:3 B B ManaCost:3 B B
Types:Creature Ogre Rogue Types:Creature Ogre Rogue
PT:3/3 PT:3/3
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Creature.nonToken+Other | TriggerZones$ Battlefield | Execute$ TrigToken | TriggerDescription$ Whenever another nontoken creature dies, you may create a 1/1 black Rat creature token. T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Creature.nonToken+Other | OptionalDecider$ You | TriggerZones$ Battlefield | Execute$ TrigToken | TriggerDescription$ Whenever another nontoken creature dies, you may create a 1/1 black Rat creature token.
SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ b_1_1_rat | TokenOwner$ You SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ b_1_1_rat | TokenOwner$ You
S:Mode$ Continuous | Affected$ Creature.Rat+YouCtrl | AddKeyword$ Deathtouch | Description$ Rats you control have deathtouch. S:Mode$ Continuous | Affected$ Creature.Rat+YouCtrl | AddKeyword$ Deathtouch | Description$ Rats you control have deathtouch.
DeckHas:Ability$Token DeckHas:Ability$Token

View File

@@ -4,7 +4,7 @@ Types:Legendary Creature Spirit Naga
PT:0/0 PT:0/0
K:etbCounter:P1P1:X K:etbCounter:P1P1:X
SVar:X:Count$xPaid SVar:X:Count$xPaid
T:Mode$ CounterAddedOnce | ValidCard$ Creature.Other+inZoneBattlefield+Colorless | OptionalDecider$ You | TriggerZones$ Battlefield | CounterType$ P1P1 | Execute$ TrigPutCounter | TriggerDescription$ Whenever you put one or more +1/+1 counters on another colorless creature, you may put a +1/+1 counter on NICKNAME. T:Mode$ CounterAddedOnce | ValidCard$ Creature.Other+inZoneBattlefield+Colorless | ValidPlayer$ You | OptionalDecider$ You | TriggerZones$ Battlefield | CounterType$ P1P1 | Execute$ TrigPutCounter | TriggerDescription$ Whenever you put one or more +1/+1 counters on another colorless creature, you may put a +1/+1 counter on NICKNAME.
SVar:TrigPutCounter:DB$ PutCounter | CounterType$ P1P1 | CounterNum$ 1 SVar:TrigPutCounter:DB$ PutCounter | CounterType$ P1P1 | CounterNum$ 1
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigManifest | TriggerDescription$ When NICKNAME dies, manifest a number of cards from the top of your library equal to the number of counters on it. T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigManifest | TriggerDescription$ When NICKNAME dies, manifest a number of cards from the top of your library equal to the number of counters on it.
SVar:TrigManifest:DB$ Manifest | Amount$ Y | Defined$ TopOfLibrary SVar:TrigManifest:DB$ Manifest | Amount$ Y | Defined$ TopOfLibrary

View File

@@ -4,7 +4,7 @@ Types:Enchantment Aura
K:Flash K:Flash
K:Enchant creature you control K:Enchant creature you control
A:SP$ Attach | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | AILogic$ Pump A:SP$ Attach | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | AILogic$ Pump
T:Mode$ ChangesZone | ValidCard$ Creature.EnchantedBy,Creature.modified+NotEnchantedBy | Origin$ Battlefield | Destination$ Graveyard | Execute$ TrigToken | TriggerDescription$ Whenever enchanted creature or another modified creature you control dies, create X 1/1 colorless Spirit creature tokens, where X is that creature's power. (Equipment, Auras you control, and counters are modifications.) T:Mode$ ChangesZone | ValidCard$ Creature.EnchantedBy,Creature.YouCtrl+modified+NotEnchantedBy | Origin$ Battlefield | Destination$ Graveyard | Execute$ TrigToken | TriggerDescription$ Whenever enchanted creature or another modified creature you control dies, create X 1/1 colorless Spirit creature tokens, where X is that creature's power. (Equipment, Auras you control, and counters are modifications.)
SVar:TrigToken:DB$ Token | TokenScript$ c_1_1_spirit | TokenAmount$ X SVar:TrigToken:DB$ Token | TokenScript$ c_1_1_spirit | TokenAmount$ X
SVar:X:TriggeredCard$CardPower SVar:X:TriggeredCard$CardPower
DeckHas:Ability$Token & Type$Spirit DeckHas:Ability$Token & Type$Spirit

View File

@@ -3,5 +3,5 @@ ManaCost:2 R
Types:Creature Goblin Rogue Types:Creature Goblin Rogue
PT:3/2 PT:3/2
T:Mode$ TapsForMana | ValidCard$ Artifact | Activator$ Opponent | TriggerZones$ Battlefield | Execute$ TrigControl | TriggerDescription$ Whenever an opponent taps an artifact for mana, gain control of that artifact until the end of your next turn. T:Mode$ TapsForMana | ValidCard$ Artifact | Activator$ Opponent | TriggerZones$ Battlefield | Execute$ TrigControl | TriggerDescription$ Whenever an opponent taps an artifact for mana, gain control of that artifact until the end of your next turn.
SVar:TrigControl:DB$ GainControl | Defined$ TriggeredCard | LoseControl$ UntilTheEndOfYourNextTurn SVar:TrigControl:DB$ GainControl | Defined$ TriggeredCardLKICopy | LoseControl$ UntilTheEndOfYourNextTurn
Oracle:Whenever an opponent taps an artifact for mana, gain control of that artifact until the end of your next turn. Oracle:Whenever an opponent taps an artifact for mana, gain control of that artifact until the end of your next turn.

View File

@@ -2,7 +2,7 @@ Name:Unexpected Allies
ManaCost:1 R ManaCost:1 R
Types:Sorcery Types:Sorcery
A:SP$ Pump | ValidTgts$ Creature.YouCtrl+nonToken | TgtPrompt$ Select target nontoken creature you control | KW$ Double team | NumAtt$ 2 | SubAbility$ TrigEffect | SpellDescription$ Target nontoken creature you control gets +2/+0 and gains double team until end of turn. It also gains first strike until end of turn if it has the same name as another creature you control or a creature card in your graveyard. A:SP$ Pump | ValidTgts$ Creature.YouCtrl+nonToken | TgtPrompt$ Select target nontoken creature you control | KW$ Double team | NumAtt$ 2 | SubAbility$ TrigEffect | SpellDescription$ Target nontoken creature you control gets +2/+0 and gains double team until end of turn. It also gains first strike until end of turn if it has the same name as another creature you control or a creature card in your graveyard.
SVar:TrigEffect:DB$ Pump | ConditionCheckSVar$ X | Defined$ Targeted | KW$ First strike SVar:TrigEffect:DB$ Pump | ConditionCheckSVar$ X | Defined$ Targeted | KW$ First Strike
SVar:X:Count$Valid Creature.NotDefinedTargeted+YouCtrl+sharesNameWith Targeted/Plus.Y SVar:X:Count$Valid Creature.NotDefinedTargeted+YouCtrl+sharesNameWith Targeted/Plus.Y
SVar:Y:Count$ValidGraveyard Creature.YouOwn+sharesNameWith Targeted SVar:Y:Count$ValidGraveyard Creature.YouOwn+sharesNameWith Targeted
DeckHas:Keyword$Double Team|First Strike DeckHas:Keyword$Double Team|First Strike

View File

@@ -69,7 +69,6 @@ public class InputAttack extends InputSyncronizedBase {
@Override @Override
public final void showMessage() { public final void showMessage() {
// TODO still seems to have some issues with multiple planeswalkers
setCurrentDefender(defenders.getFirst()); setCurrentDefender(defenders.getFirst());
if (currentDefender == null) { if (currentDefender == null) {
@@ -295,7 +294,9 @@ public class InputAttack extends InputSyncronizedBase {
getController().getGui().setHighlighted(PlayerView.get((Player) ge), ge == def); getController().getGui().setHighlighted(PlayerView.get((Player) ge), ge == def);
} }
} }
potentialBanding = isBandingPossible(); if (def != null) {
potentialBanding = isBandingPossible();
}
updateMessage(); updateMessage();
} }