forked from External/mage
[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
|
|
@ -370,7 +370,7 @@ public abstract class TriggeredAbilityImpl extends AbilityImpl implements Trigge
|
|||
}
|
||||
|
||||
public static boolean isInUseableZoneDiesTrigger(TriggeredAbility source, GameEvent event, Game game) {
|
||||
// Get the source permanent of the ability
|
||||
// Get the source permanent of the ability
|
||||
MageObject sourceObject = null;
|
||||
if (game.getState().getZone(source.getSourceId()) == Zone.BATTLEFIELD) {
|
||||
sourceObject = game.getPermanent(source.getSourceId());
|
||||
|
|
|
|||
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 String ruleText = null;
|
||||
private boolean showCondition = true;
|
||||
|
||||
public ConditionalActivatedAbility(Effect effect, Cost cost, Condition condition) {
|
||||
this(Zone.BATTLEFIELD, effect, cost, condition);
|
||||
|
|
@ -36,6 +37,7 @@ public class ConditionalActivatedAbility extends ActivatedAbilityImpl {
|
|||
protected ConditionalActivatedAbility(final ConditionalActivatedAbility ability) {
|
||||
super(ability);
|
||||
this.ruleText = ability.ruleText;
|
||||
this.showCondition = ability.showCondition;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -51,22 +53,29 @@ public class ConditionalActivatedAbility extends ActivatedAbilityImpl {
|
|||
return new ConditionalActivatedAbility(this);
|
||||
}
|
||||
|
||||
public ConditionalActivatedAbility hideCondition() {
|
||||
this.showCondition = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRule() {
|
||||
if (ruleText != null && !ruleText.isEmpty()) {
|
||||
return ruleText;
|
||||
}
|
||||
StringBuilder sb = new StringBuilder(super.getRule());
|
||||
sb.append(" Activate only ");
|
||||
if (timing == TimingRule.SORCERY) {
|
||||
sb.append("as a sorcery and only ");
|
||||
if (showCondition) {
|
||||
sb.append(" Activate only ");
|
||||
if (timing == TimingRule.SORCERY) {
|
||||
sb.append("as a sorcery and only ");
|
||||
}
|
||||
String conditionText = condition.toString();
|
||||
if (!conditionText.startsWith("during") && !conditionText.startsWith("before") && !conditionText.startsWith("if")) {
|
||||
sb.append("if ");
|
||||
}
|
||||
sb.append(conditionText);
|
||||
sb.append('.');
|
||||
}
|
||||
String conditionText = condition.toString();
|
||||
if (!conditionText.startsWith("during") && !conditionText.startsWith("before") && !conditionText.startsWith("if")) {
|
||||
sb.append("if ");
|
||||
}
|
||||
sb.append(conditionText);
|
||||
sb.append('.');
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package mage.abilities.decorator;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.Modes;
|
||||
import mage.abilities.TriggeredAbility;
|
||||
import mage.abilities.TriggeredAbilityImpl;
|
||||
|
|
@ -108,4 +109,10 @@ public class ConditionalTriggeredAbility extends TriggeredAbilityImpl {
|
|||
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 {
|
||||
|
||||
private Condition condition;
|
||||
private String trueText;
|
||||
private Color trueColor;
|
||||
private String falseText;
|
||||
private Color falseColor;
|
||||
private Boolean useIcons;
|
||||
private final Condition condition;
|
||||
private final String trueText;
|
||||
private final Color trueColor;
|
||||
private final String falseText;
|
||||
private final Color falseColor;
|
||||
private final boolean useIcons;
|
||||
|
||||
public ConditionHint(Condition condition) {
|
||||
this(condition, condition.toString());
|
||||
|
|
@ -27,7 +27,7 @@ public class ConditionHint implements Hint {
|
|||
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.trueText = CardUtil.getTextWithFirstCharUpperCase(trueText);
|
||||
this.trueColor = trueColor;
|
||||
|
|
@ -58,7 +58,7 @@ public class ConditionHint implements Hint {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Hint copy() {
|
||||
public ConditionHint copy() {
|
||||
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.condition.Condition;
|
||||
import mage.abilities.hint.ConditionHint;
|
||||
import mage.abilities.hint.Hint;
|
||||
import mage.game.Game;
|
||||
|
||||
import java.awt.*;
|
||||
|
|
@ -23,7 +22,7 @@ public class ConditionPermanentHint extends ConditionHint {
|
|||
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);
|
||||
}
|
||||
|
||||
|
|
@ -36,12 +35,11 @@ public class ConditionPermanentHint extends ConditionHint {
|
|||
if (game.getPermanent(ability.getSourceId()) == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return super.getText(game, ability);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Hint copy() {
|
||||
public ConditionPermanentHint copy() {
|
||||
return new ConditionPermanentHint(this);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue