Re-implement skip turn using replacement effect

This commit is contained in:
Alumi
2021-03-14 04:53:55 +00:00
committed by Michael Kamensky
parent 84aaccc3da
commit c5ad28a65e
11 changed files with 101 additions and 52 deletions

View File

@@ -71,6 +71,8 @@ public class UntapAi extends SpellAbilityAi {
if (!sa.usesTargeting()) {
if (mandatory) {
return true;
} else if ("Never".equals(sa.getParam("AILogic"))) {
return false;
}
// TODO: use Defined to determine, if this is an unfavorable result

View File

@@ -61,6 +61,7 @@ public enum AbilityKey {
EffectOnly("EffectOnly"),
Exploited("Exploited"),
Explorer("Explorer"),
ExtraTurn("ExtraTurn"),
Event("Event"),
Fighter("Fighter"),
Fighters("Fighters"),

View File

@@ -1,9 +1,19 @@
package forge.game.ability.effects;
import forge.game.Game;
import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityUtils;
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.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.game.trigger.TriggerType;
import forge.game.zone.ZoneType;
import forge.util.Lang;
import java.util.List;
@@ -25,12 +35,33 @@ public class SkipTurnEffect extends SpellAbilityEffect {
@Override
public void resolve(SpellAbility sa) {
final Card hostCard = sa.getHostCard();
final Game game = hostCard.getGame();
final String name = hostCard.getName() + "'s Effect";
final String image = hostCard.getImageKey();
final int numTurns = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumTurns"), sa);
String repeffstr = "Event$ BeginTurn | ActiveZones$ Command | ValidPlayer$ You " +
"| Description$ Skip your next " + (numTurns > 1 ? Lang.getNumeral(numTurns) + " turns." : "turn.");
String effect = "DB$ StoreSVar | SVar$ NumTurns | Type$ CountSVar | Expression$ NumTurns/Minus.1";
String exile = "DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile " +
"| ConditionCheckSVar$ NumTurns | ConditionSVarCompare$ EQ0";
List<Player> tgtPlayers = getTargetPlayers(sa);
for (final Player player : tgtPlayers) {
for(int i = 0; i < numTurns; i++) {
player.addKeyword("Skip your next turn.");
}
final Card eff = createEffect(sa, player, name, image);
eff.setSVar("NumTurns", "Number$" + numTurns);
SpellAbility calcTurn = AbilityFactory.getAbility(effect, eff);
calcTurn.setSubAbility((AbilitySub) AbilityFactory.getAbility(exile, eff));
ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, eff, true);
re.setLayer(ReplacementLayer.Other);
re.setOverridingAbility(calcTurn);
eff.addReplacementEffect(re);
eff.updateStateForView();
game.getTriggerHandler().suppressMode(TriggerType.ChangesZone);
game.getAction().moveTo(ZoneType.Command, eff, sa);
game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone);
}
}
}

View File

@@ -36,8 +36,9 @@ import forge.game.cost.Cost;
import forge.game.event.*;
import forge.game.keyword.Keyword;
import forge.game.player.Player;
import forge.game.player.PlayerController.BinaryChoiceType;
import forge.game.player.PlayerController.ManaPaymentPurpose;
import forge.game.replacement.ReplacementResult;
import forge.game.replacement.ReplacementType;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.LandAbility;
import forge.game.staticability.StaticAbility;
@@ -837,55 +838,28 @@ public class PhaseHandler implements java.io.Serializable {
private Player getNextActivePlayer() {
ExtraTurn extraTurn = !extraTurns.isEmpty() ? extraTurns.pop() : null;
Player nextPlayer = extraTurn != null ? extraTurn.getPlayer() : game.getNextPlayerAfter(playerTurn);
// The bottom of the extra turn stack is the normal turn
boolean isExtraTurn = !extraTurns.isEmpty();
// update ExtraTurn Count for all players
for (final Player p : game.getPlayers()) {
p.setExtraTurnCount(getExtraTurnForPlayer(p));
}
// update ExtraTurn Count
nextPlayer.setExtraTurnCount(getExtraTurnForPlayer(nextPlayer));
if (extraTurn != null) {
// The bottom of the extra turn stack is the normal turn
nextPlayer.setExtraTurn(!extraTurns.isEmpty());
if (nextPlayer.hasKeyword("If you would begin an extra turn, skip that turn instead.")) {
return getNextActivePlayer();
}
for (Trigger deltrig : extraTurn.getDelayedTriggers()) {
game.getTriggerHandler().registerThisTurnDelayedTrigger(deltrig);
}
}
else {
nextPlayer.setExtraTurn(false);
}
if (nextPlayer.hasKeyword("Skip your next turn.")) {
nextPlayer.removeKeyword("Skip your next turn.", false);
// Replacement effects
final Map<AbilityKey, Object> repRunParams = AbilityKey.mapFromAffected(nextPlayer);
repRunParams.put(AbilityKey.ExtraTurn, isExtraTurn);
ReplacementResult repres = game.getReplacementHandler().run(ReplacementType.BeginTurn, repRunParams);
if (repres != ReplacementResult.NotReplaced) {
if (extraTurn == null) {
setPlayerTurn(nextPlayer);
}
return getNextActivePlayer();
}
// TODO: This shouldn't filter by Time Vault, just in case Time Vault doesn't have it's normal ability.
CardCollection vaults = CardLists.filter(nextPlayer.getCardsIn(ZoneType.Battlefield, "Time Vault"), Presets.TAPPED);
if (!vaults.isEmpty()) {
Card crd = vaults.getFirst();
SpellAbility fakeSA = new SpellAbility.EmptySa(crd, nextPlayer);
boolean untapTimeVault = nextPlayer.getController().chooseBinary(fakeSA, "Skip a turn to untap a Time Vault?", BinaryChoiceType.UntapTimeVault, false);
if (untapTimeVault) {
if (vaults.size() > 1) {
Card c = nextPlayer.getController().chooseSingleEntityForEffect(vaults, fakeSA, "Which Time Vault do you want to Untap?", null);
if (c != null)
crd = c;
}
crd.untap();
if (extraTurn == null) {
setPlayerTurn(nextPlayer);
}
return getNextActivePlayer();
}
}
nextPlayer.setExtraTurn(isExtraTurn);
if (extraTurn != null) {
for (Trigger deltrig : extraTurn.getDelayedTriggers()) {
game.getTriggerHandler().registerThisTurnDelayedTrigger(deltrig);
}
if (extraTurn.isSkipUntap()) {
nextPlayer.addKeyword("Skip the untap step of this turn.");
}

View File

@@ -0,0 +1,34 @@
package forge.game.replacement;
import forge.game.ability.AbilityKey;
import forge.game.card.Card;
import forge.game.spellability.SpellAbility;
import java.util.Map;
public class ReplaceBeginTurn extends ReplacementEffect {
public ReplaceBeginTurn(final Map<String, String> mapParams, final Card host, final boolean intrinsic) {
super(mapParams, host, intrinsic);
}
@Override
public boolean canReplace(Map<AbilityKey, Object> runParams) {
if (hasParam("ValidPlayer")) {
if (!matchesValid(runParams.get(AbilityKey.Affected), getParam("ValidPlayer").split(","), getHostCard())) {
return false;
}
}
if (hasParam("ExtraTurn")) {
if (!(boolean) runParams.get(AbilityKey.ExtraTurn)) {
return false;
}
}
return true;
}
@Override
public void setReplacingObjects(Map<AbilityKey, Object> runParams, SpellAbility sa) {
sa.setReplacingObject(AbilityKey.Player, runParams.get(AbilityKey.Affected));
}
}

View File

@@ -349,6 +349,12 @@ public class ReplacementHandler {
}
}
if (mapParams.containsKey("Skip")) {
if (mapParams.get("Skip").equals("True")) {
return ReplacementResult.Skipped; // Event is skipped.
}
}
Player player = host.getController();
player.getController().playSpellAbilityNoStack(effectSA, true);

View File

@@ -8,5 +8,6 @@ public enum ReplacementResult {
Replaced,
NotReplaced,
Prevented,
Updated
Updated,
Skipped
}

View File

@@ -14,6 +14,7 @@ public enum ReplacementType {
AddCounter(ReplaceAddCounter.class),
AssignDealDamage(ReplaceAssignDealDamage.class),
Attached(ReplaceAttached.class),
BeginTurn(ReplaceBeginTurn.class),
Counter(ReplaceCounter.class),
CopySpell(ReplaceCopySpell.class),
CreateToken(ReplaceToken.class),

View File

@@ -2,7 +2,7 @@ Name:Stranglehold
ManaCost:3 R
Types:Enchantment
S:Mode$ Continuous | Affected$ Opponent | AddKeyword$ CantSearchLibrary | Description$ Your opponents can't search libraries.
S:Mode$ Continuous | Affected$ Opponent | AddKeyword$ If you would begin an extra turn, skip that turn instead. | Description$ If an opponent would begin an extra turn, that player skips that turn instead.
R:Event$ BeginTurn | ActiveZones$ Battlefield | ValidPlayer$ Opponent | ExtraTurn$ True | Skip$ True | Description$ If an opponent would begin an extra turn, that player skips that turn instead.
SVar:NonStackingEffect:True
AI:RemoveDeck:Random
SVar:Picture:http://www.wizards.com/global/images/magic/general/stranglehold.jpg
Oracle:Your opponents can't search libraries.\nIf an opponent would begin an extra turn, that player skips that turn instead.

View File

@@ -1,11 +1,11 @@
Name:Time Vault
ManaCost:2
Types:Artifact
Text:If you would begin your turn while CARDNAME is tapped, you may skip that turn instead. If you do, untap CARDNAME.
K:CARDNAME doesn't untap during your untap step.
K:CARDNAME enters the battlefield tapped.
K:CARDNAME doesn't untap during your untap step.
R:Event$ BeginTurn | ActiveZones$ Battlefield | ValidPlayer$ You | IsPresent$ Card.Self+tapped | Optional$ True | ReplaceWith$ DBUntap | Description$ If you would begin your turn while CARDNAME is tapped, you may skip that turn instead. If you do, untap CARDNAME.
SVar:DBUntap:DB$ Untap | Defined$ Self | AILogic$ Never
A:AB$ AddTurn | Cost$ T | NumTurns$ 1 | SpellDescription$ Take an extra turn after this one.
SVar:PlayMain1:ALWAYS
AI:RemoveDeck:Random
SVar:Picture:http://www.wizards.com/global/images/magic/general/time_vault.jpg
Oracle:Time Vault enters the battlefield tapped.\nTime Vault doesn't untap during your untap step.\nIf you would begin your turn while Time Vault is tapped, you may skip that turn instead. If you do, untap Time Vault.\n{T}: Take an extra turn after this one.

View File

@@ -1,11 +1,10 @@
Name:Ugin's Nexus
ManaCost:5
Types:Legendary Artifact
S:Mode$ Continuous | Affected$ Player | AddKeyword$ If you would begin an extra turn, skip that turn instead. | Description$ If a player would begin an extra turn, that player skips that turn instead.
R:Event$ BeginTurn | ActiveZones$ Battlefield | ExtraTurn$ True | Skip$ True | Description$ If a player would begin an extra turn, that player skips that turn instead.
R:Event$ Moved | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | ReplaceWith$ DBExile | Description$ If CARDNAME would be put into a graveyard from the battlefield, instead exile it and take an extra turn after this one.
SVar:DBExile:DB$ ChangeZone | Defined$ ReplacedCard | Origin$ Battlefield | Destination$ Exile | SubAbility$ DBAddTurn
SVar:DBAddTurn:DB$ AddTurn | Defined$ ReplacedCardLKIController | NumTurns$ 1
AI:RemoveDeck:Random
SVar:SacMe:5
SVar:Picture:http://www.wizards.com/global/images/magic/general/ugins_nexus.jpg
Oracle:If a player would begin an extra turn, that player skips that turn instead.\nIf Ugin's Nexus would be put into a graveyard from the battlefield, instead exile it and take an extra turn after this one.