[OTJ] Implement Slick Sequence

This commit is contained in:
Susucre 2024-03-30 16:26:34 +01:00
parent 18a05acd81
commit 5a16d319d2
3 changed files with 240 additions and 0 deletions

View file

@ -0,0 +1,116 @@
package mage.cards.s;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.condition.Condition;
import mage.abilities.decorator.ConditionalOneShotEffect;
import mage.abilities.effects.common.DamageTargetEffect;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.abilities.hint.ConditionHint;
import mage.abilities.hint.Hint;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.WatcherScope;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.stack.Spell;
import mage.target.common.TargetAnyTarget;
import mage.watchers.Watcher;
import java.util.*;
/**
* @author Susucr
*/
public final class SlickSequence extends CardImpl {
public SlickSequence(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{U}{R}");
// Slick Sequence deals 2 damage to any target. If you've cast another spell this turn, draw a card.
this.getSpellAbility().addEffect(new DamageTargetEffect(2));
this.getSpellAbility().addTarget(new TargetAnyTarget());
this.getSpellAbility().addEffect(
new ConditionalOneShotEffect(
new DrawCardSourceControllerEffect(1),
SlickSequenceCondition.instance
)
);
this.getSpellAbility().addHint(SlickSequenceCondition.hint);
this.getSpellAbility().addWatcher(new SlickSequenceWatcher());
}
private SlickSequence(final SlickSequence card) {
super(card);
}
@Override
public SlickSequence copy() {
return new SlickSequence(this);
}
}
enum SlickSequenceCondition implements Condition {
instance;
static final Hint hint = new ConditionHint(instance, "you've cast another spell this turn");
@Override
public boolean apply(Game game, Ability source) {
SlickSequenceWatcher watcher = game.getState().getWatcher(SlickSequenceWatcher.class);
if (watcher == null) {
return false;
}
// may be null, watcher will handle that.
Spell sourceSpell = game.getSpell(source.getSourceId());
return watcher.didPlayerCastAnotherSpellThisTurn(source.getControllerId(), sourceSpell, game);
}
@Override
public String toString() {
return "you've cast another spell this turn";
}
}
class SlickSequenceWatcher extends Watcher {
// Per player, MOR of the spells cast this turn.
private final Map<UUID, Set<MageObjectReference>> spellsCastThisTurn = new HashMap<>();
/**
* Game default watcher
*/
public SlickSequenceWatcher() {
super(WatcherScope.GAME);
}
@Override
public void watch(GameEvent event, Game game) {
if (event.getType() == GameEvent.EventType.SPELL_CAST) {
UUID playerId = event.getPlayerId();
if (playerId != null) {
MageObjectReference mor = new MageObjectReference(event.getTargetId(), game);
spellsCastThisTurn.computeIfAbsent(playerId, x -> new HashSet<>()).add(mor);
}
}
}
@Override
public void reset() {
super.reset();
spellsCastThisTurn.clear();
}
boolean didPlayerCastAnotherSpellThisTurn(UUID playerId, Spell spell, Game game) {
Set<MageObjectReference> spells = spellsCastThisTurn.getOrDefault(playerId, new HashSet<>());
if (spell == null) {
// Not currently a spell, so any spell recorded does count as another.
return !spells.isEmpty();
} else {
// Is currently a spell, so need to exclude that one.
MageObjectReference spellMOR = new MageObjectReference(spell.getId(), game);
return spells.stream().anyMatch(mor -> !mor.equals(spellMOR));
}
}
}

View file

@ -140,6 +140,7 @@ public final class OutlawsOfThunderJunction extends ExpansionSet {
cards.add(new SetCardInfo("Shepherd of the Clouds", 28, Rarity.UNCOMMON, mage.cards.s.ShepherdOfTheClouds.class));
cards.add(new SetCardInfo("Sheriff of Safe Passage", 29, Rarity.UNCOMMON, mage.cards.s.SheriffOfSafePassage.class));
cards.add(new SetCardInfo("Shoot the Sheriff", 106, Rarity.UNCOMMON, mage.cards.s.ShootTheSheriff.class));
cards.add(new SetCardInfo("Slick Sequence", 233, Rarity.UNCOMMON, mage.cards.s.SlickSequence.class));
cards.add(new SetCardInfo("Slickshot Lockpicker", 67, Rarity.UNCOMMON, mage.cards.s.SlickshotLockpicker.class));
cards.add(new SetCardInfo("Slickshot Show-Off", 146, Rarity.RARE, mage.cards.s.SlickshotShowOff.class));
cards.add(new SetCardInfo("Slickshot Vault-Buster", 68, Rarity.COMMON, mage.cards.s.SlickshotVaultBuster.class));

View file

@ -0,0 +1,123 @@
package org.mage.test.cards.single.otj;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
* @author Susucr
*/
public class SlickSequenceTest extends CardTestPlayerBase {
/**
* {@link mage.cards.s.SlickSequence Slick Sequence} {U}{R}
* Instant
* Slick Sequence deals 2 damage to any target. If youve cast another spell this turn, draw a card.
*/
private static final String sequence = "Slick Sequence";
@Test
public void testOne() {
setStrictChooseMode(true);
addCard(Zone.BATTLEFIELD, playerA, "Volcanic Island", 2);
addCard(Zone.HAND, playerA, sequence);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, sequence, playerB);
setStopAt(1, PhaseStep.BEGIN_COMBAT);
setStrictChooseMode(true);
execute();
assertGraveyardCount(playerA, sequence, 1);
assertLife(playerB, 20 - 2);
assertHandCount(playerA, 0);
}
@Test
public void testOneResolveThenAnother() {
setStrictChooseMode(true);
addCard(Zone.BATTLEFIELD, playerA, "Volcanic Island", 4);
addCard(Zone.HAND, playerA, sequence, 2);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, sequence, playerB);
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, sequence, playerB);
setStopAt(1, PhaseStep.BEGIN_COMBAT);
setStrictChooseMode(true);
execute();
assertGraveyardCount(playerA, sequence, 2);
assertLife(playerB, 20 - 2 * 2);
assertHandCount(playerA, 1);
}
@Test
public void testOneThenAnotherInResponse() {
setStrictChooseMode(true);
addCard(Zone.BATTLEFIELD, playerA, "Volcanic Island", 4);
addCard(Zone.HAND, playerA, sequence, 2);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, sequence, playerB);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, sequence, playerB);
setStopAt(1, PhaseStep.BEGIN_COMBAT);
setStrictChooseMode(true);
execute();
assertGraveyardCount(playerA, sequence, 2);
assertLife(playerB, 20 - 2 * 2);
assertHandCount(playerA, 1 * 2);
}
@Test
public void testOneRemandedThenRecast() {
setStrictChooseMode(true);
addCard(Zone.BATTLEFIELD, playerA, "Volcanic Island", 4);
addCard(Zone.HAND, playerA, sequence, 1);
addCard(Zone.BATTLEFIELD, playerB, "Island", 2);
addCard(Zone.HAND, playerB, "Remand", 1);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, sequence, playerB);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Remand", sequence);
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, sequence, playerB);
setStopAt(1, PhaseStep.BEGIN_COMBAT);
setStrictChooseMode(true);
execute();
assertGraveyardCount(playerA, sequence, 1);
assertGraveyardCount(playerB, "Remand", 1);
assertLife(playerB, 20 - 2);
assertHandCount(playerA, 1);
}
@Test
public void testOtherPlayerSpellsNotCounting() {
setStrictChooseMode(true);
addCard(Zone.BATTLEFIELD, playerA, "Volcanic Island", 2);
addCard(Zone.HAND, playerA, sequence, 1);
addCard(Zone.BATTLEFIELD, playerB, "Mountain", 1);
addCard(Zone.HAND, playerB, "Lightning Bolt", 1);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Lightning Bolt", playerB);
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, sequence, playerB);
setStopAt(1, PhaseStep.BEGIN_COMBAT);
setStrictChooseMode(true);
execute();
assertGraveyardCount(playerA, sequence, 1);
assertGraveyardCount(playerB, "Lightning Bolt", 1);
assertLife(playerB, 20 - 2 - 3);
assertHandCount(playerA, 0);
}
}