Add Saga mechanic (#1564)
* Add AtBeginOfFirstMainPhaseTrigger and adding lore counter to saga * Make add counter trigger more generic * Change begin main phase trigger to Saga-specific * Add magic.data.RomanToInt * Add chapter parsing to ARG * Add SagaChapterTrigger * Add chapter ability to MagicAbility * Add lore counter mechanic to Saga card definiton * Add Saga sacrifice state-based action * Fix import * Fix getPendingTriggers * Add History of Benalia * Fix adding ETB trigger * Remove AtBeginningOfFirstMainPhaseTrigger.Saga, Add counters directly in MagicMainPhase.executeBeginStep() * Remove null case from RomanToInt * Revert "Remove AtBeginningOfFirstMainPhaseTrigger.Saga, Add counters directly in MagicMainPhase.executeBeginStep()" This reverts commit d8e5d1f253ab7b54f401f4151b44d39ee927f44c. * Move isSaga method to IRenderableCardmaster
parent
fb306e34a8
commit
d213884dc2
|
@ -5,7 +5,7 @@ rarity=M
|
|||
type=Enchantment
|
||||
subtype=Saga
|
||||
cost={1}{W}{W}
|
||||
ability=Create a 2/2 white Knight creature token with vigilance.;\
|
||||
Knights you control get +2/+1 until end of turn.
|
||||
ability=I, II — Create a 2/2 white Knight creature token with vigilance.;\
|
||||
III — Knights you control get +2/+1 until end of turn.
|
||||
timing=enchantment
|
||||
oracle=I, II — Create a 2/2 white Knight creature token with vigilance.\nIII — Knights you control get +2/+1 until end of turn.
|
|
@ -0,0 +1,14 @@
|
|||
package magic.data;
|
||||
|
||||
public class RomanToInt {
|
||||
|
||||
public static int convert(String num) {
|
||||
// Only support 1-3 for now since Sagas only have these numbers
|
||||
switch (num) {
|
||||
case "I": return 1;
|
||||
case "II": return 2;
|
||||
case "III": return 3;
|
||||
default: throw new RuntimeException("unknown roman numeral \"" + num + "\"");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,9 +1,11 @@
|
|||
package magic.model;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import magic.data.EnglishToInt;
|
||||
import magic.data.RomanToInt;
|
||||
import magic.model.action.MagicPlayMod;
|
||||
import magic.model.event.MagicEvent;
|
||||
import magic.model.stack.MagicItemOnStack;
|
||||
|
@ -320,4 +322,11 @@ public class ARG {
|
|||
return MagicTargetFilterFactory.SPELL_OR_ABILITY;
|
||||
}
|
||||
}
|
||||
|
||||
public static final String CHAPTERS = "(?<chapters>I+(, I+)*)";
|
||||
public static int[] chapters(final Matcher m) {
|
||||
final String[] chapters = m.group("chapters").split(", ");
|
||||
return Arrays.stream(chapters).mapToInt(RomanToInt::convert).toArray();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -78,6 +78,10 @@ public interface IRenderableCard {
|
|||
return hasType(MagicType.Sorcery);
|
||||
}
|
||||
|
||||
default boolean isSaga() {
|
||||
return isEnchantment() && hasSubType(MagicSubType.Saga);
|
||||
}
|
||||
|
||||
default boolean isHidden() {
|
||||
return getCardDefinition().isHidden();
|
||||
}
|
||||
|
|
|
@ -639,6 +639,16 @@ public enum MagicAbility {
|
|||
card.add(MagicStatic.Ascend);
|
||||
}
|
||||
},
|
||||
Chapter(ARG.CHAPTERS + " — " + ARG.EFFECT, 0) {
|
||||
@Override
|
||||
protected void addAbilityImpl(final MagicAbilityStore card, final Matcher arg) {
|
||||
final int[] chapters = ARG.chapters(arg);
|
||||
final MagicSourceEvent sourceEvent = MagicRuleEventAction.create(ARG.effect(arg));
|
||||
for (int chapter : chapters) {
|
||||
card.add(SagaChapterTrigger.create(chapter, sourceEvent));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// abilities that involve SN
|
||||
ShockLand("As SN enters the battlefield, you may " + ARG.COST + "\\. If you don't, SN enters the battlefield tapped\\.", -10) {
|
||||
|
|
|
@ -9,6 +9,7 @@ import magic.model.event.*;
|
|||
import magic.model.mstatic.MagicCDA;
|
||||
import magic.model.mstatic.MagicStatic;
|
||||
import magic.model.condition.MagicCondition;
|
||||
import magic.model.trigger.AtBeginOfFirstMainPhaseTrigger;
|
||||
import magic.model.trigger.EntersBattlefieldTrigger;
|
||||
import magic.model.trigger.EntersWithCounterTrigger;
|
||||
import magic.model.trigger.MagicTrigger;
|
||||
|
@ -239,6 +240,10 @@ public class MagicCardDefinition implements MagicAbilityStore, IRenderableCard {
|
|||
startingLoyalty
|
||||
));
|
||||
}
|
||||
if (isSaga() && etbTriggers.isEmpty()) {
|
||||
add(new EntersWithCounterTrigger(MagicCounterType.Lore, 1));
|
||||
add(AtBeginOfFirstMainPhaseTrigger.Saga);
|
||||
}
|
||||
if (requiresGroovy != null) {
|
||||
CardProperty.LOAD_GROOVY_CODE.setProperty(this, requiresGroovy);
|
||||
requiresGroovy = null;
|
||||
|
|
|
@ -20,6 +20,7 @@ import magic.model.action.ChangeCountersAction;
|
|||
import magic.model.action.ChangeStateAction;
|
||||
import magic.model.action.DestroyAction;
|
||||
import magic.model.action.RemoveFromPlayAction;
|
||||
import magic.model.action.SacrificeAction;
|
||||
import magic.model.action.SoulbondAction;
|
||||
import magic.model.choice.MagicTargetChoice;
|
||||
import magic.model.event.MagicActivation;
|
||||
|
@ -883,6 +884,19 @@ public class MagicPermanent extends MagicObjectImpl implements MagicSource, Magi
|
|||
game.addDelayedAction(new RemoveFromPlayAction(this, MagicLocationType.Graveyard));
|
||||
}
|
||||
|
||||
// Only support Sagas with 3 chapters for now.
|
||||
// The comprehensive rules doesn't say that all Sagas must have exactly 3 chapters though.
|
||||
if (isSaga() && getCounters(MagicCounterType.Lore) >= 3 &&
|
||||
game.getStack().stream().noneMatch(item -> item.getSource() == this) &&
|
||||
game.getPendingTriggers().stream().noneMatch(item -> item.getSource() == this)) {
|
||||
|
||||
game.logAppendMessage(
|
||||
getController(),
|
||||
MagicMessage.format("Sacrifice %s.", this)
|
||||
);
|
||||
game.addDelayedAction(new SacrificeAction(this));
|
||||
}
|
||||
|
||||
// +1/+1 and -1/-1 counters cancel each other out.
|
||||
final int plusCounters = getCounters(MagicCounterType.PlusOne);
|
||||
if (plusCounters > 0) {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package magic.model.phase;
|
||||
|
||||
import magic.model.MagicGame;
|
||||
import magic.model.trigger.MagicTriggerType;
|
||||
|
||||
public class MagicMainPhase extends MagicPhase {
|
||||
|
||||
|
@ -21,6 +22,10 @@ public class MagicMainPhase extends MagicPhase {
|
|||
|
||||
@Override
|
||||
public void executeBeginStep(final MagicGame game) {
|
||||
if (this == FIRST_INSTANCE) {
|
||||
game.executeTrigger(MagicTriggerType.AtBeginOfFirstMainPhase,game.getTurnPlayer());
|
||||
}
|
||||
|
||||
game.setStep(MagicStep.ActivePlayer);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
package magic.model.trigger;
|
||||
|
||||
import magic.model.MagicCounterType;
|
||||
import magic.model.MagicGame;
|
||||
import magic.model.MagicPermanent;
|
||||
import magic.model.MagicPlayer;
|
||||
import magic.model.action.ChangeCountersAction;
|
||||
import magic.model.event.MagicEvent;
|
||||
|
||||
public abstract class AtBeginOfFirstMainPhaseTrigger extends MagicTrigger<MagicPlayer> {
|
||||
public AtBeginOfFirstMainPhaseTrigger(final int priority) {
|
||||
super(priority);
|
||||
}
|
||||
|
||||
public AtBeginOfFirstMainPhaseTrigger() {}
|
||||
|
||||
@Override
|
||||
public MagicTriggerType getType() {
|
||||
return MagicTriggerType.AtBeginOfFirstMainPhase;
|
||||
}
|
||||
|
||||
public static final AtBeginOfFirstMainPhaseTrigger Saga = new AtBeginOfFirstMainPhaseTrigger() {
|
||||
@Override
|
||||
public boolean accept(final MagicPermanent permanent, final MagicPlayer player) {
|
||||
return permanent.isController(player);
|
||||
}
|
||||
@Override
|
||||
public MagicEvent executeTrigger(final MagicGame game,final MagicPermanent permanent, final MagicPlayer player) {
|
||||
game.doAction(new ChangeCountersAction(player, permanent, MagicCounterType.Lore, 1));
|
||||
return MagicEvent.NONE;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
|
@ -5,6 +5,7 @@ public enum MagicTriggerType {
|
|||
AtUpkeep, // player
|
||||
AtDraw, // player
|
||||
AtEndOfTurn, // player
|
||||
AtBeginOfFirstMainPhase, // player
|
||||
AtBeginOfCombat, // player
|
||||
AtEndOfCombat, // player
|
||||
WhenDamageIsDealt, // damage
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
package magic.model.trigger;
|
||||
|
||||
import magic.model.MagicCounterType;
|
||||
import magic.model.MagicGame;
|
||||
import magic.model.MagicPermanent;
|
||||
import magic.model.event.MagicEvent;
|
||||
import magic.model.event.MagicSourceEvent;
|
||||
import magic.model.target.MagicTargetFilter;
|
||||
|
||||
public abstract class SagaChapterTrigger extends OneOrMoreCountersArePutTrigger {
|
||||
|
||||
protected int chapter;
|
||||
|
||||
public SagaChapterTrigger(final int priority, final int chapter) {
|
||||
super(priority);
|
||||
this.chapter = chapter;
|
||||
}
|
||||
|
||||
public SagaChapterTrigger(final int chapter) {
|
||||
this.chapter = chapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean accept(final MagicPermanent permanent, final MagicCounterChangeTriggerData data) {
|
||||
final int current = permanent.getCounters(MagicCounterType.Lore);
|
||||
final int before = current - data.amount;
|
||||
return super.accept(permanent, data) &&
|
||||
permanent == data.obj &&
|
||||
data.counterType == MagicCounterType.Lore &&
|
||||
before < chapter &&
|
||||
current >= chapter;
|
||||
}
|
||||
|
||||
public static SagaChapterTrigger create(final int chapter, final MagicSourceEvent sourceEvent) {
|
||||
return new SagaChapterTrigger(chapter) {
|
||||
@Override
|
||||
public MagicEvent executeTrigger(final MagicGame game,final MagicPermanent permanent, final MagicCounterChangeTriggerData data) {
|
||||
return sourceEvent.getTriggerEvent(permanent);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue