[NCC] Implemented Jolene, the Plunder Queen (#9093)

This commit is contained in:
Susucre 2022-07-04 15:55:47 +02:00 committed by GitHub
parent b52576fcf9
commit 98c554a59f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 334 additions and 0 deletions

View file

@ -0,0 +1,199 @@
package mage.cards.j;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.costs.common.SacrificeTargetCost;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.abilities.effects.common.counter.AddCountersSourceEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.counters.CounterType;
import mage.filter.common.FilterControlledPermanent;
import mage.game.Game;
import mage.game.combat.Combat;
import mage.game.events.CreateTokenEvent;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.game.permanent.token.Token;
import mage.game.permanent.token.TreasureToken;
import mage.target.common.TargetControlledPermanent;
import mage.target.targetpointer.FixedTarget;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
/**
*
* @author Susucre
*/
public final class JoleneThePlunderQueen extends CardImpl {
private static final FilterControlledPermanent filterTreasures = new FilterControlledPermanent(SubType.TREASURE, "treasures");
public JoleneThePlunderQueen(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}{G}");
this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.HUMAN);
this.subtype.add(SubType.WARRIOR);
this.power = new MageInt(2);
this.toughness = new MageInt(2);
// Whenever a player attacks one or more of your opponents, that attacking player creates a Treasure token.
this.addAbility(new JoleneThePlunderQueenTriggeredAbility());
// If you would create one or more Treasure tokens, instead create those tokens plus an additional Treasure token.
this.addAbility(new SimpleStaticAbility(new JoleneThePlunderQueenReplacementEffect()));
// Sacrifice five Treasures: Put five +1/+1 counters on Jolene.
this.addAbility(new SimpleActivatedAbility(
new AddCountersSourceEffect(CounterType.P1P1.createInstance(5)),
new SacrificeTargetCost(new TargetControlledPermanent(5, filterTreasures))
));
}
private JoleneThePlunderQueen(final JoleneThePlunderQueen card) {
super(card);
}
@Override
public JoleneThePlunderQueen copy() {
return new JoleneThePlunderQueen(this);
}
}
// Based loosely on "Breena, the Demagogue" and "Mila, Crafty Companion"'s trigger abilities
class JoleneThePlunderQueenTriggeredAbility extends TriggeredAbilityImpl {
JoleneThePlunderQueenTriggeredAbility() {
super(Zone.BATTLEFIELD, new JoleneThePlunderQueenCreateTreasureEffect(), false);
}
private JoleneThePlunderQueenTriggeredAbility(final JoleneThePlunderQueenTriggeredAbility ability) {
super(ability);
}
@Override
public JoleneThePlunderQueenTriggeredAbility copy() {
return new JoleneThePlunderQueenTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.DECLARED_ATTACKERS;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
Combat combat = game.getCombat();
UUID joleneController = game.getControllerId(sourceId);
Set<UUID> joleneOpponents = game.getOpponents(joleneController);
// At most one trigger per combat.
if(!combat.getAttackers()
.stream()
.anyMatch(attackerId -> {
// The trigger attempts to find at least one (attacker,defender)
// for which the defender is one of jolene's controller opponent
UUID defenderId = combat.getDefenderId(attackerId);
return joleneOpponents.contains(defenderId);
})){
return false;
}
getEffects().setTargetPointer(new FixedTarget(event.getPlayerId()));
return true;
}
@Override
public String getRule() {
return "Whenever a player attacks one of your opponents, " +
"that attacking player creates a Treasure token.";
}
}
class JoleneThePlunderQueenCreateTreasureEffect extends OneShotEffect {
JoleneThePlunderQueenCreateTreasureEffect() {
super(Outcome.Benefit);
staticText = "that attacking player creates a Treasure token";
}
private JoleneThePlunderQueenCreateTreasureEffect(final JoleneThePlunderQueenCreateTreasureEffect effect) {
super(effect);
}
@Override
public JoleneThePlunderQueenCreateTreasureEffect copy() {
return new JoleneThePlunderQueenCreateTreasureEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
for (UUID playerId : getTargetPointer().getTargets(game, source)){
new TreasureToken().putOntoBattlefield(1, game, source, playerId);
}
return true;
}
}
// Identical to "Xorn"'s Replacement Effect
class JoleneThePlunderQueenReplacementEffect extends ReplacementEffectImpl {
public JoleneThePlunderQueenReplacementEffect() {
super(Duration.WhileOnBattlefield, Outcome.Benefit);
this.staticText = "If you would create one or more Treasure tokens, instead create those tokens plus an additional Treasure token";
}
private JoleneThePlunderQueenReplacementEffect(final JoleneThePlunderQueenReplacementEffect effect) {
super(effect);
}
@Override
public JoleneThePlunderQueenReplacementEffect copy() {
return new JoleneThePlunderQueenReplacementEffect(this);
}
@Override
public boolean checksEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.CREATE_TOKEN;
}
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
if (event instanceof CreateTokenEvent && source.isControlledBy(event.getPlayerId())) {
for (Token token : ((CreateTokenEvent) event).getTokens().keySet()) {
if (token.hasSubtype(SubType.TREASURE, game)) {
return true;
}
}
}
return false;
}
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
if (event instanceof CreateTokenEvent) {
CreateTokenEvent tokenEvent = (CreateTokenEvent) event;
TreasureToken treasureToken = null;
Map<Token, Integer> tokens = tokenEvent.getTokens();
for (Token token : tokens.keySet()) {
if (token instanceof TreasureToken) {
treasureToken = (TreasureToken) token;
break;
}
}
if (treasureToken == null) {
treasureToken = new TreasureToken();
}
tokens.put(treasureToken, tokens.getOrDefault(treasureToken, 0) + 1);
}
return false;
}
}

View file

@ -168,6 +168,8 @@ public final class NewCapennaCommander extends ExpansionSet {
cards.add(new SetCardInfo("Izzet Signet", 369, Rarity.UNCOMMON, mage.cards.i.IzzetSignet.class));
cards.add(new SetCardInfo("Jailbreak", 17, Rarity.RARE, mage.cards.j.Jailbreak.class));
cards.add(new SetCardInfo("Jenara, Asura of War", 343, Rarity.MYTHIC, mage.cards.j.JenaraAsuraOfWar.class));
cards.add(new SetCardInfo("Jolene, the Plunder Queen", 73, Rarity.RARE, mage.cards.j.JoleneThePlunderQueen.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Jolene, the Plunder Queen", 173, Rarity.RARE, mage.cards.j.JoleneThePlunderQueen.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Jund Panorama", 408, Rarity.COMMON, mage.cards.j.JundPanorama.class));
cards.add(new SetCardInfo("Jungle Shrine", 409, Rarity.UNCOMMON, mage.cards.j.JungleShrine.class));
cards.add(new SetCardInfo("Kamiz, Obscura Oculus", 3, Rarity.MYTHIC, mage.cards.k.KamizObscuraOculus.class));

View file

@ -0,0 +1,133 @@
package org.mage.test.cards.single.ncc;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestCommander4Players;
/**
*
* @author Susucre
*/
public class JoleneThePlunderQueenTest extends CardTestCommander4Players {
/*
Jolene, the Plunder Queen {2}{R}{G}
Legendary Creature Human Warrior 2/2
Whenever a player attacks one or more of your opponents, that attacking player creates a Treasure token.
If you would create one or more Treasure tokens, instead create those tokens plus an additional Treasure token.
Sacrifice five Treasures: Put five +1/+1 counters on Jolene.
*/
String jolene = "Jolene, the Plunder Queen";
/*
Elite Vanguard {W}
Creature Human Soldier 2/1
*/
String vanguard = "Elite Vanguard";
/*
Balduvian Bears {1}{G}
Creature Bear 2/2
*/
String bear = "Balduvian Bears";
/**
* test with three players:
* A with a Jolene and an Elite Vanguard
* B, C other players.
*
* A attacks both B & C, get two treasures.
*/
@Test
public void testAttackingTwoOpponents() {
addCard(Zone.BATTLEFIELD, playerA, jolene, 1);
addCard(Zone.BATTLEFIELD, playerA, vanguard, 1);
attack(1, playerA, jolene, playerB);
attack(1, playerA, vanguard, playerC);
setStopAt(1, PhaseStep.DECLARE_BLOCKERS);
execute();
assertAllCommandsUsed();
// 1 attack trigger, +1 Treasure with the replacement effect.
assertPermanentCount(playerA, "Treasure Token", 2);
}
/**
* test with three players:
* A with an Elite Vanguard and a Balduvian Bears
* B with a Jolene
* C other player.
*
* A attacks both B & C, get one treasure.
*/
@Test
public void testAttackingJoleneAndAnotherOpponent() {
addCard(Zone.BATTLEFIELD, playerA, vanguard, 1);
addCard(Zone.BATTLEFIELD, playerA, bear, 1);
addCard(Zone.BATTLEFIELD, playerB, jolene, 1);
attack(1, playerA, bear, playerB);
attack(1, playerA, vanguard, playerC);
setStopAt(1, PhaseStep.DECLARE_BLOCKERS);
execute();
assertAllCommandsUsed();
// 1 attack trigger, not controlling Jolene so no replacement effect.
assertPermanentCount(playerA, "Treasure Token", 1);
}
/**
* test with three players:
* A with an Elite Vanguard
* B with a Jolene
* C other player.
*
* A attacks only B, no trigger, no treasure.
*/
@Test
public void testAttackingJoleneOnly() {
addCard(Zone.BATTLEFIELD, playerA, vanguard, 1);
addCard(Zone.BATTLEFIELD, playerB, jolene, 1);
attack(1, playerA, vanguard, playerB);
setStopAt(1, PhaseStep.DECLARE_BLOCKERS);
execute();
assertAllCommandsUsed();
// no trigger, no token.
assertPermanentCount(playerA, "Treasure Token", 0);
}
/**
* test with three players:
* A with a Jolene and an Elite Vanguard
* B with a Jolene.
* C with a Jolene.
*
* A attacks both B & C, 3 triggers, 6 treasures.
*/
@Test
public void testEveryoneGotAJolene() {
addCard(Zone.BATTLEFIELD, playerA, vanguard, 1);
addCard(Zone.BATTLEFIELD, playerA, jolene, 1);
addCard(Zone.BATTLEFIELD, playerB, jolene, 1);
addCard(Zone.BATTLEFIELD, playerC, jolene, 1);
attack(1, playerA, vanguard, playerB);
attack(1, playerA, jolene, playerC);
setStopAt(1, PhaseStep.DECLARE_BLOCKERS);
execute();
assertAllCommandsUsed();
// 3 triggers (1 for each Jolene), +1 Treasure for each with the replacement effect.
assertPermanentCount(playerA, "Treasure Token", 6);
}
}