mirror of
https://github.com/magefree/mage.git
synced 2025-12-20 02:30:08 -08:00
[FIC] Implement Locke, Treasure Hunter (#13695)
This commit is contained in:
parent
3d45a24959
commit
a7dcd6988f
4 changed files with 309 additions and 0 deletions
205
Mage.Sets/src/mage/cards/l/LockeTreasureHunter.java
Normal file
205
Mage.Sets/src/mage/cards/l/LockeTreasureHunter.java
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
package mage.cards.l;
|
||||
|
||||
import mage.MageIdentifier;
|
||||
import mage.MageInt;
|
||||
import mage.MageObject;
|
||||
import mage.MageObjectReference;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.AttacksTriggeredAbility;
|
||||
import mage.abilities.common.SimpleActivatedAbility;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.condition.Condition;
|
||||
import mage.abilities.costs.mana.GenericManaCost;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.abilities.effects.common.combat.CantBeBlockedByCreaturesSourceEffect;
|
||||
import mage.cards.*;
|
||||
import mage.constants.*;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.filter.common.FilterCreaturePermanent;
|
||||
import mage.filter.predicate.ObjectSourcePlayer;
|
||||
import mage.filter.predicate.ObjectSourcePlayerPredicate;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.GameEvent;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.game.permanent.token.TreasureToken;
|
||||
import mage.players.Player;
|
||||
import mage.util.CardUtil;
|
||||
import mage.watchers.Watcher;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class LockeTreasureHunter extends CardImpl {
|
||||
|
||||
private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creatures with greater power");
|
||||
|
||||
static {
|
||||
filter.add(LockeTreasureHunterPredicate.instance);
|
||||
}
|
||||
|
||||
public LockeTreasureHunter(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}{R}");
|
||||
|
||||
this.supertype.add(SuperType.LEGENDARY);
|
||||
this.subtype.add(SubType.HUMAN);
|
||||
this.subtype.add(SubType.ROGUE);
|
||||
this.power = new MageInt(2);
|
||||
this.toughness = new MageInt(3);
|
||||
|
||||
// Locke can't be blocked by creatures with greater power.
|
||||
this.addAbility(new SimpleStaticAbility(new CantBeBlockedByCreaturesSourceEffect(filter, Duration.WhileOnBattlefield)));
|
||||
|
||||
// Mug -- Whenever Locke attacks, each player mills a card. If a land card was milled this way, create a Treasure token. Until end of turn, you may cast a spell from among those cards.
|
||||
this.addAbility(new AttacksTriggeredAbility(new LockeTreasureHunterEffect())
|
||||
.withFlavorWord("Mug")
|
||||
.setIdentifier(MageIdentifier.LockeTreasureHunterWatcher), new LockeTreasureHunterWatcher());
|
||||
}
|
||||
|
||||
private LockeTreasureHunter(final LockeTreasureHunter card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LockeTreasureHunter copy() {
|
||||
return new LockeTreasureHunter(this);
|
||||
}
|
||||
|
||||
public static Ability makeTestAbility() {
|
||||
Ability ability = new SimpleActivatedAbility(
|
||||
new LockeTreasureHunterEffect(), new GenericManaCost(0)
|
||||
).setIdentifier(MageIdentifier.LockeTreasureHunterWatcher);
|
||||
ability.addWatcher(new LockeTreasureHunterWatcher());
|
||||
return ability;
|
||||
}
|
||||
}
|
||||
|
||||
enum LockeTreasureHunterPredicate implements ObjectSourcePlayerPredicate<Permanent> {
|
||||
instance;
|
||||
|
||||
@Override
|
||||
public boolean apply(ObjectSourcePlayer<Permanent> input, Game game) {
|
||||
return Optional
|
||||
.ofNullable(input.getSource().getSourcePermanentIfItStillExists(game))
|
||||
.map(MageObject::getPower)
|
||||
.map(MageInt::getValue)
|
||||
.map(x -> x < input.getObject().getPower().getValue())
|
||||
.orElse(false);
|
||||
}
|
||||
}
|
||||
|
||||
class LockeTreasureHunterEffect extends OneShotEffect {
|
||||
|
||||
LockeTreasureHunterEffect() {
|
||||
super(Outcome.Benefit);
|
||||
staticText = "each player mills a card. If a land card was milled this way, " +
|
||||
"create a Treasure token. Until end of turn, you may cast a spell from among those cards";
|
||||
}
|
||||
|
||||
private LockeTreasureHunterEffect(final LockeTreasureHunterEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LockeTreasureHunterEffect copy() {
|
||||
return new LockeTreasureHunterEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Cards cards = new CardsImpl();
|
||||
for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) {
|
||||
Player player = game.getPlayer(playerId);
|
||||
if (player != null) {
|
||||
cards.addAllCards(player.millCards(1, source, game).getCards(game));
|
||||
}
|
||||
}
|
||||
if (cards.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
if (cards.count(StaticFilters.FILTER_CARD_LAND, game) > 0) {
|
||||
new TreasureToken().putOntoBattlefield(1, game, source);
|
||||
}
|
||||
LockeTreasureHunterWatcher.saveCards(cards, game, source);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class LockeTreasureHunterWatcher extends Watcher {
|
||||
|
||||
private static class LockeTreasureHunterCondition implements Condition {
|
||||
private final UUID permissionId;
|
||||
|
||||
LockeTreasureHunterCondition(UUID permissionId) {
|
||||
this.permissionId = permissionId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
return game
|
||||
.getState()
|
||||
.getWatcher(LockeTreasureHunterWatcher.class)
|
||||
.checkPermission(permissionId, source, game);
|
||||
}
|
||||
}
|
||||
|
||||
// Maps cards to the specific instance that gave them permission to be cast
|
||||
private final Map<MageObjectReference, UUID> morMap = new HashMap<>();
|
||||
|
||||
// Maps permissions to the players who can use them
|
||||
private static final Map<UUID, UUID> playerPermissionMap = new HashMap<>();
|
||||
|
||||
// Tracks permissions which have already been used
|
||||
private final Set<UUID> usedSet = new HashSet<>();
|
||||
|
||||
LockeTreasureHunterWatcher() {
|
||||
super(WatcherScope.GAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void watch(GameEvent event, Game game) {
|
||||
if (event.getType() != GameEvent.EventType.SPELL_CAST
|
||||
|| !event.hasApprovingIdentifier(MageIdentifier.LockeTreasureHunterWatcher)) {
|
||||
return;
|
||||
}
|
||||
Optional.ofNullable(event)
|
||||
.map(GameEvent::getTargetId)
|
||||
.map(game::getSpell)
|
||||
.map(spell -> new MageObjectReference(spell.getMainCard(), game, -1))
|
||||
.map(mor -> morMap.getOrDefault(mor, null))
|
||||
.ifPresent(usedSet::add);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
super.reset();
|
||||
morMap.clear();
|
||||
playerPermissionMap.clear();
|
||||
usedSet.clear();
|
||||
}
|
||||
|
||||
static void saveCards(Cards cards, Game game, Ability source) {
|
||||
game.getState()
|
||||
.getWatcher(LockeTreasureHunterWatcher.class)
|
||||
.handleSaveCards(cards, game, source);
|
||||
}
|
||||
|
||||
private void handleSaveCards(Cards cards, Game game, Ability source) {
|
||||
UUID permissionId = UUID.randomUUID();
|
||||
playerPermissionMap.put(permissionId, source.getControllerId());
|
||||
Condition condition = new LockeTreasureHunterCondition(permissionId);
|
||||
for (Card card : cards.getCards(game)) {
|
||||
morMap.put(new MageObjectReference(card, game), permissionId);
|
||||
CardUtil.makeCardPlayable(
|
||||
game, source, card, true, Duration.EndOfTurn,
|
||||
false, source.getControllerId(), condition
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkPermission(UUID permissionId, Ability source, Game game) {
|
||||
return !usedSet.contains(permissionId)
|
||||
&& source.isControlledBy(playerPermissionMap.getOrDefault(permissionId, null));
|
||||
}
|
||||
}
|
||||
|
|
@ -222,6 +222,8 @@ public final class FinalFantasyCommander extends ExpansionSet {
|
|||
cards.add(new SetCardInfo("Lethal Scheme", 277, Rarity.RARE, mage.cards.l.LethalScheme.class));
|
||||
cards.add(new SetCardInfo("Lightning Greaves", 349, Rarity.UNCOMMON, mage.cards.l.LightningGreaves.class));
|
||||
cards.add(new SetCardInfo("Lingering Souls", 245, Rarity.UNCOMMON, mage.cards.l.LingeringSouls.class));
|
||||
cards.add(new SetCardInfo("Locke, Treasure Hunter", 177, Rarity.RARE, mage.cards.l.LockeTreasureHunter.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Locke, Treasure Hunter", 87, Rarity.RARE, mage.cards.l.LockeTreasureHunter.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Lord Jyscal Guado", 137, Rarity.RARE, mage.cards.l.LordJyscalGuado.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Lord Jyscal Guado", 23, Rarity.RARE, mage.cards.l.LordJyscalGuado.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Lulu, Stern Guardian", 143, Rarity.RARE, mage.cards.l.LuluSternGuardian.class, NON_FULL_USE_VARIOUS));
|
||||
|
|
|
|||
|
|
@ -0,0 +1,101 @@
|
|||
package org.mage.test.cards.single.fic;
|
||||
|
||||
import mage.cards.l.LockeTreasureHunter;
|
||||
import mage.constants.PhaseStep;
|
||||
import mage.constants.Zone;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public class LockeTreasureHunterTest extends CardTestPlayerBase {
|
||||
|
||||
private static final String dwarvenGrunt = "Dwarven Grunt";
|
||||
private static final String goblinMountaineer = "Goblin Mountaineer";
|
||||
private static final String mountainGoat = "Mountain Goat";
|
||||
private static final String zodiacGoat = "Zodiac Goat";
|
||||
|
||||
private void makeTester() {
|
||||
addCustomCardWithAbility("tester", playerA, LockeTreasureHunter.makeTestAbility());
|
||||
}
|
||||
|
||||
private void assertOptions(String... optionsToExpect) {
|
||||
Set<String> options = playerA
|
||||
.getPlayable(currentGame, false)
|
||||
.stream()
|
||||
.map(Objects::toString)
|
||||
.collect(Collectors.toSet());
|
||||
Set<String> failures = new HashSet<>();
|
||||
for (String option : optionsToExpect) {
|
||||
if (options.stream().noneMatch(s -> s.contains(option))) {
|
||||
failures.add(option);
|
||||
}
|
||||
}
|
||||
Assert.assertEquals(
|
||||
"The following cards should be available to cast but aren't: " +
|
||||
failures.stream().collect(Collectors.joining(", ")), 0, failures.size()
|
||||
);
|
||||
Assert.assertEquals(
|
||||
"There should be " + (2 + optionsToExpect.length) + " available actions",
|
||||
2 + optionsToExpect.length, options.size()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCastSome() {
|
||||
skipInitShuffling();
|
||||
makeTester();
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4);
|
||||
addCard(Zone.LIBRARY, playerA, dwarvenGrunt);
|
||||
addCard(Zone.LIBRARY, playerA, goblinMountaineer);
|
||||
addCard(Zone.LIBRARY, playerB, mountainGoat);
|
||||
addCard(Zone.LIBRARY, playerB, zodiacGoat);
|
||||
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{0}");
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{0}");
|
||||
|
||||
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, dwarvenGrunt);
|
||||
waitStackResolved(1, PhaseStep.POSTCOMBAT_MAIN);
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
|
||||
execute();
|
||||
|
||||
assertOptions(goblinMountaineer, zodiacGoat);
|
||||
assertPermanentCount(playerA, dwarvenGrunt, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCastAll() {
|
||||
skipInitShuffling();
|
||||
makeTester();
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4);
|
||||
addCard(Zone.LIBRARY, playerA, dwarvenGrunt);
|
||||
addCard(Zone.LIBRARY, playerA, goblinMountaineer);
|
||||
addCard(Zone.LIBRARY, playerB, mountainGoat);
|
||||
addCard(Zone.LIBRARY, playerB, zodiacGoat);
|
||||
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{0}");
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{0}");
|
||||
|
||||
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, dwarvenGrunt);
|
||||
waitStackResolved(1, PhaseStep.POSTCOMBAT_MAIN);
|
||||
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, zodiacGoat);
|
||||
waitStackResolved(1, PhaseStep.POSTCOMBAT_MAIN);
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
|
||||
execute();
|
||||
|
||||
assertOptions();
|
||||
assertPermanentCount(playerA, dwarvenGrunt, 1);
|
||||
assertPermanentCount(playerA, zodiacGoat, 1);
|
||||
}
|
||||
}
|
||||
|
|
@ -37,6 +37,7 @@ public enum MageIdentifier {
|
|||
LaraCroftTombRaiderWatcher,
|
||||
CoramTheUndertakerWatcher,
|
||||
ThundermanDragonWatcher,
|
||||
LockeTreasureHunterWatcher,
|
||||
|
||||
// ----------------------------//
|
||||
// alternate casts //
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue