mirror of
https://github.com/magefree/mage.git
synced 2026-01-26 21:29:17 -08:00
[MKM] Implement A Killer Among Us (#11704)
* [MKM] Implement A Killer Among Us * Address PR comments * Address PR comments --------- Co-authored-by: Matthew Wilson <matthew_w@vaadin.com>
This commit is contained in:
parent
e431cd90ab
commit
91312228d7
3 changed files with 370 additions and 0 deletions
250
Mage.Sets/src/mage/cards/a/AKillerAmongUs.java
Normal file
250
Mage.Sets/src/mage/cards/a/AKillerAmongUs.java
Normal file
|
|
@ -0,0 +1,250 @@
|
|||
package mage.cards.a;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import mage.MageObject;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
|
||||
import mage.abilities.common.SimpleActivatedAbility;
|
||||
import mage.abilities.costs.Cost;
|
||||
import mage.abilities.costs.CostImpl;
|
||||
import mage.abilities.costs.common.SacrificeSourceCost;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.abilities.effects.common.CreateTokenEffect;
|
||||
import mage.abilities.effects.common.continuous.GainAbilityTargetEffect;
|
||||
import mage.abilities.keyword.DeathtouchAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.choices.Choice;
|
||||
import mage.choices.ChoiceImpl;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.Duration;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.SubType;
|
||||
import mage.counters.CounterType;
|
||||
import mage.filter.common.FilterAttackingCreature;
|
||||
import mage.filter.predicate.permanent.TokenPredicate;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.game.permanent.token.GoblinToken;
|
||||
import mage.game.permanent.token.HumanToken;
|
||||
import mage.game.permanent.token.MerfolkToken;
|
||||
import mage.players.Player;
|
||||
import mage.target.TargetPermanent;
|
||||
|
||||
/**
|
||||
* A Killer Among Us {4}{G}
|
||||
* Enchantment
|
||||
* When A Killer Among Us enters the battlefield, create a 1/1 white Human creature token, a 1/1 blue Merfolk creature token, and a 1/1 red Goblin creature token. Then secretly choose Human, Merfolk, or Goblin.
|
||||
* Sacrifice A Killer Among Us, Reveal the chosen creature type: If target attacking creature token is the chosen type, put three +1/+1 counters on it and it gains deathtouch until end of turn.
|
||||
* ┌─────┐
|
||||
* ┌─┤ ┌──┴┐
|
||||
* │ │ └──┬┘
|
||||
* └─┤ ┌─┐ │
|
||||
* └─┘ └─┘
|
||||
*
|
||||
* @author DominionSpy
|
||||
*/
|
||||
public final class AKillerAmongUs extends CardImpl {
|
||||
|
||||
private static final FilterAttackingCreature filter = new FilterAttackingCreature("attacking creature token");
|
||||
|
||||
static {
|
||||
filter.add(TokenPredicate.TRUE);
|
||||
}
|
||||
|
||||
public AKillerAmongUs(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{4}{G}");
|
||||
|
||||
// When A Killer Among Us enters the battlefield, create a 1/1 white Human creature token, a 1/1 blue Merfolk creature token, and a 1/1 red Goblin creature token. Then secretly choose Human, Merfolk, or Goblin.
|
||||
Ability ability = new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new HumanToken()));
|
||||
ability.addEffect(new CreateTokenEffect(new MerfolkToken())
|
||||
.setText(", a 1/1 blue Merfolk creature token"));
|
||||
ability.addEffect(new CreateTokenEffect(new GoblinToken())
|
||||
.setText(", and a 1/1 red Goblin creature token."));
|
||||
ability.addEffect(new ChooseHumanMerfolkOrGoblinEffect());
|
||||
this.addAbility(ability);
|
||||
|
||||
// Sacrifice A Killer Among Us, Reveal the chosen creature type: If target attacking creature token is the chosen type, put three +1/+1 counters on it and it gains deathtouch until end of turn.
|
||||
ability = new SimpleActivatedAbility(new AKillerAmongUsEffect(), new SacrificeSourceCost());
|
||||
ability.addCost(new AKillerAmongUsCost());
|
||||
ability.addTarget(new TargetPermanent(filter));
|
||||
this.addAbility(ability);
|
||||
}
|
||||
|
||||
private AKillerAmongUs(final AKillerAmongUs card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AKillerAmongUs copy() {
|
||||
return new AKillerAmongUs(this);
|
||||
}
|
||||
}
|
||||
|
||||
class ChooseHumanMerfolkOrGoblinEffect extends OneShotEffect {
|
||||
|
||||
public static final String SECRET_CREATURE_TYPE = "_susCreatureType";
|
||||
public static final String SECRET_OWNER = "_secOwn";
|
||||
|
||||
ChooseHumanMerfolkOrGoblinEffect() {
|
||||
super(Outcome.Neutral);
|
||||
staticText = "Then secretly choose Human, Merfolk, or Goblin.";
|
||||
}
|
||||
|
||||
private ChooseHumanMerfolkOrGoblinEffect(final ChooseHumanMerfolkOrGoblinEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChooseHumanMerfolkOrGoblinEffect copy() {
|
||||
return new ChooseHumanMerfolkOrGoblinEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Player controller = game.getPlayer(source.getControllerId());
|
||||
if (controller == null) {
|
||||
return false;
|
||||
}
|
||||
Permanent permanent = game.getPermanentOrLKIBattlefield(source.getSourceId());
|
||||
if (permanent == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Choice choice = new ChoiceImpl();
|
||||
Set<String> choices = new LinkedHashSet<>();
|
||||
choices.add("Human");
|
||||
choices.add("Merfolk");
|
||||
choices.add("Goblin");
|
||||
choice.setChoices(choices);
|
||||
choice.setMessage("Choose Human, Merfolk, or Goblin");
|
||||
|
||||
controller.choose(outcome, choice, game);
|
||||
game.informPlayers(permanent.getName() + ": " + controller.getLogName() + " has secretly chosen a creature type.");
|
||||
|
||||
SubType chosenType = SubType.fromString(choice.getChoice());
|
||||
setSecretCreatureType(chosenType, source, game);
|
||||
setSecretOwner(source.getControllerId(), source, game);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void setSecretCreatureType(SubType type, Ability source, Game game) {
|
||||
String uniqueRef = getUniqueReference(source, game);
|
||||
if (uniqueRef != null) {
|
||||
game.getState().setValue(uniqueRef + SECRET_CREATURE_TYPE, type);
|
||||
}
|
||||
}
|
||||
|
||||
public static SubType getSecretCreatureType(Ability source, Game game) {
|
||||
String uniqueRef = getUniqueReference(source, game);
|
||||
if (uniqueRef != null) {
|
||||
return (SubType) game.getState().getValue(uniqueRef +
|
||||
ChooseHumanMerfolkOrGoblinEffect.SECRET_CREATURE_TYPE);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void setSecretOwner(UUID owner, Ability source, Game game) {
|
||||
String uniqueRef = getUniqueReference(source, game);
|
||||
if (uniqueRef != null) {
|
||||
game.getState().setValue(getUniqueReference(source, game) + SECRET_OWNER, owner);
|
||||
}
|
||||
}
|
||||
|
||||
public static UUID getSecretOwner(Ability source, Game game) {
|
||||
String uniqueRef = getUniqueReference(source, game);
|
||||
if (uniqueRef != null) {
|
||||
return (UUID) game.getState().getValue(getUniqueReference(source, game) +
|
||||
ChooseHumanMerfolkOrGoblinEffect.SECRET_OWNER);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String getUniqueReference(Ability source, Game game) {
|
||||
if (game.getPermanentOrLKIBattlefield(source.getSourceId()) != null) {
|
||||
return source.getSourceId() + "_" + (game.getPermanentOrLKIBattlefield(source.getSourceId()).getZoneChangeCounter(game));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class AKillerAmongUsEffect extends OneShotEffect {
|
||||
|
||||
AKillerAmongUsEffect() {
|
||||
super(Outcome.Benefit);
|
||||
this.staticText = "If target attacking creature token is the chosen type, " +
|
||||
"put three +1/+1 counters on it and it gains deathtouch until end of turn.";
|
||||
}
|
||||
|
||||
private AKillerAmongUsEffect(final AKillerAmongUsEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AKillerAmongUsEffect copy() {
|
||||
return new AKillerAmongUsEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Permanent creature = game.getPermanent(source.getFirstTarget());
|
||||
if (creature == null) {
|
||||
return false;
|
||||
}
|
||||
SubType creatureType = ChooseHumanMerfolkOrGoblinEffect.getSecretCreatureType(source, game);
|
||||
if (creatureType != null && creature.getSubtype().contains(creatureType)) {
|
||||
creature.addCounters(CounterType.P1P1.createInstance(3), source, game);
|
||||
game.addEffect(new GainAbilityTargetEffect(
|
||||
DeathtouchAbility.getInstance(), Duration.EndOfTurn
|
||||
), source);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class AKillerAmongUsCost extends CostImpl {
|
||||
|
||||
AKillerAmongUsCost() {
|
||||
this.text = "Reveal the chosen creature type";
|
||||
}
|
||||
|
||||
private AKillerAmongUsCost(final AKillerAmongUsCost cost) {
|
||||
super(cost);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AKillerAmongUsCost copy() {
|
||||
return new AKillerAmongUsCost(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) {
|
||||
return controllerId != null
|
||||
&& controllerId.equals(ChooseHumanMerfolkOrGoblinEffect.getSecretOwner(source, game))
|
||||
&& ChooseHumanMerfolkOrGoblinEffect.getSecretCreatureType(source, game) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean pay(Ability ability, Game game, Ability source, UUID controllerId, boolean noMana, Cost costToPay) {
|
||||
if (controllerId == null || !controllerId.equals(ChooseHumanMerfolkOrGoblinEffect.getSecretOwner(source, game))) {
|
||||
return false;
|
||||
}
|
||||
SubType creatureType = ChooseHumanMerfolkOrGoblinEffect.getSecretCreatureType(source, game);
|
||||
if (creatureType == null) {
|
||||
return paid;
|
||||
}
|
||||
|
||||
Player controller = game.getPlayer(controllerId);
|
||||
MageObject sourceObject = game.getObject(source);
|
||||
if (controller != null && sourceObject != null) {
|
||||
game.informPlayers(sourceObject.getLogName() + ": " + controller.getLogName() +
|
||||
" reveals the secretly chosen creature type " + creatureType);
|
||||
}
|
||||
|
||||
paid = true;
|
||||
return paid;
|
||||
}
|
||||
}
|
||||
|
|
@ -26,6 +26,7 @@ public final class MurdersAtKarlovManor extends ExpansionSet {
|
|||
this.hasBasicLands = true;
|
||||
this.hasBoosters = false; // temporary
|
||||
|
||||
cards.add(new SetCardInfo("A Killer Among Us", 167, Rarity.UNCOMMON, mage.cards.a.AKillerAmongUs.class));
|
||||
cards.add(new SetCardInfo("Absolving Lammasu", 2, Rarity.UNCOMMON, mage.cards.a.AbsolvingLammasu.class));
|
||||
cards.add(new SetCardInfo("Aftermath Analyst", 148, Rarity.UNCOMMON, mage.cards.a.AftermathAnalyst.class));
|
||||
cards.add(new SetCardInfo("Agrus Kos, Spirit of Justice", 184, Rarity.MYTHIC, mage.cards.a.AgrusKosSpiritOfJustice.class));
|
||||
|
|
|
|||
|
|
@ -0,0 +1,119 @@
|
|||
package org.mage.test.cards.single.mkm;
|
||||
|
||||
import mage.abilities.keyword.DeathtouchAbility;
|
||||
import mage.constants.PhaseStep;
|
||||
import mage.constants.Zone;
|
||||
import mage.counters.CounterType;
|
||||
import org.junit.Test;
|
||||
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||
|
||||
public class AKillerAmongUsTest extends CardTestPlayerBase {
|
||||
|
||||
@Test
|
||||
public void test_TargetChosenCreatureType() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Forest", 5);
|
||||
addCard(Zone.HAND, playerA, "A Killer Among Us");
|
||||
|
||||
// When A Killer Among Us enters the battlefield, create a 1/1 white Human creature token, a 1/1 blue Merfolk creature token, and a 1/1 red Goblin creature token. Then secretly choose Human, Merfolk, or Goblin.
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "A Killer Among Us");
|
||||
setChoice(playerA, "Merfolk");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
|
||||
checkPermanentCount("human token", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Human Token", 1);
|
||||
checkPermanentCount("merfolk token", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Merfolk Token", 1);
|
||||
checkPermanentCount("goblin token", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Goblin Token", 1);
|
||||
|
||||
attack(3, playerA, "Merfolk Token");
|
||||
// Sacrifice A Killer Among Us, Reveal the chosen creature type: If target attacking creature token is the chosen type, put three +1/+1 counters on it and it gains deathtouch until end of turn.
|
||||
activateAbility(3, PhaseStep.DECLARE_BLOCKERS, playerA, "Sacrifice", "Merfolk Token");
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(3, PhaseStep.POSTCOMBAT_MAIN);
|
||||
execute();
|
||||
|
||||
assertPermanentCount(playerA, "A Killer Among Us", 0);
|
||||
assertCounterCount(playerA, "Merfolk Token", CounterType.P1P1, 3);
|
||||
assertAbility(playerA, "Merfolk Token", DeathtouchAbility.getInstance(), true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_TargetNotChosenCreatureType() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Forest", 5);
|
||||
addCard(Zone.HAND, playerA, "A Killer Among Us");
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "A Killer Among Us");
|
||||
setChoice(playerA, "Merfolk");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
|
||||
checkPermanentCount("human token", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Human Token", 1);
|
||||
checkPermanentCount("merfolk token", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Merfolk Token", 1);
|
||||
checkPermanentCount("goblin token", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Goblin Token", 1);
|
||||
|
||||
attack(3, playerA, "Human Token");
|
||||
activateAbility(3, PhaseStep.DECLARE_BLOCKERS, playerA, "Sacrifice", "Human Token");
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(3, PhaseStep.POSTCOMBAT_MAIN);
|
||||
execute();
|
||||
|
||||
assertCounterCount(playerA, "Human Token", CounterType.P1P1, 0);
|
||||
assertAbility(playerA, "Human Token", DeathtouchAbility.getInstance(), false);
|
||||
}
|
||||
|
||||
// If the A Killer Among Us ETB trigger is copied, the last chosen creature type
|
||||
// will be used for the second ability
|
||||
@Test
|
||||
public void test_CopyTrigger_TargetLastChosenCreatureType() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Taiga", 5 + 2);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Lithoform Engine");
|
||||
addCard(Zone.HAND, playerA, "A Killer Among Us");
|
||||
addCard(Zone.HAND, playerA, "Tremor");
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "A Killer Among Us");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, 1);
|
||||
// Copy A Killer Among Us ETB trigger
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}, {T}", "stack ability (When");
|
||||
setChoice(playerA, "Human");
|
||||
setChoice(playerA, "Merfolk");
|
||||
|
||||
attack(3, playerA, "Merfolk Token");
|
||||
activateAbility(3, PhaseStep.DECLARE_BLOCKERS, playerA, "Sacrifice", "Merfolk Token");
|
||||
|
||||
// Destroy all the other tokens
|
||||
castSpell(3, PhaseStep.POSTCOMBAT_MAIN, playerA, "Tremor");
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(3, PhaseStep.POSTCOMBAT_MAIN);
|
||||
execute();
|
||||
|
||||
assertCounterCount(playerA, "Merfolk Token", CounterType.P1P1, 3);
|
||||
assertAbility(playerA, "Merfolk Token", DeathtouchAbility.getInstance(), true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_CopyTrigger_TargetNotLastChosenCreatureType() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Taiga", 5 + 2);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Lithoform Engine");
|
||||
addCard(Zone.HAND, playerA, "A Killer Among Us");
|
||||
addCard(Zone.HAND, playerA, "Tremor");
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "A Killer Among Us");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, 1);
|
||||
// Copy A Killer Among Us ETB trigger
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}, {T}", "stack ability (When");
|
||||
setChoice(playerA, "Human");
|
||||
setChoice(playerA, "Merfolk");
|
||||
|
||||
attack(3, playerA, "Human Token");
|
||||
activateAbility(3, PhaseStep.DECLARE_BLOCKERS, playerA, "Sacrifice", "Human Token");
|
||||
|
||||
// Destroy all the tokens
|
||||
castSpell(3, PhaseStep.POSTCOMBAT_MAIN, playerA, "Tremor");
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(3, PhaseStep.POSTCOMBAT_MAIN);
|
||||
execute();
|
||||
|
||||
assertPermanentCount(playerA, "Human Token", 0);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue