mirror of
https://github.com/magefree/mage.git
synced 2025-12-22 19:41:59 -08:00
[MKM] Implement Cases (#11713)
* Implementing "case" mechanic * [MKM] Implement Case of the Burning Masks * [MKM] Implement Case of the Filched Falcon * [MKM] Implement Case of the Crimson Pulse * [MKM] Implement Case of the Locked Hothouse * Address PR comments * some minor adjustments * adjustments to hints --------- Co-authored-by: Matthew Wilson <matthew_w@vaadin.com> Co-authored-by: xenohedron <xenohedron@users.noreply.github.com>
This commit is contained in:
parent
25a08c736f
commit
f8d15cd6ba
18 changed files with 941 additions and 22 deletions
196
Mage.Sets/src/mage/cards/c/CaseOfTheBurningMasks.java
Normal file
196
Mage.Sets/src/mage/cards/c/CaseOfTheBurningMasks.java
Normal file
|
|
@ -0,0 +1,196 @@
|
||||||
|
package mage.cards.c;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import mage.MageObjectReference;
|
||||||
|
import mage.abilities.Ability;
|
||||||
|
import mage.abilities.common.CaseAbility;
|
||||||
|
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
|
||||||
|
import mage.abilities.condition.Condition;
|
||||||
|
import mage.abilities.condition.common.SolvedSourceCondition;
|
||||||
|
import mage.abilities.costs.common.SacrificeSourceCost;
|
||||||
|
import mage.abilities.decorator.ConditionalActivatedAbility;
|
||||||
|
import mage.abilities.effects.OneShotEffect;
|
||||||
|
import mage.abilities.effects.common.DamageTargetEffect;
|
||||||
|
import mage.abilities.hint.common.CaseSolvedHint;
|
||||||
|
import mage.cards.Card;
|
||||||
|
import mage.cards.Cards;
|
||||||
|
import mage.cards.CardsImpl;
|
||||||
|
import mage.constants.Duration;
|
||||||
|
import mage.constants.Outcome;
|
||||||
|
import mage.constants.SubType;
|
||||||
|
import mage.cards.CardImpl;
|
||||||
|
import mage.cards.CardSetInfo;
|
||||||
|
import mage.constants.CardType;
|
||||||
|
import mage.constants.WatcherScope;
|
||||||
|
import mage.constants.Zone;
|
||||||
|
import mage.filter.StaticFilters;
|
||||||
|
import mage.game.Game;
|
||||||
|
import mage.game.events.GameEvent;
|
||||||
|
import mage.players.Player;
|
||||||
|
import mage.target.TargetCard;
|
||||||
|
import mage.target.common.TargetCardInExile;
|
||||||
|
import mage.target.common.TargetOpponentsCreaturePermanent;
|
||||||
|
import mage.util.CardUtil;
|
||||||
|
import mage.watchers.Watcher;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Case of the Burning Masks {1}{R}{R}
|
||||||
|
* Enchantment - Case
|
||||||
|
* When this Case enters the battlefield, it deals 3 damage to target creature an opponent controls.
|
||||||
|
* To solve -- Three or more sources you controlled dealt damage this turn.
|
||||||
|
* Solved -- Sacrifice this Case: Exile the top three cards of your library. Choose one of them. You may play that card this turn.
|
||||||
|
*
|
||||||
|
* @author DominionSpy
|
||||||
|
*/
|
||||||
|
public final class CaseOfTheBurningMasks extends CardImpl {
|
||||||
|
|
||||||
|
public CaseOfTheBurningMasks(UUID ownerId, CardSetInfo setInfo) {
|
||||||
|
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{R}{R}");
|
||||||
|
|
||||||
|
this.subtype.add(SubType.CASE);
|
||||||
|
|
||||||
|
// When this Case enters the battlefield, it deals 3 damage to target creature an opponent controls.
|
||||||
|
Ability initialAbility = new EntersBattlefieldTriggeredAbility(new DamageTargetEffect(3));
|
||||||
|
initialAbility.addTarget(new TargetOpponentsCreaturePermanent());
|
||||||
|
// To solve -- Three or more sources you controlled dealt damage this turn.
|
||||||
|
// Solved -- Sacrifice this Case: Exile the top three cards of your library. Choose one of them. You may play that card this turn.
|
||||||
|
Ability solvedAbility = new ConditionalActivatedAbility(new CaseOfTheBurningMasksEffect(),
|
||||||
|
new SacrificeSourceCost().setText("sacrifice this Case"), SolvedSourceCondition.SOLVED);
|
||||||
|
|
||||||
|
this.addAbility(new CaseAbility(initialAbility, CaseOfTheBurningMasksCondition.instance, solvedAbility)
|
||||||
|
.addHint(new CaseOfTheBurningMasksHint()),
|
||||||
|
new CaseOfTheBurningMasksWatcher());
|
||||||
|
}
|
||||||
|
|
||||||
|
private CaseOfTheBurningMasks(final CaseOfTheBurningMasks card) {
|
||||||
|
super(card);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CaseOfTheBurningMasks copy() {
|
||||||
|
return new CaseOfTheBurningMasks(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum CaseOfTheBurningMasksCondition implements Condition {
|
||||||
|
instance;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean apply(Game game, Ability source) {
|
||||||
|
CaseOfTheBurningMasksWatcher watcher = game.getState().getWatcher(CaseOfTheBurningMasksWatcher.class);
|
||||||
|
return watcher != null && watcher.damagingCountByController(source.getControllerId()) >= 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Three or more sources you controlled dealt damage this turn";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CaseOfTheBurningMasksHint extends CaseSolvedHint {
|
||||||
|
|
||||||
|
CaseOfTheBurningMasksHint() {
|
||||||
|
super(CaseOfTheBurningMasksCondition.instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CaseOfTheBurningMasksHint(final CaseOfTheBurningMasksHint hint) {
|
||||||
|
super(hint);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CaseOfTheBurningMasksHint copy() {
|
||||||
|
return new CaseOfTheBurningMasksHint(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getConditionText(Game game, Ability ability) {
|
||||||
|
int sources = game.getState()
|
||||||
|
.getWatcher(CaseOfTheBurningMasksWatcher.class)
|
||||||
|
.damagingCountByController(ability.getControllerId());
|
||||||
|
return "Sources that dealt damage: " + sources + " (need 3).";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CaseOfTheBurningMasksWatcher extends Watcher {
|
||||||
|
|
||||||
|
private final Map<UUID, Set<MageObjectReference>> damagingObjects;
|
||||||
|
|
||||||
|
CaseOfTheBurningMasksWatcher() {
|
||||||
|
super(WatcherScope.GAME);
|
||||||
|
this.damagingObjects = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void watch(GameEvent event, Game game) {
|
||||||
|
switch (event.getType()) {
|
||||||
|
case DAMAGED_PERMANENT:
|
||||||
|
case DAMAGED_PLAYER: {
|
||||||
|
damagingObjects
|
||||||
|
.computeIfAbsent(game.getControllerId(event.getSourceId()), k -> new HashSet<>())
|
||||||
|
.add(new MageObjectReference(event.getSourceId(), game));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset() {
|
||||||
|
super.reset();
|
||||||
|
damagingObjects.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int damagingCountByController(UUID controllerId) {
|
||||||
|
return damagingObjects.getOrDefault(controllerId, Collections.emptySet()).size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CaseOfTheBurningMasksEffect extends OneShotEffect {
|
||||||
|
|
||||||
|
CaseOfTheBurningMasksEffect() {
|
||||||
|
super(Outcome.Benefit);
|
||||||
|
staticText = "Exile the top three cards of your library. Choose one of them. You may play that card this turn.";
|
||||||
|
}
|
||||||
|
|
||||||
|
private CaseOfTheBurningMasksEffect(final CaseOfTheBurningMasksEffect effect) {
|
||||||
|
super(effect);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CaseOfTheBurningMasksEffect copy() {
|
||||||
|
return new CaseOfTheBurningMasksEffect(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean apply(Game game, Ability source) {
|
||||||
|
Player player = game.getPlayer(source.getControllerId());
|
||||||
|
if (player == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Cards cards = new CardsImpl(player.getLibrary().getTopCards(game, 3));
|
||||||
|
player.moveCards(cards, Zone.EXILED, source, game);
|
||||||
|
cards.retainZone(Zone.EXILED, game);
|
||||||
|
|
||||||
|
Card card;
|
||||||
|
switch (cards.size()) {
|
||||||
|
case 0:
|
||||||
|
return false;
|
||||||
|
case 1:
|
||||||
|
card = cards.getRandom(game);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
TargetCard target = new TargetCardInExile(StaticFilters.FILTER_CARD);
|
||||||
|
target.withNotTarget(true);
|
||||||
|
player.choose(outcome, cards, target, source, game);
|
||||||
|
card = game.getCard(target.getFirstTarget());
|
||||||
|
}
|
||||||
|
if (card != null) {
|
||||||
|
CardUtil.makeCardPlayable(game, source, card, Duration.EndOfTurn, false);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
88
Mage.Sets/src/mage/cards/c/CaseOfTheCrimsonPulse.java
Normal file
88
Mage.Sets/src/mage/cards/c/CaseOfTheCrimsonPulse.java
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
package mage.cards.c;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import mage.abilities.Ability;
|
||||||
|
import mage.abilities.common.BeginningOfUpkeepTriggeredAbility;
|
||||||
|
import mage.abilities.common.CaseAbility;
|
||||||
|
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
|
||||||
|
import mage.abilities.condition.common.HellbentCondition;
|
||||||
|
import mage.abilities.condition.common.SolvedSourceCondition;
|
||||||
|
import mage.abilities.decorator.ConditionalTriggeredAbility;
|
||||||
|
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
|
||||||
|
import mage.abilities.effects.common.discard.DiscardControllerEffect;
|
||||||
|
import mage.abilities.effects.common.discard.DiscardHandControllerEffect;
|
||||||
|
import mage.abilities.hint.common.CaseSolvedHint;
|
||||||
|
import mage.constants.SubType;
|
||||||
|
import mage.cards.CardImpl;
|
||||||
|
import mage.cards.CardSetInfo;
|
||||||
|
import mage.constants.CardType;
|
||||||
|
import mage.constants.TargetController;
|
||||||
|
import mage.game.Game;
|
||||||
|
import mage.players.Player;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Case of the Crimson Pulse {2}{R}
|
||||||
|
* Enchantment - Case
|
||||||
|
* When this Case enters the battlefield, discard a card, then draw two cards.
|
||||||
|
* To solve -- You have no cards in hand.
|
||||||
|
* Solved -- At the beginning of your upkeep, discard your hand, then draw two cards.
|
||||||
|
*
|
||||||
|
* @author DominionSpy
|
||||||
|
*/
|
||||||
|
public final class CaseOfTheCrimsonPulse extends CardImpl {
|
||||||
|
|
||||||
|
public CaseOfTheCrimsonPulse(UUID ownerId, CardSetInfo setInfo) {
|
||||||
|
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{R}");
|
||||||
|
|
||||||
|
this.subtype.add(SubType.CASE);
|
||||||
|
|
||||||
|
// When this Case enters the battlefield, discard a card, then draw two cards.
|
||||||
|
Ability initialAbility = new EntersBattlefieldTriggeredAbility(new DiscardControllerEffect(1));
|
||||||
|
initialAbility.addEffect(new DrawCardSourceControllerEffect(2).setText(", then draw two cards."));
|
||||||
|
// To solve -- You have no cards in hand.
|
||||||
|
// Solved -- At the beginning of your upkeep, discard your hand, then draw two cards.
|
||||||
|
Ability solvedAbility = new ConditionalTriggeredAbility(new BeginningOfUpkeepTriggeredAbility(
|
||||||
|
new DiscardHandControllerEffect(), TargetController.YOU, false),
|
||||||
|
SolvedSourceCondition.SOLVED, null);
|
||||||
|
solvedAbility.addEffect(new DrawCardSourceControllerEffect(2).concatBy(", then"));
|
||||||
|
|
||||||
|
this.addAbility(new CaseAbility(initialAbility, HellbentCondition.instance, solvedAbility)
|
||||||
|
.addHint(new CaseOfTheCrimsonPulseHint()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private CaseOfTheCrimsonPulse(final CaseOfTheCrimsonPulse card) {
|
||||||
|
super(card);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CaseOfTheCrimsonPulse copy() {
|
||||||
|
return new CaseOfTheCrimsonPulse(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CaseOfTheCrimsonPulseHint extends CaseSolvedHint {
|
||||||
|
|
||||||
|
CaseOfTheCrimsonPulseHint() {
|
||||||
|
super(HellbentCondition.instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CaseOfTheCrimsonPulseHint(final CaseOfTheCrimsonPulseHint hint) {
|
||||||
|
super(hint);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CaseOfTheCrimsonPulseHint copy() {
|
||||||
|
return new CaseOfTheCrimsonPulseHint(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getConditionText(Game game, Ability ability) {
|
||||||
|
Player controller = game.getPlayer(ability.getControllerId());
|
||||||
|
if (controller == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
int handSize = controller.getHand().size();
|
||||||
|
return "Cards in hand: " + handSize + " (need 0).";
|
||||||
|
}
|
||||||
|
}
|
||||||
120
Mage.Sets/src/mage/cards/c/CaseOfTheFilchedFalcon.java
Normal file
120
Mage.Sets/src/mage/cards/c/CaseOfTheFilchedFalcon.java
Normal file
|
|
@ -0,0 +1,120 @@
|
||||||
|
package mage.cards.c;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import mage.MageInt;
|
||||||
|
import mage.abilities.Ability;
|
||||||
|
import mage.abilities.common.CaseAbility;
|
||||||
|
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
|
||||||
|
import mage.abilities.condition.Condition;
|
||||||
|
import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition;
|
||||||
|
import mage.abilities.condition.common.SolvedSourceCondition;
|
||||||
|
import mage.abilities.costs.common.SacrificeSourceCost;
|
||||||
|
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||||
|
import mage.abilities.decorator.ConditionalActivatedAbility;
|
||||||
|
import mage.abilities.effects.common.continuous.BecomesCreatureTargetEffect;
|
||||||
|
import mage.abilities.effects.common.counter.AddCountersTargetEffect;
|
||||||
|
import mage.abilities.effects.keyword.InvestigateEffect;
|
||||||
|
import mage.abilities.hint.common.CaseSolvedHint;
|
||||||
|
import mage.abilities.keyword.FlyingAbility;
|
||||||
|
import mage.constants.ComparisonType;
|
||||||
|
import mage.constants.Duration;
|
||||||
|
import mage.constants.SubType;
|
||||||
|
import mage.cards.CardImpl;
|
||||||
|
import mage.cards.CardSetInfo;
|
||||||
|
import mage.constants.CardType;
|
||||||
|
import mage.counters.CounterType;
|
||||||
|
import mage.filter.FilterPermanent;
|
||||||
|
import mage.filter.StaticFilters;
|
||||||
|
import mage.filter.common.FilterArtifactPermanent;
|
||||||
|
import mage.game.Game;
|
||||||
|
import mage.game.permanent.token.TokenImpl;
|
||||||
|
import mage.target.TargetPermanent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author DominionSpy
|
||||||
|
*/
|
||||||
|
public final class CaseOfTheFilchedFalcon extends CardImpl {
|
||||||
|
|
||||||
|
private static final FilterPermanent filter = new FilterArtifactPermanent("You control three or more artifacts");
|
||||||
|
|
||||||
|
public CaseOfTheFilchedFalcon(UUID ownerId, CardSetInfo setInfo) {
|
||||||
|
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{U}");
|
||||||
|
|
||||||
|
this.subtype.add(SubType.CASE);
|
||||||
|
|
||||||
|
// When this Case enters the battlefield, investigate.
|
||||||
|
Ability initialAbility = new EntersBattlefieldTriggeredAbility(new InvestigateEffect());
|
||||||
|
// To solve -- You control three or more artifacts.
|
||||||
|
Condition toSolveCondition = new PermanentsOnTheBattlefieldCondition(
|
||||||
|
filter, ComparisonType.MORE_THAN, 2, true);
|
||||||
|
// Solved -- {2}{U}, Sacrifice this Case: Put four +1/+1 counters on target noncreature artifact. It becomes a 0/0 Bird creature with flying in addition to its other types.
|
||||||
|
Ability solvedAbility = new ConditionalActivatedAbility(
|
||||||
|
new AddCountersTargetEffect(CounterType.P1P1.createInstance(4)),
|
||||||
|
new ManaCostsImpl<>("{2}{U}"), SolvedSourceCondition.SOLVED);
|
||||||
|
solvedAbility.addEffect(new BecomesCreatureTargetEffect(new CaseOfTheFilchedFalconToken(),
|
||||||
|
false, false, Duration.WhileOnBattlefield)
|
||||||
|
.setText("It becomes a 0/0 Bird creature with flying in addition to its other types"));
|
||||||
|
solvedAbility.addCost(new SacrificeSourceCost().setText("sacrifice this Case"));
|
||||||
|
solvedAbility.addTarget(new TargetPermanent(StaticFilters.FILTER_ARTIFACT_NON_CREATURE));
|
||||||
|
|
||||||
|
this.addAbility(new CaseAbility(initialAbility, toSolveCondition, solvedAbility)
|
||||||
|
.addHint(new CaseOfTheFilchedFalconHint(toSolveCondition)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private CaseOfTheFilchedFalcon(final CaseOfTheFilchedFalcon card) {
|
||||||
|
super(card);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CaseOfTheFilchedFalcon copy() {
|
||||||
|
return new CaseOfTheFilchedFalcon(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CaseOfTheFilchedFalconHint extends CaseSolvedHint {
|
||||||
|
|
||||||
|
CaseOfTheFilchedFalconHint(Condition condition) {
|
||||||
|
super(condition);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CaseOfTheFilchedFalconHint(final CaseOfTheFilchedFalconHint hint) {
|
||||||
|
super(hint);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CaseOfTheFilchedFalconHint copy() {
|
||||||
|
return new CaseOfTheFilchedFalconHint(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getConditionText(Game game, Ability ability) {
|
||||||
|
int artifacts = game.getBattlefield()
|
||||||
|
.count(StaticFilters.FILTER_CONTROLLED_PERMANENT_ARTIFACT, ability.getControllerId(),
|
||||||
|
ability, game);
|
||||||
|
return "Artifacts: " + artifacts + " (need 3).";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CaseOfTheFilchedFalconToken extends TokenImpl {
|
||||||
|
|
||||||
|
public CaseOfTheFilchedFalconToken() {
|
||||||
|
super("", "0/0 Bird creature with flying");
|
||||||
|
this.cardType.add(CardType.CREATURE);
|
||||||
|
|
||||||
|
this.subtype.add(SubType.BIRD);
|
||||||
|
this.power = new MageInt(0);
|
||||||
|
this.toughness = new MageInt(0);
|
||||||
|
this.addAbility(FlyingAbility.getInstance());
|
||||||
|
}
|
||||||
|
|
||||||
|
private CaseOfTheFilchedFalconToken(final CaseOfTheFilchedFalconToken token) {
|
||||||
|
super(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CaseOfTheFilchedFalconToken copy() {
|
||||||
|
return new CaseOfTheFilchedFalconToken(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
107
Mage.Sets/src/mage/cards/c/CaseOfTheLockedHothouse.java
Normal file
107
Mage.Sets/src/mage/cards/c/CaseOfTheLockedHothouse.java
Normal file
|
|
@ -0,0 +1,107 @@
|
||||||
|
package mage.cards.c;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import mage.abilities.Ability;
|
||||||
|
import mage.abilities.common.CaseAbility;
|
||||||
|
import mage.abilities.common.SimpleStaticAbility;
|
||||||
|
import mage.abilities.condition.Condition;
|
||||||
|
import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition;
|
||||||
|
import mage.abilities.condition.common.SolvedSourceCondition;
|
||||||
|
import mage.abilities.decorator.ConditionalAsThoughEffect;
|
||||||
|
import mage.abilities.decorator.ConditionalContinuousEffect;
|
||||||
|
import mage.abilities.effects.common.continuous.LookAtTopCardOfLibraryAnyTimeEffect;
|
||||||
|
import mage.abilities.effects.common.continuous.PlayAdditionalLandsControllerEffect;
|
||||||
|
import mage.abilities.effects.common.continuous.PlayTheTopCardEffect;
|
||||||
|
import mage.abilities.hint.common.CaseSolvedHint;
|
||||||
|
import mage.constants.ComparisonType;
|
||||||
|
import mage.constants.Duration;
|
||||||
|
import mage.constants.SubType;
|
||||||
|
import mage.cards.CardImpl;
|
||||||
|
import mage.cards.CardSetInfo;
|
||||||
|
import mage.constants.CardType;
|
||||||
|
import mage.constants.TargetController;
|
||||||
|
import mage.filter.FilterCard;
|
||||||
|
import mage.filter.FilterPermanent;
|
||||||
|
import mage.filter.StaticFilters;
|
||||||
|
import mage.filter.common.FilterLandPermanent;
|
||||||
|
import mage.filter.predicate.Predicates;
|
||||||
|
import mage.game.Game;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Case of the Locked Hothouse {3}{G}
|
||||||
|
* Enchantment - Case
|
||||||
|
* You may play an additional land on each of your turns.
|
||||||
|
* To solve -- You control seven or more lands.
|
||||||
|
* Solved -- You may look at the top card of your library any time, and you may play lands and cast creature and enchantment spells from the top of your library.
|
||||||
|
*
|
||||||
|
* @author DominionSpy
|
||||||
|
*/
|
||||||
|
public final class CaseOfTheLockedHothouse extends CardImpl {
|
||||||
|
|
||||||
|
private static final FilterPermanent filter = new FilterLandPermanent("You control seven or more lands");
|
||||||
|
private static final FilterCard filter2 = new FilterCard("play lands and cast creature and enchantment spells");
|
||||||
|
|
||||||
|
static {
|
||||||
|
filter2.add(Predicates.or(
|
||||||
|
CardType.LAND.getPredicate(),
|
||||||
|
CardType.CREATURE.getPredicate(),
|
||||||
|
CardType.ENCHANTMENT.getPredicate()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
public CaseOfTheLockedHothouse(UUID ownerId, CardSetInfo setInfo) {
|
||||||
|
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{G}");
|
||||||
|
|
||||||
|
this.subtype.add(SubType.CASE);
|
||||||
|
|
||||||
|
// You may play an additional land on each of your turns.
|
||||||
|
Ability initialAbility = new SimpleStaticAbility(new PlayAdditionalLandsControllerEffect(1, Duration.WhileOnBattlefield));
|
||||||
|
// To solve -- You control seven or more lands.
|
||||||
|
Condition toSolveCondition = new PermanentsOnTheBattlefieldCondition(
|
||||||
|
filter, ComparisonType.MORE_THAN, 6, true);
|
||||||
|
// Solved -- You may look at the top card of your library any time, and you may play lands and cast creature and enchantment spells from the top of your library.
|
||||||
|
Ability solvedAbility = new SimpleStaticAbility(new ConditionalContinuousEffect(
|
||||||
|
new LookAtTopCardOfLibraryAnyTimeEffect(), SolvedSourceCondition.SOLVED, ""));
|
||||||
|
solvedAbility.addEffect(new ConditionalAsThoughEffect(
|
||||||
|
new PlayTheTopCardEffect(TargetController.YOU, filter2, false),
|
||||||
|
SolvedSourceCondition.SOLVED)
|
||||||
|
.setText(", and you may play lands and cast creature and enchantment spells from the top of your library."));
|
||||||
|
|
||||||
|
this.addAbility(new CaseAbility(initialAbility, toSolveCondition, solvedAbility)
|
||||||
|
.addHint(new CaseOfTheLockedHothouseHint(toSolveCondition)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private CaseOfTheLockedHothouse(final CaseOfTheLockedHothouse card) {
|
||||||
|
super(card);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CaseOfTheLockedHothouse copy() {
|
||||||
|
return new CaseOfTheLockedHothouse(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CaseOfTheLockedHothouseHint extends CaseSolvedHint {
|
||||||
|
|
||||||
|
CaseOfTheLockedHothouseHint(Condition condition) {
|
||||||
|
super(condition);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CaseOfTheLockedHothouseHint(final CaseOfTheLockedHothouseHint hint) {
|
||||||
|
super(hint);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CaseOfTheLockedHothouseHint copy() {
|
||||||
|
return new CaseOfTheLockedHothouseHint(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getConditionText(Game game, Ability ability) {
|
||||||
|
int lands = game.getBattlefield()
|
||||||
|
.count(StaticFilters.FILTER_CONTROLLED_PERMANENT_LAND, ability.getControllerId(),
|
||||||
|
ability, game);
|
||||||
|
return "Lands: " + lands + " (need 7).";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -47,6 +47,10 @@ public final class MurdersAtKarlovManor extends ExpansionSet {
|
||||||
cards.add(new SetCardInfo("Branch of Vitu-Ghazi", 258, Rarity.UNCOMMON, mage.cards.b.BranchOfVituGhazi.class));
|
cards.add(new SetCardInfo("Branch of Vitu-Ghazi", 258, Rarity.UNCOMMON, mage.cards.b.BranchOfVituGhazi.class));
|
||||||
cards.add(new SetCardInfo("Burden of Proof", 42, Rarity.UNCOMMON, mage.cards.b.BurdenOfProof.class));
|
cards.add(new SetCardInfo("Burden of Proof", 42, Rarity.UNCOMMON, mage.cards.b.BurdenOfProof.class));
|
||||||
cards.add(new SetCardInfo("Candlestick", 43, Rarity.UNCOMMON, mage.cards.c.Candlestick.class));
|
cards.add(new SetCardInfo("Candlestick", 43, Rarity.UNCOMMON, mage.cards.c.Candlestick.class));
|
||||||
|
cards.add(new SetCardInfo("Case of the Burning Masks", 113, Rarity.UNCOMMON, mage.cards.c.CaseOfTheBurningMasks.class));
|
||||||
|
cards.add(new SetCardInfo("Case of the Crimson Pulse", 114, Rarity.RARE, mage.cards.c.CaseOfTheCrimsonPulse.class));
|
||||||
|
cards.add(new SetCardInfo("Case of the Filched Falcon", 44, Rarity.UNCOMMON, mage.cards.c.CaseOfTheFilchedFalcon.class));
|
||||||
|
cards.add(new SetCardInfo("Case of the Locked Hothouse", 155, Rarity.RARE, mage.cards.c.CaseOfTheLockedHothouse.class));
|
||||||
cards.add(new SetCardInfo("Caught Red-Handed", 115, Rarity.UNCOMMON, mage.cards.c.CaughtRedHanded.class));
|
cards.add(new SetCardInfo("Caught Red-Handed", 115, Rarity.UNCOMMON, mage.cards.c.CaughtRedHanded.class));
|
||||||
cards.add(new SetCardInfo("Cease // Desist", 246, Rarity.UNCOMMON, mage.cards.c.CeaseDesist.class));
|
cards.add(new SetCardInfo("Cease // Desist", 246, Rarity.UNCOMMON, mage.cards.c.CeaseDesist.class));
|
||||||
cards.add(new SetCardInfo("Cerebral Confiscation", 81, Rarity.COMMON, mage.cards.c.CerebralConfiscation.class));
|
cards.add(new SetCardInfo("Cerebral Confiscation", 81, Rarity.COMMON, mage.cards.c.CerebralConfiscation.class));
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,102 @@
|
||||||
|
package org.mage.test.cards.enchantments;
|
||||||
|
|
||||||
|
import mage.constants.PhaseStep;
|
||||||
|
import mage.constants.Zone;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||||
|
|
||||||
|
public class CaseTest extends CardTestPlayerBase {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_CaseOfTheBurningMasks() {
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 6);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Aminatou, the Fateshifter");
|
||||||
|
addCard(Zone.HAND, playerA, "Case of the Burning Masks");
|
||||||
|
addCard(Zone.HAND, playerA, "Lightning Bolt", 3);
|
||||||
|
addCard(Zone.HAND, playerA, "Impact Tremors");
|
||||||
|
addCard(Zone.HAND, playerA, "Goblin Chainwhirler");
|
||||||
|
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerB);
|
||||||
|
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Case of the Burning Masks");
|
||||||
|
|
||||||
|
checkStackSize("case is not solved", 1, PhaseStep.END_TURN, playerA, 0);
|
||||||
|
|
||||||
|
castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerB);
|
||||||
|
waitStackResolved(3, PhaseStep.PRECOMBAT_MAIN, playerA);
|
||||||
|
castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Impact Tremors");
|
||||||
|
waitStackResolved(3, PhaseStep.PRECOMBAT_MAIN, playerA);
|
||||||
|
castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Goblin Chainwhirler");
|
||||||
|
setChoice(playerA, "Whenever"); // Choose trigger order
|
||||||
|
|
||||||
|
checkStackObject("case is solved", 3, PhaseStep.END_TURN, playerA, "<i>To solve", 1);
|
||||||
|
|
||||||
|
// Activate Aminatou, the Fateshifter ability to put Lightning Bolt on top of library
|
||||||
|
activateAbility(5, PhaseStep.PRECOMBAT_MAIN, playerA, "+1");
|
||||||
|
addTarget(playerA, "Lightning Bolt");
|
||||||
|
waitStackResolved(5, PhaseStep.PRECOMBAT_MAIN, playerA);
|
||||||
|
// Activate Case of the Burning Masks "Solved" ability
|
||||||
|
activateAbility(5, PhaseStep.PRECOMBAT_MAIN, playerA, "<i>Solved");
|
||||||
|
setChoice(playerA, "Lightning Bolt");
|
||||||
|
waitStackResolved(5, PhaseStep.PRECOMBAT_MAIN, playerA);
|
||||||
|
|
||||||
|
castSpell(5, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerB);
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
setStopAt(5, PhaseStep.END_TURN);
|
||||||
|
execute();
|
||||||
|
|
||||||
|
assertPermanentCount(playerA, "Case of the Burning Masks", 0);
|
||||||
|
assertLife(playerB, 9);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_CaseOfTheCrimsonPulse() {
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Badlands", 3 + 7);
|
||||||
|
addCard(Zone.HAND, playerA, "Mountain");
|
||||||
|
addCard(Zone.HAND, playerA, "Case of the Crimson Pulse");
|
||||||
|
addCard(Zone.HAND, playerA, "Wit's End");
|
||||||
|
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Case of the Crimson Pulse");
|
||||||
|
setChoice(playerA, "Mountain");
|
||||||
|
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||||
|
|
||||||
|
checkGraveyardCount("mountain in graveyard", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Mountain", 1);
|
||||||
|
checkStackSize("case is not solved", 1, PhaseStep.END_TURN, playerA, 0);
|
||||||
|
|
||||||
|
castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Wit's End", playerA);
|
||||||
|
|
||||||
|
checkStackObject("case is solved", 3, PhaseStep.END_TURN, playerA, "<i>To solve", 1);
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
setStopAt(5, PhaseStep.UPKEEP);
|
||||||
|
execute();
|
||||||
|
|
||||||
|
assertHandCount(playerA, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_CaseOfTheLockedHothouse() {
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Forest", 5);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Case of the Locked Hothouse");
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Llanowar Elves");
|
||||||
|
addCard(Zone.HAND, playerA, "Plains", 1);
|
||||||
|
addCard(Zone.HAND, playerA, "Island", 1);
|
||||||
|
addCard(Zone.HAND, playerA, "Griptide");
|
||||||
|
|
||||||
|
checkStackSize("case is not solved", 1, PhaseStep.END_TURN, playerA, 0);
|
||||||
|
|
||||||
|
playLand(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Plains");
|
||||||
|
playLand(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Island");
|
||||||
|
|
||||||
|
checkStackObject("case is solved", 3, PhaseStep.END_TURN, playerA, "<i>To solve", 1);
|
||||||
|
|
||||||
|
castSpell(5, PhaseStep.PRECOMBAT_MAIN, playerA, "Griptide", "Llanowar Elves");
|
||||||
|
waitStackResolved(5, PhaseStep.PRECOMBAT_MAIN);
|
||||||
|
castSpell(5, PhaseStep.PRECOMBAT_MAIN, playerA, "Llanowar Elves");
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
setStopAt(5, PhaseStep.PRECOMBAT_MAIN);
|
||||||
|
execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
166
Mage/src/main/java/mage/abilities/common/CaseAbility.java
Normal file
166
Mage/src/main/java/mage/abilities/common/CaseAbility.java
Normal file
|
|
@ -0,0 +1,166 @@
|
||||||
|
package mage.abilities.common;
|
||||||
|
|
||||||
|
import mage.abilities.Ability;
|
||||||
|
import mage.abilities.condition.CompoundCondition;
|
||||||
|
import mage.abilities.condition.Condition;
|
||||||
|
import mage.abilities.condition.common.SolvedSourceCondition;
|
||||||
|
import mage.abilities.decorator.ConditionalActivatedAbility;
|
||||||
|
import mage.abilities.decorator.ConditionalAsThoughEffect;
|
||||||
|
import mage.abilities.decorator.ConditionalContinuousEffect;
|
||||||
|
import mage.abilities.decorator.ConditionalTriggeredAbility;
|
||||||
|
import mage.abilities.effects.Effect;
|
||||||
|
import mage.abilities.effects.OneShotEffect;
|
||||||
|
import mage.constants.Outcome;
|
||||||
|
import mage.constants.TargetController;
|
||||||
|
import mage.constants.Zone;
|
||||||
|
import mage.game.Game;
|
||||||
|
import mage.game.permanent.Permanent;
|
||||||
|
import mage.util.CardUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Case mechanic was added in Murders at Karlov Manor [MKM].
|
||||||
|
* <ul>
|
||||||
|
* <li>Each Case has two special keyword abilities: to solve and solved.</li>
|
||||||
|
* <li>"To Solve — [condition]" means "At the beginning of your end step,
|
||||||
|
* if [condition] and this Case is not solved, it becomes solved."</li>
|
||||||
|
* <li>The meaning of "solved" differs based on what type of ability follows it.
|
||||||
|
* "Solved — [activated ability]" means "[Activated ability].
|
||||||
|
* Activate only if this Case is solved." Activated abilities contain a colon.
|
||||||
|
* They're generally written "[Cost]: [Effect]."</li>
|
||||||
|
* <li>"Solved — [Triggered ability]" means "[Triggered ability].
|
||||||
|
* This ability triggers only if this Case is solved."
|
||||||
|
* Triggered abilities use the word "when," "whenever," or "at."
|
||||||
|
* They're often written as "[Trigger condition], [effect]."</li>
|
||||||
|
* <li>"Solved — [static ability]" means "As long as this Case is solved, [static ability]."
|
||||||
|
* Static abilities are written as statements, such as "Creatures you control get +1/+1"
|
||||||
|
* or "Instant and sorcery spells you cast cost {1} less to cast."</li>
|
||||||
|
* <li>"To solve" abilities will check for their condition twice:
|
||||||
|
* once when the ability would trigger, and once when it resolves.
|
||||||
|
* If the condition isn't true at the beginning of your end step,
|
||||||
|
* the ability won't trigger at all.
|
||||||
|
* If the condition isn't true when the ability resolves, the Case won't become solved.</li>
|
||||||
|
* <li>Once a Case becomes solved, it stays solved until it leaves the battlefield.</li>
|
||||||
|
* <li>Cases don't lose their other abilities when they become solved.</li>
|
||||||
|
* <li>Being solved is not part of a permanent's copiable values.
|
||||||
|
* A permanent that becomes a copy of a solved Case is not solved.
|
||||||
|
* A solved Case that somehow becomes a copy of a different Case stays solved.</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @author DominionSpy
|
||||||
|
*/
|
||||||
|
public class CaseAbility extends SimpleStaticAbility {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a Case with three abilities:
|
||||||
|
* <ul>
|
||||||
|
* <li>A initial ability the Case has at all times</li>
|
||||||
|
* <li>A "To solve" ability that will conditionally solve the Case
|
||||||
|
* at the beginning of the controller's end step</li>
|
||||||
|
* <li>A "Solved" ability the Case has when solved</li>
|
||||||
|
* </ul>
|
||||||
|
* The "Solved" ability must be one of the following:
|
||||||
|
* <ul>
|
||||||
|
* <li>{@link ConditionalActivatedAbility} using the condition {@link SolvedSourceCondition}.SOLVED</li>
|
||||||
|
* <li>{@link ConditionalTriggeredAbility} using the condition {@link SolvedSourceCondition}.SOLVED</li>
|
||||||
|
* <li>{@link SimpleStaticAbility} with only {@link ConditionalAsThoughEffect} or {@link ConditionalContinuousEffect} effects</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @param initialAbility The ability that a Case has at all times
|
||||||
|
* @param toSolveCondition The condition to be checked when solving
|
||||||
|
* @param solvedAbility The ability that a solved Case has
|
||||||
|
*/
|
||||||
|
public CaseAbility(Ability initialAbility, Condition toSolveCondition, Ability solvedAbility) {
|
||||||
|
super(Zone.ALL, null);
|
||||||
|
|
||||||
|
if (initialAbility instanceof EntersBattlefieldTriggeredAbility) {
|
||||||
|
((EntersBattlefieldTriggeredAbility) initialAbility).setTriggerPhrase("When this Case enters the battlefield, ");
|
||||||
|
}
|
||||||
|
addSubAbility(initialAbility);
|
||||||
|
|
||||||
|
addSubAbility(new CaseSolveAbility(toSolveCondition));
|
||||||
|
|
||||||
|
if (solvedAbility instanceof ConditionalActivatedAbility) {
|
||||||
|
((ConditionalActivatedAbility) solvedAbility).hideCondition();
|
||||||
|
} else if (!(solvedAbility instanceof ConditionalTriggeredAbility)) {
|
||||||
|
if (solvedAbility instanceof SimpleStaticAbility) {
|
||||||
|
for (Effect effect : solvedAbility.getEffects()) {
|
||||||
|
if (!(effect instanceof ConditionalContinuousEffect ||
|
||||||
|
effect instanceof ConditionalAsThoughEffect)) {
|
||||||
|
throw new IllegalArgumentException("solvedAbility must be one of ConditionalActivatedAbility, " +
|
||||||
|
"ConditionalTriggeredAbility, or StaticAbility with conditional effects.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("solvedAbility must be one of ConditionalActivatedAbility, " +
|
||||||
|
"ConditionalTriggeredAbility, or StaticAbility with conditional effects.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addSubAbility(solvedAbility.withFlavorWord("Solved")); // TODO: Technically this shouldn't be italicized
|
||||||
|
}
|
||||||
|
|
||||||
|
protected CaseAbility(final CaseAbility ability) {
|
||||||
|
super(ability);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CaseAbility copy() {
|
||||||
|
return new CaseAbility(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class CaseSolveAbility extends BeginningOfEndStepTriggeredAbility {
|
||||||
|
|
||||||
|
CaseSolveAbility(Condition condition) {
|
||||||
|
super(new SolveEffect(), TargetController.YOU,
|
||||||
|
new CompoundCondition(condition, SolvedSourceCondition.UNSOLVED), false);
|
||||||
|
withFlavorWord("To solve"); // TODO: technically this shouldn't be italicized
|
||||||
|
setTriggerPhrase(CardUtil.getTextWithFirstCharUpperCase(trimIf(condition.toString())));
|
||||||
|
}
|
||||||
|
|
||||||
|
private CaseSolveAbility(final CaseSolveAbility ability) {
|
||||||
|
super(ability);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CaseSolveAbility copy() {
|
||||||
|
return new CaseSolveAbility(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getRule() {
|
||||||
|
return super.getRule() + ". <i>(If unsolved, solve at the beginning of your end step.)</i>";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String trimIf(String text) {
|
||||||
|
if (text.startsWith("if ")) {
|
||||||
|
return text.substring(3);
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SolveEffect extends OneShotEffect {
|
||||||
|
|
||||||
|
SolveEffect() {
|
||||||
|
super(Outcome.Benefit);
|
||||||
|
}
|
||||||
|
|
||||||
|
private SolveEffect(final SolveEffect effect) {
|
||||||
|
super(effect);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SolveEffect copy() {
|
||||||
|
return new SolveEffect(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean apply(Game game, Ability source) {
|
||||||
|
Permanent permanent = source.getSourcePermanentIfItStillExists(game);
|
||||||
|
if (permanent == null || permanent.isSolved()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return permanent.solve(game, source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
package mage.abilities.condition.common;
|
||||||
|
|
||||||
|
import mage.abilities.Ability;
|
||||||
|
import mage.abilities.condition.Condition;
|
||||||
|
import mage.game.Game;
|
||||||
|
import mage.game.permanent.Permanent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a Permanent is solved
|
||||||
|
*
|
||||||
|
* @author DominionSpy
|
||||||
|
*/
|
||||||
|
public enum SolvedSourceCondition implements Condition {
|
||||||
|
SOLVED(true),
|
||||||
|
UNSOLVED(false);
|
||||||
|
private final boolean solved;
|
||||||
|
|
||||||
|
SolvedSourceCondition(boolean solved) {
|
||||||
|
this.solved = solved;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean apply(Game game, Ability source) {
|
||||||
|
Permanent permanent = game.getPermanent(source.getSourceId());
|
||||||
|
return permanent != null && permanent.isSolved() == solved;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "{this} is " + (solved ? "solved" : "unsolved");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -18,6 +18,7 @@ public class ConditionalActivatedAbility extends ActivatedAbilityImpl {
|
||||||
private static final Effects emptyEffects = new Effects();
|
private static final Effects emptyEffects = new Effects();
|
||||||
|
|
||||||
private String ruleText = null;
|
private String ruleText = null;
|
||||||
|
private boolean showCondition = true;
|
||||||
|
|
||||||
public ConditionalActivatedAbility(Effect effect, Cost cost, Condition condition) {
|
public ConditionalActivatedAbility(Effect effect, Cost cost, Condition condition) {
|
||||||
this(Zone.BATTLEFIELD, effect, cost, condition);
|
this(Zone.BATTLEFIELD, effect, cost, condition);
|
||||||
|
|
@ -36,6 +37,7 @@ public class ConditionalActivatedAbility extends ActivatedAbilityImpl {
|
||||||
protected ConditionalActivatedAbility(final ConditionalActivatedAbility ability) {
|
protected ConditionalActivatedAbility(final ConditionalActivatedAbility ability) {
|
||||||
super(ability);
|
super(ability);
|
||||||
this.ruleText = ability.ruleText;
|
this.ruleText = ability.ruleText;
|
||||||
|
this.showCondition = ability.showCondition;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -51,12 +53,18 @@ public class ConditionalActivatedAbility extends ActivatedAbilityImpl {
|
||||||
return new ConditionalActivatedAbility(this);
|
return new ConditionalActivatedAbility(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ConditionalActivatedAbility hideCondition() {
|
||||||
|
this.showCondition = false;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getRule() {
|
public String getRule() {
|
||||||
if (ruleText != null && !ruleText.isEmpty()) {
|
if (ruleText != null && !ruleText.isEmpty()) {
|
||||||
return ruleText;
|
return ruleText;
|
||||||
}
|
}
|
||||||
StringBuilder sb = new StringBuilder(super.getRule());
|
StringBuilder sb = new StringBuilder(super.getRule());
|
||||||
|
if (showCondition) {
|
||||||
sb.append(" Activate only ");
|
sb.append(" Activate only ");
|
||||||
if (timing == TimingRule.SORCERY) {
|
if (timing == TimingRule.SORCERY) {
|
||||||
sb.append("as a sorcery and only ");
|
sb.append("as a sorcery and only ");
|
||||||
|
|
@ -67,6 +75,7 @@ public class ConditionalActivatedAbility extends ActivatedAbilityImpl {
|
||||||
}
|
}
|
||||||
sb.append(conditionText);
|
sb.append(conditionText);
|
||||||
sb.append('.');
|
sb.append('.');
|
||||||
|
}
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package mage.abilities.decorator;
|
package mage.abilities.decorator;
|
||||||
|
|
||||||
|
import mage.abilities.Ability;
|
||||||
import mage.abilities.Modes;
|
import mage.abilities.Modes;
|
||||||
import mage.abilities.TriggeredAbility;
|
import mage.abilities.TriggeredAbility;
|
||||||
import mage.abilities.TriggeredAbilityImpl;
|
import mage.abilities.TriggeredAbilityImpl;
|
||||||
|
|
@ -108,4 +109,10 @@ public class ConditionalTriggeredAbility extends TriggeredAbilityImpl {
|
||||||
return ability.isOptional();
|
return ability.isOptional();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Ability withFlavorWord(String flavorWord) {
|
||||||
|
ability.withFlavorWord(flavorWord);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,12 @@ import java.awt.*;
|
||||||
*/
|
*/
|
||||||
public class ConditionHint implements Hint {
|
public class ConditionHint implements Hint {
|
||||||
|
|
||||||
private Condition condition;
|
private final Condition condition;
|
||||||
private String trueText;
|
private final String trueText;
|
||||||
private Color trueColor;
|
private final Color trueColor;
|
||||||
private String falseText;
|
private final String falseText;
|
||||||
private Color falseColor;
|
private final Color falseColor;
|
||||||
private Boolean useIcons;
|
private final boolean useIcons;
|
||||||
|
|
||||||
public ConditionHint(Condition condition) {
|
public ConditionHint(Condition condition) {
|
||||||
this(condition, condition.toString());
|
this(condition, condition.toString());
|
||||||
|
|
@ -27,7 +27,7 @@ public class ConditionHint implements Hint {
|
||||||
this(condition, textWithIcons, null, textWithIcons, null, true);
|
this(condition, textWithIcons, null, textWithIcons, null, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ConditionHint(Condition condition, String trueText, Color trueColor, String falseText, Color falseColor, Boolean useIcons) {
|
public ConditionHint(Condition condition, String trueText, Color trueColor, String falseText, Color falseColor, boolean useIcons) {
|
||||||
this.condition = condition;
|
this.condition = condition;
|
||||||
this.trueText = CardUtil.getTextWithFirstCharUpperCase(trueText);
|
this.trueText = CardUtil.getTextWithFirstCharUpperCase(trueText);
|
||||||
this.trueColor = trueColor;
|
this.trueColor = trueColor;
|
||||||
|
|
@ -58,7 +58,7 @@ public class ConditionHint implements Hint {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Hint copy() {
|
public ConditionHint copy() {
|
||||||
return new ConditionHint(this);
|
return new ConditionHint(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
package mage.abilities.hint.common;
|
||||||
|
|
||||||
|
import mage.abilities.Ability;
|
||||||
|
import mage.abilities.condition.Condition;
|
||||||
|
import mage.abilities.condition.common.SolvedSourceCondition;
|
||||||
|
import mage.abilities.hint.ConditionHint;
|
||||||
|
import mage.game.Game;
|
||||||
|
import mage.game.permanent.Permanent;
|
||||||
|
|
||||||
|
public class CaseSolvedHint extends ConditionHint {
|
||||||
|
|
||||||
|
private final Condition condition;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hint for use with CaseAbility
|
||||||
|
* @param condition Same condition added to CaseAbility
|
||||||
|
*/
|
||||||
|
public CaseSolvedHint(Condition condition) {
|
||||||
|
super(SolvedSourceCondition.SOLVED, "Case is solved.", null, "Case is unsolved.", null, true);
|
||||||
|
this.condition = condition;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected CaseSolvedHint(final CaseSolvedHint hint) {
|
||||||
|
super(hint);
|
||||||
|
this.condition = hint.condition;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getText(Game game, Ability ability) {
|
||||||
|
Permanent permanent = game.getPermanent(ability.getSourceId());
|
||||||
|
if (permanent == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
String text = super.getText(game, ability);
|
||||||
|
if (!permanent.isSolved()) {
|
||||||
|
text += " " + getConditionText(game, ability);
|
||||||
|
if (condition.apply(game, ability) && game.isActivePlayer(ability.getControllerId())) {
|
||||||
|
text += " Case will be solved at the end step.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override to add specific information on satisfying the condition.
|
||||||
|
*/
|
||||||
|
protected String getConditionText(Game game, Ability ability) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,7 +3,6 @@ package mage.abilities.hint.common;
|
||||||
import mage.abilities.Ability;
|
import mage.abilities.Ability;
|
||||||
import mage.abilities.condition.Condition;
|
import mage.abilities.condition.Condition;
|
||||||
import mage.abilities.hint.ConditionHint;
|
import mage.abilities.hint.ConditionHint;
|
||||||
import mage.abilities.hint.Hint;
|
|
||||||
import mage.game.Game;
|
import mage.game.Game;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
|
|
@ -23,7 +22,7 @@ public class ConditionPermanentHint extends ConditionHint {
|
||||||
super(condition, textWithIcons);
|
super(condition, textWithIcons);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ConditionPermanentHint(Condition condition, String trueText, Color trueColor, String falseText, Color falseColor, Boolean useIcons) {
|
public ConditionPermanentHint(Condition condition, String trueText, Color trueColor, String falseText, Color falseColor, boolean useIcons) {
|
||||||
super(condition, trueText, trueColor, falseText, falseColor, useIcons);
|
super(condition, trueText, trueColor, falseText, falseColor, useIcons);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -36,12 +35,11 @@ public class ConditionPermanentHint extends ConditionHint {
|
||||||
if (game.getPermanent(ability.getSourceId()) == null) {
|
if (game.getPermanent(ability.getSourceId()) == null) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.getText(game, ability);
|
return super.getText(game, ability);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Hint copy() {
|
public ConditionPermanentHint copy() {
|
||||||
return new ConditionPermanentHint(this);
|
return new ConditionPermanentHint(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ public enum SubType {
|
||||||
AURA("Aura", SubTypeSet.EnchantmentType),
|
AURA("Aura", SubTypeSet.EnchantmentType),
|
||||||
BACKGROUND("Background", SubTypeSet.EnchantmentType),
|
BACKGROUND("Background", SubTypeSet.EnchantmentType),
|
||||||
CARTOUCHE("Cartouche", SubTypeSet.EnchantmentType),
|
CARTOUCHE("Cartouche", SubTypeSet.EnchantmentType),
|
||||||
|
CASE("Case", SubTypeSet.EnchantmentType),
|
||||||
CLASS("Class", SubTypeSet.EnchantmentType),
|
CLASS("Class", SubTypeSet.EnchantmentType),
|
||||||
CURSE("Curse", SubTypeSet.EnchantmentType),
|
CURSE("Curse", SubTypeSet.EnchantmentType),
|
||||||
ROLE("Role", SubTypeSet.EnchantmentType),
|
ROLE("Role", SubTypeSet.EnchantmentType),
|
||||||
|
|
|
||||||
|
|
@ -567,6 +567,12 @@ public class GameEvent implements Serializable {
|
||||||
playerId the player crafting
|
playerId the player crafting
|
||||||
*/
|
*/
|
||||||
EXILED_WHILE_CRAFTING,
|
EXILED_WHILE_CRAFTING,
|
||||||
|
/* Solving a Case
|
||||||
|
targetId the permanent being solved
|
||||||
|
sourceId of the ability solving
|
||||||
|
playerId the player solving
|
||||||
|
*/
|
||||||
|
SOLVE_CASE, CASE_SOLVED,
|
||||||
/* Become suspected
|
/* Become suspected
|
||||||
targetId the permanent being suspected
|
targetId the permanent being suspected
|
||||||
sourceId of the ability suspecting
|
sourceId of the ability suspecting
|
||||||
|
|
|
||||||
|
|
@ -446,6 +446,10 @@ public interface Permanent extends Card, Controllable {
|
||||||
|
|
||||||
void setRingBearer(Game game, boolean value);
|
void setRingBearer(Game game, boolean value);
|
||||||
|
|
||||||
|
boolean isSolved();
|
||||||
|
|
||||||
|
boolean solve(Game game, Ability source);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
Permanent copy();
|
Permanent copy();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
||||||
// maximal number of creatures the creature can be blocked by 0 = no restriction
|
// maximal number of creatures the creature can be blocked by 0 = no restriction
|
||||||
protected int maxBlockedBy = 0;
|
protected int maxBlockedBy = 0;
|
||||||
protected boolean deathtouched;
|
protected boolean deathtouched;
|
||||||
|
protected boolean solved = false;
|
||||||
|
|
||||||
protected Map<String, List<UUID>> connectedCards = new HashMap<>();
|
protected Map<String, List<UUID>> connectedCards = new HashMap<>();
|
||||||
protected Set<MageObjectReference> dealtDamageByThisTurn;
|
protected Set<MageObjectReference> dealtDamageByThisTurn;
|
||||||
|
|
@ -145,6 +146,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
||||||
this.blocking = permanent.blocking;
|
this.blocking = permanent.blocking;
|
||||||
this.maxBlocks = permanent.maxBlocks;
|
this.maxBlocks = permanent.maxBlocks;
|
||||||
this.deathtouched = permanent.deathtouched;
|
this.deathtouched = permanent.deathtouched;
|
||||||
|
this.solved = permanent.solved;
|
||||||
this.markedLifelink = permanent.markedLifelink;
|
this.markedLifelink = permanent.markedLifelink;
|
||||||
this.connectedCards = CardUtil.deepCopyObject(permanent.connectedCards);
|
this.connectedCards = CardUtil.deepCopyObject(permanent.connectedCards);
|
||||||
this.dealtDamageByThisTurn = CardUtil.deepCopyObject(permanent.dealtDamageByThisTurn);
|
this.dealtDamageByThisTurn = CardUtil.deepCopyObject(permanent.dealtDamageByThisTurn);
|
||||||
|
|
@ -1913,6 +1915,33 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
||||||
return ringBearerFlag;
|
return ringBearerFlag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSolved() {
|
||||||
|
return solved;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean solve(Game game, Ability source) {
|
||||||
|
if (this.solved) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
GameEvent event = new GameEvent(GameEvent.EventType.SOLVE_CASE, getId(),
|
||||||
|
source, source.getControllerId());
|
||||||
|
if (game.replaceEvent(event)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Player controller = game.getPlayer(source.getControllerId());
|
||||||
|
if (controller != null) {
|
||||||
|
game.informPlayers(controller.getLogName() + " solved " + this.getLogName() +
|
||||||
|
CardUtil.getSourceLogName(game, source));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.solved = true;
|
||||||
|
game.fireEvent(new GameEvent(EventType.CASE_SOLVED, getId(), source,
|
||||||
|
source.getControllerId()));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean fight(Permanent fightTarget, Ability source, Game game) {
|
public boolean fight(Permanent fightTarget, Ability source, Game game) {
|
||||||
return this.fight(fightTarget, source, game, true);
|
return this.fight(fightTarget, source, game, true);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue