mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-18 19:58:00 +00:00
- DelayedTriggerAi: port over AILogic NarsetRebound and SpellCopy (the latter doesn't quite work yet and the spell somehow magically fizzles with no trace).
This commit is contained in:
committed by
Michael Kamensky
parent
d5639f5395
commit
9b040b063b
@@ -103,7 +103,7 @@ public class GameAction {
|
||||
boolean wasFacedown = c.isFaceDown();
|
||||
|
||||
//Rule 110.5g: A token that has left the battlefield can't move to another zone
|
||||
if (c.isToken() && zoneFrom != null && !fromBattlefield) {
|
||||
if (c.isToken() && zoneFrom != null && !fromBattlefield && !zoneFrom.is(ZoneType.Stack)) {
|
||||
return c;
|
||||
}
|
||||
|
||||
|
||||
@@ -1660,12 +1660,8 @@ public class AbilityUtils {
|
||||
|
||||
// Count$Kicked.<numHB>.<numNotHB>
|
||||
if (sq[0].startsWith("Kicked")) {
|
||||
if (((SpellAbility)ctb).isKicked()) {
|
||||
return CardFactoryUtil.doXMath(Integer.parseInt(sq[1]), expr, c); // Kicked
|
||||
}
|
||||
else {
|
||||
return CardFactoryUtil.doXMath(Integer.parseInt(sq[2]), expr, c); // not Kicked
|
||||
}
|
||||
boolean kicked = ((SpellAbility)ctb).isKicked() || c.getKickerMagnitude() > 0;
|
||||
return CardFactoryUtil.doXMath(Integer.parseInt(kicked ? sq[1] : sq[2]), expr, c);
|
||||
}
|
||||
|
||||
//Count$SearchedLibrary.<DefinedPlayer>
|
||||
|
||||
@@ -24,14 +24,23 @@ import java.util.List;
|
||||
public class AttachEffect extends SpellAbilityEffect {
|
||||
@Override
|
||||
public void resolve(SpellAbility sa) {
|
||||
if (sa.getHostCard().isAura() && sa.isSpell()) {
|
||||
|
||||
final Card host = sa.getHostCard();
|
||||
if (host.isAura() && sa.isSpell()) {
|
||||
final Player ap = sa.getActivatingPlayer();
|
||||
// The Spell_Permanent (Auras) version of this AF needs to
|
||||
// move the card into play before Attaching
|
||||
|
||||
sa.getHostCard().setController(ap, 0);
|
||||
final Card c = ap.getGame().getAction().moveTo(ap.getZone(ZoneType.Battlefield), sa.getHostCard(), sa);
|
||||
host.setController(ap, 0);
|
||||
|
||||
// 111.11. A copy of a permanent spell becomes a token as it resolves.
|
||||
// The token has the characteristics of the spell that became that token.
|
||||
// The token is not “created” for the purposes of any replacement effects or triggered abilities that refer to creating a token.
|
||||
if (host.isCopiedSpell()) {
|
||||
host.setCopiedSpell(false);
|
||||
host.setToken(true);
|
||||
}
|
||||
|
||||
final Card c = ap.getGame().getAction().moveToPlay(host, ap, sa);
|
||||
sa.setHostCard(c);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,9 @@ import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.game.Game;
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.Card;
|
||||
@@ -12,15 +14,16 @@ import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardFactory;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.replacement.ReplacementType;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.util.Lang;
|
||||
import forge.util.Localizer;
|
||||
import forge.util.CardTranslation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
public class CopySpellAbilityEffect extends SpellAbilityEffect {
|
||||
|
||||
@@ -56,7 +59,8 @@ public class CopySpellAbilityEffect extends SpellAbilityEffect {
|
||||
@Override
|
||||
public void resolve(SpellAbility sa) {
|
||||
final Card card = sa.getHostCard();
|
||||
Player controller = sa.getActivatingPlayer();
|
||||
final Game game = card.getGame();
|
||||
List<Player> controllers = Lists.newArrayList(sa.getActivatingPlayer());
|
||||
|
||||
int amount = 1;
|
||||
if (sa.hasParam("Amount")) {
|
||||
@@ -64,13 +68,9 @@ public class CopySpellAbilityEffect extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
if (sa.hasParam("Controller")) {
|
||||
controller = AbilityUtils.getDefinedPlayers(card, sa.getParam("Controller"), sa).get(0);
|
||||
controllers = AbilityUtils.getDefinedPlayers(card, sa.getParam("Controller"), sa);
|
||||
}
|
||||
|
||||
boolean isOptional = sa.hasParam("Optional");
|
||||
if (isOptional && !controller.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoyouWantCopyTheSpell", CardTranslation.getTranslatedName(card.getName())))) {
|
||||
return;
|
||||
}
|
||||
|
||||
final List<SpellAbility> tgtSpells = getTargetSpells(sa);
|
||||
|
||||
@@ -79,123 +79,143 @@ public class CopySpellAbilityEffect extends SpellAbilityEffect {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean mayChooseNewTargets = true;
|
||||
List<SpellAbility> copies = new ArrayList<>();
|
||||
|
||||
if (sa.hasParam("CopyMultipleSpells")) {
|
||||
final int spellCount = Integer.parseInt(sa.getParam("CopyMultipleSpells"));
|
||||
boolean isOptional = sa.hasParam("Optional");
|
||||
|
||||
for (int multi = 0; multi < spellCount && !tgtSpells.isEmpty(); multi++) {
|
||||
String prompt = Localizer.getInstance().getMessage("lblSelectMultiSpellCopyToStack", Lang.getOrdinal(multi + 1));
|
||||
SpellAbility chosen = controller.getController().chooseSingleSpellForEffect(tgtSpells, sa, prompt,
|
||||
ImmutableMap.of());
|
||||
SpellAbility copiedSpell = CardFactory.copySpellAbilityAndPossiblyHost(card, chosen.getHostCard(), chosen, true);
|
||||
copiedSpell.getHostCard().setController(card.getController(), card.getGame().getNextTimestamp());
|
||||
copiedSpell.setActivatingPlayer(controller);
|
||||
copies.add(copiedSpell);
|
||||
tgtSpells.remove(chosen);
|
||||
for (Player controller : controllers) {
|
||||
if (isOptional && !controller.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoyouWantCopyTheSpell", CardTranslation.getTranslatedName(card.getName())))) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (sa.hasParam("CopyForEachCanTarget")) {
|
||||
|
||||
List<SpellAbility> copies = Lists.newArrayList();
|
||||
|
||||
SpellAbility chosenSA = controller.getController().chooseSingleSpellForEffect(tgtSpells, sa,
|
||||
Localizer.getInstance().getMessage("lblSelectASpellCopy"), ImmutableMap.of());
|
||||
chosenSA.setActivatingPlayer(controller);
|
||||
// Find subability or rootability that has targets
|
||||
SpellAbility targetedSA = chosenSA;
|
||||
while (targetedSA != null) {
|
||||
if (targetedSA.usesTargeting() && targetedSA.getTargets().getNumTargeted() != 0) {
|
||||
break;
|
||||
|
||||
if (sa.hasParam("CopyForEachCanTarget")) {
|
||||
// Find subability or rootability that has targets
|
||||
SpellAbility targetedSA = chosenSA;
|
||||
while (targetedSA != null) {
|
||||
if (targetedSA.usesTargeting() && targetedSA.getTargets().getNumTargeted() != 0) {
|
||||
break;
|
||||
}
|
||||
targetedSA = targetedSA.getSubAbility();
|
||||
}
|
||||
targetedSA = targetedSA.getSubAbility();
|
||||
}
|
||||
if (targetedSA == null) {
|
||||
return;
|
||||
}
|
||||
final List<GameEntity> candidates = targetedSA.getTargetRestrictions().getAllCandidates(targetedSA, true);
|
||||
if (sa.hasParam("CanTargetPlayer")) {
|
||||
// Radiate
|
||||
// Remove targeted players because getAllCandidates include all the valid players
|
||||
for(Player p : targetedSA.getTargets().getTargetPlayers())
|
||||
candidates.remove(p);
|
||||
|
||||
mayChooseNewTargets = false;
|
||||
for (GameEntity o : candidates) {
|
||||
SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(card, chosenSA.getHostCard(), chosenSA, true);
|
||||
resetFirstTargetOnCopy(copy, o, targetedSA);
|
||||
copies.add(copy);
|
||||
if (targetedSA == null) {
|
||||
continue;
|
||||
}
|
||||
} else {// Precursor Golem, Ink-Treader Nephilim
|
||||
final String type = sa.getParam("CopyForEachCanTarget");
|
||||
CardCollection valid = new CardCollection();
|
||||
List<Player> players = Lists.newArrayList();
|
||||
Player originalTargetPlayer = Iterables.getFirst(getTargetPlayers(chosenSA), null);
|
||||
for (final GameEntity o : candidates) {
|
||||
if (o instanceof Card) {
|
||||
valid.add((Card) o);
|
||||
} else if (o instanceof Player) {
|
||||
final Player p = (Player) o;
|
||||
if (p.equals(originalTargetPlayer))
|
||||
continue;
|
||||
if (p.isValid(type.split(","), chosenSA.getActivatingPlayer(), chosenSA.getHostCard(), sa)) {
|
||||
players.add(p);
|
||||
final List<GameEntity> candidates = targetedSA.getTargetRestrictions().getAllCandidates(targetedSA, true);
|
||||
if (sa.hasParam("CanTargetPlayer")) {
|
||||
// Radiate
|
||||
// Remove targeted players because getAllCandidates include all the valid players
|
||||
for(Player p : targetedSA.getTargets().getTargetPlayers())
|
||||
candidates.remove(p);
|
||||
|
||||
for (GameEntity o : candidates) {
|
||||
SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(sa, chosenSA);
|
||||
resetFirstTargetOnCopy(copy, o, targetedSA);
|
||||
copies.add(copy);
|
||||
}
|
||||
} else {// Precursor Golem, Ink-Treader Nephilim
|
||||
final String type = sa.getParam("CopyForEachCanTarget");
|
||||
CardCollection valid = new CardCollection();
|
||||
List<Player> players = Lists.newArrayList();
|
||||
Player originalTargetPlayer = Iterables.getFirst(getTargetPlayers(chosenSA), null);
|
||||
for (final GameEntity o : candidates) {
|
||||
if (o instanceof Card) {
|
||||
valid.add((Card) o);
|
||||
} else if (o instanceof Player) {
|
||||
final Player p = (Player) o;
|
||||
if (p.equals(originalTargetPlayer))
|
||||
continue;
|
||||
if (p.isValid(type.split(","), chosenSA.getActivatingPlayer(), chosenSA.getHostCard(), sa)) {
|
||||
players.add(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
valid = CardLists.getValidCards(valid, type.split(","), chosenSA.getActivatingPlayer(), chosenSA.getHostCard(), sa);
|
||||
Card originalTarget = Iterables.getFirst(getTargetCards(chosenSA), null);
|
||||
valid.remove(originalTarget);
|
||||
mayChooseNewTargets = false;
|
||||
if (sa.hasParam("ChooseOnlyOne")) {
|
||||
Card choice = controller.getController().chooseSingleEntityForEffect(valid, sa, Localizer.getInstance().getMessage("lblChooseOne"), null);
|
||||
SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(card, chosenSA.getHostCard(), chosenSA, true);
|
||||
resetFirstTargetOnCopy(copy, choice, targetedSA);
|
||||
copies.add(copy);
|
||||
} else {
|
||||
for (final Card c : valid) {
|
||||
SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(card, chosenSA.getHostCard(), chosenSA, true);
|
||||
valid = CardLists.getValidCards(valid, type.split(","), chosenSA.getActivatingPlayer(), chosenSA.getHostCard(), sa);
|
||||
Card originalTarget = Iterables.getFirst(getTargetCards(chosenSA), null);
|
||||
valid.remove(originalTarget);
|
||||
|
||||
if (sa.hasParam("ChooseOnlyOne")) {
|
||||
Card choice = controller.getController().chooseSingleEntityForEffect(valid, sa, Localizer.getInstance().getMessage("lblChooseOne"), null);
|
||||
if (choice != null) {
|
||||
valid = new CardCollection(choice);
|
||||
}
|
||||
}
|
||||
|
||||
for (final Card c : valid) {
|
||||
SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(sa, chosenSA);
|
||||
resetFirstTargetOnCopy(copy, c, targetedSA);
|
||||
copies.add(copy);
|
||||
}
|
||||
}
|
||||
for (final Player p : players) {
|
||||
SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(sa, chosenSA);
|
||||
resetFirstTargetOnCopy(copy, p, targetedSA);
|
||||
copies.add(copy);
|
||||
}
|
||||
}
|
||||
for (final Player p : players) {
|
||||
SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(card, chosenSA.getHostCard(), chosenSA, true);
|
||||
resetFirstTargetOnCopy(copy, p, targetedSA);
|
||||
}
|
||||
else {
|
||||
for (int i = 0; i < amount; i++) {
|
||||
SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(sa, chosenSA);
|
||||
if (sa.hasParam("MayChooseTarget")) {
|
||||
copy.setMayChooseNewTargets(true);
|
||||
if (copy.usesTargeting()) {
|
||||
copy.getTargetRestrictions().setMandatory(true);
|
||||
}
|
||||
}
|
||||
|
||||
// extra case for Epic to remove the keyword and the last part of the SpellAbility
|
||||
if (sa.hasParam("Epic")) {
|
||||
copy.getHostCard().removeIntrinsicKeyword("Epic");
|
||||
SpellAbility sub = copy;
|
||||
while (sub.getSubAbility() != null && !sub.hasParam("Epic")) {
|
||||
sub = sub.getSubAbility();
|
||||
}
|
||||
if (sub != null) {
|
||||
sub.getParent().setSubAbility(sub.getSubAbility());
|
||||
}
|
||||
}
|
||||
|
||||
copies.add(copy);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
SpellAbility chosenSA = controller.getController().chooseSingleSpellForEffect(tgtSpells, sa,
|
||||
Localizer.getInstance().getMessage("lblSelectASpellCopy"), ImmutableMap.of());
|
||||
chosenSA.setActivatingPlayer(controller);
|
||||
for (int i = 0; i < amount; i++) {
|
||||
SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(
|
||||
card, chosenSA.getHostCard(), chosenSA, true);
|
||||
|
||||
// extra case for Epic to remove the keyword and the last part of the SpellAbility
|
||||
if (sa.hasParam("Epic")) {
|
||||
copy.getHostCard().removeIntrinsicKeyword("Epic");
|
||||
SpellAbility sub = copy;
|
||||
while (sub.getSubAbility() != null && !sub.hasParam("Epic")) {
|
||||
sub = sub.getSubAbility();
|
||||
}
|
||||
if (sub != null) {
|
||||
sub.getParent().setSubAbility(sub.getSubAbility());
|
||||
}
|
||||
if (copies.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int addAmount = copies.size();
|
||||
final Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(controller);
|
||||
repParams.put(AbilityKey.SpellAbility, chosenSA);
|
||||
repParams.put(AbilityKey.Amount, addAmount);
|
||||
|
||||
switch (game.getReplacementHandler().run(ReplacementType.CopySpell, repParams)) {
|
||||
case NotReplaced:
|
||||
break;
|
||||
case Updated: {
|
||||
addAmount = (int) repParams.get(AbilityKey.Amount);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
addAmount = 0;
|
||||
}
|
||||
|
||||
if (addAmount <= 0) {
|
||||
continue;
|
||||
}
|
||||
int extraAmount = addAmount - copies.size();
|
||||
for (int i = 0; i < extraAmount; i++) {
|
||||
SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(sa, chosenSA);
|
||||
// extra copies added with CopySpellReplacenment currently always has new choose targets
|
||||
copy.setMayChooseNewTargets(true);
|
||||
if (copy.usesTargeting()) {
|
||||
copy.getTargetRestrictions().setMandatory(true);
|
||||
}
|
||||
|
||||
copies.add(copy);
|
||||
}
|
||||
}
|
||||
|
||||
for(SpellAbility copySA : copies) {
|
||||
if (mayChooseNewTargets && copySA.usesTargeting()) {
|
||||
// TODO: ideally this should be implemented by way of allowing the player to cancel targeting
|
||||
// but in that case preserving whatever target was specified for the original spell (since
|
||||
// "changing targets" is the optional part).
|
||||
copySA.getTargetRestrictions().setMandatory(true);
|
||||
}
|
||||
controller.getController().playSpellAbilityForFree(copySA, mayChooseNewTargets);
|
||||
|
||||
controller.getController().orderAndPlaySimultaneousSa(copies);
|
||||
}
|
||||
} // end resolve
|
||||
|
||||
|
||||
@@ -5,7 +5,10 @@ import com.google.common.collect.Maps;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardUtil;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.trigger.Trigger;
|
||||
import forge.game.trigger.TriggerHandler;
|
||||
@@ -21,6 +24,8 @@ public class DelayedTriggerEffect extends SpellAbilityEffect {
|
||||
protected String getStackDescription(SpellAbility sa) {
|
||||
if (sa.hasParam("TriggerDescription")) {
|
||||
return sa.getParam("TriggerDescription");
|
||||
} else if (sa.hasParam("SpellDescription")) {
|
||||
return sa.getParam("SpellDescription");
|
||||
}
|
||||
|
||||
return "";
|
||||
@@ -44,7 +49,8 @@ public class DelayedTriggerEffect extends SpellAbilityEffect {
|
||||
triggerRemembered = sa.getParam("RememberObjects");
|
||||
}
|
||||
|
||||
final Trigger delTrig = TriggerHandler.parseTrigger(mapParams, sa.getHostCard(), true);
|
||||
Card lki = CardUtil.getLKICopy(sa.getHostCard());
|
||||
final Trigger delTrig = TriggerHandler.parseTrigger(mapParams, lki, true);
|
||||
|
||||
if (triggerRemembered != null) {
|
||||
for (final String rem : triggerRemembered.split(",")) {
|
||||
@@ -67,7 +73,9 @@ public class DelayedTriggerEffect extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
if (mapParams.containsKey("Execute") || sa.hasAdditionalAbility("Execute")) {
|
||||
SpellAbility overridingSA = sa.getAdditionalAbility("Execute");
|
||||
AbilitySub overridingSA = (AbilitySub)sa.getAdditionalAbility("Execute").copy(lki, false);
|
||||
// need to reset the parent, additionalAbility does set it to this
|
||||
overridingSA.setParent(null);
|
||||
overridingSA.setActivatingPlayer(sa.getActivatingPlayer());
|
||||
overridingSA.setDeltrigActivatingPlayer(sa.getActivatingPlayer()); // ensure that the original activator can be restored later
|
||||
// Set Transform timestamp when the delayed trigger is created
|
||||
|
||||
@@ -22,6 +22,14 @@ public class PermanentEffect extends SpellAbilityEffect {
|
||||
sa.getHostCard().setController(p, 0);
|
||||
final Card host = sa.getHostCard();
|
||||
|
||||
// 111.11. A copy of a permanent spell becomes a token as it resolves.
|
||||
// The token has the characteristics of the spell that became that token.
|
||||
// The token is not “created” for the purposes of any replacement effects or triggered abilities that refer to creating a token.
|
||||
if (host.isCopiedSpell()) {
|
||||
host.setCopiedSpell(false);
|
||||
host.setToken(true);
|
||||
}
|
||||
|
||||
final Card c = p.getGame().getAction().moveToPlay(host, p, sa);
|
||||
sa.setHostCard(c);
|
||||
|
||||
|
||||
@@ -3612,8 +3612,8 @@ public class Card extends GameEntity implements Comparable<Card> {
|
||||
if (multiKickerMagnitude > 0) {
|
||||
return multiKickerMagnitude;
|
||||
}
|
||||
boolean hasK1 = costsPaid.contains(OptionalCost.Kicker1);
|
||||
return hasK1 == costsPaid.contains(OptionalCost.Kicker2) ? (hasK1 ? 2 : 0) : 1;
|
||||
boolean hasK1 = isOptionalCostPaid(OptionalCost.Kicker1);
|
||||
return hasK1 == isOptionalCostPaid(OptionalCost.Kicker2) ? (hasK1 ? 2 : 0) : 1;
|
||||
}
|
||||
|
||||
private int pseudoKickerMagnitude = 0;
|
||||
|
||||
@@ -125,12 +125,13 @@ public class CardFactory {
|
||||
* which wouldn't ordinarily get set during a simple Card.copy() call.
|
||||
* </p>
|
||||
* */
|
||||
private final static Card copySpellHost(final Card source, final Card original, final SpellAbility sa, final boolean bCopyDetails){
|
||||
Player controller = sa.getActivatingPlayer();
|
||||
private final static Card copySpellHost(final SpellAbility sourceSA, final SpellAbility targetSA){
|
||||
final Card source = sourceSA.getHostCard();
|
||||
final Card original = targetSA.getHostCard();
|
||||
Player controller = sourceSA.getActivatingPlayer();
|
||||
final Card c = copyCard(original, true);
|
||||
|
||||
// change the color of the copy (eg: Fork)
|
||||
final SpellAbility sourceSA = source.getFirstSpellAbility();
|
||||
if (null != sourceSA && sourceSA.hasParam("CopyIsColor")) {
|
||||
String tmp = "";
|
||||
final String newColor = sourceSA.getParam("CopyIsColor");
|
||||
@@ -148,13 +149,14 @@ public class CardFactory {
|
||||
c.setOwner(controller);
|
||||
c.setCopiedSpell(true);
|
||||
|
||||
if (bCopyDetails) {
|
||||
c.setXManaCostPaidByColor(original.getXManaCostPaidByColor());
|
||||
c.setKickerMagnitude(original.getKickerMagnitude());
|
||||
c.setXManaCostPaidByColor(original.getXManaCostPaidByColor());
|
||||
c.setKickerMagnitude(original.getKickerMagnitude());
|
||||
|
||||
for (OptionalCost cost : original.getOptionalCostsPaid()) {
|
||||
c.addOptionalCostPaid(cost);
|
||||
}
|
||||
for (OptionalCost cost : original.getOptionalCostsPaid()) {
|
||||
c.addOptionalCostPaid(cost);
|
||||
}
|
||||
if (targetSA.isBestow()) {
|
||||
c.animateBestow();
|
||||
}
|
||||
return c;
|
||||
}
|
||||
@@ -174,44 +176,33 @@ public class CardFactory {
|
||||
* @param bCopyDetails
|
||||
* a boolean.
|
||||
*/
|
||||
public final static SpellAbility copySpellAbilityAndPossiblyHost(final Card source, final Card original, final SpellAbility sa, final boolean bCopyDetails) {
|
||||
Player controller = sa.getActivatingPlayer();
|
||||
public final static SpellAbility copySpellAbilityAndPossiblyHost(final SpellAbility sourceSA, final SpellAbility targetSA) {
|
||||
Player controller = sourceSA.getActivatingPlayer();
|
||||
|
||||
//it is only necessary to copy the host card if the SpellAbility is a spell, not an ability
|
||||
final Card c;
|
||||
if (sa.isSpell()){
|
||||
c = copySpellHost(source, original, sa, bCopyDetails);
|
||||
}
|
||||
else {
|
||||
c = original;
|
||||
}
|
||||
final Card c = targetSA.isSpell() ? copySpellHost(sourceSA, targetSA) : targetSA.getHostCard();
|
||||
|
||||
final SpellAbility copySA;
|
||||
if (sa.isTrigger() && sa.isWrapper()) {
|
||||
copySA = getCopiedTriggeredAbility((WrappedAbility)sa, c);
|
||||
if (targetSA.isTrigger() && targetSA.isWrapper()) {
|
||||
copySA = getCopiedTriggeredAbility((WrappedAbility)targetSA, c);
|
||||
} else {
|
||||
copySA = sa.copy(c, false);
|
||||
}
|
||||
|
||||
if (sa.isSpell()){
|
||||
//only update c's abilities if c is a copy.
|
||||
//(it would be nice to move this into `copySpellHost`,
|
||||
// so all the c-mutating code is together in one place.
|
||||
// but copySA doesn't exist until after `copySpellHost` finishes executing,
|
||||
// so it's hard to resolve that dependency.)
|
||||
c.getCurrentState().setNonManaAbilities(copySA);
|
||||
copySA = targetSA.copy(c, false);
|
||||
}
|
||||
|
||||
copySA.setCopied(true);
|
||||
|
||||
if (targetSA.usesTargeting()) {
|
||||
// do for SubAbilities too?
|
||||
copySA.setTargets(targetSA.getTargets().clone());
|
||||
}
|
||||
|
||||
//remove all costs
|
||||
if (!copySA.isTrigger()) {
|
||||
copySA.setPayCosts(new Cost("", sa.isAbility()));
|
||||
copySA.setPayCosts(new Cost("", targetSA.isAbility()));
|
||||
}
|
||||
copySA.setActivatingPlayer(controller);
|
||||
|
||||
if (bCopyDetails) {
|
||||
copySA.setPaidHash(sa.getPaidHash());
|
||||
}
|
||||
copySA.setPaidHash(targetSA.getPaidHash());
|
||||
return copySA;
|
||||
}
|
||||
|
||||
|
||||
@@ -2434,7 +2434,7 @@ public class CardFactoryUtil {
|
||||
inst.addTrigger(parsedTrigReturn);
|
||||
} else if (keyword.equals("Conspire")) {
|
||||
final String trigScript = "Mode$ SpellCast | ValidCard$ Card.Self | CheckSVar$ Conspire | Secondary$ True | TriggerDescription$ Copy CARDNAME if its conspire cost was paid";
|
||||
final String abString = "DB$ CopySpellAbility | Defined$ TriggeredSpellAbility | Amount$ 1";
|
||||
final String abString = "DB$ CopySpellAbility | Defined$ TriggeredSpellAbility | Amount$ 1 | MayChooseTarget$ True";
|
||||
|
||||
final Trigger conspireTrigger = TriggerHandler.parseTrigger(trigScript, card, intrinsic);
|
||||
conspireTrigger.setOverridingAbility(AbilityFactory.getAbility(abString, card));
|
||||
@@ -2630,6 +2630,18 @@ public class CardFactoryUtil {
|
||||
|
||||
trigger.setOverridingAbility(AbilityFactory.getAbility(sb.toString(), card));
|
||||
|
||||
inst.addTrigger(trigger);
|
||||
} else if (keyword.startsWith("Gravestorm")) {
|
||||
String trigStr = "Mode$ SpellCast | ValidCard$ Card.Self | TriggerDescription$ Gravestorm (" + inst.getReminderText() + ")";
|
||||
String copyStr = "DB$ CopySpellAbility | Defined$ TriggeredSpellAbility | Amount$ GravestormCount | MayChooseTarget$ True";
|
||||
|
||||
SpellAbility copySa = AbilityFactory.getAbility(copyStr, card);
|
||||
copySa.setSVar("GravestormCount", "Count$ThisTurnEntered_Graveyard_from_Battlefield_Permanent");
|
||||
|
||||
final Trigger trigger = TriggerHandler.parseTrigger(trigStr, card, intrinsic);
|
||||
|
||||
trigger.setOverridingAbility(copySa);
|
||||
|
||||
inst.addTrigger(trigger);
|
||||
} else if (keyword.startsWith("Haunt")) {
|
||||
final String[] k = keyword.split(":");
|
||||
@@ -3069,7 +3081,7 @@ public class CardFactoryUtil {
|
||||
inst.addTrigger(myTrigger);
|
||||
} else if (keyword.startsWith("Replicate")) {
|
||||
final String trigScript = "Mode$ SpellCast | ValidCard$ Card.Self | CheckSVar$ ReplicateAmount | Secondary$ True | TriggerDescription$ Copy CARDNAME for each time you paid its replicate cost";
|
||||
final String abString = "DB$ CopySpellAbility | Defined$ TriggeredSpellAbility | Amount$ ReplicateAmount";
|
||||
final String abString = "DB$ CopySpellAbility | Defined$ TriggeredSpellAbility | Amount$ ReplicateAmount | MayChooseTarget$ True";
|
||||
|
||||
final Trigger replicateTrigger = TriggerHandler.parseTrigger(trigScript, card, intrinsic);
|
||||
final SpellAbility replicateAbility = AbilityFactory.getAbility(abString, card);
|
||||
@@ -3197,7 +3209,7 @@ public class CardFactoryUtil {
|
||||
final String actualTrigger = "Mode$ SpellCast | ValidCard$ Card.Self | Secondary$ True"
|
||||
+ "| TriggerDescription$ Storm (" + inst.getReminderText() + ")";
|
||||
|
||||
String effect = "DB$ CopySpellAbility | Defined$ TriggeredSpellAbility | Amount$ StormCount";
|
||||
String effect = "DB$ CopySpellAbility | Defined$ TriggeredSpellAbility | Amount$ StormCount | MayChooseTarget$ True";
|
||||
|
||||
final Trigger parsedTrigger = TriggerHandler.parseTrigger(actualTrigger, card, intrinsic);
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ public enum Keyword {
|
||||
FORTIFY("Fortify", KeywordWithCost.class, false, "%s: Attach to target land you control. Fortify only as a sorcery."),
|
||||
FRENZY("Frenzy", KeywordWithAmount.class, false, "Whenever this creature attacks and isn't blocked, it gets +%d/+0 until end of turn."),
|
||||
GRAFT("Graft", KeywordWithAmount.class, false, "This permanent enters the battlefield with {%d:+1/+1 counter} on it. Whenever another creature enters the battlefield, you may move a +1/+1 counter from this permanent onto it."),
|
||||
GRAVESTORM("Gravestorm", SimpleKeyword.class, false, "When you cast this spell, copy it for each permanent that was put into a graveyard from the battlefield this turn. You may choose new targets for the copies."),
|
||||
GRAVESTORM("Gravestorm", SimpleKeyword.class, false, "When you cast this spell, copy it for each permanent that was put into a graveyard from the battlefield this turn. If the spell has any targets, you may choose new targets for any of the copies."),
|
||||
HASTE("Haste", SimpleKeyword.class, true, "This creature can attack and {T} as soon as it comes under your control."),
|
||||
HAUNT("Haunt", SimpleKeyword.class, false, "When this is put into a graveyard, exile it haunting target creature."),
|
||||
HEXPROOF("Hexproof", Hexproof.class, false, "This can't be the target of %s spells or abilities your opponents control."),
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package forge.game.replacement;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
public class ReplaceCopySpell extends ReplacementEffect {
|
||||
|
||||
public ReplaceCopySpell(Map<String, String> map, Card host, boolean intrinsic) {
|
||||
super(map, host, intrinsic);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.replacement.ReplacementEffect#canReplace(java.util.Map)
|
||||
*/
|
||||
@Override
|
||||
public boolean canReplace(Map<AbilityKey, Object> runParams) {
|
||||
if (((int) runParams.get(AbilityKey.Amount)) <= 0) {
|
||||
return false;
|
||||
}
|
||||
if (hasParam("ValidPlayer")) {
|
||||
if (!matchesValid(runParams.get(AbilityKey.Affected), getParam("ValidPlayer").split(","), getHostCard())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (hasParam("ValidSpell")) {
|
||||
if (!matchesValid(runParams.get(AbilityKey.SpellAbility), getParam("ValidSpell").split(","), getHostCard())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.replacement.ReplacementEffect#setReplacingObjects(java.util.HashMap, forge.card.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
public void setReplacingObjects(Map<AbilityKey, Object> runParams, SpellAbility sa) {
|
||||
sa.setReplacingObject(AbilityKey.Amount, runParams.get(AbilityKey.Amount));
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ public enum ReplacementType {
|
||||
AddCounter(ReplaceAddCounter.class),
|
||||
Attached(ReplaceAttached.class),
|
||||
Counter(ReplaceCounter.class),
|
||||
CopySpell(ReplaceCopySpell.class),
|
||||
CreateToken(ReplaceToken.class),
|
||||
DamageDone(ReplaceDamage.class),
|
||||
Destroy(ReplaceDestroy.class),
|
||||
|
||||
@@ -43,6 +43,7 @@ import forge.game.cost.CostRemoveCounter;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.mana.Mana;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerCollection;
|
||||
import forge.game.replacement.ReplacementEffect;
|
||||
import forge.game.staticability.StaticAbility;
|
||||
import forge.game.trigger.Trigger;
|
||||
@@ -52,6 +53,7 @@ import forge.game.zone.ZoneType;
|
||||
import forge.util.Aggregates;
|
||||
import forge.util.Expressions;
|
||||
import forge.util.TextUtil;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.*;
|
||||
@@ -148,6 +150,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
||||
private boolean undoable;
|
||||
|
||||
private boolean isCopied = false;
|
||||
private boolean mayChooseNewTargets = false;
|
||||
|
||||
private EnumSet<OptionalCost> optionalCosts = EnumSet.noneOf(OptionalCost.class);
|
||||
private TargetRestrictions targetRestrictions = null;
|
||||
@@ -863,6 +866,9 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
||||
|
||||
copyHelper(clone, host);
|
||||
|
||||
// always set this to false, it is only set in CopyEffect
|
||||
clone.mayChooseNewTargets = false;
|
||||
|
||||
clone.triggeringObjects = AbilityKey.newMap(this.triggeringObjects);
|
||||
|
||||
clone.setPayCosts(getPayCosts().copy());
|
||||
@@ -884,6 +890,11 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
||||
|
||||
clone.setPaidHash(Maps.newHashMap(getPaidHash()));
|
||||
|
||||
if (usesTargeting()) {
|
||||
// the targets need to be cloned, otherwise they might be cleared
|
||||
clone.targetChosen = getTargets().clone();
|
||||
}
|
||||
|
||||
// clear maps for copy, the values will be added later
|
||||
clone.additionalAbilities = Maps.newHashMap();
|
||||
clone.additionalAbilityLists = Maps.newHashMap();
|
||||
@@ -1287,6 +1298,13 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isMayChooseNewTargets() {
|
||||
return mayChooseNewTargets;
|
||||
}
|
||||
public void setMayChooseNewTargets(boolean value) {
|
||||
mayChooseNewTargets = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether variable was present in the announce list.
|
||||
*/
|
||||
@@ -1358,6 +1376,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
||||
}
|
||||
|
||||
public void setTargets(TargetChoices targets) {
|
||||
// TODO should copy the target choices?
|
||||
targetChosen = targets;
|
||||
}
|
||||
|
||||
@@ -1656,6 +1675,48 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
||||
return p != null && p.isTargeting(o);
|
||||
}
|
||||
|
||||
public boolean setupTargets() {
|
||||
// Skip to paying if parent ability doesn't target and has no subAbilities.
|
||||
// (or trigger case where its already targeted)
|
||||
SpellAbility currentAbility = this;
|
||||
final Card source = getHostCard();
|
||||
do {
|
||||
final TargetRestrictions tgt = currentAbility.getTargetRestrictions();
|
||||
if (tgt != null && tgt.doesTarget()) {
|
||||
currentAbility.clearTargets();
|
||||
Player targetingPlayer;
|
||||
if (currentAbility.hasParam("TargetingPlayer")) {
|
||||
final PlayerCollection candidates = AbilityUtils.getDefinedPlayers(source, currentAbility.getParam("TargetingPlayer"), currentAbility);
|
||||
// activator chooses targeting player
|
||||
targetingPlayer = getActivatingPlayer().getController().chooseSingleEntityForEffect(
|
||||
candidates, currentAbility, "Choose the targeting player", null);
|
||||
} else {
|
||||
targetingPlayer = getActivatingPlayer();
|
||||
}
|
||||
currentAbility.setTargetingPlayer(targetingPlayer);
|
||||
if (!targetingPlayer.getController().chooseTargetsFor(currentAbility)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
final AbilitySub subAbility = currentAbility.getSubAbility();
|
||||
if (subAbility != null) {
|
||||
// This is necessary for "TargetsWithDefinedController$ ParentTarget"
|
||||
subAbility.setParent(currentAbility);
|
||||
}
|
||||
currentAbility = subAbility;
|
||||
} while (currentAbility != null);
|
||||
return true;
|
||||
}
|
||||
public final void clearTargets() {
|
||||
final TargetRestrictions tg = getTargetRestrictions();
|
||||
if (tg != null) {
|
||||
resetTargets();
|
||||
if (hasParam("DividedAsYouChoose")) {
|
||||
tg.calculateStillToDivide(getParam("DividedAsYouChoose"), getHostCard(), this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Takes one argument like Permanent.Blue+withFlying
|
||||
@Override
|
||||
public final boolean isValid(final String restriction, final Player sourceController, final Card source, SpellAbility spellAbility) {
|
||||
|
||||
@@ -25,6 +25,7 @@ import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerCollection;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -42,7 +43,7 @@ public class TargetChoices implements Cloneable {
|
||||
|
||||
// Card or Player are legal targets.
|
||||
private final CardCollection targetCards = new CardCollection();
|
||||
private final List<Player> targetPlayers = new ArrayList<>();
|
||||
private final PlayerCollection targetPlayers = new PlayerCollection();
|
||||
private final List<SpellAbility> targetSpells = new ArrayList<>();
|
||||
|
||||
public final int getNumTargeted() {
|
||||
@@ -70,8 +71,7 @@ public class TargetChoices implements Cloneable {
|
||||
}
|
||||
|
||||
private final boolean addTarget(final Card c) {
|
||||
if (!targetCards.contains(c)) {
|
||||
targetCards.add(c);
|
||||
if (targetCards.add(c)) {
|
||||
numTargeted++;
|
||||
return true;
|
||||
}
|
||||
@@ -79,8 +79,7 @@ public class TargetChoices implements Cloneable {
|
||||
}
|
||||
|
||||
private final boolean addTarget(final Player p) {
|
||||
if (!targetPlayers.contains(p)) {
|
||||
targetPlayers.add(p);
|
||||
if (targetPlayers.add(p)) {
|
||||
numTargeted++;
|
||||
return true;
|
||||
}
|
||||
@@ -213,16 +212,10 @@ public class TargetChoices implements Cloneable {
|
||||
if (obj instanceof TargetChoices) {
|
||||
TargetChoices compare = (TargetChoices)obj;
|
||||
|
||||
if (this.getNumTargeted() != compare.getNumTargeted()) {
|
||||
if (getNumTargeted() != compare.getNumTargeted()) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < this.getTargets().size(); i++) {
|
||||
if (!compare.getTargets().get(i).equals(this.getTargets().get(i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
return getTargets().equals(compare.getTargets());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -516,10 +516,17 @@ public class TriggerHandler {
|
||||
else {
|
||||
// get CardState does not work for transformed cards
|
||||
// also its about LKI
|
||||
// TODO remove this part after all spellAbility can handle LKI as host
|
||||
// Currently only true for delayed Trigger
|
||||
if (host.isInZone(ZoneType.Battlefield) || !host.hasAlternateState()) {
|
||||
// if host changes Zone with other cards, try to use original host
|
||||
if (!regtrig.getMode().equals(TriggerType.ChangesZone))
|
||||
host = game.getCardState(host);
|
||||
if (!regtrig.getMode().equals(TriggerType.ChangesZone)) {
|
||||
Card gameHost = game.getCardState(host);
|
||||
// TODO only set when the host equals the game state
|
||||
if (gameHost.equalsWithTimestamp(host)) {
|
||||
host = gameHost;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -269,7 +269,7 @@ public class TriggerSpellAbilityCast extends Trigger {
|
||||
final SpellAbility castSA = (SpellAbility) runParams.get(AbilityKey.CastSA);
|
||||
final SpellAbilityStackInstance si = sa.getHostCard().getGame().getStack().getInstanceFromSpellAbility(castSA);
|
||||
sa.setTriggeringObject(AbilityKey.Card, castSA.getHostCard());
|
||||
sa.setTriggeringObject(AbilityKey.SpellAbility, castSA);
|
||||
sa.setTriggeringObject(AbilityKey.SpellAbility, castSA.copy(castSA.getHostCard(), true));
|
||||
sa.setTriggeringObject(AbilityKey.StackInstance, si);
|
||||
sa.setTriggeringObject(AbilityKey.SpellAbilityTargetingCards, (si != null ? si.getSpellAbility(true) : castSA).getTargets().getTargetCards());
|
||||
sa.setTriggeringObjectsFrom(
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
@@ -65,7 +65,7 @@ import forge.util.TextUtil;
|
||||
* <p>
|
||||
* MagicStack class.
|
||||
* </p>
|
||||
*
|
||||
*
|
||||
* @author Forge
|
||||
* @version $Id$
|
||||
*/
|
||||
@@ -135,14 +135,12 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
|
||||
|
||||
// if the ability is a spell, but not a copied spell and its not already
|
||||
// on the stack zone, move there
|
||||
if (ability.isSpell()) {
|
||||
if (!source.isCopiedSpell()) {
|
||||
if (!source.isInZone(ZoneType.Stack)) {
|
||||
ability.setHostCard(game.getAction().moveToStack(source, ability));
|
||||
}
|
||||
if (ability.equals(source.getCastSA())) {
|
||||
source.setCastSA(ability.copy(source, true));
|
||||
}
|
||||
if (ability.isSpell() && !source.isCopiedSpell()) {
|
||||
if (!source.isInZone(ZoneType.Stack)) {
|
||||
ability.setHostCard(game.getAction().moveToStack(source, ability));
|
||||
}
|
||||
if (ability.equals(source.getCastSA())) {
|
||||
source.setCastSA(ability.copy(source, true));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,6 +243,11 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
|
||||
} else if (source.isFaceDown()) {
|
||||
source.turnFaceUp(null);
|
||||
}
|
||||
|
||||
// copied always add to stack zone
|
||||
if (source.isCopiedSpell()) {
|
||||
game.getStackZone().add(source);
|
||||
}
|
||||
}
|
||||
|
||||
if (sp.getApi() == ApiType.Charm && sp.hasParam("ChoiceRestriction")) {
|
||||
@@ -254,7 +257,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
|
||||
|
||||
//cancel auto-pass for all opponents of activating player
|
||||
//when a new non-triggered ability is put on the stack
|
||||
if (!sp.isTrigger()) {
|
||||
if (!sp.isTrigger()) {
|
||||
for (final Player p : activator.getOpponents()) {
|
||||
p.getController().autoPassCancel();
|
||||
}
|
||||
@@ -271,18 +274,15 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
|
||||
// TODO: make working triggered ability
|
||||
sp.setTotalManaSpent(totManaSpent);
|
||||
AbilityUtils.resolve(sp);
|
||||
// AbilityStatic should do nothing below
|
||||
return;
|
||||
}
|
||||
else {
|
||||
for (OptionalCost s : sp.getOptionalCosts()) {
|
||||
source.addOptionalCostPaid(s);
|
||||
}
|
||||
if (sp.isCopied()) {
|
||||
si = push(sp);
|
||||
}
|
||||
else {
|
||||
// The ability is added to stack HERE
|
||||
si = push(sp);
|
||||
}
|
||||
// The ability is added to stack HERE
|
||||
si = push(sp);
|
||||
}
|
||||
|
||||
sp.setTotalManaSpent(totManaSpent);
|
||||
@@ -292,7 +292,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
|
||||
|
||||
// Copied spells aren't cast per se so triggers shouldn't run for them.
|
||||
Map<AbilityKey, Object> runParams = AbilityKey.newMap();
|
||||
if (!(sp instanceof AbilityStatic) && !sp.isCopied()) {
|
||||
if (!sp.isCopied()) {
|
||||
// Run SpellAbilityCast triggers
|
||||
runParams.put(AbilityKey.Cost, sp.getPayCosts());
|
||||
runParams.put(AbilityKey.Player, sp.getHostCard().getController());
|
||||
@@ -322,17 +322,15 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
|
||||
if (sp.isCycling()) {
|
||||
activator.addCycled(sp);
|
||||
}
|
||||
|
||||
|
||||
if (sp.hasParam("Crew")) {
|
||||
// Trigger crews!
|
||||
runParams.put(AbilityKey.Vehicle, sp.getHostCard());
|
||||
runParams.put(AbilityKey.Crew, sp.getPaidList("TappedCards"));
|
||||
game.getTriggerHandler().runTrigger(TriggerType.Crewed, runParams, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Run SpellAbilityCopy triggers
|
||||
if (sp.isCopied()) {
|
||||
} else {
|
||||
// Run SpellAbilityCopy triggers
|
||||
runParams.put(AbilityKey.Activator, sp.getActivatingPlayer());
|
||||
runParams.put(AbilityKey.CopySA, si.getSpellAbility(true));
|
||||
// Run SpellCopy triggers
|
||||
@@ -345,7 +343,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
|
||||
// Run BecomesTarget triggers
|
||||
// Create a new object, since the triggers aren't happening right away
|
||||
List<TargetChoices> chosenTargets = sp.getAllTargetChoices();
|
||||
if (!chosenTargets.isEmpty()) {
|
||||
if (!chosenTargets.isEmpty()) {
|
||||
runParams = Maps.newHashMap();
|
||||
SpellAbility s = sp;
|
||||
if (si != null) {
|
||||
@@ -362,7 +360,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
|
||||
if (distinctObjects.contains(tgt)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
distinctObjects.add(tgt);
|
||||
if (tgt instanceof Card && !((Card) tgt).hasBecomeTargetThisTurn()) {
|
||||
runParams.put(AbilityKey.FirstTime, null);
|
||||
@@ -443,7 +441,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
|
||||
// Resolving the Stack
|
||||
|
||||
// freeze the stack while we're in the middle of resolving
|
||||
freezeStack();
|
||||
freezeStack();
|
||||
setResolving(true);
|
||||
|
||||
// The SpellAbility isn't removed from the Stack until it finishes resolving
|
||||
@@ -452,21 +450,21 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
|
||||
//final SpellAbility sa = pop();
|
||||
|
||||
// ActivePlayer gains priority first after Resolve
|
||||
game.getPhaseHandler().resetPriority();
|
||||
game.getPhaseHandler().resetPriority();
|
||||
|
||||
final Card source = sa.getHostCard();
|
||||
curResolvingCard = source;
|
||||
|
||||
|
||||
boolean thisHasFizzled = hasFizzled(sa, source, null);
|
||||
|
||||
|
||||
if (!thisHasFizzled) {
|
||||
game.copyLastState();
|
||||
}
|
||||
|
||||
if (thisHasFizzled) { // Fizzle
|
||||
if (sa.isBestow()) {
|
||||
// 702.102d: if its target is illegal,
|
||||
// the effect making it an Aura spell ends.
|
||||
// 702.102d: if its target is illegal,
|
||||
// the effect making it an Aura spell ends.
|
||||
// It continues resolving as a creature spell.
|
||||
source.unanimateBestow();
|
||||
game.fireEvent(new GameEventCardStatsChanged(source));
|
||||
@@ -482,7 +480,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
|
||||
sa.resolve();
|
||||
// do creatures ETB from here?
|
||||
}
|
||||
|
||||
|
||||
game.fireEvent(new GameEventSpellResolved(sa, thisHasFizzled));
|
||||
finishResolving(sa, thisHasFizzled);
|
||||
|
||||
@@ -501,7 +499,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
|
||||
|
||||
// remove SA and card from the stack
|
||||
removeCardFromStack(sa, si, fizzle);
|
||||
|
||||
|
||||
if (si != null) {
|
||||
remove(si);
|
||||
}
|
||||
@@ -531,10 +529,17 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
|
||||
// need to update active trigger
|
||||
game.getTriggerHandler().resetActiveTriggers();
|
||||
|
||||
if (source.isCopiedSpell() || sa.isAbility()) {
|
||||
if (sa.isAbility()) {
|
||||
// do nothing
|
||||
return;
|
||||
}
|
||||
else if ((source.isInstant() || source.isSorcery() || fizzle) &&
|
||||
|
||||
if (source.isCopiedSpell() && source.isInZone(ZoneType.Stack)) {
|
||||
source.ceaseToExist();
|
||||
return;
|
||||
}
|
||||
|
||||
if ((source.isInstant() || source.isSorcery() || fizzle) &&
|
||||
source.isInZone(ZoneType.Stack)) {
|
||||
// If Spell and still on the Stack then let it goto the graveyard or replace its own movement
|
||||
Map<AbilityKey, Object> params = AbilityKey.newMap();
|
||||
@@ -864,8 +869,8 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
|
||||
game.updateStackForView();
|
||||
game.fireEvent(new GameEventSpellRemovedFromStack(null));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return TextUtil.concatNoSpace(simultaneousStackEntryList.toString(),"==", frozenStack.toString(), "==", stack.toString());
|
||||
}
|
||||
|
||||
@@ -104,8 +104,12 @@ public class Zone implements java.io.Serializable, Iterable<Card> {
|
||||
if (!c.isImmutable()) {
|
||||
final Zone oldZone = game.getZoneOf(c);
|
||||
final ZoneType zt = oldZone == null ? ZoneType.Stack : oldZone.getZoneType();
|
||||
cardsAddedThisTurn.add(zt, c);
|
||||
latestStateCardsAddedThisTurn.add(zt, latestState != null ? latestState : c);
|
||||
|
||||
// only if the zoneType differss from this
|
||||
if (zt != zoneType) {
|
||||
cardsAddedThisTurn.add(zt, c);
|
||||
latestStateCardsAddedThisTurn.add(zt, latestState != null ? latestState : c);
|
||||
}
|
||||
}
|
||||
|
||||
c.setTurnInZone(game.getPhaseHandler().getTurn());
|
||||
|
||||
Reference in New Issue
Block a user