Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 23 additions & 49 deletions forge-ai/src/main/java/forge/ai/simulation/GameCopier.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import forge.card.CardRarity;
import forge.card.CardRules;
import forge.game.*;
import forge.game.ability.effects.DetachedCardEffect;
import forge.game.card.Card;
import forge.game.card.CardCloneStates;
import forge.game.card.CardCopyService;
Expand All @@ -21,7 +20,6 @@
import forge.game.player.RegisteredPlayer;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityStackInstance;
import forge.game.staticability.StaticAbility;
import forge.game.trigger.TriggerType;
import forge.game.zone.PlayerZoneBattlefield;
import forge.game.zone.ZoneType;
Expand All @@ -32,14 +30,14 @@
import java.util.Map;

public class GameCopier {
private static final ZoneType[] ZONES = new ZoneType[] {
ZoneType.Battlefield,
ZoneType.Hand,
ZoneType.Graveyard,
ZoneType.Library,
ZoneType.Exile,
ZoneType.Stack,
ZoneType.Command,
private static final ZoneType[] ZONES = new ZoneType[]{
ZoneType.Battlefield,
ZoneType.Hand,
ZoneType.Graveyard,
ZoneType.Library,
ZoneType.Exile,
ZoneType.Stack,
ZoneType.Command,
};

private Game origGame;
Expand All @@ -66,6 +64,7 @@ public Game getCopiedGame() {
public Game makeCopy() {
return makeCopy(null, null);
}

public Game makeCopy(PhaseType advanceToPhase, Player aiPlayer) {
if (origGame.EXPERIMENTAL_RESTORE_SNAPSHOT) {
// How do we advance to phase when using restores?
Expand Down Expand Up @@ -138,8 +137,8 @@ public Game makeCopy(PhaseType advanceToPhase, Player aiPlayer) {
// Sometimes, a spell can "remember" a token card that's not in any zone
// (and thus wouldn't have been copied) - for example Swords to Plowshares
// remembering its target for LKI. Skip these to not crash in find().
if (o instanceof Card && ((Card)o).getZone() == null) {
continue;
if (o instanceof Card && ((Card) o).getZone() == null) {
continue;
}
c.addRemembered(find((GameObject) o));
} else {
Expand Down Expand Up @@ -198,7 +197,7 @@ private static void copyStack(Game origGame, Game newGame, IEntityMap map) {
}
newGame.getStack().add(newSa);
}
}
}
}

private RegisteredPlayer clonePlayer(RegisteredPlayer p) {
Expand Down Expand Up @@ -283,51 +282,25 @@ private void copyGameState(Game newGame, Player aiPlayer) {

private static PaperCard hidden_info_card = new PaperCard(CardRules.fromScript(Lists.newArrayList("Name:hidden", "Types:Artifact", "Oracle:")), "", CardRarity.Common);
private static final boolean PRUNE_HIDDEN_INFO = false;
private static final boolean USE_FROM_PAPER_CARD = true;

private Card createCardCopy(Game newGame, Player newOwner, Card c, Player aiPlayer) {
if (c.isToken() && !c.isImmutable()) {
Card result = new TokenInfo(c).makeOneToken(newOwner);
new CardCopyService(c).copyCopiableCharacteristics(result, null, null);
return result;
}
if (USE_FROM_PAPER_CARD && !c.isImmutable() && c.getPaperCard() != null) {
Card newCard;
if (PRUNE_HIDDEN_INFO && !c.getView().canBeShownTo(aiPlayer.getView())) {
// TODO also check REVEALED_CARDS memory
newCard = new Card(newGame.nextCardId(), hidden_info_card, newGame);
newCard.setOwner(newOwner);
} else {
newCard = Card.fromPaperCard(c.getPaperCard(), newOwner);
}
newCard.setCommander(c.isCommander());
return newCard;
}

// TODO: The above is very expensive and accounts for the vast majority of GameCopier execution time.
// The issue is that it requires parsing the original card from scratch from the paper card. We should
// improve the copier to accurately copy the card from its actual state, so that the paper card shouldn't
// be needed. Once the below code accurately copies the card, remove the USE_FROM_PAPER_CARD code path.
Card newCard;
if (c instanceof DetachedCardEffect)
newCard = new DetachedCardEffect((DetachedCardEffect) c, newGame, true);
else
newCard = new Card(newGame.nextCardId(), c.getPaperCard(), newGame);
newCard.setOwner(newOwner);
newCard.setName(c.getName());
newCard.setCommander(c.isCommander());
newCard.addType(c.getType());
for (StaticAbility stAb : c.getStaticAbilities()) {
newCard.addStaticAbility(stAb.copy(newCard, true));
}
for (SpellAbility sa : c.getSpellAbilities()) {
SpellAbility saCopy = sa.copy(newCard, true);
if (saCopy != null) {
newCard.addSpellAbility(saCopy);
} else {
System.err.println(sa.toString());
}
if (PRUNE_HIDDEN_INFO && !c.getView().canBeShownTo(aiPlayer.getView())) {
// TODO also check REVEALED_CARDS memory
newCard = new Card(newGame.nextCardId(), hidden_info_card, newGame);
newCard.setOwner(newOwner);
} else {
// Use copyStatsToGame which copies all card states (abilities, triggers, etc.)
// directly from the source card without re-parsing from PaperCard definition.
newCard = CardCopyService.copyStatsToGame(c, newOwner, newGame);
}

newCard.setCommander(c.isCommander());
return newCard;
}

Expand Down Expand Up @@ -495,6 +468,7 @@ public GameObject find(GameObject o) {
throw new RuntimeException("Couldn't map " + o + "/" + System.identityHashCode(o));
return result;
}

public GameObject reverseFind(GameObject o) {
if (origGame.EXPERIMENTAL_RESTORE_SNAPSHOT) {
return snapshot.reverseFind(o);
Expand Down
36 changes: 36 additions & 0 deletions forge-game/src/main/java/forge/game/card/CardCopyService.java
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,42 @@ public static Card copyStats(final Card in, final Player newOwner, boolean assig
return c;
}

/**
* Copy card stats to a new game instance. This is an optimized path for GameCopier
* that avoids re-parsing the card from its PaperCard definition.
*
* @param in The source card to copy from
* @param newOwner The owner in the new game
* @param targetGame The target game instance
* @return A new card with copied stats in the target game
*/
public static Card copyStatsToGame(final Card in, final Player newOwner, final Game targetGame) {
int id = targetGame.nextCardId();
final Card c;
if (in instanceof DetachedCardEffect) {
c = new DetachedCardEffect((DetachedCardEffect) in, targetGame, true);
} else {
c = new Card(id, in.getPaperCard(), targetGame);
}

c.setOwner(newOwner);
c.setSetCode(in.getSetCode());

for (final CardStateName state : in.getStates()) {
copyState(in, state, c, state, false);
}

c.setState(in.getCurrentStateName(), false);
c.setRules(in.getRules());
c.setBackSide(in.isBackSide());
if(in.getEffectSource() != null)
c.setEffectSource(in.getEffectSource());

c.updateStateForView();

return c;
}

@Deprecated
public void copyCopiableCharacteristics(final Card to, SpellAbility sourceSA, SpellAbility targetSA) {
final boolean toIsFaceDown = to.isFaceDown();
Expand Down
2 changes: 2 additions & 0 deletions forge-game/src/main/java/forge/game/card/CardState.java
Original file line number Diff line number Diff line change
Expand Up @@ -726,6 +726,8 @@ public final void copyFrom(final CardState source, final boolean lki, final Card
setFunctionalVariantName(source.getFunctionalVariantName());
setBasePower(source.getBasePower());
setBaseToughness(source.getBaseToughness());
setBasePowerString(source.getBasePowerString());
setBaseToughnessString(source.getBaseToughnessString());
setBaseLoyalty(source.getBaseLoyalty());
setBaseDefense(source.getBaseDefense());
setAttractionLights(source.getAttractionLights());
Expand Down
Loading
Loading