Merge pull request #4993 from Zzooouhh/Zzooouhh-woc

Implemented Word of Command (presumably buggy)
This commit is contained in:
LevelX2 2018-07-14 10:40:38 +02:00 committed by GitHub
commit 0d9f8ad19a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 443 additions and 17 deletions

View file

@ -78,6 +78,7 @@ class DrainPowerEffect extends OneShotEffect {
TargetPermanent target = null;
while (true) {
targetPlayer.setPayManaMode(true);
manaAbilitiesMap.clear();
for (Permanent permanent : game.getBattlefield().getAllActivePermanents(filter, targetPlayer.getId(), game)) {
if (!ignorePermanents.contains(permanent)) {
@ -138,6 +139,7 @@ class DrainPowerEffect extends OneShotEffect {
}
}
}
targetPlayer.setPayManaMode(false);
// 106.12. One card (Drain Power) causes one player to lose unspent mana and another to add the mana lost this way. (Note that these may be the same player.)
// This empties the former player's mana pool and causes the mana emptied this way to be put into the latter player's mana pool. Which permanents, spells, and/or

View file

@ -0,0 +1,251 @@
package mage.cards.w;
import java.util.UUID;
import mage.MageObject;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.AsThoughEffect;
import mage.abilities.effects.AsThoughEffectImpl;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.RestrictionEffect;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.AsThoughEffectType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.filter.FilterCard;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.game.stack.Spell;
import mage.players.ManaPool;
import mage.players.Player;
import mage.target.TargetCard;
import mage.target.common.TargetOpponent;
import mage.target.targetpointer.FixedTarget;
/**
*
* @author L_J
*/
public final class WordOfCommand extends CardImpl {
public WordOfCommand(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{B}{B}");
// 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.
this.getSpellAbility().addEffect(new WordOfCommandEffect());
this.getSpellAbility().addTarget(new TargetOpponent());
}
public WordOfCommand(final WordOfCommand card) {
super(card);
}
@Override
public WordOfCommand copy() {
return new WordOfCommand(this);
}
}
class WordOfCommandEffect extends OneShotEffect {
public WordOfCommandEffect() {
super(Outcome.GainControl);
this.staticText = "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";
}
public WordOfCommandEffect(final WordOfCommandEffect effect) {
super(effect);
}
@Override
public WordOfCommandEffect copy() {
return new WordOfCommandEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player sourceController = game.getPlayer(source.getControllerId());
Player targetPlayer = game.getPlayer(source.getFirstTarget());
MageObject sourceObject = game.getObject(source.getSourceId());
Card card = null;
if (sourceController != null && targetPlayer != null && sourceObject != null) {
Player controller = null;
Spell wordOfCommand = game.getSpell(source.getSourceId());
if (wordOfCommand != null) {
if (wordOfCommand.getCommandedBy() != null) {
controller = game.getPlayer(wordOfCommand.getCommandedBy());
} else {
controller = game.getPlayer(sourceController.getTurnControlledBy());
}
}
if (controller == null) {
controller = sourceController; // reset the controller to avoid NPE
}
// Look at target opponent's hand and choose a card from it
TargetCard targetCard = new TargetCard(Zone.HAND, new FilterCard());
if (controller.choose(Outcome.Discard, targetPlayer.getHand(), targetCard, game)) {
card = game.getCard(targetCard.getFirstTarget());
}
// You control that player until Word of Command finishes resolving
controller.controlPlayersTurn(game, targetPlayer.getId());
while (controller != null && controller.canRespond()) {
if (controller.chooseUse(Outcome.Benefit, "Resolve " + sourceObject.getLogName() + " now" + (card != null ? " and play " + card.getLogName() : "") + '?', source, game)) {
// this is used to give the controller a little space to utilize his player controlling effect (look at face down creatures, hand, etc.)
break;
}
}
// The player plays that card if able
if (card != null) {
// While doing so, the player can activate mana abilities only if they're from lands that player controls
RestrictionEffect effect = new WordOfCommandCantActivateEffect();
effect.setTargetPointer(new FixedTarget(targetPlayer.getId()));
game.addEffect(effect, source);
// and only if mana they produce is spent to activate other mana abilities of lands he or she controls and/or play that card
ManaPool manaPool = targetPlayer.getManaPool();
manaPool.setForcedToPay(true);
manaPool.storeMana();
int bookmark = game.bookmarkState();
boolean canPlay = checkPlayability(card, targetPlayer, game, source);
while (canPlay
&& targetPlayer.canRespond()
&& !targetPlayer.playCard(card, game, false, true, new MageObjectReference(source.getSourceObject(game), game))) {
SpellAbility spellAbility = card.getSpellAbility();
if (spellAbility != null) {
spellAbility.getManaCostsToPay().clear();
spellAbility.getManaCostsToPay().addAll(spellAbility.getManaCosts());
((ManaCostsImpl) spellAbility.getManaCostsToPay()).forceManaRollback(game, manaPool); // force rollback if card was deemed playable
canPlay = checkPlayability(card, targetPlayer, game, source);
} else {
break;
}
}
if (!canPlay) {
game.informPlayers(targetPlayer.getLogName() + " didn't play " + card.getLogName() + " (card can't be played)");
}
manaPool.setForcedToPay(false); // duplicate in case of a new mana pool existing - probably not necessary, but just in case
manaPool = targetPlayer.getManaPool(); // a rollback creates a new mana pool for the player, so it's necessary to find it again
manaPool.setForcedToPay(false);
game.removeBookmark(bookmark);
targetPlayer.resetStoredBookmark(game);
for (RestrictionEffect eff : game.getContinuousEffects().getRestrictionEffects()) {
if (eff instanceof WordOfCommandCantActivateEffect) {
eff.discard();
break;
}
}
game.getContinuousEffects().removeInactiveEffects(game);
Spell spell = game.getSpell(card.getId());
if (spell != null) {
spell.setCommandedBy(controller.getId()); // If the chosen card is cast as a spell, you control the player while that spell is resolving
}
}
wordOfCommand = game.getSpell(source.getSourceId());
if (wordOfCommand != null) {
wordOfCommand.setCommandedBy(controller.getId()); // You control the player until Word of Command finishes resolving
} else {
targetPlayer.setGameUnderYourControl(true, false);
if (!targetPlayer.getTurnControlledBy().equals(controller.getId())) {
controller.getPlayersUnderYourControl().remove(targetPlayer.getId());
}
}
return true;
}
return false;
}
private boolean checkPlayability(Card card, Player targetPlayer, Game game, Ability source) {
// check for card playability
boolean canPlay = false;
if (card.isLand()) { // we can't use getPlayableInHand(game) in here because it disallows playing lands outside the main step
if (targetPlayer.canPlayLand()
&& game.getActivePlayerId().equals(targetPlayer.getId())) {
canPlay = true;
for (Ability ability : card.getAbilities(game)) {
if (!game.getContinuousEffects().preventedByRuleModification(GameEvent.getEvent(GameEvent.EventType.PLAY_LAND, ability.getSourceId(), ability.getSourceId(), targetPlayer.getId()), ability, game, true)) {
canPlay &= true;
}
}
}
} else { // Word of Command allows the chosen card to be played "as if it had flash" so we need to invoke such effect to bypass the check
AsThoughEffectImpl effect2 = new WordOfCommandTestFlashEffect();
game.addEffect(effect2, source);
if (targetPlayer.getPlayableInHand(game).contains(card.getId())) {
canPlay = true;
}
for (AsThoughEffect eff : game.getContinuousEffects().getApplicableAsThoughEffects(AsThoughEffectType.CAST_AS_INSTANT, game)) {
if (eff instanceof WordOfCommandTestFlashEffect) {
eff.discard();
break;
}
}
}
return canPlay;
}
}
class WordOfCommandCantActivateEffect extends RestrictionEffect {
public WordOfCommandCantActivateEffect() {
super(Duration.EndOfTurn);
}
public WordOfCommandCantActivateEffect(final WordOfCommandCantActivateEffect effect) {
super(effect);
}
@Override
public WordOfCommandCantActivateEffect copy() {
return new WordOfCommandCantActivateEffect(this);
}
@Override
public boolean applies(Permanent permanent, Ability source, Game game) {
return !permanent.isLand() && permanent.getControllerId().equals(this.targetPointer.getFirst(game, source));
}
@Override
public boolean canUseActivatedAbilities(Permanent permanent, Ability source, Game game) {
return false;
}
}
class WordOfCommandTestFlashEffect extends AsThoughEffectImpl {
public WordOfCommandTestFlashEffect() {
super(AsThoughEffectType.CAST_AS_INSTANT, Duration.EndOfTurn, Outcome.Benefit);
}
public WordOfCommandTestFlashEffect(final WordOfCommandTestFlashEffect effect) {
super(effect);
}
@Override
public WordOfCommandTestFlashEffect copy() {
return new WordOfCommandTestFlashEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
return true;
}
@Override
public boolean applies(UUID affectedSpellId, Ability source, UUID affectedControllerId, Game game) {
return true;
}
}

View file

@ -309,6 +309,7 @@ public final class LimitedEditionAlpha extends ExpansionSet {
cards.add(new SetCardInfo("Will-o'-the-Wisp", 135, Rarity.RARE, mage.cards.w.WillOTheWisp.class));
cards.add(new SetCardInfo("Winter Orb", 275, Rarity.RARE, mage.cards.w.WinterOrb.class));
cards.add(new SetCardInfo("Wooden Sphere", 276, Rarity.UNCOMMON, mage.cards.w.WoodenSphere.class));
cards.add(new SetCardInfo("Word of Command", 136, Rarity.RARE, mage.cards.w.WordOfCommand.class));
cards.add(new SetCardInfo("Wrath of God", 45, Rarity.RARE, mage.cards.w.WrathOfGod.class));
cards.add(new SetCardInfo("Zombie Master", 137, Rarity.RARE, mage.cards.z.ZombieMaster.class));
}

View file

@ -316,6 +316,7 @@ public final class LimitedEditionBeta extends ExpansionSet {
cards.add(new SetCardInfo("Will-o'-the-Wisp", 136, Rarity.RARE, mage.cards.w.WillOTheWisp.class));
cards.add(new SetCardInfo("Winter Orb", 276, Rarity.RARE, mage.cards.w.WinterOrb.class));
cards.add(new SetCardInfo("Wooden Sphere", 277, Rarity.UNCOMMON, mage.cards.w.WoodenSphere.class));
cards.add(new SetCardInfo("Word of Command", 137, Rarity.RARE, mage.cards.w.WordOfCommand.class));
cards.add(new SetCardInfo("Wrath of God", 46, Rarity.RARE, mage.cards.w.WrathOfGod.class));
cards.add(new SetCardInfo("Zombie Master", 138, Rarity.RARE, mage.cards.z.ZombieMaster.class));
}

View file

@ -298,6 +298,7 @@ public final class MastersEditionIV extends ExpansionSet {
cards.add(new SetCardInfo("Wild Griffin", 35, Rarity.COMMON, mage.cards.w.WildGriffin.class));
cards.add(new SetCardInfo("Wild Ox", 174, Rarity.UNCOMMON, mage.cards.w.WildOx.class));
cards.add(new SetCardInfo("Wood Elemental", 175, Rarity.RARE, mage.cards.w.WoodElemental.class));
cards.add(new SetCardInfo("Word of Command", 103, Rarity.RARE, mage.cards.w.WordOfCommand.class));
cards.add(new SetCardInfo("Xenic Poltergeist", 104, Rarity.UNCOMMON, mage.cards.x.XenicPoltergeist.class));
cards.add(new SetCardInfo("Yotian Soldier", 240, Rarity.COMMON, mage.cards.y.YotianSoldier.class));
cards.add(new SetCardInfo("Zombie Master", 105, Rarity.UNCOMMON, mage.cards.z.ZombieMaster.class));

View file

@ -316,6 +316,7 @@ public final class UnlimitedEdition extends ExpansionSet {
cards.add(new SetCardInfo("Will-o'-the-Wisp", 136, Rarity.RARE, mage.cards.w.WillOTheWisp.class));
cards.add(new SetCardInfo("Winter Orb", 276, Rarity.RARE, mage.cards.w.WinterOrb.class));
cards.add(new SetCardInfo("Wooden Sphere", 277, Rarity.UNCOMMON, mage.cards.w.WoodenSphere.class));
cards.add(new SetCardInfo("Word of Command", 137, Rarity.RARE, mage.cards.w.WordOfCommand.class));
cards.add(new SetCardInfo("Wrath of God", 46, Rarity.RARE, mage.cards.w.WrathOfGod.class));
cards.add(new SetCardInfo("Zombie Master", 138, Rarity.RARE, mage.cards.z.ZombieMaster.class));
}

View file

@ -207,7 +207,9 @@ public abstract class ManaCostImpl extends CostImpl implements ManaCost {
return true;
}
Player player = game.getPlayer(controllerId);
assignPayment(game, ability, player.getManaPool(), costToPay);
if (!player.getManaPool().isForcedToPay()) {
assignPayment(game, ability, player.getManaPool(), costToPay);
}
game.getState().getSpecialActions().removeManaActions();
while (!isPaid()) {
ManaCost unpaid = this.getUnpaid();

View file

@ -117,7 +117,9 @@ public class ManaCostsImpl<T extends ManaCost> extends ArrayList<T> implements M
}
Player player = game.getPlayer(controllerId);
assignPayment(game, ability, player.getManaPool(), this);
if (!player.getManaPool().isForcedToPay()) {
assignPayment(game, ability, player.getManaPool(), this);
}
game.getState().getSpecialActions().removeManaActions();
while (!isPaid()) {
ManaCost unpaid = this.getUnpaid();
@ -235,10 +237,15 @@ public class ManaCostsImpl<T extends ManaCost> extends ArrayList<T> implements M
@Override
public void assignPayment(Game game, Ability ability, ManaPool pool, Cost costToPay) {
if (!pool.isAutoPayment() && pool.getUnlockedManaType() == null) {
boolean wasUnlockedManaType = (pool.getUnlockedManaType() != null);
if (!pool.isAutoPayment() && !wasUnlockedManaType) {
// if auto payment is inactive and no mana type was clicked manually - do nothing
return;
}
ManaCosts referenceCosts = null;
if (pool.isForcedToPay()) {
referenceCosts = this.copy();
}
// attempt to pay colorless costs (not generic) mana costs first
for (ManaCost cost : this) {
if (!cost.isPaid() && cost instanceof ColorlessManaCost) {
@ -318,6 +325,31 @@ public class ManaCostsImpl<T extends ManaCost> extends ArrayList<T> implements M
}
// stop using mana of the clicked mana type
pool.lockManaType();
if (!wasUnlockedManaType) {
handleForcedToPayOnlyForCurrentPayment(game, pool, referenceCosts);
}
}
private void handleForcedToPayOnlyForCurrentPayment(Game game, ManaPool pool, ManaCosts referenceCosts) {
// for Word of Command
if (pool.isForcedToPay()) {
if (referenceCosts != null && this.getText().equals(referenceCosts.getText())) {
UUID playerId = pool.getPlayerId();
Player player = game.getPlayer(playerId);
if (player != null) {
game.undo(playerId);
this.clearPaid();
this.setX(referenceCosts.getX());
player.getManaPool().restoreMana(pool.getPoolBookmark());
game.bookmarkState();
}
}
}
}
public void forceManaRollback(Game game, ManaPool pool) {
// for Word of Command
handleForcedToPayOnlyForCurrentPayment(game, pool, this);
}
@Override

View file

@ -543,7 +543,7 @@ public class ContinuousEffects implements Serializable {
* @param game
* @return
*/
private List<AsThoughEffect> getApplicableAsThoughEffects(AsThoughEffectType type, Game game) {
public List<AsThoughEffect> getApplicableAsThoughEffects(AsThoughEffectType type, Game game) {
List<AsThoughEffect> asThoughEffectsList = new ArrayList<>();
if (asThoughEffectsMap.containsKey(type)) {
for (AsThoughEffect effect : asThoughEffectsMap.get(type)) {

View file

@ -398,6 +398,8 @@ public interface Game extends MageItem, Serializable {
void playPriority(UUID activePlayerId, boolean resuming);
void resetControlAfterSpellResolve(UUID topId);
boolean endTurn(Ability source);
int doAction(MageAction action);

View file

@ -1395,6 +1395,7 @@ public abstract class GameImpl implements Game, Serializable {
try {
top = state.getStack().peek();
top.resolve(this);
resetControlAfterSpellResolve(top.getId());
} finally {
if (top != null) {
state.getStack().remove(top, this); // seems partly redundant because move card from stack to grave is already done and the stack removed
@ -1408,6 +1409,37 @@ public abstract class GameImpl implements Game, Serializable {
}
}
}
@Override
public void resetControlAfterSpellResolve(UUID topId) {
// for Word of Command
Spell spell = getSpellOrLKIStack(topId);
if (spell != null) {
if (spell.getCommandedBy() != null) {
UUID commandedBy = spell.getCommandedBy();
UUID spellControllerId = null;
if (commandedBy.equals(spell.getControllerId())) {
spellControllerId = spell.getSpellAbility().getFirstTarget(); // i.e. resolved spell is Word of Command
} else {
spellControllerId = spell.getControllerId(); // i.e. resolved spell is the target opponent's spell
}
if (commandedBy != null && spellControllerId != null) {
Player turnController = getPlayer(commandedBy);
if (turnController != null) {
Player targetPlayer = getPlayer(spellControllerId);
if (targetPlayer != null) {
targetPlayer.setGameUnderYourControl(true, false);
informPlayers(turnController.getLogName() + " lost control over " + targetPlayer.getLogName());
if (targetPlayer.getTurnControlledBy().equals(turnController.getId())) {
turnController.getPlayersUnderYourControl().remove(targetPlayer.getId());
}
}
}
}
spell.setCommandedBy(null);
}
}
}
/**
* This checks if the stack gets filled iterated, without ever getting empty

View file

@ -67,6 +67,7 @@ public class Spell extends StackObjImpl implements Card {
private boolean faceDown;
private boolean countered;
private boolean resolving = false;
private UUID commandedBy = null; // for Word of Command
private boolean doneActivatingManaAbilities; // if this is true, the player is no longer allowed to pay the spell costs with activating of mana abilies
@ -121,6 +122,7 @@ public class Spell extends StackObjImpl implements Card {
this.faceDown = spell.faceDown;
this.countered = spell.countered;
this.resolving = spell.resolving;
this.commandedBy = spell.commandedBy;
this.doneActivatingManaAbilities = spell.doneActivatingManaAbilities;
this.targetChanged = spell.targetChanged;
@ -179,6 +181,12 @@ public class Spell extends StackObjImpl implements Card {
return false;
}
this.resolving = true;
if (commandedBy != null && !commandedBy.equals(getControllerId())) {
Player turnController = game.getPlayer(commandedBy);
if (turnController != null) {
turnController.controlPlayersTurn(game, controller.getId());
}
}
if (this.isInstant() || this.isSorcery()) {
int index = 0;
result = false;
@ -1024,4 +1032,12 @@ public class Spell extends StackObjImpl implements Card {
throw new UnsupportedOperationException("Not supported."); //To change body of generated methods, choose Tools | Templates.
}
public void setCommandedBy(UUID playerId) {
this.commandedBy = playerId;
}
public UUID getCommandedBy() {
return commandedBy;
}
}

View file

@ -37,10 +37,11 @@ public class SpellStack extends ArrayDeque<StackObject> {
try {
top = this.peek();
top.resolve(game);
game.resetControlAfterSpellResolve(top.getId());
} finally {
if (top != null) {
if (contains(top)) {
logger.warn("StackObject was still on the stack after resoving" + top.getName());
logger.warn("StackObject was still on the stack after resolving" + top.getName());
this.remove(top, game);
}
}

View file

@ -35,6 +35,8 @@ public class ManaPool implements Serializable {
private boolean autoPayment; // auto payment from mana pool: true - mode is active
private boolean autoPaymentRestricted; // auto payment from mana pool: true - if auto Payment is on, it will only pay if one kind of mana is in the pool
private ManaType unlockedManaType; // type of mana that was selected to pay manually
private boolean forcedToPay; // for Word of Command
private final List<ManaPoolItem> poolBookmark = new ArrayList<>(); // mana pool bookmark for rollback purposes
private final Set<ManaType> doNotEmptyManaTypes = new HashSet<>();
@ -43,6 +45,7 @@ public class ManaPool implements Serializable {
autoPayment = true;
autoPaymentRestricted = true;
unlockedManaType = null;
forcedToPay = false;
}
public ManaPool(final ManaPool pool) {
@ -53,6 +56,10 @@ public class ManaPool implements Serializable {
this.autoPayment = pool.autoPayment;
this.autoPaymentRestricted = pool.autoPaymentRestricted;
this.unlockedManaType = pool.unlockedManaType;
this.forcedToPay = pool.forcedToPay;
for (ManaPoolItem item : pool.poolBookmark) {
poolBookmark.add(item.copy());
}
this.doNotEmptyManaTypes.addAll(pool.doNotEmptyManaTypes);
}
@ -87,12 +94,12 @@ public class ManaPool implements Serializable {
* @return
*/
public boolean pay(ManaType manaType, Ability ability, Filter filter, Game game, Cost costToPay, Mana usedManaToPay) {
if (!autoPayment && manaType != unlockedManaType) {
if (!isAutoPayment() && manaType != unlockedManaType) {
// if manual payment and the needed mana type was not unlocked, nothing will be paid
return false;
}
ManaType possibleAsThoughtPoolManaType = null;
if (autoPayment && autoPaymentRestricted && !wasManaAddedBeyondStock() && manaType != unlockedManaType) {
if (isAutoPayment() && isAutoPaymentRestricted() && !wasManaAddedBeyondStock() && manaType != unlockedManaType) {
// if automatic restricted payment and there is already mana in the pool
// and the needed mana type was not unlocked, nothing will be paid
if (unlockedManaType != null) {
@ -111,6 +118,7 @@ public class ManaPool implements Serializable {
lockManaType(); // pay only one mana if mana payment is set to manually
return true;
}
for (ManaPoolItem mana : manaItems) {
if (filter != null) {
if (!filter.match(mana.getSourceObject(), game)) {
@ -120,7 +128,7 @@ public class ManaPool implements Serializable {
}
}
}
if (possibleAsThoughtPoolManaType == null && manaType != unlockedManaType && autoPayment && autoPaymentRestricted && mana.count() == mana.getStock()) {
if (possibleAsThoughtPoolManaType == null && manaType != unlockedManaType && isAutoPayment() && isAutoPaymentRestricted() && mana.count() == mana.getStock()) {
// no mana added beyond the stock so don't auto pay this
continue;
}
@ -436,19 +444,19 @@ public class ManaPool implements Serializable {
}
public boolean isAutoPayment() {
return autoPayment;
return autoPayment || forcedToPay;
}
public void setAutoPayment(boolean autoPayment) {
this.autoPayment = autoPayment;
}
public void setAutoPaymentRestricted(boolean autoPaymentRestricted) {
this.autoPaymentRestricted = autoPaymentRestricted;
public boolean isAutoPaymentRestricted() {
return autoPaymentRestricted || forcedToPay;
}
public boolean isAutoPaymentRestricted() {
return autoPaymentRestricted;
public void setAutoPaymentRestricted(boolean autoPaymentRestricted) {
this.autoPaymentRestricted = autoPaymentRestricted;
}
public ManaType getUnlockedManaType() {
@ -490,4 +498,39 @@ public class ManaPool implements Serializable {
return itemsCopy;
}
public void setForcedToPay(boolean forcedToPay) {
this.forcedToPay = forcedToPay;
}
public boolean isForcedToPay() {
return forcedToPay;
}
public UUID getPlayerId() {
return playerId;
}
public void storeMana() {
poolBookmark.clear();
poolBookmark.addAll(getManaItems());
}
public List<ManaPoolItem> getPoolBookmark() {
List<ManaPoolItem> itemsCopy = new ArrayList<>();
for (ManaPoolItem manaItem : poolBookmark) {
itemsCopy.add(manaItem.copy());
}
return itemsCopy;
}
public void restoreMana(List<ManaPoolItem> manaList) {
manaItems.clear();
if (!manaList.isEmpty()) {
List<ManaPoolItem> itemsCopy = new ArrayList<>();
for (ManaPoolItem manaItem : manaList) {
itemsCopy.add(manaItem.copy());
}
manaItems.addAll(itemsCopy);
}
}
}

View file

@ -278,6 +278,8 @@ public interface Player extends MageItem, Copyable<Player> {
*/
void setTurnControlledBy(UUID playerId);
List<UUID> getTurnControllers();
UUID getTurnControlledBy();
/**
@ -305,6 +307,8 @@ public interface Player extends MageItem, Copyable<Player> {
*/
void setGameUnderYourControl(boolean value);
void setGameUnderYourControl(boolean value, boolean fullRestore);
boolean isTestMode();
void setTestMode(boolean value);
@ -856,6 +860,8 @@ public interface Player extends MageItem, Copyable<Player> {
Set<UUID> getUsersAllowedToSeeHandCards();
void setPayManaMode(boolean payManaMode);
boolean isInPayManaMode();
void setMatchPlayer(MatchPlayer matchPlayer);

View file

@ -155,6 +155,7 @@ public abstract class PlayerImpl implements Player, Serializable {
protected boolean isGameUnderControl = true;
protected UUID turnController;
protected List<UUID> turnControllers = new ArrayList<>();
protected Set<UUID> playersUnderYourControl = new HashSet<>();
protected Set<UUID> usersAllowedToSeeHandCards = new HashSet<>();
@ -262,6 +263,8 @@ public abstract class PlayerImpl implements Player, Serializable {
this.isGameUnderControl = player.isGameUnderControl;
this.turnController = player.turnController;
this.turnControllers.clear();
this.turnControllers.addAll(player.turnControllers);
this.passed = player.passed;
this.passedTurn = player.passedTurn;
@ -342,6 +345,8 @@ public abstract class PlayerImpl implements Player, Serializable {
this.isGameUnderControl = player.isGameUnderControl();
this.turnController = player.getTurnControlledBy();
this.turnControllers.clear();
this.turnControllers.addAll(player.getTurnControllers());
this.reachedNextTurnAfterLeaving = player.hasReachedNextTurnAfterLeaving();
this.castSourceIdWithAlternateMana = player.getCastSourceIdWithAlternateMana();
this.castSourceIdManaCosts = player.getCastSourceIdManaCosts();
@ -397,6 +402,7 @@ public abstract class PlayerImpl implements Player, Serializable {
this.turns = 0;
this.isGameUnderControl = true;
this.turnController = this.getId();
this.turnControllers.clear();
this.playersUnderYourControl.clear();
this.passed = false;
@ -523,13 +529,13 @@ public abstract class PlayerImpl implements Player, Serializable {
@Override
public void controlPlayersTurn(Game game, UUID playerId) {
Player player = game.getPlayer(playerId);
player.setTurnControlledBy(this.getId());
game.informPlayers(getLogName() + " controls the turn of " + player.getLogName());
if (!playerId.equals(this.getId())) {
this.playersUnderYourControl.add(playerId);
Player player = game.getPlayer(playerId);
if (!player.hasLeft() && !player.hasLost()) {
player.setGameUnderYourControl(false);
player.setTurnControlledBy(this.getId());
game.informPlayers(getLogName() + " controls the turn of " + player.getLogName());
}
DelayedTriggeredAbility ability = new AtTheEndOfTurnStepPostDelayedTriggeredAbility(new LoseControlOnOtherPlayersControllerEffect(this.getLogName(), player.getLogName()));
ability.setSourceId(getId());
@ -541,6 +547,12 @@ public abstract class PlayerImpl implements Player, Serializable {
@Override
public void setTurnControlledBy(UUID playerId) {
this.turnController = playerId;
this.turnControllers.add(playerId);
}
@Override
public List<UUID> getTurnControllers() {
return this.turnControllers;
}
@Override
@ -566,9 +578,27 @@ public abstract class PlayerImpl implements Player, Serializable {
@Override
public void setGameUnderYourControl(boolean value) {
setGameUnderYourControl(value, true);
}
@Override
public void setGameUnderYourControl(boolean value, boolean fullRestore) {
this.isGameUnderControl = value;
if (isGameUnderControl) {
this.turnController = getId();
if (fullRestore) {
this.turnControllers.clear();
this.turnController = getId();
} else {
if (turnControllers.size() > 0) {
this.turnControllers.remove(turnControllers.size() - 1);
}
if (turnControllers.isEmpty()) {
this.turnController = getId();
} else {
this.turnController = turnControllers.get(turnControllers.size() - 1);
isGameUnderControl = false;
}
}
}
}
@ -983,6 +1013,11 @@ public abstract class PlayerImpl implements Player, Serializable {
return castSourceIdManaCosts;
}
@Override
public void setPayManaMode(boolean payManaMode) {
this.payManaMode = payManaMode;
}
@Override
public boolean isInPayManaMode() {
return payManaMode;