mirror of
https://github.com/correl/mage.git
synced 2024-11-14 11:09:31 +00:00
* Oathbreaker format - Fixed that signate spell didn't return to command zone. Added unit test for oathbreaker format (fixes #6695).
This commit is contained in:
parent
5ae041f39a
commit
29e5230469
6 changed files with 220 additions and 19 deletions
48
Mage.Tests/Oathbreaker_UR.dck
Normal file
48
Mage.Tests/Oathbreaker_UR.dck
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
NAME:Oathbreaker_UR
|
||||||
|
1 [M15:44] Aetherspouts
|
||||||
|
1 [ISD:130] Blasphemous Act
|
||||||
|
1 [M21:46] Cancel
|
||||||
|
1 [C20:146] Chaos Warp
|
||||||
|
1 [GRN:233] Chromatic Lantern
|
||||||
|
1 [7ED:67] Counterspell
|
||||||
|
1 [M15:242] Darksteel Citadel
|
||||||
|
1 [EMN:55] Displace
|
||||||
|
1 [RAV:46] Drift of Phantasms
|
||||||
|
1 [JMP:313] Dualcaster Mage
|
||||||
|
1 [NPH:35] Gitaxian Probe
|
||||||
|
1 [MRD:282] Great Furnace
|
||||||
|
1 [M19:145] Guttersnipe
|
||||||
|
1 [LRW:175] Heat Shimmer
|
||||||
|
1 [DTK:140] Impact Tremors
|
||||||
|
1 [VIS:34] Impulse
|
||||||
|
9 [M21:263] Island
|
||||||
|
1 [GRN:251] Izzet Guildgate
|
||||||
|
1 [GRN:238] Izzet Locket
|
||||||
|
1 [M12:63] Mana Leak
|
||||||
|
1 [SOI:73] Manic Scribe
|
||||||
|
7 [M21:269] Mountain
|
||||||
|
1 [M20:69] Negate
|
||||||
|
1 [M21:59] Opt
|
||||||
|
1 [AER:167] Ornithopter
|
||||||
|
1 [ELD:60] Overwhelmed Apprentice
|
||||||
|
1 [M11:70] Preordain
|
||||||
|
1 [M19:68] Psychic Corrosion
|
||||||
|
1 [THS:135] Purphoros, God of the Forge
|
||||||
|
1 [KLD:126] Reckless Fireweaver
|
||||||
|
1 [M19:254] Reliquary Tower
|
||||||
|
1 [MRD:283] Seat of the Synod
|
||||||
|
1 [CHK:268] Sensei's Divining Top
|
||||||
|
1 [M15:161] Shrapnel Blast
|
||||||
|
1 [5DN:150] Silent Arbiter
|
||||||
|
1 [XLN:81] Spell Pierce
|
||||||
|
1 [M10:220] Spellbook
|
||||||
|
1 [SOM:46] Stoic Rebuttal
|
||||||
|
1 [MH1:231] Talisman of Creativity
|
||||||
|
1 [M11:229] Terramorphic Expanse
|
||||||
|
1 [KTK:59] Treasure Cruise
|
||||||
|
1 [JOU:115] Twinflame
|
||||||
|
1 [DOM:72] Unwind
|
||||||
|
1 [M14:163] Young Pyromancer
|
||||||
|
SB: 1 [WAR:234] Saheeli, Sublime Artificer
|
||||||
|
SB: 1 [MRD:54] Thoughtcast
|
||||||
|
|
|
@ -91,4 +91,41 @@ public class BolsterTest extends CardTestPlayerBase {
|
||||||
|
|
||||||
assertLife(playerB, 16);
|
assertLife(playerB, 16);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void EliteScaleguardTriggerTwiceTest() {
|
||||||
|
// When Elite Scaleguard enters the battlefield, bolster 2.
|
||||||
|
// Whenever a creature you control with a +1/+1 counter on it attacks, tap target creature defending player controls.
|
||||||
|
addCard(Zone.HAND, playerA, "Elite Scaleguard"); // Creature 2/3 {4}{W}
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Plains", 5);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 1);
|
||||||
|
|
||||||
|
addCard(Zone.BATTLEFIELD, playerB, "Pillarfield Ox", 1);
|
||||||
|
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Elite Scaleguard");
|
||||||
|
|
||||||
|
attack(3, playerA, "Silvercoat Lion");
|
||||||
|
addTarget(playerA, "Pillarfield Ox"); // Tap from triggered ability of Elite Scaleguard
|
||||||
|
|
||||||
|
attack(5, playerA, "Silvercoat Lion");
|
||||||
|
addTarget(playerA, "Pillarfield Ox"); // Tap from triggered ability of Elite Scaleguard
|
||||||
|
|
||||||
|
setStopAt(5, PhaseStep.POSTCOMBAT_MAIN);
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
execute();
|
||||||
|
assertAllCommandsUsed();
|
||||||
|
|
||||||
|
assertPowerToughness(playerA, "Elite Scaleguard", 2, 3);
|
||||||
|
assertCounterCount("Silvercoat Lion", CounterType.P1P1, 2);
|
||||||
|
assertPowerToughness(playerA, "Silvercoat Lion", 4, 4);
|
||||||
|
assertTapped("Silvercoat Lion", true);
|
||||||
|
|
||||||
|
assertPermanentCount(playerB, "Pillarfield Ox", 1);
|
||||||
|
assertTapped("Pillarfield Ox", true);
|
||||||
|
|
||||||
|
assertLife(playerB, 12);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
/*
|
||||||
|
* To change this license header, choose License Headers in Project Properties.
|
||||||
|
* To change this template file, choose Tools | Templates
|
||||||
|
* and open the template in the editor.
|
||||||
|
*/
|
||||||
|
package org.mage.test.oathbreaker.FFA3;
|
||||||
|
|
||||||
|
import mage.constants.PhaseStep;
|
||||||
|
import mage.constants.Zone;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.mage.test.serverside.base.CardTestOathbreaker3PlayersFFA;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author LevelX2
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class BasicSaheekiSublimeArtificerTest extends CardTestOathbreaker3PlayersFFA {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that if a player left the game, it's commander is also removed
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void commanderRemovedTest() {
|
||||||
|
|
||||||
|
concede(1, PhaseStep.PRECOMBAT_MAIN, playerA);
|
||||||
|
setStopAt(1, PhaseStep.DECLARE_ATTACKERS);
|
||||||
|
execute();
|
||||||
|
|
||||||
|
assertCommandZoneCount(playerB, "Saheeli, Sublime Artificer", 1);
|
||||||
|
assertCommandZoneCount(playerB, "Thoughtcast", 1);
|
||||||
|
assertCommandZoneCount(playerC, "Saheeli, Sublime Artificer", 1);
|
||||||
|
assertCommandZoneCount(playerC, "Thoughtcast", 1);
|
||||||
|
assertCommandZoneCount(playerA, "Saheeli, Sublime Artificer", 0);
|
||||||
|
assertCommandZoneCount(playerA, "Thoughtcast", 0);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void castCommanderTest() {
|
||||||
|
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Island", 4);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Ornithopter", 4);
|
||||||
|
|
||||||
|
// Planeswalker 5 Loyality Counter
|
||||||
|
// Whenever you cast a noncreature spell, create a 1/1 colorless Servo artifact creature token.
|
||||||
|
// -2: Target artifact you control becomes a copy of another target artifact or creature you control until end of turn,
|
||||||
|
// except it's an artifact in addition to its other types.
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Saheeli, Sublime Artificer"); // Planeswalker 5 {1}{U/R}{U/R}
|
||||||
|
|
||||||
|
// Affinity for artifacts
|
||||||
|
// Draw two cards.
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Thoughtcast"); // Sorcery {4}{U}
|
||||||
|
|
||||||
|
setStopAt(1, PhaseStep.DECLARE_ATTACKERS);
|
||||||
|
execute();
|
||||||
|
|
||||||
|
|
||||||
|
assertCommandZoneCount(playerB, "Saheeli, Sublime Artificer", 1);
|
||||||
|
assertCommandZoneCount(playerB, "Thoughtcast", 1);
|
||||||
|
assertCommandZoneCount(playerC, "Saheeli, Sublime Artificer", 1);
|
||||||
|
assertCommandZoneCount(playerC, "Thoughtcast", 1);
|
||||||
|
|
||||||
|
assertHandCount(playerA, 3);
|
||||||
|
assertPermanentCount(playerA, "Saheeli, Sublime Artificer", 1);
|
||||||
|
assertCommandZoneCount(playerA, "Thoughtcast", 1);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* To change this license header, choose License Headers in Project Properties.
|
||||||
|
* To change this template file, choose Tools | Templates
|
||||||
|
* and open the template in the editor.
|
||||||
|
*/
|
||||||
|
package org.mage.test.serverside.base;
|
||||||
|
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import mage.constants.MultiplayerAttackOption;
|
||||||
|
import mage.constants.RangeOfInfluence;
|
||||||
|
import mage.game.Game;
|
||||||
|
import mage.game.GameException;
|
||||||
|
import mage.game.OathbreakerFreeForAll;
|
||||||
|
import mage.game.mulligan.MulliganType;
|
||||||
|
import org.mage.test.serverside.base.impl.CardTestPlayerAPIImpl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author LevelX2
|
||||||
|
*/
|
||||||
|
public abstract class CardTestOathbreaker3PlayersFFA extends CardTestPlayerAPIImpl {
|
||||||
|
|
||||||
|
public CardTestOathbreaker3PlayersFFA() {
|
||||||
|
super();
|
||||||
|
this.deckNameA = "Oathbreaker_UR.dck"; // PW: Saheeli, Sublime Artificer SS: Thoughtcast
|
||||||
|
this.deckNameB = "Oathbreaker_UR.dck";
|
||||||
|
this.deckNameC = "Oathbreaker_UR.dck";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException {
|
||||||
|
Game game = new OathbreakerFreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ONE, MulliganType.GAME_DEFAULT.getMulligan(0), 20);
|
||||||
|
playerA = createPlayer(game, playerA, "PlayerA", deckNameA);
|
||||||
|
playerB = createPlayer(game, playerB, "PlayerB", deckNameB);
|
||||||
|
playerC = createPlayer(game, playerC, "PlayerC", deckNameC);
|
||||||
|
return game;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -22,9 +22,9 @@ import java.util.UUID;
|
||||||
*/
|
*/
|
||||||
public class OathbreakerOnBattlefieldCondition implements Condition {
|
public class OathbreakerOnBattlefieldCondition implements Condition {
|
||||||
|
|
||||||
private UUID playerId;
|
private final UUID playerId;
|
||||||
private FilterControlledPermanent filter;
|
private final FilterControlledPermanent filter;
|
||||||
private String compatibleNames;
|
private final String compatibleNames;
|
||||||
|
|
||||||
public OathbreakerOnBattlefieldCondition(Game game, UUID playerId, UUID signatureSpellId, Set<UUID> oathbreakersToSearch) {
|
public OathbreakerOnBattlefieldCondition(Game game, UUID playerId, UUID signatureSpellId, Set<UUID> oathbreakersToSearch) {
|
||||||
this.playerId = playerId;
|
this.playerId = playerId;
|
||||||
|
@ -35,17 +35,17 @@ public class OathbreakerOnBattlefieldCondition implements Condition {
|
||||||
|
|
||||||
// spell can be casted by any compatible oathbreakers
|
// spell can be casted by any compatible oathbreakers
|
||||||
List<PermanentIdPredicate> compatibleList = new ArrayList<>();
|
List<PermanentIdPredicate> compatibleList = new ArrayList<>();
|
||||||
List<String> compatibleNames = new ArrayList<>();
|
List<String> compatibleNamesList = new ArrayList<>();
|
||||||
if (oathbreakersToSearch != null && !oathbreakersToSearch.isEmpty()) {
|
if (oathbreakersToSearch != null && !oathbreakersToSearch.isEmpty()) {
|
||||||
for (UUID id : oathbreakersToSearch) {
|
for (UUID id : oathbreakersToSearch) {
|
||||||
Card commander = game.getCard(id);
|
Card commander = game.getCard(id);
|
||||||
if (commander != null && ManaUtil.isColorIdentityCompatible(commander.getColorIdentity(), spellColors)) {
|
if (commander != null && ManaUtil.isColorIdentityCompatible(commander.getColorIdentity(), spellColors)) {
|
||||||
compatibleList.add(new PermanentIdPredicate(id));
|
compatibleList.add(new PermanentIdPredicate(id));
|
||||||
compatibleNames.add(commander.getName());
|
compatibleNamesList.add(commander.getName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.compatibleNames = String.join("; ", compatibleNames);
|
this.compatibleNames = String.join("; ", compatibleNamesList);
|
||||||
|
|
||||||
if (compatibleList.isEmpty()) {
|
if (compatibleList.isEmpty()) {
|
||||||
// random id to disable condition
|
// random id to disable condition
|
||||||
|
|
|
@ -28,9 +28,7 @@ import java.util.UUID;
|
||||||
that permanent and the card representing it that isn’t a commander are put into the appropriate zone, and the card
|
that permanent and the card representing it that isn’t a commander are put into the appropriate zone, and the card
|
||||||
that represents it and is a commander is put into the command zone.
|
that represents it and is a commander is put into the command zone.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Oathbreaker mode: If your Oathbreaker changes zones, you may return it to the Command Zone. The Signature Spell must return to the Command Zone.
|
// Oathbreaker mode: If your Oathbreaker changes zones, you may return it to the Command Zone. The Signature Spell must return to the Command Zone.
|
||||||
|
|
||||||
public class CommanderReplacementEffect extends ReplacementEffectImpl {
|
public class CommanderReplacementEffect extends ReplacementEffectImpl {
|
||||||
|
|
||||||
private final UUID commanderId;
|
private final UUID commanderId;
|
||||||
|
@ -39,6 +37,17 @@ public class CommanderReplacementEffect extends ReplacementEffectImpl {
|
||||||
private final boolean forceToMove;
|
private final boolean forceToMove;
|
||||||
private final String commanderTypeName;
|
private final String commanderTypeName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param commanderId
|
||||||
|
* @param alsoHand is the replacement effect also applied if commander
|
||||||
|
* object goes to hand zone
|
||||||
|
* @param alsoLibrary is the replacement effect also applied if commander
|
||||||
|
* object goes to library zone
|
||||||
|
* @param forceToMove used for signature spell of Oathbreaker format (spell
|
||||||
|
* is mandatory moved to command zone instead)
|
||||||
|
* @param commanderTypeName type of commander object to set the correct text
|
||||||
|
*/
|
||||||
public CommanderReplacementEffect(UUID commanderId, boolean alsoHand, boolean alsoLibrary, boolean forceToMove, String commanderTypeName) {
|
public CommanderReplacementEffect(UUID commanderId, boolean alsoHand, boolean alsoLibrary, boolean forceToMove, String commanderTypeName) {
|
||||||
super(Duration.WhileOnBattlefield, Outcome.Benefit);
|
super(Duration.WhileOnBattlefield, Outcome.Benefit);
|
||||||
String mayStr = forceToMove ? " " : " may ";
|
String mayStr = forceToMove ? " " : " may ";
|
||||||
|
@ -89,11 +98,11 @@ public class CommanderReplacementEffect extends ReplacementEffectImpl {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean applies(GameEvent event, Ability source, Game game) {
|
public boolean applies(GameEvent event, Ability source, Game game) {
|
||||||
ZoneChangeEvent zEvent = (ZoneChangeEvent) event;
|
if (!commanderId.equals(event.getTargetId())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// if (!game.isSimulation() && commanderId.equals(zEvent.getTargetId())) {
|
ZoneChangeEvent zEvent = (ZoneChangeEvent) event;
|
||||||
// System.out.println("applies " + game.getTurnNum() + ": " + game.getObject(event.getTargetId()).getName() + ": " + zEvent.getFromZone() + " -> " + zEvent.getToZone() + "; " + game.getObject(zEvent.getSourceId()));
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (zEvent.getToZone().equals(Zone.HAND) && !alsoHand) {
|
if (zEvent.getToZone().equals(Zone.HAND) && !alsoHand) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -106,10 +115,14 @@ public class CommanderReplacementEffect extends ReplacementEffectImpl {
|
||||||
switch (zEvent.getToZone()) {
|
switch (zEvent.getToZone()) {
|
||||||
case LIBRARY:
|
case LIBRARY:
|
||||||
case HAND:
|
case HAND:
|
||||||
if (commanderId.equals(zEvent.getTargetId())) {
|
return true;
|
||||||
|
}
|
||||||
|
if (forceToMove) {
|
||||||
|
switch (zEvent.getToZone()) {
|
||||||
|
case BATTLEFIELD:
|
||||||
|
case GRAVEYARD:
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -119,10 +132,6 @@ public class CommanderReplacementEffect extends ReplacementEffectImpl {
|
||||||
ZoneChangeEvent zEvent = (ZoneChangeEvent) event;
|
ZoneChangeEvent zEvent = (ZoneChangeEvent) event;
|
||||||
String originToZone = zEvent.getToZone().toString().toLowerCase(Locale.ENGLISH);
|
String originToZone = zEvent.getToZone().toString().toLowerCase(Locale.ENGLISH);
|
||||||
|
|
||||||
if (!game.isSimulation()) {
|
|
||||||
//System.out.println("replace " + game.getTurnNum() + ": " + game.getObject(event.getTargetId()).getName() + ": " + zEvent.getFromZone() + " -> " + zEvent.getToZone() + "; " + game.getObject(zEvent.getSourceId()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (zEvent.getFromZone() == Zone.BATTLEFIELD) {
|
if (zEvent.getFromZone() == Zone.BATTLEFIELD) {
|
||||||
Permanent permanent = zEvent.getTarget();
|
Permanent permanent = zEvent.getTarget();
|
||||||
if (permanent != null) {
|
if (permanent != null) {
|
||||||
|
|
Loading…
Reference in a new issue