mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-11 16:26:22 +00:00
GameAction: cleanup Static giving Abilities to the Stack (#8587)
* Add test that fails on master engine (before KeywordCollection sanity fix)
This commit is contained in:
@@ -5,6 +5,7 @@ import forge.ai.ComputerUtil;
|
||||
import forge.ai.PlayerControllerAi;
|
||||
import forge.ai.simulation.GameStateEvaluator.Score;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameActionUtil;
|
||||
import forge.game.GameObject;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.phase.PhaseType;
|
||||
@@ -131,6 +132,19 @@ public class GameSimulator {
|
||||
Card hostCard = (Card) copier.find(origHostCard);
|
||||
String desc = sa.getDescription();
|
||||
FCollectionView<SpellAbility> candidates = hostCard.getSpellAbilities();
|
||||
|
||||
SpellAbility result = saMatcher(candidates, desc);
|
||||
for (SpellAbility cSa : candidates) {
|
||||
if (result != null) {
|
||||
break;
|
||||
}
|
||||
result = saMatcher(GameActionUtil.getAlternativeCosts(cSa, aiPlayer, true), desc);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private SpellAbility saMatcher(Iterable<SpellAbility> candidates, String desc) {
|
||||
// first pass for accuracy (spells with alternative costs)
|
||||
for (SpellAbility cSa : candidates) {
|
||||
if (desc.equals(cSa.getDescription())) {
|
||||
|
||||
@@ -223,6 +223,7 @@ public class GameAction {
|
||||
}
|
||||
|
||||
if (cause != null && cause.isSpell() && c.equals(cause.getHostCard())) {
|
||||
copied.setCastFrom(zoneFrom);
|
||||
copied.setCastSA(cause);
|
||||
copied.setSplitStateToPlayAbility(cause);
|
||||
|
||||
@@ -232,6 +233,13 @@ public class GameAction {
|
||||
KeywordInterface kw = cause.getKeyword();
|
||||
if (kw != null) {
|
||||
copied.addKeywordForStaticAbility(kw);
|
||||
|
||||
// CR 400.7g If an effect grants a nonland card an ability that allows it to be cast,
|
||||
// that ability will continue to apply to the new object that card became after it moved to the stack as a result of being cast this way.
|
||||
if (!cause.isIntrinsic()) {
|
||||
kw.setHostCard(copied);
|
||||
copied.addChangedCardKeywordsInternal(ImmutableList.of(kw), null, false, copied.getGameTimestamp(), kw.getStatic(), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -300,7 +308,6 @@ public class GameAction {
|
||||
// Temporary disable commander replacement effect
|
||||
// 903.9a
|
||||
if (fromBattlefield && !toBattlefield && c.isCommander() && c.hasMergedCard()) {
|
||||
// Disable the commander replacement effect
|
||||
c.getOwner().setCommanderReplacementSuppressed(true);
|
||||
}
|
||||
|
||||
@@ -445,14 +452,8 @@ public class GameAction {
|
||||
}
|
||||
|
||||
if (zoneFrom.is(ZoneType.Stack) && toBattlefield) {
|
||||
// 400.7a Effects from static abilities that give a permanent spell on the stack an ability
|
||||
// that allows it to be cast for an alternative cost continue to apply to the permanent that spell becomes.
|
||||
if (c.getCastSA() != null && !c.getCastSA().isIntrinsic() && c.getKeywords().contains(c.getCastSA().getKeyword())) {
|
||||
KeywordInterface ki = c.getCastSA().getKeyword();
|
||||
ki.setHostCard(copied);
|
||||
copied.addChangedCardKeywordsInternal(ImmutableList.of(ki), null, false, copied.getGameTimestamp(), null, true);
|
||||
}
|
||||
// TODO hot fix for non-intrinsic offspring
|
||||
// CR 400.7b Effects from static abilities that grant an ability to a permanent spell that functions on the battlefield
|
||||
// continue to apply to the permanent that spell becomes
|
||||
Multimap<StaticAbility, KeywordInterface> addKw = MultimapBuilder.hashKeys().arrayListValues().build();
|
||||
for (KeywordInterface kw : c.getKeywords(Keyword.OFFSPRING)) {
|
||||
if (!kw.isIntrinsic()) {
|
||||
@@ -465,7 +466,7 @@ public class GameAction {
|
||||
}
|
||||
}
|
||||
|
||||
// 607.2q linked ability can find cards exiled as cost while it was a spell
|
||||
// CR 607.2q linked ability can find cards exiled as cost while it was a spell
|
||||
copied.addExiledCards(c.getExiledCards());
|
||||
}
|
||||
|
||||
@@ -486,7 +487,7 @@ public class GameAction {
|
||||
if (card.isRealCommander()) {
|
||||
card.setMoveToCommandZone(true);
|
||||
}
|
||||
// 727.3e & 903.9a
|
||||
// CR 727.3e & 903.9a
|
||||
if (wasToken && !card.isRealToken() || card.isRealCommander()) {
|
||||
Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(card);
|
||||
repParams.put(AbilityKey.CardLKI, card);
|
||||
@@ -561,13 +562,6 @@ public class GameAction {
|
||||
// update static abilities after etb counters have been placed
|
||||
checkStaticAbilities();
|
||||
|
||||
// 400.7g try adding keyword back into card if it doesn't already have it
|
||||
if (zoneTo.is(ZoneType.Stack) && cause != null && cause.isSpell() && !cause.isIntrinsic() && c.equals(cause.getHostCard())) {
|
||||
if (cause.getKeyword() != null && !copied.getKeywords().contains(cause.getKeyword())) {
|
||||
copied.addChangedCardKeywordsInternal(ImmutableList.of(cause.getKeyword()), null, false, game.getNextTimestamp(), null, true);
|
||||
}
|
||||
}
|
||||
|
||||
// CR 603.6b
|
||||
if (toBattlefield) {
|
||||
zoneTo.saveLKI(copied, lastKnownInfo);
|
||||
@@ -827,19 +821,6 @@ public class GameAction {
|
||||
}
|
||||
}
|
||||
|
||||
if (zoneTo.is(ZoneType.Stack)) {
|
||||
c.setCastFrom(zoneFrom);
|
||||
if (cause != null && cause.isSpell() && c.equals(cause.getHostCard())) {
|
||||
c.setCastSA(cause);
|
||||
} else {
|
||||
c.setCastSA(null);
|
||||
}
|
||||
} else if (zoneFrom == null || !(zoneFrom.is(ZoneType.Stack) &&
|
||||
(zoneTo.is(ZoneType.Battlefield) || zoneTo.is(ZoneType.Merged)))) {
|
||||
c.setCastFrom(null);
|
||||
c.setCastSA(null);
|
||||
}
|
||||
|
||||
if (c.isRealCommander()) {
|
||||
c.setMoveToCommandZone(true);
|
||||
}
|
||||
|
||||
@@ -2582,7 +2582,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
sbLong.append(" ").append(ManaCostParser.parse(k[1]));
|
||||
sbLong.append(" (").append(inst.getReminderText()).append(")");
|
||||
sbLong.append("\r\n");
|
||||
|
||||
} else if (inst.getKeyword().equals(Keyword.COMPANION)) {
|
||||
sbLong.append("Companion — ");
|
||||
sbLong.append(((Companion)inst).getDescription());
|
||||
|
||||
@@ -151,19 +151,19 @@ public class AITest {
|
||||
return c;
|
||||
}
|
||||
|
||||
void playUntilStackClear(Game game) {
|
||||
protected void playUntilStackClear(Game game) {
|
||||
do {
|
||||
game.getPhaseHandler().mainLoopStep();
|
||||
} while (!game.isGameOver() && !game.getStack().isEmpty());
|
||||
}
|
||||
|
||||
void playUntilPhase(Game game, PhaseType phase) {
|
||||
protected void playUntilPhase(Game game, PhaseType phase) {
|
||||
do {
|
||||
game.getPhaseHandler().mainLoopStep();
|
||||
} while (!game.isGameOver() && !game.getPhaseHandler().is(phase));
|
||||
}
|
||||
|
||||
void playUntilNextTurn(Game game) {
|
||||
protected void playUntilNextTurn(Game game) {
|
||||
Player current = game.getPhaseHandler().getPlayerTurn();
|
||||
do {
|
||||
game.getPhaseHandler().mainLoopStep();
|
||||
|
||||
@@ -2610,6 +2610,35 @@ public class GameSimulationTest extends SimulationTest {
|
||||
AssertJUnit.assertTrue(nonBasicForest.getType().hasSubtype("Mountain"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHenzie() {
|
||||
Game game = initAndCreateGame();
|
||||
Player p = game.getPlayers().get(0);
|
||||
game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p);
|
||||
|
||||
addCard("Henzie \"Toolbox\" Torre", p);
|
||||
addCardToZone("Wastes", p, ZoneType.Library);
|
||||
addCards("Plains", 5, p);
|
||||
Card spell = addCardToZone("Serra Angel", p, ZoneType.Hand);
|
||||
|
||||
game.getAction().checkStaticAbilities();
|
||||
List<SpellAbility> sas = spell.getAllPossibleAbilities(p, true);
|
||||
SpellAbility blitz = sas.get(1);
|
||||
|
||||
GameSimulator sim = createSimulator(game, p);
|
||||
game = sim.getSimulatedGameState();
|
||||
sim.simulateSpellAbility(blitz);
|
||||
spell = findCardWithName(game, "Serra Angel");
|
||||
|
||||
AssertJUnit.assertEquals(1, spell.getAmountOfKeyword(Keyword.BLITZ));
|
||||
AssertJUnit.assertTrue(spell.hasKeyword(Keyword.HASTE));
|
||||
|
||||
playUntilNextTurn(game);
|
||||
|
||||
AssertJUnit.assertEquals(1, game.getPlayers().get(0).getCardsIn(ZoneType.Hand).size());
|
||||
AssertJUnit.assertTrue(spell.isInZone(ZoneType.Graveyard));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for "Volo's Journal" usage by the AI. This test checks if the AI correctly
|
||||
* adds the correct types to the "Volo's Journal" when casting the spells in order
|
||||
|
||||
Reference in New Issue
Block a user