Skip to content

Commit

Permalink
Support engine tracking card mover vs. controller
Browse files Browse the repository at this point in the history
  • Loading branch information
tool4EvEr authored and tool4EvEr committed Oct 23, 2024
1 parent 5a3f2ed commit 2f62447
Show file tree
Hide file tree
Showing 82 changed files with 138 additions and 118 deletions.
74 changes: 44 additions & 30 deletions forge-game/src/main/java/forge/game/GameAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -209,11 +209,27 @@ private Card changeZone(final Zone zoneFrom, Zone zoneTo, final Card c, Integer
c.getController().setRevolt(true);
}

Player putter = c.getController();

// Don't copy Tokens, copy only cards leaving the battlefield
// and returning to hand (to recreate their spell ability information)
if (toBattlefield || (suppress && zoneTo.getZoneType().isHidden())) {
copied = c;

// in some cases it's always affected that puts them in play (initially)
String defPutter;
if (cause == null || (!cause.hasParam("Putter") && (c.isToken() ||
cause.getApi() == ApiType.Cloak || cause.getApi() == ApiType.Manifest || cause.getApi() == ApiType.ManifestDread))) {
defPutter = "Owner";
} else {
defPutter = cause.getParamOrDefault("Putter", "You");
}
if ("Owner".equals(defPutter)) {
putter = c.getOwner();
} else {
putter = AbilityUtils.getDefinedPlayers(cause.getHostCard(), defPutter, cause).getFirst();
}

if (lastKnownInfo == null) {
lastKnownInfo = CardCopyService.getLKICopy(c);
}
Expand Down Expand Up @@ -249,13 +265,6 @@ private Card changeZone(final Zone zoneFrom, Zone zoneTo, final Card c, Integer

copied = new CardCopyService(c).copyCard(false);

// CR 707.12 casting of a card copy
if (zoneTo.is(ZoneType.Stack) && c.isRealToken()) {
copied.setCopiedPermanent(c.getCopiedPermanent());
//TODO: Feels like this should fit here and seems to work but it'll take a fair bit more testing to be sure.
//copied.setGamePieceType(GamePieceType.COPIED_SPELL);
}

copied.setGameTimestamp(c.getGameTimestamp());

if (zoneTo.is(ZoneType.Stack)) {
Expand All @@ -267,6 +276,13 @@ private Card changeZone(final Zone zoneFrom, Zone zoneTo, final Card c, Integer
copied.setExiledBy(c.getExiledBy());
copied.setDrawnThisTurn(c.getDrawnThisTurn());

// CR 707.12 casting of a card copy
if (c.isRealToken()) {
copied.setCopiedPermanent(c.getCopiedPermanent());
//TODO: Feels like this should fit here and seems to work but it'll take a fair bit more testing to be sure.
//copied.setGamePieceType(GamePieceType.COPIED_SPELL);
}

if (c.isTransformed()) {
copied.incrementTransformedTimestamp();
}
Expand All @@ -277,6 +293,7 @@ private Card changeZone(final Zone zoneFrom, Zone zoneTo, final Card c, Integer

// CR 112.2 A spell’s controller is, by default, the player who put it on the stack.
copied.setController(cause.getActivatingPlayer(), 0);

KeywordInterface kw = cause.getKeyword();
if (kw != null) {
copied.addKeywordForStaticAbility(kw);
Expand All @@ -290,25 +307,24 @@ private Card changeZone(final Zone zoneFrom, Zone zoneTo, final Card c, Integer
copied.setBackSide(false);
}

copied.setUnearthed(c.isUnearthed());

// need to copy counters when card enters another zone than hand or library
if (lastKnownInfo.hasKeyword("Counters remain on CARDNAME as it moves to any zone other than a player's hand or library.") &&
!(zoneTo.is(ZoneType.Hand) || zoneTo.is(ZoneType.Library))) {
copied.setCounters(Maps.newHashMap(lastKnownInfo.getCounters()));
}
}

// perpetual stuff
if (c.hasIntensity()) {
copied.setIntensity(c.getIntensity(false));
}
if (c.isSpecialized()) {
copied.setState(c.getCurrentStateName(), false);
}
if (c.hasPerpetual()) {
copied.setPerpetual(c);
// perpetual stuff
if (c.hasIntensity()) {
copied.setIntensity(c.getIntensity(false));
}
if (c.isSpecialized()) {
copied.setState(c.getCurrentStateName(), false);
}
if (c.hasPerpetual()) {
copied.setPerpetual(c);
}
}

// ensure that any leftover keyword/type changes are cleared in the state view
copied.updateStateForView();

Expand Down Expand Up @@ -343,6 +359,7 @@ private Card changeZone(final Zone zoneFrom, Zone zoneTo, final Card c, Integer
repParams.put(AbilityKey.EffectOnly, true);
repParams.put(AbilityKey.CounterTable, table);
repParams.put(AbilityKey.CounterMap, table.column(copied));
repParams.put(AbilityKey.Putter, putter);
}

if (params != null) {
Expand Down Expand Up @@ -632,6 +649,7 @@ private Card changeZone(final Zone zoneFrom, Zone zoneTo, final Card c, Integer
runParams.put(AbilityKey.Destination, zoneTo.getZoneType().name());
runParams.put(AbilityKey.IndividualCostPaymentInstance, game.costPaymentStack.peek());
runParams.put(AbilityKey.MergedCards, mergedCards);
runParams.put(AbilityKey.Putter, putter);

if (params != null) {
runParams.putAll(params);
Expand Down Expand Up @@ -780,8 +798,6 @@ private Card moveTo(final Zone zoneTo, Card c, Integer position, SpellAbility ca
final Zone zoneFrom = game.getZoneOf(c);
// String prevName = prev != null ? prev.getZoneName() : "";

// Card lastKnownInfo = c;

// Handle the case that one component of a merged permanent got take to the subgame
if (zoneTo.is(ZoneType.Subgame) && (c.hasMergedCard() || c.isMerged())) {
c.moveMergedToSubgame(cause);
Expand Down Expand Up @@ -913,7 +929,7 @@ public final Card moveToVariantDeck(Card c, ZoneType zone, int deckPosition, Spe
}
return changeZone(game.getZoneOf(c), deck, c, deckPosition, cause, params);
}

public final Card moveToJunkyard(Card c, SpellAbility cause, Map<AbilityKey, Object> params) {
final PlayerZone junkyard = c.getOwner().getZone(ZoneType.Junkyard);
return moveTo(junkyard, c, cause, params);
Expand All @@ -931,7 +947,6 @@ public final Card exile(final Card c, SpellAbility cause, Map<AbilityKey, Object
final PlayerZone removed = c.getOwner().getZone(ZoneType.Exile);
final Card copied = moveTo(removed, c, cause, params);

// Run triggers
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromCard(c);
runParams.put(AbilityKey.Cause, cause);
if (origin != null) { // is generally null when adding via dev mode
Expand Down Expand Up @@ -1264,18 +1279,19 @@ public boolean checkStateEffects(final boolean runEvents, final Set<Card> affect
AbilityKey.addCardZoneTableParams(mapParams, table);

for (final Player p : game.getPlayers()) {
for (final ZoneType zt : ZoneType.values()) {
if (zt == ZoneType.Command)
p.checkKeywordCard();
p.checkKeywordCard();

for (final ZoneType zt : ZoneType.values()) {
if (zt == ZoneType.Battlefield) {
continue;
}
for (final Card c : p.getCardsIn(zt).threadSafeIterable()) {
checkAgain |= stateBasedAction704_5d(c);
// Dungeon Card won't affect other cards, so don't need to set checkAgain
stateBasedAction_Dungeon(c);
stateBasedAction_Scheme(c);
if (zt == ZoneType.Command) {
stateBasedAction_Scheme(c);
}
}
}
}
Expand Down Expand Up @@ -1557,7 +1573,7 @@ private void stateBasedAction_Scheme(Card c) {
return;
}
if (!game.getStack().hasSourceOnStack(c, null)) {
moveTo(ZoneType.SchemeDeck, c, null, AbilityKey.newMap());
moveTo(ZoneType.SchemeDeck, c, -1, null, AbilityKey.newMap());
}
}

Expand Down Expand Up @@ -1676,7 +1692,6 @@ public void checkGameOverCondition() {
FCollectionView<Player> allPlayers = game.getPlayers();
for (Player p : allPlayers) {
if (p.checkLoseCondition()) { // this will set appropriate outcomes
// Run triggers
if (losers == null) {
losers = Lists.newArrayListWithCapacity(3);
}
Expand Down Expand Up @@ -1898,7 +1913,6 @@ public final CardCollection sacrifice(final Iterable<Card> list, final SpellAbil
}
}
for (Map.Entry<Player, Collection<Card>> e : lki.asMap().entrySet()) {
// Run triggers
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPlayer(e.getKey());
runParams.put(AbilityKey.Cards, new CardCollection(e.getValue()));
runParams.put(AbilityKey.Cause, source);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ public enum AbilityKey {
Player("Player"),
PreventedAmount("PreventedAmount"),
Produced("Produced"),
Putter("Putter"),
Regeneration("Regeneration"),
ReplacementEffect("ReplacementEffect"),
ReplacementResult("ReplacementResult"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ public void resolve(SpellAbility sa) {
}

protected void manifestLoop(SpellAbility sa, Player p, final int amount) {

final Card source = sa.getHostCard();
final Player activator = sa.getActivatingPlayer();
final Game game = source.getGame();
Expand Down
3 changes: 1 addition & 2 deletions forge-game/src/main/java/forge/game/card/CardDamageMap.java
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ public void triggerExcessDamage(boolean isCombat, Map<Card, Integer> lethalDamag

damaged.getKey().setHasBeenDealtExcessDamageThisTurn(true);
damaged.getKey().logExcessDamage(excess);
// Run triggers

final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
runParams.put(AbilityKey.DamageTarget, damaged.getKey());
runParams.put(AbilityKey.DamageAmount, excess);
Expand All @@ -158,7 +158,6 @@ public void triggerExcessDamage(boolean isCombat, Map<Card, Integer> lethalDamag
}

if (!damagedList.isEmpty()) {
// Run triggers
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
runParams.put(AbilityKey.DamageTargets, damagedList);
runParams.put(AbilityKey.IsCombatDamage, isCombat);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ public boolean canReplace(Map<AbilityKey, Object> runParams) {
if (!matchesValidParam("ValidCause", runParams.get(AbilityKey.Cause))) {
return false;
}
if (!matchesValidParam("ValidPutter", runParams.get(AbilityKey.Putter))) {
return false;
}

if (hasParam("Origin")) {
ZoneType zt = (ZoneType) runParams.get(AbilityKey.Origin);
Expand Down Expand Up @@ -113,7 +116,7 @@ public boolean canReplace(Map<AbilityKey, Object> runParams) {
@Override
public void setReplacingObjects(Map<AbilityKey, Object> runParams, SpellAbility sa) {
sa.setReplacingObject(AbilityKey.Card, runParams.get(AbilityKey.Affected));
sa.setReplacingObjectsFrom(runParams, AbilityKey.NewCard, AbilityKey.CardLKI, AbilityKey.Cause,
sa.setReplacingObjectsFrom(runParams, AbilityKey.NewCard, AbilityKey.CardLKI, AbilityKey.Cause, AbilityKey.Putter,
AbilityKey.LastStateBattlefield, AbilityKey.LastStateGraveyard, AbilityKey.CounterTable, AbilityKey.CounterMap);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,10 @@ public final boolean performTest(final Map<AbilityKey, Object> runParams) {
return false;
}

if (!matchesValidParam("ValidPutter", runParams.get(AbilityKey.Putter))) {
return false;
}

if (hasParam("Fizzle")) {
if (!runParams.containsKey(AbilityKey.Fizzle)) {
return false;
Expand Down Expand Up @@ -203,7 +207,7 @@ public final void setTriggeringObjects(final SpellAbility sa, Map<AbilityKey, Ob
sa.setTriggeringObject(AbilityKey.Card, runParams.get(AbilityKey.CardLKI));
sa.setTriggeringObject(AbilityKey.NewCard, runParams.get(AbilityKey.Card));
} else {
sa.setTriggeringObjectsFrom(runParams, AbilityKey.Card, AbilityKey.CardLKI);
sa.setTriggeringObjectsFrom(runParams, AbilityKey.Card, AbilityKey.CardLKI, AbilityKey.Putter);
}
}

Expand Down
2 changes: 1 addition & 1 deletion forge-gui/res/cardsfolder/a/all_hallows_eve.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | IsPresent$ Card.Self+counters_GE1_SCREAM | PresentZone$ Exile | Execute$ TrigRemoveCounter | TriggerZones$ Exile | TriggerDescription$ At the beginning of your upkeep, if CARDNAME is exiled with a scream counter on it, remove a scream counter from it. If there are no more scream counters on it, put it into your graveyard and each player returns all creature cards from their graveyard to the battlefield.
SVar:TrigRemoveCounter:DB$ RemoveCounter | Defined$ Self | CounterType$ SCREAM | CounterNum$ 1 | SubAbility$ DBMoveToGraveyard
SVar:DBMoveToGraveyard:DB$ ChangeZone | Origin$ Exile | Destination$ Graveyard | Defined$ Self | SubAbility$ DBResurrection | ConditionDefined$ Self | ConditionPresent$ Card.counters_EQ0_SCREAM
SVar:DBResurrection:DB$ ChangeZoneAll | Origin$ Graveyard | Destination$ Battlefield | ChangeType$ Creature | ConditionDefined$ Self | ConditionPresent$ Card.counters_EQ0_SCREAM
SVar:DBResurrection:DB$ ChangeZoneAll | Putter$ Owner | Origin$ Graveyard | Destination$ Battlefield | ChangeType$ Creature | ConditionDefined$ Self | ConditionPresent$ Card.counters_EQ0_SCREAM
SVar:IsReanimatorCard:TRUE
SVar:NeedsToPlayVar:CountOpps LECountMe
SVar:CountMe:Count$ValidGraveyard Creature.YouOwn/Minus.NumOpps
Expand Down
2 changes: 1 addition & 1 deletion forge-gui/res/cardsfolder/a/arboria.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Types:World Enchantment
S:Mode$ CantAttack | ValidCard$ Creature | Target$ Player.IsNotRemembered | Description$ Creatures can't attack a player unless that player cast a spell or put a nontoken permanent onto the battlefield during their last turn.
T:Mode$ SpellCast | ValidCard$ Card | ValidActivatingPlayer$ Player.Active+IsNotRemembered | Static$ True | Execute$ RememberCaster
SVar:RememberCaster:DB$ Pump | Defined$ TriggeredCardController | RememberObjects$ TriggeredCardController
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.ActivePlayerCtrl+nonToken | Static$ True | Execute$ RememberController
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidPutter$ Player.Active | ValidCard$ Card.nonToken | Static$ True | Execute$ RememberController
SVar:RememberController:DB$ Pump | Defined$ TriggeredCardController | RememberObjects$ TriggeredCardController
T:Mode$ TurnBegin | ValidPlayer$ Player | Execute$ ClearActivePlayer | Static$ True
SVar:ClearActivePlayer:DB$ Cleanup | ForgetDefined$ ActivePlayer
Expand Down
2 changes: 1 addition & 1 deletion forge-gui/res/cardsfolder/a/assassins_trophy.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ Name:Assassin's Trophy
ManaCost:B G
Types:Instant
A:SP$ Destroy | ValidTgts$ Permanent.OppCtrl | AITgts$ Permanent.nonLand,Land.nonBasic | TgtPrompt$ Select target permanent an opponent controls | SubAbility$ DBChange | SpellDescription$ Destroy target permanent an opponent controls. Its controller may search their library for a basic land card, put it onto the battlefield, then shuffle.
SVar:DBChange:DB$ ChangeZone | Optional$ True | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | DefinedPlayer$ TargetedController | ShuffleNonMandatory$ True
SVar:DBChange:DB$ ChangeZone | Optional$ True | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | DefinedPlayer$ TargetedController | Putter$ Owner | ShuffleNonMandatory$ True
Oracle:Destroy target permanent an opponent controls. Its controller may search their library for a basic land card, put it onto the battlefield, then shuffle.
2 changes: 1 addition & 1 deletion forge-gui/res/cardsfolder/a/avatar_of_growth.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ PT:4/4
K:Trample
S:Mode$ ReduceCost | ValidCard$ Card.Self | Type$ Spell | Amount$ X | EffectZone$ All | Description$ Trample
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigSearch | TriggerDescription$ When CARDNAME enters, each player searches their library for up to two basic land cards, puts them onto the battlefield, then shuffles.
SVar:TrigSearch:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | DefinedPlayer$ Player | ChangeNum$ 2 | Shuffle$ True
SVar:TrigSearch:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | DefinedPlayer$ Player | Putter$ Owner | ChangeNum$ 2 | Shuffle$ True
SVar:X:PlayerCountOpponents$Amount
Oracle:This spell costs {1} less to cast for each opponent you have.\nTrample\nWhen Avatar of Growth enters, each player searches their library for up to two basic land cards, puts them onto the battlefield, then shuffles.
2 changes: 1 addition & 1 deletion forge-gui/res/cardsfolder/b/balthor_the_defiled.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ ManaCost:2 B B
Types:Legendary Creature Zombie Dwarf
PT:2/2
S:Mode$ Continuous | Affected$ Creature.Minion | AddPower$ 1 | AddToughness$ 1 | Description$ Minion creatures get +1/+1.
A:AB$ ChangeZoneAll | Cost$ B B B Exile<1/CARDNAME> | ChangeType$ Creature.Black,Creature.Red | Origin$ Graveyard | Destination$ Battlefield | SpellDescription$ Each player returns all black and all red creature cards from their graveyard to the battlefield.
A:AB$ ChangeZoneAll | Cost$ B B B Exile<1/CARDNAME> | ChangeType$ Creature.Black,Creature.Red | Origin$ Graveyard | Destination$ Battlefield | Putter$ Owner | SpellDescription$ Each player returns all black and all red creature cards from their graveyard to the battlefield.
AI:RemoveDeck:All
Oracle:Minion creatures get +1/+1.\n{B}{B}{B}, Exile Balthor the Defiled: Each player returns all black and all red creature cards from their graveyard to the battlefield.
2 changes: 1 addition & 1 deletion forge-gui/res/cardsfolder/b/blessed_reincarnation.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ ManaCost:3 U
Types:Instant
K:Rebound
A:SP$ ChangeZone | ValidTgts$ Creature.OppCtrl | TgtPrompt$ Select target creature an opponent controls | Origin$ Battlefield | Destination$ Exile | SubAbility$ DBDig | SpellDescription$ Exile target creature an opponent controls. That player reveals cards from the top of their library until a creature card is revealed. The player puts that card onto the battlefield, then shuffles the rest into their library.
SVar:DBDig:DB$ DigUntil | Defined$ TargetedController | Valid$ Creature | ValidDescription$ creature | FoundDestination$ Battlefield | RevealedDestination$ Library | Shuffle$ True
SVar:DBDig:DB$ DigUntil | Defined$ TargetedController | Valid$ Creature | ValidDescription$ creature | FoundDestination$ Battlefield | RevealedDestination$ Library | Shuffle$ True | Putter$ Owner
AI:RemoveDeck:All
Oracle:Exile target creature an opponent controls. That player reveals cards from the top of their library until a creature card is revealed. The player puts that card onto the battlefield, then shuffles the rest into their library.\nRebound (If you cast this spell from your hand, exile it as it resolves. At the beginning of your next upkeep, you may cast this card from exile without paying its mana cost.)
2 changes: 1 addition & 1 deletion forge-gui/res/cardsfolder/b/bloodbond_march.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ Name:Bloodbond March
ManaCost:2 B G
Types:Enchantment
T:Mode$ SpellCast | ValidCard$ Creature | TriggerZones$ Battlefield | Execute$ TrigReturn | TriggerDescription$ Whenever a player casts a creature spell, each player returns all cards with the same name as that spell from their graveyard to the battlefield.
SVar:TrigReturn:DB$ ChangeZoneAll | ChangeType$ Triggered.sameName | Origin$ Graveyard | Destination$ Battlefield
SVar:TrigReturn:DB$ ChangeZoneAll | ChangeType$ Triggered.sameName | Origin$ Graveyard | Destination$ Battlefield | Putter$ Owner
AI:RemoveDeck:Random
Oracle:Whenever a player casts a creature spell, each player returns all cards with the same name as that spell from their graveyard to the battlefield.
Loading

0 comments on commit 2f62447

Please sign in to comment.