Add Word of Command

This commit is contained in:
Lyu Zong-Hong
2021-03-27 12:25:20 +09:00
parent 407c742124
commit 2e768cb77b
7 changed files with 101 additions and 1 deletions

View File

@@ -7,6 +7,7 @@ import forge.game.card.Card;
import forge.game.card.CardState;
import forge.game.cost.Cost;
import forge.game.player.Player;
import forge.game.spellability.AbilityManaPart;
import forge.game.spellability.SpellAbility;
import forge.game.staticability.StaticAbility;
import forge.util.Expressions;
@@ -189,6 +190,18 @@ public class ForgeScript {
if (!Expressions.compare(y, property, x)) {
return false;
}
} else if (property.equals("ManaAbilityCantPaidFor")) {
if (!sa.isManaAbility()) {
return false;
}
SpellAbility paidFor = sourceController.getPaidForSA();
do {
AbilityManaPart mana = sa.getManaPart();
if (paidFor != null && mana.meetsManaRestrictions(paidFor) && !mana.getExpressChoice().isEmpty()) {
return false;
}
sa = sa.getSubAbility();
} while(sa != null);
} else if (sa.getHostCard() != null) {
return sa.getHostCard().hasProperty(property, sourceController, source, spellAbility);
}

View File

@@ -62,6 +62,8 @@ public class PlayEffect extends SpellAbilityEffect {
public void resolve(final SpellAbility sa) {
final Card source = sa.getHostCard();
Player activator = sa.getActivatingPlayer();
Player controlledByPlayer = null;
long controlledByTimeStamp = -1;
final Game game = activator.getGame();
final boolean optional = sa.hasParam("Optional");
boolean remember = sa.hasParam("RememberPlayed");
@@ -74,6 +76,11 @@ public class PlayEffect extends SpellAbilityEffect {
activator = AbilityUtils.getDefinedPlayers(source, sa.getParam("Controller"), sa).get(0);
}
if (sa.hasParam("ControlledByPlayer")) {
controlledByTimeStamp = game.getNextTimestamp();
controlledByPlayer = AbilityUtils.getDefinedPlayers(source, sa.getParam("ControlledByPlayer"), sa).get(0);
}
final Player controller = activator;
CardCollection tgtCards;
CardCollection showCards = new CardCollection();
@@ -164,13 +171,17 @@ public class PlayEffect extends SpellAbilityEffect {
amount = tgtCards.size();
}
if (controlledByPlayer != null) {
activator.addController(controlledByTimeStamp, controlledByPlayer);
}
final CardCollection saidNoTo = new CardCollection();
while (tgtCards.size() > saidNoTo.size() && saidNoTo.size() < amount && amount > 0) {
activator.getController().tempShowCards(showCards);
Card tgtCard = controller.getController().chooseSingleEntityForEffect(tgtCards, sa, Localizer.getInstance().getMessage("lblSelectCardToPlay"), null);
activator.getController().endTempShowCards();
if (tgtCard == null) {
return;
break;
}
final boolean wasFaceDown;
@@ -300,6 +311,9 @@ public class PlayEffect extends SpellAbilityEffect {
tgtSA.setSVar("IsCastFromPlayEffect", "True");
// Add controlled by player to target SA so when the spell is resolving, the controller would be changed again
tgtSA.setControlledByPlayer(controlledByTimeStamp, controlledByPlayer);
if (controller.getController().playSaFromPlayEffect(tgtSA)) {
if (remember) {
source.addRemembered(tgtSA.getHostCard());
@@ -317,6 +331,11 @@ public class PlayEffect extends SpellAbilityEffect {
amount--;
}
// Remove controlled by player if any
if (controlledByPlayer != null) {
activator.removeController(controlledByTimeStamp);
}
} // end resolve

View File

@@ -17,10 +17,12 @@
*/
package forge.game.player;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
@@ -219,6 +221,8 @@ public class Player extends GameEntity implements Comparable<Player> {
private CardCollection lostOwnership = new CardCollection();
private CardCollection gainedOwnership = new CardCollection();
private int numManaConversion = 0;
// The SA currently being paid for
private Deque<SpellAbility> paidForStack = new ArrayDeque<>();
private Card monarchEffect = null;
private Card blessingEffect = null;
@@ -3281,6 +3285,16 @@ public class Player extends GameEntity implements Comparable<Player> {
return view;
}
public SpellAbility getPaidForSA() {
return paidForStack.peek();
}
public void pushPaidForSA(SpellAbility sa) {
paidForStack.push(sa);
}
public void popPaidForSA() {
paidForStack.pop();
}
public boolean isMonarch() {
return equals(game.getMonarch());
}

View File

@@ -28,6 +28,7 @@ import java.util.Objects;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
@@ -108,6 +109,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
private ManaCost multiKickerManaCost = null;
private Player activatingPlayer = null;
private Player targetingPlayer = null;
private Pair<Long, Player> controlledByPlayer = null;
private SpellAbility grantorOriginal = null;
private StaticAbility grantorStatic = null;
@@ -454,6 +456,24 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
targetingPlayer = targetingPlayer0;
}
/**
* @return returns who controls the controller of this sa when it is resolving (for Word of Command effect). Null means not being controlled by other
*/
public Pair<Long, Player> getControlledByPlayer() {
return controlledByPlayer;
}
/**
* @param ts time stamp of the control player effect
* @param controller the player who will control the controller of this sa
*/
public void setControlledByPlayer(long ts, Player controller) {
if (controller != null) {
controlledByPlayer = Pair.of(ts, controller);
} else {
controlledByPlayer = null;
}
}
public boolean isSpell() { return false; }
public boolean isAbility() { return true; }
public boolean isActivatedAbility() { return false; }

View File

@@ -479,6 +479,11 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
game.copyLastState();
}
// Change controller of activating player if it was set in SA
if (sa.getControlledByPlayer() != null) {
sa.getActivatingPlayer().addController(sa.getControlledByPlayer().getLeft(), sa.getControlledByPlayer().getRight());
}
if (thisHasFizzled) { // Fizzle
if (sa.isBestow()) {
// 702.102d: if its target is illegal,
@@ -508,6 +513,13 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
// do creatures ETB from here?
}
// Change controller back if it was changed
if (sa.getControlledByPlayer() != null) {
sa.getActivatingPlayer().removeController(sa.getControlledByPlayer().getLeft());
// Cleanup controlled by player states
sa.setControlledByPlayer(-1, null);
}
game.fireEvent(new GameEventSpellResolved(sa, thisHasFizzled));
finishResolving(sa, thisHasFizzled);

View File

@@ -0,0 +1,13 @@
Name:Word of Command
ManaCost:B B
Types:Instant
A:SP$ RevealHand | ValidTgts$ Opponent | RememberRevealed$ True | SubAbility$ DBChoose | StackDescription$ SpellDescription | SpellDescription$ Look at target opponent's hand and choose a card from it. You control that player until CARDNAME finishes resolving. The player plays that card if able. While doing so, the player can activate mana abilities only if they're from lands that player controls and only if mana they produce is spent to activate other mana abilities of lands the player controls and/or to play that card. If the chosen card is cast as a spell, you control the player while that spell is resolving.
SVar:DBChoose:DB$ ChooseCard | Defined$ You | Choices$ Card.IsRemembered | ChoiceZone$ Hand | Amount$ 1 | Mandatory$ True | SubAbility$ DBManaLandOnlyEffect
SVar:DBManaLandOnlyEffect:DB$ Effect | EffectOwner$ TargetedPlayer | StaticAbilities$ DBLimitMana | ImprintOnHost$ True | Duration$ Permanent | SubAbility$ DBPlay
SVar:DBLimitMana:Mode$ CantBeActivated | ValidSA$ Activated.nonLand,Activated.ManaAbilityCantPaidFor | EffectZone$ Command | Description$ While doing so, the player can activate mana abilities only if they're from lands that player controls and only if mana they produce is spent to activate other mana abilities of lands the player controls and/or to play that card.
SVar:DBPlay:DB$ Play | Defined$ ChosenCard | Controller$ TargetedPlayer | ControlledByPlayer$ You | SubAbility$ DBExileEffect
SVar:DBExileEffect:DB$ ChangeZone | Defined$ Imprinted | Origin$ Command | Destination$ Exile | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True | ClearImprinted$ True | ClearChosenCard$ True
AI:RemoveDeck:All
AI:RemoveDeck:Random
Oracle:Look at target opponent's hand and choose a card from it. You control that player until Word of Command finishes resolving. The player plays that card if able. While doing so, the player can activate mana abilities only if they're from lands that player controls and only if mana they produce is spent to activate other mana abilities of lands the player controls and/or to play that card. If the chosen card is cast as a spell, you control the player while that spell is resolving.

View File

@@ -57,6 +57,9 @@ public abstract class InputPayMana extends InputSyncronizedBase {
game = player.getGame();
saPaidFor = saPaidFor0;
// Set current paid for SA for player to be able to reference it later
player.pushPaidForSA(saPaidFor);
//if player is floating mana, show mana pool to make it easier to use that mana
wasFloatingMana = !player.getManaPool().isEmpty();
if (wasFloatingMana) {
@@ -66,6 +69,9 @@ public abstract class InputPayMana extends InputSyncronizedBase {
@Override
protected void onStop() {
// Clear current paid for SA
player.popPaidForSA();
if (wasFloatingMana) { //hide mana pool if it was shown due to floating mana
getController().getGui().hideManaPool(PlayerView.get(player));
}
@@ -351,6 +357,9 @@ public abstract class InputPayMana extends InputSyncronizedBase {
}
onManaAbilityPaid();
onStateChanged();
} else {
// Need to call this to unlock
onStateChanged();
}
}
});