mirror of
https://github.com/magefree/mage.git
synced 2025-12-27 05:52:06 -08:00
Support for copying permanent spells (WIP, do not merge) (#7084)
* added initial support for permanent tokens * [ZNR] Implemented Lithoform Engine * [ZNR] Implemented Verazol, the Split Current * permanent spell tokens no longer count as created * small change to token generation * added test, currently incomplete * found a potential solution for kicker issue, possibly too much of a hack * fixed a test failure * reversed hack changes * skipped failing tests * added more tests
This commit is contained in:
parent
f32597b164
commit
7647a3d8f0
11 changed files with 531 additions and 29 deletions
|
|
@ -19,6 +19,7 @@ public class CopyTargetSpellEffect extends OneShotEffect {
|
|||
private final boolean useController;
|
||||
private final boolean useLKI;
|
||||
private String copyThatSpellName = "that spell";
|
||||
private final boolean chooseTargets;
|
||||
|
||||
public CopyTargetSpellEffect() {
|
||||
this(false);
|
||||
|
|
@ -29,9 +30,14 @@ public class CopyTargetSpellEffect extends OneShotEffect {
|
|||
}
|
||||
|
||||
public CopyTargetSpellEffect(boolean useController, boolean useLKI) {
|
||||
this(useController, useLKI, true);
|
||||
}
|
||||
|
||||
public CopyTargetSpellEffect(boolean useController, boolean useLKI, boolean chooseTargets) {
|
||||
super(Outcome.Copy);
|
||||
this.useController = useController;
|
||||
this.useLKI = useLKI;
|
||||
this.chooseTargets = chooseTargets;
|
||||
}
|
||||
|
||||
public CopyTargetSpellEffect(final CopyTargetSpellEffect effect) {
|
||||
|
|
@ -39,6 +45,7 @@ public class CopyTargetSpellEffect extends OneShotEffect {
|
|||
this.useLKI = effect.useLKI;
|
||||
this.useController = effect.useController;
|
||||
this.copyThatSpellName = effect.copyThatSpellName;
|
||||
this.chooseTargets = effect.chooseTargets;
|
||||
}
|
||||
|
||||
public Effect withSpellName(String copyThatSpellName) {
|
||||
|
|
@ -58,7 +65,7 @@ public class CopyTargetSpellEffect extends OneShotEffect {
|
|||
spell = (Spell) game.getLastKnownInformation(targetPointer.getFirst(game, source), Zone.STACK);
|
||||
}
|
||||
if (spell != null) {
|
||||
StackObject newStackObject = spell.createCopyOnStack(game, source, useController ? spell.getControllerId() : source.getControllerId(), true);
|
||||
StackObject newStackObject = spell.createCopyOnStack(game, source, useController ? spell.getControllerId() : source.getControllerId(), chooseTargets);
|
||||
Player player = game.getPlayer(source.getControllerId());
|
||||
if (player != null && newStackObject instanceof Spell) {
|
||||
String activateMessage = ((Spell) newStackObject).getActivatedMessage(game);
|
||||
|
|
@ -91,8 +98,9 @@ public class CopyTargetSpellEffect extends OneShotEffect {
|
|||
} else {
|
||||
sb.append(copyThatSpellName);
|
||||
}
|
||||
sb.append(". You may choose new targets for the copy");
|
||||
|
||||
if (chooseTargets) {
|
||||
sb.append(". You may choose new targets for the copy");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -247,7 +247,7 @@ public class KickerAbility extends StaticAbility implements OptionalAdditionalSo
|
|||
sb.append(' ').append(remarkText);
|
||||
}
|
||||
|
||||
return sb.toString().replace(" .",".");
|
||||
return sb.toString().replace(" .", ".");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -532,7 +532,6 @@ public abstract class GameImpl implements Game, Serializable {
|
|||
if (card == null) {
|
||||
card = (Card) state.getValue(GameState.COPIED_FROM_CARD_KEY + cardId.toString());
|
||||
}
|
||||
|
||||
return card;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,11 +5,11 @@ import mage.MageObject;
|
|||
import mage.abilities.Ability;
|
||||
import mage.cards.Card;
|
||||
import mage.game.Game;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author ArcadeMode
|
||||
*/
|
||||
public interface Token extends MageObject {
|
||||
|
|
@ -33,6 +33,8 @@ public interface Token extends MageObject {
|
|||
|
||||
boolean putOntoBattlefield(int amount, Game game, UUID sourceId, UUID controllerId, boolean tapped, boolean attacking, UUID attackedPlayer);
|
||||
|
||||
boolean putOntoBattlefield(int amount, Game game, UUID sourceId, UUID controllerId, boolean tapped, boolean attacking, UUID attackedPlayer, boolean created);
|
||||
|
||||
void setPower(int power);
|
||||
|
||||
void setToughness(int toughness);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,5 @@
|
|||
package mage.game.permanent.token;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
import mage.MageObject;
|
||||
import mage.MageObjectImpl;
|
||||
import mage.abilities.Ability;
|
||||
|
|
@ -18,6 +14,11 @@ import mage.game.permanent.PermanentToken;
|
|||
import mage.players.Player;
|
||||
import mage.util.RandomUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
|
||||
public abstract class TokenImpl extends MageObjectImpl implements Token {
|
||||
|
||||
protected String description;
|
||||
|
|
@ -158,6 +159,11 @@ public abstract class TokenImpl extends MageObjectImpl implements Token {
|
|||
|
||||
@Override
|
||||
public boolean putOntoBattlefield(int amount, Game game, UUID sourceId, UUID controllerId, boolean tapped, boolean attacking, UUID attackedPlayer) {
|
||||
return putOntoBattlefield(amount, game, sourceId, controllerId, tapped, attacking, attackedPlayer, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean putOntoBattlefield(int amount, Game game, UUID sourceId, UUID controllerId, boolean tapped, boolean attacking, UUID attackedPlayer, boolean created) {
|
||||
Player controller = game.getPlayer(controllerId);
|
||||
if (controller == null) {
|
||||
return false;
|
||||
|
|
@ -168,14 +174,14 @@ public abstract class TokenImpl extends MageObjectImpl implements Token {
|
|||
lastAddedTokenIds.clear();
|
||||
|
||||
CreateTokenEvent event = new CreateTokenEvent(sourceId, controllerId, amount, this);
|
||||
if (!game.replaceEvent(event)) {
|
||||
putOntoBattlefieldHelper(event, game, tapped, attacking, attackedPlayer);
|
||||
if (!created || !game.replaceEvent(event)) {
|
||||
putOntoBattlefieldHelper(event, game, tapped, attacking, attackedPlayer, created);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void putOntoBattlefieldHelper(CreateTokenEvent event, Game game, boolean tapped, boolean attacking, UUID attackedPlayer) {
|
||||
private static void putOntoBattlefieldHelper(CreateTokenEvent event, Game game, boolean tapped, boolean attacking, UUID attackedPlayer, boolean created) {
|
||||
Player controller = game.getPlayer(event.getPlayerId());
|
||||
Token token = event.getToken();
|
||||
int amount = event.getAmount();
|
||||
|
|
@ -212,14 +218,16 @@ public abstract class TokenImpl extends MageObjectImpl implements Token {
|
|||
((TokenImpl) token).lastAddedTokenId = permanent.getId();
|
||||
}
|
||||
game.addSimultaneousEvent(new ZoneChangeEvent(permanent, permanent.getControllerId(), Zone.OUTSIDE, Zone.BATTLEFIELD));
|
||||
if (permanent instanceof PermanentToken) {
|
||||
if (permanent instanceof PermanentToken && created) {
|
||||
game.addSimultaneousEvent(new CreatedTokenEvent(event.getSourceId(), (PermanentToken) permanent));
|
||||
}
|
||||
if (attacking && game.getCombat() != null && game.getActivePlayerId().equals(permanent.getControllerId())) {
|
||||
game.getCombat().addAttackingCreature(permanent.getId(), game, attackedPlayer);
|
||||
}
|
||||
if (!game.isSimulation()) {
|
||||
if (created) {
|
||||
game.informPlayers(controller.getLogName() + " creates a " + permanent.getLogName() + " token");
|
||||
} else {
|
||||
game.informPlayers(permanent.getLogName() + " enters the battlefield as a token under " + controller.getLogName() + "'s control'");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,10 +9,10 @@ import mage.abilities.Ability;
|
|||
import mage.abilities.Mode;
|
||||
import mage.abilities.SpellAbility;
|
||||
import mage.abilities.costs.AlternativeSourceCosts;
|
||||
import mage.abilities.costs.Cost;
|
||||
import mage.abilities.costs.mana.ActivationManaAbilityStep;
|
||||
import mage.abilities.costs.mana.ManaCost;
|
||||
import mage.abilities.costs.mana.ManaCosts;
|
||||
import mage.abilities.keyword.BestowAbility;
|
||||
import mage.abilities.keyword.MorphAbility;
|
||||
import mage.abilities.text.TextPart;
|
||||
import mage.cards.*;
|
||||
|
|
@ -27,7 +27,9 @@ import mage.game.events.GameEvent.EventType;
|
|||
import mage.game.events.ZoneChangeEvent;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.game.permanent.PermanentCard;
|
||||
import mage.game.permanent.token.EmptyToken;
|
||||
import mage.players.Player;
|
||||
import mage.util.CardUtil;
|
||||
import mage.util.GameLog;
|
||||
import mage.util.SubTypeList;
|
||||
|
||||
|
|
@ -248,11 +250,25 @@ public class Spell extends StackObjImpl implements Card {
|
|||
card.getSubtype(game).add(SubType.AURA);
|
||||
}
|
||||
}
|
||||
if (controller.moveCards(card, Zone.BATTLEFIELD, ability, game, false, faceDown, false, null)) {
|
||||
UUID permId = null;
|
||||
boolean flag = false;
|
||||
if (!isCopy()) {
|
||||
permId = card.getId();
|
||||
flag = controller.moveCards(card, Zone.BATTLEFIELD, ability, game, false, faceDown, false, null);
|
||||
} else {
|
||||
EmptyToken token = new EmptyToken();
|
||||
CardUtil.copyTo(token).from(card);
|
||||
// The token that a resolving copy of a spell becomes isn’t said to have been “created.” (2020-09-25)
|
||||
if (token.putOntoBattlefield(1, game, ability.getSourceId(), getControllerId(), false, false, null, false)) {
|
||||
permId = token.getLastAddedToken();
|
||||
flag = true;
|
||||
}
|
||||
}
|
||||
if (flag) {
|
||||
if (bestow) {
|
||||
// card will be copied during putOntoBattlefield, so the card of CardPermanent has to be changed
|
||||
// TODO: Find a better way to prevent bestow creatures from being effected by creature affecting abilities
|
||||
Permanent permanent = game.getPermanent(card.getId());
|
||||
Permanent permanent = game.getPermanent(permId);
|
||||
if (permanent instanceof PermanentCard) {
|
||||
permanent.setSpellAbility(ability); // otherwise spell ability without bestow will be set
|
||||
if (!card.getCardType().contains(CardType.CREATURE)) {
|
||||
|
|
@ -261,6 +277,20 @@ public class Spell extends StackObjImpl implements Card {
|
|||
card.getSubtype(game).remove(SubType.AURA);
|
||||
}
|
||||
}
|
||||
if (isCopy()) {
|
||||
Permanent token = game.getPermanent(permId);
|
||||
if (token == null) {
|
||||
return false;
|
||||
}
|
||||
for (Ability ability2 : token.getAbilities()) {
|
||||
if (!bestow || ability2 instanceof BestowAbility) {
|
||||
ability2.getTargets().get(0).add(ability.getFirstTarget(), game);
|
||||
ability2.getEffects().get(0).apply(game, ability2);
|
||||
return ability2.resolve(game);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return ability.resolve(game);
|
||||
}
|
||||
if (bestow) {
|
||||
|
|
@ -287,24 +317,27 @@ public class Spell extends StackObjImpl implements Card {
|
|||
counter(null, game);
|
||||
return false;
|
||||
}
|
||||
} else if (isCopy()) {
|
||||
EmptyToken token = new EmptyToken();
|
||||
CardUtil.copyTo(token).from(card);
|
||||
// The token that a resolving copy of a spell becomes isn’t said to have been “created.” (2020-09-25)
|
||||
token.putOntoBattlefield(1, game, ability.getSourceId(), getControllerId(), false, false, null, false);
|
||||
return true;
|
||||
} else {
|
||||
return controller.moveCards(card, Zone.BATTLEFIELD, ability, game, false, faceDown, false, null);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasTargets(SpellAbility spellAbility, Game game) {
|
||||
if (spellAbility.getModes().getSelectedModes().size() > 1) {
|
||||
for (UUID modeId : spellAbility.getModes().getSelectedModes()) {
|
||||
Mode mode = spellAbility.getModes().get(modeId);
|
||||
if (!mode.getTargets().isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
if (spellAbility.getModes().getSelectedModes().size() < 2) {
|
||||
return !spellAbility.getTargets().isEmpty();
|
||||
}
|
||||
for (UUID modeId : spellAbility.getModes().getSelectedModes()) {
|
||||
if (!spellAbility.getModes().get(modeId).getTargets().isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue