Merge branch 'runChaosEffect' into 'master'

RunChaos: better effect for Pools of Becoming

See merge request core-developers/forge!3362
This commit is contained in:
Michael Kamensky
2020-11-09 13:23:41 +00:00
13 changed files with 84 additions and 121 deletions

View File

@@ -31,7 +31,6 @@ import forge.deck.CardPool;
import forge.deck.Deck; import forge.deck.Deck;
import forge.deck.DeckSection; import forge.deck.DeckSection;
import forge.game.*; import forge.game.*;
import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType; import forge.game.ability.ApiType;
import forge.game.ability.SpellApiBased; import forge.game.ability.SpellApiBased;
@@ -64,7 +63,6 @@ import forge.util.collect.FCollectionView;
import io.sentry.Sentry; import io.sentry.Sentry;
import io.sentry.event.BreadcrumbBuilder; import io.sentry.event.BreadcrumbBuilder;
import java.security.InvalidParameterException;
import java.util.*; import java.util.*;
import java.util.Map.Entry; import java.util.Map.Entry;
@@ -204,24 +202,22 @@ public class AiController {
return api == null; return api == null;
} }
boolean rightapi = false; boolean rightapi = false;
String battlefield = ZoneType.Battlefield.toString();
Player activatingPlayer = sa.getActivatingPlayer(); Player activatingPlayer = sa.getActivatingPlayer();
// Trigger play improvements // Trigger play improvements
for (final Trigger tr : card.getTriggers()) { for (final Trigger tr : card.getTriggers()) {
// These triggers all care for ETB effects // These triggers all care for ETB effects
final Map<String, String> params = tr.getMapParams();
if (tr.getMode() != TriggerType.ChangesZone) { if (tr.getMode() != TriggerType.ChangesZone) {
continue; continue;
} }
if (!params.get("Destination").equals(battlefield)) { if (!ZoneType.Battlefield.toString().equals(tr.getParam("Destination"))) {
continue; continue;
} }
if (params.containsKey("ValidCard")) { if (tr.hasParam("ValidCard")) {
String validCard = params.get("ValidCard"); String validCard = tr.getParam("ValidCard");
if (!validCard.contains("Self")) { if (!validCard.contains("Self")) {
continue; continue;
} }
@@ -245,21 +241,11 @@ public class AiController {
} }
// if trigger is not mandatory - no problem // if trigger is not mandatory - no problem
if (params.get("OptionalDecider") != null && api == null) { if (tr.hasParam("OptionalDecider") && api == null) {
continue; continue;
} }
SpellAbility exSA = tr.getOverridingAbility(); SpellAbility exSA = tr.ensureAbility().copy(activatingPlayer);
if (exSA == null) {
// Maybe better considerations
if (!params.containsKey("Execute")) {
continue;
}
exSA = AbilityFactory.getAbility(card, params.get("Execute"));
} else {
exSA = exSA.copy();
}
if (api != null) { if (api != null) {
if (exSA.getApi() != api) { if (exSA.getApi() != api) {
@@ -273,13 +259,7 @@ public class AiController {
} }
} }
if (sa != null) { exSA.setTrigger(tr);
exSA.setActivatingPlayer(activatingPlayer);
}
else {
exSA.setActivatingPlayer(player);
}
exSA.setTrigger(true);
// for trigger test, need to ignore the conditions // for trigger test, need to ignore the conditions
SpellAbilityCondition cons = exSA.getConditions(); SpellAbilityCondition cons = exSA.getConditions();
@@ -304,18 +284,16 @@ public class AiController {
// Replacement effects // Replacement effects
for (final ReplacementEffect re : card.getReplacementEffects()) { for (final ReplacementEffect re : card.getReplacementEffects()) {
// These Replacements all care for ETB effects // These Replacements all care for ETB effects
final Map<String, String> params = re.getMapParams();
if (!(re instanceof ReplaceMoved)) { if (!(re instanceof ReplaceMoved)) {
continue; continue;
} }
if (!params.get("Destination").equals(battlefield)) { if (!ZoneType.Battlefield.toString().equals(re.getParam("Destination"))) {
continue; continue;
} }
if (params.containsKey("ValidCard")) { if (re.hasParam("ValidCard")) {
String validCard = params.get("ValidCard"); String validCard = re.getParam("ValidCard");
if (!validCard.contains("Self")) { if (!validCard.contains("Self")) {
continue; continue;
} }
@@ -337,27 +315,18 @@ public class AiController {
if (!re.requirementsCheck(game)) { if (!re.requirementsCheck(game)) {
continue; continue;
} }
final SpellAbility exSA = re.getOverridingAbility(); SpellAbility exSA = re.getOverridingAbility();
if (exSA != null) { if (exSA != null) {
if (sa != null) { exSA = exSA.copy(activatingPlayer);
exSA.setActivatingPlayer(activatingPlayer);
}
else {
exSA.setActivatingPlayer(player);
}
if (exSA.getActivatingPlayer() == null) {
throw new InvalidParameterException("Executing SpellAbility for Replacement Effect has no activating player");
}
}
// ETBReplacement uses overriding abilities. // ETBReplacement uses overriding abilities.
// These checks only work if the Executing SpellAbility is an Ability_Sub. // These checks only work if the Executing SpellAbility is an Ability_Sub.
if (exSA != null && (exSA instanceof AbilitySub) && !doTrigger(exSA, false)) { if ((exSA instanceof AbilitySub) && !doTrigger(exSA, false)) {
return false; return false;
} }
} }
}
return true; return true;
} }

View File

@@ -745,14 +745,9 @@ public class ComputerUtil {
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) {
final String execute = t.getParam("Execute"); final SpellAbility exSA = t.ensureAbility().copy(ai);
if (execute == null) {
continue;
}
final SpellAbility exSA = AbilityFactory.getAbility(host.getSVar(execute), host);
exSA.setActivatingPlayer(ai); exSA.setTrigger(t);
exSA.setTrigger(true);
// Run non-mandatory trigger. // Run non-mandatory trigger.
// These checks only work if the Executing SpellAbility is an Ability_Sub. // These checks only work if the Executing SpellAbility is an Ability_Sub.

View File

@@ -142,7 +142,7 @@ public enum SpellApiToAi {
.put(ApiType.RevealHand, RevealHandAi.class) .put(ApiType.RevealHand, RevealHandAi.class)
.put(ApiType.ReverseTurnOrder, AlwaysPlayAi.class) .put(ApiType.ReverseTurnOrder, AlwaysPlayAi.class)
.put(ApiType.RollPlanarDice, RollPlanarDiceAi.class) .put(ApiType.RollPlanarDice, RollPlanarDiceAi.class)
.put(ApiType.RunSVarAbility, AlwaysPlayAi.class) .put(ApiType.RunChaos, AlwaysPlayAi.class)
.put(ApiType.Sacrifice, SacrificeAi.class) .put(ApiType.Sacrifice, SacrificeAi.class)
.put(ApiType.SacrificeAll, SacrificeAllAi.class) .put(ApiType.SacrificeAll, SacrificeAllAi.class)
.put(ApiType.Scry, ScryAi.class) .put(ApiType.Scry, ScryAi.class)

View File

@@ -142,7 +142,7 @@ public enum ApiType {
RevealHand (RevealHandEffect.class), RevealHand (RevealHandEffect.class),
ReverseTurnOrder (ReverseTurnOrderEffect.class), ReverseTurnOrder (ReverseTurnOrderEffect.class),
RollPlanarDice (RollPlanarDiceEffect.class), RollPlanarDice (RollPlanarDiceEffect.class),
RunSVarAbility (RunSVarAbilityEffect.class), RunChaos (RunChaosEffect.class),
Sacrifice (SacrificeEffect.class), Sacrifice (SacrificeEffect.class),
SacrificeAll (SacrificeAllEffect.class), SacrificeAll (SacrificeAllEffect.class),
Scry (ScryEffect.class), Scry (ScryEffect.class),

View File

@@ -44,7 +44,6 @@ public class CharmEffect extends SpellAbilityEffect {
} }
// set CharmOrder // set CharmOrder
for (AbilitySub sub : choices) { for (AbilitySub sub : choices) {
sub.setTrigger(sa.isTrigger());
sub.setSVar("CharmOrder", Integer.toString(indx)); sub.setSVar("CharmOrder", Integer.toString(indx));
indx++; indx++;
} }

View File

@@ -0,0 +1,49 @@
package forge.game.ability.effects;
import java.util.List;
import java.util.Map;
import com.google.common.collect.Lists;
import forge.game.PlanarDice;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerType;
import forge.game.trigger.WrappedAbility;
public class RunChaosEffect extends SpellAbilityEffect {
@Override
public void resolve(SpellAbility sa) {
Map<AbilityKey, Object> map = AbilityKey.newMap();
map.put(AbilityKey.Player, sa.getActivatingPlayer());
map.put(AbilityKey.Result, PlanarDice.Chaos);
List<SpellAbility> validSA = Lists.newArrayList();
for (final Card c : getTargetCards(sa)) {
for (Trigger t : c.getTriggers()) {
if (TriggerType.PlanarDice.equals(t.getMode()) && t.performTest(map)) {
SpellAbility triggerSA = t.ensureAbility().copy(sa.getActivatingPlayer());
Player decider = sa.getActivatingPlayer();
if (t.hasParam("OptionalDecider")) {
sa.setOptionalTrigger(true);
decider = AbilityUtils.getDefinedPlayers(c, t.getParam("OptionalDecider"), sa).get(0);
} else if (t.hasParam("Cost")) {
sa.setOptionalTrigger(true);
}
final WrappedAbility wrapperAbility = new WrappedAbility(t, triggerSA, decider);
validSA.add(wrapperAbility);
}
}
}
sa.getActivatingPlayer().getController().orderAndPlaySimultaneousSa(validSA);
}
}

View File

@@ -1,38 +0,0 @@
package forge.game.ability.effects;
import forge.game.ability.AbilityFactory;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.spellability.SpellAbility;
import java.util.ArrayList;
import java.util.List;
public class RunSVarAbilityEffect extends SpellAbilityEffect {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellEffect#resolve(forge.card.spellability.SpellAbility)
*/
@Override
public void resolve(SpellAbility sa) {
String sVars = sa.getParam("SVars");
List<Card> cards = getTargetCards(sa);
if (sVars == null || cards.isEmpty()) {
return;
}
List<SpellAbility> validSA = new ArrayList<>();
final boolean isTrigger = sa.hasParam("IsTrigger");
for (final Card tgtC : cards) {
if (!tgtC.hasSVar(sVars)) {
continue;
}
final SpellAbility actualSA = AbilityFactory.getAbility(tgtC.getSVar(sVars), tgtC);
actualSA.setTrigger(isTrigger);
actualSA.setActivatingPlayer(sa.getActivatingPlayer());
actualSA.setDescription(tgtC.getName() + "'s ability");
validSA.add(actualSA);
}
sa.getActivatingPlayer().getController().orderAndPlaySimultaneousSa(validSA);
}
}

View File

@@ -299,19 +299,20 @@ public class CardFactory {
private static void buildPlaneAbilities(Card card) { private static void buildPlaneAbilities(Card card) {
StringBuilder triggerSB = new StringBuilder(); StringBuilder triggerSB = new StringBuilder();
triggerSB.append("Mode$ PlanarDice | Result$ Planeswalk | TriggerZones$ Command | Execute$ RolledWalk | "); triggerSB.append("Mode$ PlanarDice | Result$ Planeswalk | TriggerZones$ Command | Secondary$ True | ");
triggerSB.append("Secondary$ True | TriggerDescription$ Whenever you roll Planeswalk, put this card on the "); triggerSB.append("TriggerDescription$ Whenever you roll Planeswalk, put this card on the bottom of its owner's planar deck face down, ");
triggerSB.append("bottom of its owner's planar deck face down, then move the top card of your planar deck off "); triggerSB.append("then move the top card of your planar deck off that planar deck and turn it face up");
triggerSB.append("that planar deck and turn it face up");
String rolledWalk = "DB$ Planeswalk";
Trigger planesWalkTrigger = TriggerHandler.parseTrigger(triggerSB.toString(), card, true);
planesWalkTrigger.setOverridingAbility(AbilityFactory.getAbility(rolledWalk, card));
card.addTrigger(planesWalkTrigger);
StringBuilder saSB = new StringBuilder(); StringBuilder saSB = new StringBuilder();
saSB.append("AB$ RollPlanarDice | Cost$ X | SorcerySpeed$ True | Activator$ Player | ActivationZone$ Command | "); saSB.append("AB$ RollPlanarDice | Cost$ X | SorcerySpeed$ True | Activator$ Player | ActivationZone$ Command | ");
saSB.append("SpellDescription$ Roll the planar dice. X is equal to the amount of times the planar die has been rolled this turn."); saSB.append("SpellDescription$ Roll the planar dice. X is equal to the amount of times the planar die has been rolled this turn.");
card.setSVar("RolledWalk", "DB$ Planeswalk | Cost$ 0");
Trigger planesWalkTrigger = TriggerHandler.parseTrigger(triggerSB.toString(), card, true);
card.addTrigger(planesWalkTrigger);
SpellAbility planarRoll = AbilityFactory.getAbility(saSB.toString(), card); SpellAbility planarRoll = AbilityFactory.getAbility(saSB.toString(), card);
planarRoll.setSVar("X", "Count$RolledThisTurn"); planarRoll.setSVar("X", "Count$RolledThisTurn");

View File

@@ -99,7 +99,6 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
private CardCollection splicedCards = null; private CardCollection splicedCards = null;
private boolean basicSpell = true; private boolean basicSpell = true;
private boolean trigger = false;
private Trigger triggerObj = null; private Trigger triggerObj = null;
private boolean optionalTrigger = false; private boolean optionalTrigger = false;
private ReplacementEffect replacementEffect = null; private ReplacementEffect replacementEffect = null;
@@ -938,10 +937,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
} }
public boolean isTrigger() { public boolean isTrigger() {
return trigger; return triggerObj != null;
}
public void setTrigger(final boolean trigger0) {
trigger = trigger0;
} }
public Trigger getTrigger() { public Trigger getTrigger() {

View File

@@ -539,7 +539,7 @@ public class TriggerHandler {
sa.setLastStateBattlefield(game.getLastStateBattlefield()); sa.setLastStateBattlefield(game.getLastStateBattlefield());
sa.setLastStateGraveyard(game.getLastStateGraveyard()); sa.setLastStateGraveyard(game.getLastStateGraveyard());
sa.setTrigger(true); sa.setTrigger(regtrig);
sa.setSourceTrigger(regtrig.getId()); sa.setSourceTrigger(regtrig.getId());
regtrig.setTriggeringObjects(sa, runParams); regtrig.setTriggeringObjects(sa, runParams);
sa.setTriggerRemembered(regtrig.getTriggerRemembered()); sa.setTriggerRemembered(regtrig.getTriggerRemembered());
@@ -560,8 +560,6 @@ public class TriggerHandler {
sa.setStackDescription(sa.toString()); sa.setStackDescription(sa.toString());
if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) { if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) {
// need to be set for demonic pact to look for chosen modes
sa.setTrigger(regtrig);
if (!CharmEffect.makeChoices(sa)) { if (!CharmEffect.makeChoices(sa)) {
// 603.3c If no mode is chosen, the ability is removed from the stack. // 603.3c If no mode is chosen, the ability is removed from the stack.
return; return;

View File

@@ -51,8 +51,8 @@ public class WrappedAbility extends Ability {
public WrappedAbility(final Trigger regtrig0, final SpellAbility sa0, final Player decider0) { public WrappedAbility(final Trigger regtrig0, final SpellAbility sa0, final Player decider0) {
super(sa0.getHostCard(), ManaCost.ZERO, sa0.getView()); super(sa0.getHostCard(), ManaCost.ZERO, sa0.getView());
setTrigger(regtrig0); setTrigger(regtrig0);
setTrigger(true);
sa = sa0; sa = sa0;
sa.setTrigger(regtrig0);
decider = decider0; decider = decider0;
sa.setDescription(this.getStackDescription()); sa.setDescription(this.getStackDescription());
} }
@@ -474,9 +474,6 @@ public class WrappedAbility extends Ability {
} }
} }
// set Trigger
sa.setTrigger(regtrig);
if (decider != null && !decider.getController().confirmTrigger(this)) { if (decider != null && !decider.getController().confirmTrigger(this)) {
return; return;
} }

View File

@@ -5,11 +5,9 @@ T:Mode$ PlaneswalkedTo | ValidCard$ Card.Self | Execute$ PutCounter | TriggerDes
T:Mode$ Phase | PreCombatMain$ True | ValidPlayer$ You | TriggerZones$ Command | Execute$ PutCounter | Secondary$ True | TriggerDescription$ When you planeswalk to CARDNAME or at the beginning of your precombat main phase, put a charge counter on CARDNAME, then add {R} for each charge counter on it. T:Mode$ Phase | PreCombatMain$ True | ValidPlayer$ You | TriggerZones$ Command | Execute$ PutCounter | Secondary$ True | TriggerDescription$ When you planeswalk to CARDNAME or at the beginning of your precombat main phase, put a charge counter on CARDNAME, then add {R} for each charge counter on it.
SVar:PutCounter:DB$PutCounter | Defined$ Self | CounterType$ CHARGE | CounterNum$ 1 | SubAbility$ DBMana SVar:PutCounter:DB$PutCounter | Defined$ Self | CounterType$ CHARGE | CounterNum$ 1 | SubAbility$ DBMana
SVar:DBMana:DB$ Mana | Produced$ R | Amount$ Y | References$ Y SVar:DBMana:DB$ Mana | Produced$ R | Amount$ Y | References$ Y
T:Mode$ PlanarDice | Result$ Chaos | TriggerZones$ Command | Execute$ DBPay | TriggerDescription$ Whenever you roll {CHAOS}, you may pay {X}. If you do, CARDNAME deals X damage to any target. T:Mode$ PlanarDice | Result$ Chaos | TriggerZones$ Command | Execute$ RolledChaos | TriggerDescription$ Whenever you roll {CHAOS}, you may pay {X}. If you do, CARDNAME deals X damage to any target.
SVar:DBPay:DB$ ChooseNumber | Defined$ TriggeredPlayer | ChooseAnyNumber$ True | ListTitle$ X to pay | SubAbility$ RolledChaos SVar:RolledChaos:AB$ DealDamage | Cost$ X | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ X | References$ X
SVar:RolledChaos:DB$ DealDamage | UnlessCost$ ChosenNumber | UnlessPayer$ TriggeredPlayer | UnlessSwitched$ True | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ PaidChaos | References$ PaidChaos SVar:X:Count$xPaid
SVar:PaidChaos:Count$ChosenNumber
SVar:Y:Count$CardCounters.CHARGE SVar:Y:Count$CardCounters.CHARGE
SVar:AIRollPlanarDieParams:Mode$ Always SVar:AIRollPlanarDieParams:Mode$ Always
SVar:Picture:http://www.wizards.com/global/images/magic/general/kilnspire_district.jpg
Oracle:When you planeswalk to Kilnspire District or at the beginning of your precombat main phase, put a charge counter on Kilnspire District, then add {R} for each charge counter on it.\nWhenever you roll {CHAOS}, you may pay {X}. If you do, Kilnspire District deals X damage to any target. Oracle:When you planeswalk to Kilnspire District or at the beginning of your precombat main phase, put a charge counter on Kilnspire District, then add {R} for each charge counter on it.\nWhenever you roll {CHAOS}, you may pay {X}. If you do, Kilnspire District deals X damage to any target.

View File

@@ -8,8 +8,7 @@ SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:Y:Remembered$Amount SVar:Y:Remembered$Amount
T:Mode$ PlanarDice | Result$ Chaos | TriggerZones$ Command | Execute$ RolledChaos | TriggerDescription$ Whenever you roll {CHAOS}, reveal the top three cards of your planar deck. Each of the revealed cards' {CHAOS} abilities triggers. Then put the revealed cards on the bottom of your planar deck in any order. T:Mode$ PlanarDice | Result$ Chaos | TriggerZones$ Command | Execute$ RolledChaos | TriggerDescription$ Whenever you roll {CHAOS}, reveal the top three cards of your planar deck. Each of the revealed cards' {CHAOS} abilities triggers. Then put the revealed cards on the bottom of your planar deck in any order.
SVar:RolledChaos:DB$ Dig | DigNum$ 3 | NoMove$ True | Reveal$ True | SourceZone$ PlanarDeck | RememberRevealed$ True | SubAbility$ DBRunChaos SVar:RolledChaos:DB$ Dig | DigNum$ 3 | NoMove$ True | Reveal$ True | SourceZone$ PlanarDeck | RememberRevealed$ True | SubAbility$ DBRunChaos
SVar:DBRunChaos:DB$ RunSVarAbility | Defined$ Remembered | SVars$ RolledChaos | IsTrigger$ True | SubAbility$ DBChangeZone SVar:DBRunChaos:DB$ RunChaos | Defined$ Remembered | SubAbility$ DBChangeZone
SVar:DBChangeZone:DB$ ChangeZoneAll | ChangeType$ Card.IsRemembered | Origin$ PlanarDeck | Destination$ PlanarDeck | LibraryPosition$ -1 | SubAbility$ DBCleanup SVar:DBChangeZone:DB$ ChangeZoneAll | ChangeType$ Card.IsRemembered | Origin$ PlanarDeck | Destination$ PlanarDeck | LibraryPosition$ -1 | SubAbility$ DBCleanup
SVar:Picture:http://www.wizards.com/global/images/magic/general/pools_of_becoming.jpg
SVar:AIRollPlanarDieParams:Mode$ Always SVar:AIRollPlanarDieParams:Mode$ Always
Oracle:At the beginning of your end step, put the cards in your hand on the bottom of your library in any order, then draw that many cards.\nWhenever you roll {CHAOS}, reveal the top three cards of your planar deck. Each of the revealed cards' {CHAOS} abilities triggers. Then put the revealed cards on the bottom of your planar deck in any order. Oracle:At the beginning of your end step, put the cards in your hand on the bottom of your library in any order, then draw that many cards.\nWhenever you roll {CHAOS}, reveal the top three cards of your planar deck. Each of the revealed cards' {CHAOS} abilities triggers. Then put the revealed cards on the bottom of your planar deck in any order.