mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-19 20:28:00 +00:00
Refactor skip phase/step using replacement effect. Also add Fasting.
This commit is contained in:
@@ -153,6 +153,7 @@ public enum ApiType {
|
||||
SetLife (LifeSetEffect.class),
|
||||
SetState (SetStateEffect.class),
|
||||
Shuffle (ShuffleEffect.class),
|
||||
SkipPhase (SkipPhaseEffect.class),
|
||||
SkipTurn (SkipTurnEffect.class),
|
||||
StoreSVar (StoreSVarEffect.class),
|
||||
Subgame (SubgameEffect.class),
|
||||
|
||||
@@ -57,6 +57,7 @@ public class AddTurnEffect extends SpellAbilityEffect {
|
||||
}
|
||||
if (sa.hasParam("SkipUntap")) {
|
||||
extra.setSkipUntap(true);
|
||||
extra.setSkipUntapSA(sa);
|
||||
}
|
||||
if (sa.hasParam("NoSchemes")) {
|
||||
extra.setCantSetSchemesInMotion(true);
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
package forge.game.ability.effects;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import forge.GameCommand;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityFactory;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.replacement.ReplacementEffect;
|
||||
import forge.game.replacement.ReplacementHandler;
|
||||
import forge.game.replacement.ReplacementLayer;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.trigger.TriggerType;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
public class SkipPhaseEffect extends SpellAbilityEffect {
|
||||
|
||||
@Override
|
||||
protected String getStackDescription(SpellAbility sa) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
final String duration = sa.getParam("Duration");
|
||||
final String phase = sa.getParam("Phase");
|
||||
final String step = sa.getParam("Step");
|
||||
|
||||
List<Player> tgtPlayers = getTargetPlayers(sa);
|
||||
for (final Player player : tgtPlayers) {
|
||||
sb.append(player).append(" ");
|
||||
sb.append("skips their ");
|
||||
if (duration == null) {
|
||||
sb.append("next ");
|
||||
}
|
||||
if (phase != null) {
|
||||
sb.append(phase.toLowerCase()).append(" phase.");
|
||||
} else {
|
||||
sb.append(step.toLowerCase()).append(" step.");
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resolve(SpellAbility sa) {
|
||||
final String duration = sa.getParam("Duration");
|
||||
final String phase = sa.getParam("Phase");
|
||||
final String step = sa.getParam("Step");
|
||||
|
||||
List<Player> tgtPlayers = getTargetPlayers(sa);
|
||||
for (final Player player : tgtPlayers) {
|
||||
createSkipPhaseEffect(sa, player, duration, phase, step);
|
||||
}
|
||||
}
|
||||
|
||||
public static void createSkipPhaseEffect(SpellAbility sa, final Player player,
|
||||
final String duration, final String phase, final String step) {
|
||||
final Card hostCard = sa.getHostCard();
|
||||
final Game game = hostCard.getGame();
|
||||
final String name = hostCard.getName() + "'s Effect";
|
||||
final String image = hostCard.getImageKey();
|
||||
final boolean isNextThisTurn = duration != null && duration.equals("NextThisTurn");
|
||||
|
||||
final Card eff = createEffect(sa, player, name, image);
|
||||
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append("Event$ BeginPhase | ActiveZones$ Command | ValidPlayer$ You | Phase$ ");
|
||||
sb.append(phase != null ? phase : step);
|
||||
if (duration != null && !isNextThisTurn) {
|
||||
sb.append(" | Skip$ True");
|
||||
}
|
||||
sb.append("| Description$ Skip ");
|
||||
if (duration == null || isNextThisTurn) {
|
||||
sb.append("your next ");
|
||||
} else {
|
||||
sb.append("each ");
|
||||
}
|
||||
if (phase != null) {
|
||||
sb.append(phase.toLowerCase()).append(" phase");
|
||||
} else {
|
||||
sb.append(step.toLowerCase()).append(" step");
|
||||
}
|
||||
if (duration == null) {
|
||||
sb.append(".");
|
||||
} else {
|
||||
if (game.getPhaseHandler().getPlayerTurn().equals(player)) {
|
||||
sb.append(" of this turn.");
|
||||
} else {
|
||||
sb.append(" of your next turn.");
|
||||
}
|
||||
}
|
||||
|
||||
final String repeffstr = sb.toString();
|
||||
ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, eff, true);
|
||||
// Set to layer to Control so it will be applied before "would begin your X phase/step" replacement effects
|
||||
// (Any layer before Other is OK, since default layer is Other.)
|
||||
re.setLayer(ReplacementLayer.Control);
|
||||
if (duration == null || isNextThisTurn) {
|
||||
String exilestr = "DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile";
|
||||
SpellAbility exile = AbilityFactory.getAbility(exilestr, eff);
|
||||
re.setOverridingAbility(exile);
|
||||
}
|
||||
if (duration != null) {
|
||||
final GameCommand endEffect = new GameCommand() {
|
||||
private static final long serialVersionUID = -5861759814760561373L;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
game.getAction().exile(eff, null);
|
||||
}
|
||||
};
|
||||
|
||||
if (duration.equals("EndOfTurn") || isNextThisTurn) {
|
||||
game.getEndOfTurn().addUntil(endEffect);
|
||||
} else if (duration.equals("UntilTheEndOfYourNextTurn")) {
|
||||
game.getEndOfTurn().addUntilEnd(player, endEffect);
|
||||
}
|
||||
}
|
||||
eff.addReplacementEffect(re);
|
||||
eff.updateStateForView();
|
||||
|
||||
game.getTriggerHandler().suppressMode(TriggerType.ChangesZone);
|
||||
game.getAction().moveTo(ZoneType.Command, eff, sa);
|
||||
game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone);
|
||||
}
|
||||
}
|
||||
@@ -54,7 +54,9 @@ public class SkipTurnEffect extends SpellAbilityEffect {
|
||||
calcTurn.setSubAbility((AbilitySub) AbilityFactory.getAbility(exile, eff));
|
||||
|
||||
ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, eff, true);
|
||||
re.setLayer(ReplacementLayer.Other);
|
||||
// Set to layer to Control so it will be applied before "would begin your turn" replacement effects
|
||||
// (Any layer before Other is OK, since default layer is Other.)
|
||||
re.setLayer(ReplacementLayer.Control);
|
||||
re.setOverridingAbility(calcTurn);
|
||||
eff.addReplacementEffect(re);
|
||||
eff.updateStateForView();
|
||||
|
||||
@@ -22,6 +22,7 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.trigger.Trigger;
|
||||
|
||||
/**
|
||||
@@ -38,6 +39,7 @@ public class ExtraTurn {
|
||||
private Player player = null;
|
||||
private List<Trigger> delTrig = Collections.synchronizedList(new ArrayList<>());
|
||||
private boolean skipUntap = false;
|
||||
private SpellAbility skipUntapSA;
|
||||
private boolean cantSetSchemesInMotion = false;
|
||||
/**
|
||||
* TODO: Write javadoc for Constructor.
|
||||
@@ -89,6 +91,20 @@ public class ExtraTurn {
|
||||
this.skipUntap = skipUntap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the skipUntapSA;
|
||||
*/
|
||||
public SpellAbility getSkipUntapSA() {
|
||||
return skipUntapSA;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param skipUntapSA the skipUntapSA to set
|
||||
*/
|
||||
public void setSkipUntapSA(SpellAbility skipUntapSA) {
|
||||
this.skipUntapSA = skipUntapSA;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if Schemes can't be played during the extra turn
|
||||
*/
|
||||
|
||||
@@ -24,6 +24,7 @@ import com.google.common.collect.Multimap;
|
||||
import forge.card.mana.ManaCost;
|
||||
import forge.game.*;
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.ability.effects.SkipPhaseEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardLists;
|
||||
@@ -166,6 +167,21 @@ public class PhaseHandler implements java.io.Serializable {
|
||||
turnEnded = PhaseType.isLast(phase, isTopsy);
|
||||
setPhase(PhaseType.getNext(phase, isTopsy));
|
||||
}
|
||||
|
||||
// Replacement effects
|
||||
final Map<AbilityKey, Object> repRunParams = AbilityKey.mapFromAffected(playerTurn);
|
||||
repRunParams.put(AbilityKey.Phase, phase.nameForScripts);
|
||||
ReplacementResult repres = game.getReplacementHandler().run(ReplacementType.BeginPhase, repRunParams);
|
||||
if (repres != ReplacementResult.NotReplaced) {
|
||||
// Currently there is no effect to skip entire beginning phase
|
||||
// If in the future that kind of effect is added, need to handle it too.
|
||||
// Handle skipping of entire combat phase
|
||||
if (phase == PhaseType.COMBAT_BEGIN) {
|
||||
setPhase(PhaseType.COMBAT_END);
|
||||
}
|
||||
advanceToNextPhase();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
game.getStack().clearUndoStack(); //can't undo action from previous phase
|
||||
@@ -197,30 +213,19 @@ public class PhaseHandler implements java.io.Serializable {
|
||||
}
|
||||
|
||||
private boolean isSkippingPhase(final PhaseType phase) {
|
||||
// TODO: Refactor this method to replacement effect
|
||||
switch (phase) {
|
||||
case UNTAP:
|
||||
if (playerTurn.hasKeyword("Skip your next untap step.")) {
|
||||
playerTurn.removeKeyword("Skip your next untap step.", false); // Skipping your "next" untap step is cumulative.
|
||||
return true;
|
||||
}
|
||||
return playerTurn.hasKeyword("Skip the untap step of this turn.") || playerTurn.hasKeyword("Skip your untap step.");
|
||||
|
||||
case UPKEEP:
|
||||
return playerTurn.hasKeyword("Skip your upkeep step.");
|
||||
|
||||
case DRAW:
|
||||
return playerTurn.isSkippingDraw() || turn == 1 && game.getPlayers().size() == 2;
|
||||
|
||||
case MAIN1:
|
||||
case MAIN2:
|
||||
return playerTurn.isSkippingMain();
|
||||
return turn == 1 && game.getPlayers().size() == 2;
|
||||
|
||||
case COMBAT_BEGIN:
|
||||
case COMBAT_DECLARE_ATTACKERS:
|
||||
return playerTurn.isSkippingCombat();
|
||||
|
||||
case COMBAT_DECLARE_BLOCKERS:
|
||||
if (combat != null && combat.getAttackers().isEmpty()) {
|
||||
endCombat();
|
||||
}
|
||||
// Fall through
|
||||
case COMBAT_FIRST_STRIKE_DAMAGE:
|
||||
case COMBAT_DAMAGE:
|
||||
return !inCombat();
|
||||
@@ -237,9 +242,6 @@ public class PhaseHandler implements java.io.Serializable {
|
||||
if (isSkippingPhase(phase)) {
|
||||
skipped = true;
|
||||
givePriorityToPlayer = false;
|
||||
if (phase == PhaseType.COMBAT_DECLARE_ATTACKERS) {
|
||||
playerTurn.removeKeyword("Skip your next combat phase.");
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Perform turn-based actions
|
||||
@@ -289,11 +291,6 @@ public class PhaseHandler implements java.io.Serializable {
|
||||
declareAttackersTurnBasedAction();
|
||||
game.getStack().unfreezeStack();
|
||||
|
||||
if (combat != null && combat.getAttackers().isEmpty()
|
||||
&& !game.getTriggerHandler().hasDelayedTriggers()) {
|
||||
endCombat();
|
||||
}
|
||||
|
||||
if (combat != null) {
|
||||
for (Card c : combat.getAttackers()) {
|
||||
if (combat.getDefenderByAttacker(c) instanceof Player) {
|
||||
@@ -408,7 +405,6 @@ public class PhaseHandler implements java.io.Serializable {
|
||||
player.clearAssignedDamage();
|
||||
}
|
||||
|
||||
playerTurn.removeKeyword("Skip all combat phases of this turn.");
|
||||
nUpkeepsThisTurn = 0;
|
||||
nMain1sThisTurn = 0;
|
||||
game.getStack().resetMaxDistinctSources();
|
||||
@@ -861,7 +857,7 @@ public class PhaseHandler implements java.io.Serializable {
|
||||
game.getTriggerHandler().registerThisTurnDelayedTrigger(deltrig);
|
||||
}
|
||||
if (extraTurn.isSkipUntap()) {
|
||||
nextPlayer.addKeyword("Skip the untap step of this turn.");
|
||||
SkipPhaseEffect.createSkipPhaseEffect(extraTurn.getSkipUntapSA(), nextPlayer, null, null, "Untap");
|
||||
}
|
||||
if (extraTurn.isCantSetSchemesInMotion()) {
|
||||
nextPlayer.addKeyword("Schemes can't be set in motion this turn.");
|
||||
|
||||
@@ -2698,28 +2698,9 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
if (hasLost()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (hasKeyword("Skip your next combat phase.")) {
|
||||
return true;
|
||||
}
|
||||
if (hasKeyword("Skip your combat phase.")) {
|
||||
return true;
|
||||
}
|
||||
if (hasKeyword("Skip all combat phases of your next turn.")) {
|
||||
replaceAllKeywordInstances("Skip all combat phases of your next turn.",
|
||||
"Skip all combat phases of this turn.");
|
||||
return true;
|
||||
}
|
||||
if (hasKeyword("Skip all combat phases of this turn.")) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isSkippingMain() {
|
||||
return hasKeyword("Skip your main phase.");
|
||||
}
|
||||
|
||||
public int getStartingHandSize() {
|
||||
return startingHandSize;
|
||||
}
|
||||
@@ -2829,17 +2810,6 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isSkippingDraw() {
|
||||
if (hasKeyword("Skip your next draw step.")) {
|
||||
removeKeyword("Skip your next draw step.");
|
||||
return true;
|
||||
}
|
||||
if (hasKeyword("Skip your draw step.")) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public CardCollectionView getInboundTokens() {
|
||||
return inboundTokens;
|
||||
}
|
||||
@@ -3393,7 +3363,6 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
setLibrarySearched(0);
|
||||
setNumManaConversion(0);
|
||||
|
||||
removeKeyword("Skip the untap step of this turn.");
|
||||
removeKeyword("Schemes can't be set in motion this turn.");
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package forge.game.replacement;
|
||||
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class ReplaceBeginPhase extends ReplacementEffect {
|
||||
|
||||
public ReplaceBeginPhase(final Map<String, String> mapParams, final Card host, final boolean intrinsic) {
|
||||
super(mapParams, host, intrinsic);
|
||||
// set default layer to control
|
||||
if (!mapParams.containsKey("Layer")) {
|
||||
this.setLayer(ReplacementLayer.Control);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canReplace(Map<AbilityKey, Object> runParams) {
|
||||
Player affected = (Player) runParams.get(AbilityKey.Affected);
|
||||
if (!matchesValidParam("ValidPlayer", affected)) {
|
||||
return false;
|
||||
}
|
||||
if (hasParam("Phase")) {
|
||||
final String phase = getParam("Phase");
|
||||
final String currentPhase = (String) runParams.get(AbilityKey.Phase);
|
||||
if (phase.equals("Combat") && currentPhase.equals("BeginCombat")) {
|
||||
return true;
|
||||
}
|
||||
if (phase.equals("Main") && (currentPhase.equals("Main1") || currentPhase.equals("Main2"))) {
|
||||
return true;
|
||||
}
|
||||
if (!phase.equals(currentPhase)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (hasParam("Condition")) {
|
||||
if (getParam("Condition").equals("Hellbent") && !affected.hasHellbent()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReplacingObjects(Map<AbilityKey, Object> runParams, SpellAbility sa) {
|
||||
sa.setReplacingObject(AbilityKey.Player, runParams.get(AbilityKey.Affected));
|
||||
}
|
||||
}
|
||||
@@ -420,4 +420,17 @@ public class ReplacementHandler {
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to check if a phase would be skipped for AI.
|
||||
*/
|
||||
public boolean wouldPhaseBeSkipped(final Player player, final String phase) {
|
||||
final Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(player);
|
||||
repParams.put(AbilityKey.Phase, phase);
|
||||
List<ReplacementEffect> list = getReplacementList(ReplacementType.BeginPhase, repParams, ReplacementLayer.Control);
|
||||
if (!list.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ public enum ReplacementType {
|
||||
AddCounter(ReplaceAddCounter.class),
|
||||
AssignDealDamage(ReplaceAssignDealDamage.class),
|
||||
Attached(ReplaceAttached.class),
|
||||
BeginPhase(ReplaceBeginPhase.class),
|
||||
BeginTurn(ReplaceBeginTurn.class),
|
||||
Counter(ReplaceCounter.class),
|
||||
CopySpell(ReplaceCopySpell.class),
|
||||
|
||||
Reference in New Issue
Block a user