Start your Engines as StaticAction (#6987)

* Start your Engines as StaticAction

* ~ fix trigger desc

* ~ moved keyword to better place
This commit is contained in:
Hans Mackowiak
2025-02-09 10:52:17 +01:00
committed by GitHub
parent 83438ef72b
commit 03fe3d63ea
9 changed files with 42 additions and 37 deletions

View File

@@ -1560,6 +1560,12 @@ public class GameAction {
} }
} }
// 704.5z If a player controls a permanent with start your engines! and that player has no speed, that players speed becomes 1. See rule 702.179, “Start Your Engines!”
if (p.getSpeed() == 0 && p.getCardsIn(ZoneType.Battlefield).anyMatch(c -> c.hasKeyword(Keyword.START_YOUR_ENGINES))) {
p.increaseSpeed();
checkAgain = true;
}
if (handlePlaneswalkerRule(p, noRegCreats)) { if (handlePlaneswalkerRule(p, noRegCreats)) {
checkAgain = true; checkAgain = true;
} }

View File

@@ -2550,7 +2550,8 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|| keyword.equals("Battle cry") || keyword.equals("Devoid") || keyword.equals("Riot") || keyword.equals("Battle cry") || keyword.equals("Devoid") || keyword.equals("Riot")
|| keyword.equals("Daybound") || keyword.equals("Nightbound") || keyword.equals("Daybound") || keyword.equals("Nightbound")
|| keyword.equals("Friends forever") || keyword.equals("Choose a Background") || keyword.equals("Friends forever") || keyword.equals("Choose a Background")
|| keyword.equals("Space sculptor") || keyword.equals("Doctor's companion")) { || keyword.equals("Space sculptor") || keyword.equals("Doctor's companion")
|| keyword.equals("Start your engines")) {
sbLong.append(keyword).append(" (").append(inst.getReminderText()).append(")"); sbLong.append(keyword).append(" (").append(inst.getReminderText()).append(")");
} else if (keyword.startsWith("Partner:")) { } else if (keyword.startsWith("Partner:")) {
final String[] k = keyword.split(":"); final String[] k = keyword.split(":");
@@ -2706,8 +2707,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|| keyword.startsWith("Class") || keyword.startsWith("Blitz") || keyword.startsWith("Class") || keyword.startsWith("Blitz")
|| keyword.startsWith("Specialize") || keyword.equals("Ravenous") || keyword.startsWith("Specialize") || keyword.equals("Ravenous")
|| keyword.equals("For Mirrodin") || keyword.startsWith("Craft") || keyword.equals("For Mirrodin") || keyword.startsWith("Craft")
|| keyword.startsWith("Landwalk") || keyword.startsWith("Visit") || keyword.startsWith("Landwalk") || keyword.startsWith("Visit")) {
|| keyword.equals("Start your engines")) {
// keyword parsing takes care of adding a proper description // keyword parsing takes care of adding a proper description
} else if (keyword.equals("Read ahead")) { } else if (keyword.equals("Read ahead")) {
sb.append(Localizer.getInstance().getMessage("lblReadAhead")).append(" (").append(Localizer.getInstance().getMessage("lblReadAheadDesc")); sb.append(Localizer.getInstance().getMessage("lblReadAhead")).append(" (").append(Localizer.getInstance().getMessage("lblReadAheadDesc"));

View File

@@ -1835,15 +1835,6 @@ public class CardFactoryUtil {
squadTrigger.setOverridingAbility(squadAbility); squadTrigger.setOverridingAbility(squadAbility);
squadTrigger.setSVar("SquadAmount", "Count$OptionalKeywordAmount"); squadTrigger.setSVar("SquadAmount", "Count$OptionalKeywordAmount");
inst.addTrigger(squadTrigger); inst.addTrigger(squadTrigger);
} else if (keyword.equals("Start your engines")) {
final String trig = "Mode$ Always | TriggerZones$ Battlefield | Static$ True | CheckDefinedPlayer$ " +
"You.NoSpeed | TriggerDescription$ Start your engines! (" + inst.getReminderText() + ")";
final String effect = "DB$ ChangeSpeed";
final Trigger trigger = TriggerHandler.parseTrigger(trig, card, intrinsic);
trigger.setOverridingAbility(AbilityFactory.getAbility(effect, card));
inst.addTrigger(trigger);
} else if (keyword.equals("Storm")) { } else if (keyword.equals("Storm")) {
final String actualTrigger = "Mode$ SpellCast | ValidCard$ Card.Self | TriggerZones$ Stack | Secondary$ True" final String actualTrigger = "Mode$ SpellCast | ValidCard$ Card.Self | TriggerZones$ Stack | Secondary$ True"
+ "| TriggerDescription$ Storm (" + inst.getReminderText() + ")"; + "| TriggerDescription$ Storm (" + inst.getReminderText() + ")";

View File

@@ -0,0 +1,21 @@
package forge.game.event;
import forge.game.player.Player;
public class GameEventSpeedChanged extends GameEvent {
public final Player player;
public final int oldValue;
public final int newValue;
public GameEventSpeedChanged(Player affected, int oldValue, int newValue) {
player = affected;
this.oldValue = oldValue;
this.newValue = newValue;
}
@Override
public <T> T visit(IGameEventVisitor<T> visitor) {
return visitor.visit(this);
}
}

View File

@@ -1,17 +0,0 @@
package forge.game.event;
import forge.game.player.Player;
public class GameEventSpeedUp extends GameEvent {
public final Player player;
public GameEventSpeedUp(Player affected) {
player = affected;
}
@Override
public <T> T visit(IGameEventVisitor<T> visitor) {
return visitor.visit(this);
}
}

View File

@@ -44,7 +44,7 @@ public interface IGameEventVisitor<T> {
T visit(GameEventRollDie event); T visit(GameEventRollDie event);
T visit(GameEventScry event); T visit(GameEventScry event);
T visit(GameEventShuffle event); T visit(GameEventShuffle event);
T visit(GameEventSpeedUp event); T visit(GameEventSpeedChanged event);
T visit(GameEventSpellAbilityCast event); T visit(GameEventSpellAbilityCast event);
T visit(GameEventSpellResolved event); T visit(GameEventSpellResolved event);
T visit(GameEventSpellRemovedFromStack event); T visit(GameEventSpellRemovedFromStack event);
@@ -101,7 +101,7 @@ public interface IGameEventVisitor<T> {
public T visit(GameEventRollDie event) { return null; } public T visit(GameEventRollDie event) { return null; }
public T visit(GameEventScry event) { return null; } public T visit(GameEventScry event) { return null; }
public T visit(GameEventShuffle event) { return null; } public T visit(GameEventShuffle event) { return null; }
public T visit(GameEventSpeedUp event) { return null; } public T visit(GameEventSpeedChanged event) { return null; }
public T visit(GameEventSpellResolved event) { return null; } public T visit(GameEventSpellResolved event) { return null; }
public T visit(GameEventSpellAbilityCast event) { return null; } public T visit(GameEventSpellAbilityCast event) { return null; }
public T visit(GameEventSpellRemovedFromStack event) { return null; } public T visit(GameEventSpellRemovedFromStack event) { return null; }

View File

@@ -1973,16 +1973,18 @@ public class Player extends GameEntity implements Comparable<Player> {
public final void increaseSpeed() { public final void increaseSpeed() {
if (speedEffect == null) createSpeedEffect(); if (speedEffect == null) createSpeedEffect();
if (!maxSpeed()) { // can't increase past 4 if (!maxSpeed()) { // can't increase past 4
int old = speed;
speed++; speed++;
view.updateSpeed(this); view.updateSpeed(this);
game.fireEvent(new GameEventSpeedUp(this)); //play sound effect getGame().fireEvent(new GameEventSpeedChanged(this, old, speed)); //play sound effect
} }
} }
public final void decreaseSpeed() { public final void decreaseSpeed() {
if (speed > 1) { // can't decrease speed below 1 if (speed > 1) { // can't decrease speed below 1
int old = speed;
speed--; speed--;
view.updateSpeed(this); view.updateSpeed(this);
getGame().fireEvent(new GameEventPlayerStatsChanged(this, false)); game.fireEvent(new GameEventSpeedChanged(this, old, speed));
} }
} }
public final boolean noSpeed() { public final boolean noSpeed() {
@@ -1998,9 +2000,11 @@ public class Player extends GameEntity implements Comparable<Player> {
public final void createSpeedEffect() { public final void createSpeedEffect() {
final PlayerZone com = getZone(ZoneType.Command); final PlayerZone com = getZone(ZoneType.Command);
DetachedCardEffect eff = new DetachedCardEffect(this, "Speed Effect"); DetachedCardEffect eff = new DetachedCardEffect(this, "Speed Effect");
String trigger = "Mode$ LifeLost | ValidPlayer$ Opponent | TriggerZones$ Command | ActivationLimit$ 1 | " + // 702.179d There is an inherent triggered ability associated with a player having 1 or more speed. This ability has no source and is controlled by that player.
// That ability is “Whenever one or more opponents lose life during your turn, if your speed is less than 4, your speed increases by 1. This ability triggers only once each turn.”
String trigger = "Mode$ LifeLostAll | ValidPlayer$ Opponent | TriggerZones$ Command | ActivationLimit$ 1 | " +
"PlayerTurn$ True | CheckSVar$ Count$YourSpeed | SVarCompare$ LT4 | " "PlayerTurn$ True | CheckSVar$ Count$YourSpeed | SVarCompare$ LT4 | "
+ "TriggerDescription$ Your speed increases once on each of your turns when an opponent loses life."; + "TriggerDescription$ Whenever one or more opponents lose life during your turn, if your speed is less than 4, your speed increases by 1. This ability triggers only once each turn.";
String speedUp = "DB$ ChangeSpeed"; String speedUp = "DB$ ChangeSpeed";
Trigger lifeLostTrigger = TriggerHandler.parseTrigger(trigger, eff, true); Trigger lifeLostTrigger = TriggerHandler.parseTrigger(trigger, eff, true);
lifeLostTrigger.setOverridingAbility(AbilityFactory.getAbility(speedUp, eff)); lifeLostTrigger.setOverridingAbility(AbilityFactory.getAbility(speedUp, eff));

View File

@@ -451,7 +451,7 @@ public class FControlGameEventHandler extends IGameEventVisitor.Base<Void> {
return processCards(cards, cardsRefreshDetails); return processCards(cards, cardsRefreshDetails);
} }
public Void visit(final GameEventSpeedUp event) { public Void visit(final GameEventSpeedChanged event) {
Player p = event.player; Player p = event.player;
processPlayer(p, livesUpdate); processPlayer(p, livesUpdate);
return processEvent(); return processEvent();

View File

@@ -80,7 +80,7 @@ public class EventVisualizer extends IGameEventVisitor.Base<SoundEffectType> imp
@Override @Override
public SoundEffectType visit(final GameEventShuffle event) { return SoundEffectType.Shuffle; } public SoundEffectType visit(final GameEventShuffle event) { return SoundEffectType.Shuffle; }
@Override @Override
public SoundEffectType visit(final GameEventSpeedUp event) { return SoundEffectType.SpeedUp; } public SoundEffectType visit(final GameEventSpeedChanged event) { return event.newValue > event.oldValue ? SoundEffectType.SpeedUp : null; }
@Override @Override
public SoundEffectType visit(final GameEventTokenCreated event) { return SoundEffectType.Token; } public SoundEffectType visit(final GameEventTokenCreated event) { return SoundEffectType.Token; }
@Override @Override