mirror of
https://github.com/magefree/mage.git
synced 2025-12-28 14:32:06 -08:00
Merge branch 'master' into Network_Upgrade
Conflicts: Mage.Client/src/main/java/mage/client/MageFrame.java Mage.Client/src/main/java/mage/client/chat/ChatPanel.java Mage.Client/src/main/java/mage/client/dialog/ConnectDialog.java Mage.Client/src/main/java/mage/client/dialog/UserRequestDialog.java Mage.Client/src/main/java/mage/client/game/PlayAreaPanel.java Mage.Client/src/main/java/mage/client/table/TablesPanel.java Mage.Common/src/mage/remote/SessionImpl.java Mage.Server/src/main/java/mage/server/game/GamesRoomImpl.java
This commit is contained in:
commit
38da157f8c
807 changed files with 13691 additions and 4039 deletions
|
|
@ -26,7 +26,7 @@ public interface MageObject extends MageItem, Serializable {
|
|||
Abilities<Ability> getAbilities();
|
||||
boolean hasAbility(UUID abilityId, Game game);
|
||||
|
||||
ObjectColor getColor();
|
||||
ObjectColor getColor(Game game);
|
||||
ManaCosts<ManaCost> getManaCost();
|
||||
|
||||
MageInt getPower();
|
||||
|
|
|
|||
|
|
@ -157,7 +157,7 @@ public abstract class MageObjectImpl implements MageObject {
|
|||
}
|
||||
|
||||
@Override
|
||||
public ObjectColor getColor() {
|
||||
public ObjectColor getColor(Game game) {
|
||||
return color;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -537,4 +537,6 @@ public interface Ability extends Controllable, Serializable {
|
|||
*/
|
||||
|
||||
MageObject getSourceObjectIfItStillExists(Game game);
|
||||
|
||||
String getTargetDescription(Targets targets, Game game);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -352,10 +352,18 @@ public abstract class AbilityImpl implements Ability {
|
|||
//20100716 - 601.2e
|
||||
if (sourceObject != null) {
|
||||
sourceObject.adjustCosts(this, game);
|
||||
for (Ability ability : sourceObject.getAbilities()) {
|
||||
if (ability instanceof AdjustingSourceCosts) {
|
||||
((AdjustingSourceCosts)ability).adjustCosts(this, game);
|
||||
}
|
||||
if (sourceObject instanceof Card) {
|
||||
for (Ability ability : ((Card)sourceObject).getAbilities(game)) {
|
||||
if (ability instanceof AdjustingSourceCosts) {
|
||||
((AdjustingSourceCosts)ability).adjustCosts(this, game);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (Ability ability : sourceObject.getAbilities()) {
|
||||
if (ability instanceof AdjustingSourceCosts) {
|
||||
((AdjustingSourceCosts)ability).adjustCosts(this, game);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1074,6 +1082,7 @@ public abstract class AbilityImpl implements Ability {
|
|||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTargetDescription(Targets targets, Game game) {
|
||||
return getTargetDescriptionForLog(targets, game);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import java.util.UUID;
|
|||
import mage.abilities.effects.Effect;
|
||||
import mage.constants.Zone;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.GameEvent;
|
||||
|
||||
/**
|
||||
*
|
||||
|
|
@ -48,23 +49,29 @@ public abstract class StateTriggeredAbility extends TriggeredAbilityImpl {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void trigger(Game game, UUID controllerId) {
|
||||
public final boolean checkEventType(GameEvent event, Game game) {
|
||||
//20100716 - 603.8
|
||||
Boolean triggered = (Boolean) game.getState().getValue(this.getSourceId().toString() + "triggered");
|
||||
Boolean triggered = (Boolean) game.getState().getValue(getSourceId().toString() + "triggered");
|
||||
if (triggered == null) {
|
||||
triggered = Boolean.FALSE;
|
||||
}
|
||||
if (!triggered) {
|
||||
game.getState().setValue(this.getSourceId().toString() + "triggered", Boolean.TRUE);
|
||||
super.trigger(game, controllerId);
|
||||
}
|
||||
return !triggered;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void trigger(Game game, UUID controllerId) {
|
||||
//20100716 - 603.8
|
||||
game.getState().setValue(this.getSourceId().toString() + "triggered", Boolean.TRUE);
|
||||
super.trigger(game, controllerId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean resolve(Game game) {
|
||||
//20100716 - 603.8
|
||||
boolean result = super.resolve(game);
|
||||
game.getState().setValue(this.getSourceId().toString() + "triggered", Boolean.FALSE);
|
||||
return super.resolve(game);
|
||||
return result;
|
||||
}
|
||||
|
||||
public void counter(Game game) {
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ import mage.abilities.TriggeredAbilityImpl;
|
|||
import mage.abilities.effects.Effect;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.GameEvent;
|
||||
import mage.game.events.GameEvent.EventType;
|
||||
|
||||
/**
|
||||
*
|
||||
|
|
@ -60,7 +59,7 @@ public class BecomesTargetTriggeredAbility extends TriggeredAbilityImpl {
|
|||
|
||||
@Override
|
||||
public boolean checkTrigger(GameEvent event, Game game) {
|
||||
return event.getTargetId().equals(sourceId);
|
||||
return event.getTargetId().equals(getSourceId());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ public class EntersBattlefieldTriggeredAbility extends TriggeredAbilityImpl {
|
|||
if (noRule) {
|
||||
return super.getRule();
|
||||
}
|
||||
return new StringBuilder(rulePrefix != null ? rulePrefix : "").append("When {this} enters the battlefield, ").append(super.getRule()).toString();
|
||||
return (rulePrefix != null ? rulePrefix : "") + "When {this} enters the battlefield, "+ super.getRule();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@ import mage.abilities.TriggeredAbilityImpl;
|
|||
import mage.abilities.effects.Effect;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.GameEvent;
|
||||
import mage.game.events.GameEvent.EventType;
|
||||
import mage.game.permanent.Permanent;
|
||||
|
||||
/**
|
||||
|
|
@ -44,10 +43,10 @@ import mage.game.permanent.Permanent;
|
|||
public class LandfallAbility extends TriggeredAbilityImpl {
|
||||
|
||||
public LandfallAbility(Effect effect, boolean optional) {
|
||||
super(Zone.BATTLEFIELD, effect, optional);
|
||||
this(Zone.BATTLEFIELD, effect, optional);
|
||||
}
|
||||
|
||||
public LandfallAbility ( Zone zone, Effect effect, Boolean optional ) {
|
||||
public LandfallAbility (Zone zone, Effect effect, Boolean optional ) {
|
||||
super(zone, effect, optional);
|
||||
}
|
||||
|
||||
|
|
@ -63,15 +62,12 @@ public class LandfallAbility extends TriggeredAbilityImpl {
|
|||
@Override
|
||||
public boolean checkTrigger(GameEvent event, Game game) {
|
||||
Permanent permanent = game.getPermanent(event.getTargetId());
|
||||
if (permanent != null && permanent.getCardType().contains(CardType.LAND) && permanent.getControllerId().equals(this.controllerId)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return permanent != null && permanent.getCardType().contains(CardType.LAND) && permanent.getControllerId().equals(this.controllerId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRule() {
|
||||
return "Landfall - Whenever a land enters the battlefield under your control, " + super.getRule();
|
||||
return "<i>Landfall</i> — Whenever a land enters the battlefield under your control, " + super.getRule();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -54,12 +54,14 @@ public class PutIntoGraveFromBattlefieldAllTriggeredAbility extends TriggeredAbi
|
|||
super(Zone.BATTLEFIELD, effect, optional);
|
||||
this.filter = filter;
|
||||
this.onlyToControllerGraveyard = onlyToControllerGraveyard;
|
||||
this.setTargetPointer = setTargetPointer;
|
||||
}
|
||||
|
||||
public PutIntoGraveFromBattlefieldAllTriggeredAbility(final PutIntoGraveFromBattlefieldAllTriggeredAbility ability) {
|
||||
super(ability);
|
||||
this.filter = ability.filter;
|
||||
this.onlyToControllerGraveyard = ability.onlyToControllerGraveyard;
|
||||
this.setTargetPointer = ability.setTargetPointer;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -3,13 +3,13 @@ package mage.abilities.condition.common;
|
|||
import mage.abilities.Ability;
|
||||
import mage.abilities.condition.Condition;
|
||||
import mage.game.Game;
|
||||
import mage.watchers.Watcher;
|
||||
import mage.watchers.common.LandfallWatcher;
|
||||
|
||||
/**
|
||||
* @author Loki
|
||||
*/
|
||||
public class LandfallCondition implements Condition {
|
||||
private static LandfallCondition instance = new LandfallCondition();
|
||||
private final static LandfallCondition instance = new LandfallCondition();
|
||||
|
||||
public static LandfallCondition getInstance() {
|
||||
return instance;
|
||||
|
|
@ -20,7 +20,7 @@ public class LandfallCondition implements Condition {
|
|||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Watcher watcher = game.getState().getWatchers().get("LandPlayed", source.getControllerId());
|
||||
return watcher.conditionMet();
|
||||
LandfallWatcher watcher = (LandfallWatcher) game.getState().getWatchers().get("LandPlayed");
|
||||
return watcher != null && watcher.landPlayed(source.getControllerId());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ public class RemoveCounterCost extends CostImpl {
|
|||
int numberOfCountersSelected = 1;
|
||||
if (countersLeft > 1 && countersOnPermanent > 1) {
|
||||
numberOfCountersSelected = controller.getAmount(1, Math.min(countersLeft, countersOnPermanent),
|
||||
new StringBuilder("Remove how many counters from ").append(permanent.getLogName()).toString(), game);
|
||||
new StringBuilder("Remove how many counters from ").append(permanent.getIdName()).toString(), game);
|
||||
}
|
||||
permanent.removeCounters(counterName, numberOfCountersSelected, game);
|
||||
if (permanent.getCounters().getCount(counterName) == 0 ){
|
||||
|
|
|
|||
|
|
@ -390,7 +390,16 @@ public class ContinuousEffects implements Serializable {
|
|||
}
|
||||
|
||||
private boolean checkAbilityStillExists(Ability ability, ContinuousEffect effect, GameEvent event, Game game) {
|
||||
if (effect.getDuration().equals(Duration.OneUse) || effect.getDuration().equals(Duration.Custom) || ability.getSourceId() == null) { // needed for some special replacment effects (e.g. Undying) or commander replacement effect
|
||||
switch(effect.getDuration()) { // effects with fixed duration don't need an object with the source ability (e.g. a silence cast with isochronic Scepter has no more a card object
|
||||
case EndOfCombat:
|
||||
case EndOfGame:
|
||||
case EndOfStep:
|
||||
case EndOfTurn:
|
||||
case OneUse:
|
||||
case Custom: // custom duration means the effect ends itself if needed
|
||||
return true;
|
||||
}
|
||||
if (ability.getSourceId() == null) { // commander replacement effect
|
||||
return true;
|
||||
}
|
||||
MageObject object;
|
||||
|
|
@ -794,7 +803,7 @@ public class ContinuousEffects implements Serializable {
|
|||
}
|
||||
}
|
||||
// Must be called here for some effects to be able to work correctly
|
||||
// TODO: add info which effects that need
|
||||
// TODO: add info which effects need that call
|
||||
game.applyEffects();
|
||||
} while (true);
|
||||
return caught;
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ public class AddManaOfAnyTypeProducedEffect extends ManaEffect {
|
|||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Permanent permanent = game.getPermanent(this.targetPointer.getFirst(game, source));
|
||||
Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source));
|
||||
if (permanent != null) {
|
||||
Player targetController = game.getPlayer(permanent.getControllerId());
|
||||
if (targetController == null) {
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ public class ChooseNewTargetsTargetEffect extends OneShotEffect {
|
|||
super(effect);
|
||||
this.forceChange = effect.forceChange;
|
||||
this.onlyOneTarget = effect.onlyOneTarget;
|
||||
this.filterNewTarget = effect.filterNewTarget;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ public class CopyEffect extends ContinuousEffectImpl {
|
|||
return false;
|
||||
}
|
||||
permanent.setName(target.getName());
|
||||
permanent.getColor().setColor(target.getColor());
|
||||
permanent.getColor(game).setColor(target.getColor(game));
|
||||
permanent.getManaCost().clear();
|
||||
permanent.getManaCost().add(target.getManaCost());
|
||||
permanent.getCardType().clear();
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ public class CopyTokenEffect extends ContinuousEffectImpl {
|
|||
public boolean apply(Game game, Ability source) {
|
||||
Permanent permanent = game.getPermanent(source.getSourceId());
|
||||
permanent.setName(token.getName());
|
||||
permanent.getColor().setColor(token.getColor());
|
||||
permanent.getColor(game).setColor(token.getColor(game));
|
||||
permanent.getCardType().clear();
|
||||
for (CardType type: token.getCardType()) {
|
||||
permanent.getCardType().add(type);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification, are
|
||||
* permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this list of
|
||||
* conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||
* of conditions and the following disclaimer in the documentation and/or other materials
|
||||
* provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* The views and conclusions contained in the software and documentation are those of the
|
||||
* authors and should not be interpreted as representing official policies, either expressed
|
||||
* or implied, of BetaSteward_at_googlemail.com.
|
||||
*/
|
||||
package mage.abilities.effects.common;
|
||||
|
||||
import java.util.UUID;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.cards.Card;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.Zone;
|
||||
import mage.game.Game;
|
||||
import mage.players.Player;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Jgod
|
||||
*/
|
||||
public class ExileGraveyardAllPlayersEffect extends OneShotEffect {
|
||||
public ExileGraveyardAllPlayersEffect() {
|
||||
super(Outcome.Detriment);
|
||||
staticText = "exile all cards from all graveyards";
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExileGraveyardAllPlayersEffect copy() {
|
||||
return new ExileGraveyardAllPlayersEffect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Player controller = game.getPlayer(source.getControllerId());
|
||||
if (controller == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (UUID playerId : controller.getInRange()) {
|
||||
Player player = game.getPlayer(playerId);
|
||||
if (player != null) {
|
||||
for (UUID cid : player.getGraveyard().copy()) {
|
||||
Card card = game.getCard(cid);
|
||||
if (card != null) {
|
||||
controller.moveCardToExileWithInfo(card, null, "", source.getSourceId(), game, Zone.GRAVEYARD, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -54,12 +54,11 @@ public class PreventDamageBySourceEffect extends PreventionEffectImpl {
|
|||
|
||||
public PreventDamageBySourceEffect(FilterObject filterObject) {
|
||||
super(Duration.EndOfTurn);
|
||||
if (filterObject.getMessage().equals("a")) {
|
||||
this.target = new TargetSource(new FilterObject("source"));
|
||||
} else {
|
||||
this.target = new TargetSource(new FilterObject(filterObject.getMessage() + " source"));
|
||||
if (!filterObject.getMessage().endsWith("source")) {
|
||||
filterObject.setMessage(filterObject.getMessage() + " source");
|
||||
}
|
||||
staticText = "Prevent all damage " + filterObject.getMessage() + " source of your choice would deal this turn";
|
||||
this.target = new TargetSource(filterObject);
|
||||
staticText = "Prevent all damage " + filterObject.getMessage() + " of your choice would deal this turn";
|
||||
}
|
||||
|
||||
public PreventDamageBySourceEffect(final PreventDamageBySourceEffect effect) {
|
||||
|
|
|
|||
|
|
@ -82,6 +82,9 @@ public class PutLibraryIntoGraveTargetEffect extends OneShotEffect {
|
|||
|
||||
@Override
|
||||
public String getText(Mode mode) {
|
||||
if (staticText != null && !staticText.isEmpty()) {
|
||||
return staticText;
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String message = amount.getMessage();
|
||||
|
||||
|
|
|
|||
|
|
@ -27,14 +27,13 @@
|
|||
*/
|
||||
package mage.abilities.effects.common;
|
||||
|
||||
import java.util.UUID;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.cards.CardsImpl;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.Zone;
|
||||
import mage.filter.common.FilterControlledPermanent;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.players.Player;
|
||||
import mage.target.common.TargetControlledPermanent;
|
||||
import mage.util.CardUtil;
|
||||
|
|
@ -44,13 +43,14 @@ import mage.util.CardUtil;
|
|||
* @author Plopmans
|
||||
*/
|
||||
public class ReturnToHandChosenControlledPermanentEffect extends OneShotEffect {
|
||||
|
||||
|
||||
private final FilterControlledPermanent filter;
|
||||
private int number;
|
||||
|
||||
|
||||
public ReturnToHandChosenControlledPermanentEffect(FilterControlledPermanent filter) {
|
||||
this(filter, 1);
|
||||
}
|
||||
|
||||
public ReturnToHandChosenControlledPermanentEffect(FilterControlledPermanent filter, int number) {
|
||||
super(Outcome.ReturnToHand);
|
||||
this.filter = filter;
|
||||
|
|
@ -71,15 +71,13 @@ public class ReturnToHandChosenControlledPermanentEffect extends OneShotEffect {
|
|||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Player player = game.getPlayer(source.getControllerId());
|
||||
if (player != null) {
|
||||
TargetControlledPermanent target = new TargetControlledPermanent(number, number, filter, true);
|
||||
if (player.choose(this.outcome, target, source.getSourceId(), game)) {
|
||||
for (UUID targetCreatureId : target.getTargets()) {
|
||||
Permanent permanent = game.getPermanent(targetCreatureId);
|
||||
if (permanent != null) {
|
||||
player.moveCardToHandWithInfo(permanent, source.getSourceId(), game, Zone.BATTLEFIELD);
|
||||
}
|
||||
Player controller = game.getPlayer(source.getControllerId());
|
||||
if (controller != null) {
|
||||
int available = game.getBattlefield().count(filter, source.getSourceId(), source.getControllerId(), game);
|
||||
if (available > 0) {
|
||||
TargetControlledPermanent target = new TargetControlledPermanent(Math.min(number, available), number, filter, true);
|
||||
if (controller.chooseTarget(this.outcome, target, source, game)) {
|
||||
controller.moveCards(new CardsImpl(target.getTargets()), Zone.BATTLEFIELD, Zone.HAND, source, game);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -90,7 +90,6 @@ public class SacrificeEffect extends OneShotEffect{
|
|||
// A spell or ability could have removed the only legal target this player
|
||||
// had, if thats the case this ability should fizzle.
|
||||
if (amount > 0 && target.canChoose(source.getSourceId(), player.getId(), game)) {
|
||||
boolean abilityApplied = false;
|
||||
while (!target.isChosen() && target.canChoose(player.getId(), game) && player.isInGame()) {
|
||||
player.chooseTarget(Outcome.Sacrifice, target, source, game);
|
||||
}
|
||||
|
|
@ -99,11 +98,11 @@ public class SacrificeEffect extends OneShotEffect{
|
|||
Permanent permanent = game.getPermanent(target.getTargets().get(idx));
|
||||
|
||||
if ( permanent != null ) {
|
||||
abilityApplied |= permanent.sacrifice(source.getSourceId(), game);
|
||||
permanent.sacrifice(source.getSourceId(), game);
|
||||
}
|
||||
}
|
||||
|
||||
return abilityApplied;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,14 +87,11 @@ public class SacrificeOpponentsEffect extends OneShotEffect {
|
|||
filter.add(new ControllerPredicate(TargetController.YOU));
|
||||
for (UUID playerId : game.getOpponents(source.getControllerId())) {
|
||||
Player player = game.getPlayer(playerId);
|
||||
if (player != null) {
|
||||
|
||||
if (player != null) {
|
||||
int numTargets = Math.min(amount.calculate(game, source, this), game.getBattlefield().countAll(filter, player.getId(), game));
|
||||
TargetPermanent target = new TargetPermanent(numTargets, numTargets, filter, false);
|
||||
TargetPermanent target = new TargetPermanent(numTargets, numTargets, filter, true);
|
||||
if (target.canChoose(player.getId(), game)) {
|
||||
while (!target.isChosen() && player.isInGame()) {
|
||||
player.chooseTarget(Outcome.Sacrifice, target, source, game);
|
||||
}
|
||||
player.chooseTarget(Outcome.Sacrifice, target, source, game);
|
||||
perms.addAll(target.getTargets());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@
|
|||
|
||||
package mage.abilities.effects.common;
|
||||
|
||||
import mage.MageObject;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.constants.Outcome;
|
||||
|
|
@ -56,13 +57,17 @@ public class SacrificeSourceEffect extends OneShotEffect {
|
|||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Permanent permanent = game.getPermanent(source.getSourceId());
|
||||
if (permanent != null) {
|
||||
MageObject sourceObject = source.getSourceObjectIfItStillExists(game);
|
||||
if (sourceObject instanceof Permanent) {
|
||||
Permanent permanent = (Permanent) sourceObject;
|
||||
// you can only sacrifice a permanent you control
|
||||
if (source.getControllerId().equals(permanent.getControllerId())) {
|
||||
return permanent.sacrifice(source.getSourceId(), game);
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
// no permanent?
|
||||
sourceObject.getName();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ public class CanBlockOnlyFlyingAttachedEffect extends RestrictionEffect {
|
|||
if (attachmentType.equals(AttachmentType.AURA)) {
|
||||
this.staticText = "Enchanted creature can block only creatures with flying";
|
||||
} else {
|
||||
this.staticText = "Equiped creature can block only creatures with flying";
|
||||
this.staticText = "Equipped creature can block only creatures with flying";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ public class CantBeBlockedAttachedEffect extends RestrictionEffect {
|
|||
if (attachmentType.equals(AttachmentType.AURA)) {
|
||||
this.staticText = "Enchanted creature can't be blocked";
|
||||
} else {
|
||||
this.staticText = "Equiped creature can't be blocked";
|
||||
this.staticText = "Equipped creature can't be blocked";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -48,9 +48,9 @@ public class CantBeBlockedByCreaturesAttachedEffect extends RestrictionEffect {
|
|||
this.filter = filter;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (attachmentType.equals(AttachmentType.AURA)) {
|
||||
sb.append("Enchanted");
|
||||
sb.append("Enchanted ");
|
||||
} else {
|
||||
sb.append("Equipped");
|
||||
sb.append("Equipped ");
|
||||
}
|
||||
staticText = sb.append("creature can't be blocked ")
|
||||
.append(filter.getMessage().startsWith("except by") ? "":"by ").append(filter.getMessage()).toString();
|
||||
|
|
|
|||
|
|
@ -63,15 +63,15 @@ public class AddCardColorAttachedEffect extends ContinuousEffectImpl {
|
|||
Permanent target = game.getPermanent(equipment.getAttachedTo());
|
||||
if (target != null) {
|
||||
if (addedColor.isBlack())
|
||||
target.getColor().setBlack(true);
|
||||
target.getColor(game).setBlack(true);
|
||||
if (addedColor.isBlue())
|
||||
target.getColor().setBlue(true);
|
||||
target.getColor(game).setBlue(true);
|
||||
if (addedColor.isWhite())
|
||||
target.getColor().setWhite(true);
|
||||
target.getColor(game).setWhite(true);
|
||||
if (addedColor.isGreen())
|
||||
target.getColor().setGreen(true);
|
||||
target.getColor(game).setGreen(true);
|
||||
if (addedColor.isRed())
|
||||
target.getColor().setRed(true);
|
||||
target.getColor(game).setRed(true);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -85,11 +85,11 @@ public class BecomesBasicLandEnchantedEffect extends ContinuousEffectImpl {
|
|||
if (permanent != null) {
|
||||
switch (layer) {
|
||||
case ColorChangingEffects_5:
|
||||
permanent.getColor().setWhite(false);
|
||||
permanent.getColor().setGreen(false);
|
||||
permanent.getColor().setBlack(false);
|
||||
permanent.getColor().setBlue(false);
|
||||
permanent.getColor().setRed(false);
|
||||
permanent.getColor(game).setWhite(false);
|
||||
permanent.getColor(game).setGreen(false);
|
||||
permanent.getColor(game).setBlack(false);
|
||||
permanent.getColor(game).setBlue(false);
|
||||
permanent.getColor(game).setRed(false);
|
||||
break;
|
||||
case AbilityAddingRemovingEffects_6:
|
||||
permanent.removeAllAbilities(source.getSourceId(), game);
|
||||
|
|
|
|||
|
|
@ -41,8 +41,6 @@ import mage.constants.Layer;
|
|||
import mage.constants.Outcome;
|
||||
import mage.constants.SubLayer;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.game.stack.StackObject;
|
||||
import mage.players.Player;
|
||||
|
||||
/**
|
||||
|
|
@ -50,7 +48,7 @@ import mage.players.Player;
|
|||
*/
|
||||
public class BecomesColorTargetEffect extends ContinuousEffectImpl {
|
||||
|
||||
private final ObjectColor setColor;
|
||||
private ObjectColor setColor;
|
||||
|
||||
/**
|
||||
* Set the color of a spell or permanent
|
||||
|
|
@ -76,49 +74,55 @@ public class BecomesColorTargetEffect extends ContinuousEffectImpl {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
public void init(Ability source, Game game) {
|
||||
Player controller = game.getPlayer(source.getControllerId());
|
||||
if (controller == null) {
|
||||
return false;
|
||||
}
|
||||
boolean result = false;
|
||||
ObjectColor objectColor;
|
||||
return;
|
||||
}
|
||||
if (setColor == null) {
|
||||
ChoiceColor choice = new ChoiceColor();
|
||||
while (!choice.isChosen()) {
|
||||
controller.choose(Outcome.PutManaInPool, choice, game);
|
||||
if (!controller.isInGame()) {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (choice.getColor() != null) {
|
||||
objectColor = choice.getColor();
|
||||
setColor = choice.getColor();
|
||||
} else {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
if (!game.isSimulation()) {
|
||||
game.informPlayers(controller.getLogName() + " has chosen the color: " + objectColor.toString());
|
||||
game.informPlayers(controller.getLogName() + " has chosen the color: " + setColor.toString());
|
||||
}
|
||||
} else {
|
||||
objectColor = this.setColor;
|
||||
}
|
||||
|
||||
|
||||
super.init(source, game); //To change body of generated methods, choose Tools | Templates.
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Player controller = game.getPlayer(source.getControllerId());
|
||||
if (controller == null) {
|
||||
return false;
|
||||
}
|
||||
if (objectColor != null) {
|
||||
if (setColor != null) {
|
||||
boolean objectFound = false;
|
||||
for (UUID targetId :targetPointer.getTargets(game, source)) {
|
||||
MageObject o = game.getObject(targetId);
|
||||
if (o != null) {
|
||||
if (o instanceof Permanent || o instanceof StackObject) {
|
||||
o.getColor().setColor(objectColor);
|
||||
result = true;
|
||||
}
|
||||
MageObject targetObject = game.getObject(targetId);
|
||||
if (targetObject != null) {
|
||||
objectFound = true;
|
||||
targetObject.getColor(game).setColor(setColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!result) {
|
||||
if (this.getDuration().equals(Duration.Custom)) {
|
||||
if (!objectFound && this.getDuration().equals(Duration.Custom)) {
|
||||
this.discard();
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
throw new UnsupportedOperationException("No color set");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -93,8 +93,8 @@ public class BecomesCreatureAllEffect extends ContinuousEffectImpl {
|
|||
break;
|
||||
case ColorChangingEffects_5:
|
||||
if (sublayer == SubLayer.NA) {
|
||||
if (token.getColor().hasColor())
|
||||
permanent.getColor().setColor(token.getColor());
|
||||
if (token.getColor(game).hasColor())
|
||||
permanent.getColor(game).setColor(token.getColor(game));
|
||||
}
|
||||
break;
|
||||
case AbilityAddingRemovingEffects_6:
|
||||
|
|
|
|||
|
|
@ -115,14 +115,14 @@ public class BecomesCreatureAttachedEffect extends ContinuousEffectImpl {
|
|||
case ColorChangingEffects_5:
|
||||
if (sublayer == SubLayer.NA) {
|
||||
if (loseOther) {
|
||||
permanent.getColor().setBlack(false);
|
||||
permanent.getColor().setGreen(false);
|
||||
permanent.getColor().setBlue(false);
|
||||
permanent.getColor().setWhite(false);
|
||||
permanent.getColor().setRed(false);
|
||||
permanent.getColor(game).setBlack(false);
|
||||
permanent.getColor(game).setGreen(false);
|
||||
permanent.getColor(game).setBlue(false);
|
||||
permanent.getColor(game).setWhite(false);
|
||||
permanent.getColor(game).setRed(false);
|
||||
}
|
||||
if (token.getColor().hasColor()) {
|
||||
permanent.getColor().setColor(token.getColor());
|
||||
if (token.getColor(game).hasColor()) {
|
||||
permanent.getColor(game).setColor(token.getColor(game));
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -105,8 +105,8 @@ public class BecomesCreatureSourceEffect extends ContinuousEffectImpl implements
|
|||
break;
|
||||
case ColorChangingEffects_5:
|
||||
if (sublayer == SubLayer.NA) {
|
||||
if (token.getColor().hasColor()) {
|
||||
permanent.getColor().setColor(token.getColor());
|
||||
if (token.getColor(game).hasColor()) {
|
||||
permanent.getColor(game).setColor(token.getColor(game));
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -111,14 +111,14 @@ public class BecomesCreatureTargetEffect extends ContinuousEffectImpl {
|
|||
case ColorChangingEffects_5:
|
||||
if (sublayer == SubLayer.NA) {
|
||||
if (loseAllAbilities) {
|
||||
permanent.getColor().setBlack(false);
|
||||
permanent.getColor().setGreen(false);
|
||||
permanent.getColor().setBlue(false);
|
||||
permanent.getColor().setWhite(false);
|
||||
permanent.getColor().setBlack(false);
|
||||
permanent.getColor(game).setBlack(false);
|
||||
permanent.getColor(game).setGreen(false);
|
||||
permanent.getColor(game).setBlue(false);
|
||||
permanent.getColor(game).setWhite(false);
|
||||
permanent.getColor(game).setBlack(false);
|
||||
}
|
||||
if (token.getColor().hasColor()) {
|
||||
permanent.getColor().setColor(token.getColor());
|
||||
if (token.getColor(game).hasColor()) {
|
||||
permanent.getColor(game).setColor(token.getColor(game));
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -118,7 +118,7 @@ public class BecomesFaceDownCreatureAllEffect extends ContinuousEffectImpl imple
|
|||
permanent.getManaCost().clear();
|
||||
break;
|
||||
case ColorChangingEffects_5:
|
||||
permanent.getColor().setColor(new ObjectColor());
|
||||
permanent.getColor(game).setColor(new ObjectColor());
|
||||
break;
|
||||
case AbilityAddingRemovingEffects_6:
|
||||
Card card = game.getCard(permanent.getId()); //
|
||||
|
|
|
|||
|
|
@ -175,7 +175,7 @@ public class BecomesFaceDownCreatureEffect extends ContinuousEffectImpl implemen
|
|||
permanent.getSubtype().clear();
|
||||
break;
|
||||
case ColorChangingEffects_5:
|
||||
permanent.getColor().setColor(new ObjectColor());
|
||||
permanent.getColor(game).setColor(new ObjectColor());
|
||||
break;
|
||||
case AbilityAddingRemovingEffects_6:
|
||||
Card card = game.getCard(permanent.getId()); //
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ public class GainProtectionFromColorTargetEffect extends GainAbilityTargetEffect
|
|||
|
||||
public GainProtectionFromColorTargetEffect(Duration duration) {
|
||||
super(new ProtectionAbility(new FilterCard()), duration);
|
||||
choice = new ChoiceColor();
|
||||
choice = new ChoiceColor(true);
|
||||
}
|
||||
|
||||
public GainProtectionFromColorTargetEffect(final GainProtectionFromColorTargetEffect effect) {
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ public class SetCardColorAttachedEffect extends ContinuousEffectImpl {
|
|||
if (equipment != null && equipment.getAttachedTo() != null) {
|
||||
Permanent target = game.getPermanent(equipment.getAttachedTo());
|
||||
if (target != null) {
|
||||
target.getColor().setColor(setColor);
|
||||
target.getColor(game).setColor(setColor);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ public class SetCardColorSourceEffect extends ContinuousEffectImpl {
|
|||
MageObject o = game.getObject(source.getSourceId());
|
||||
if (o != null) {
|
||||
if (o instanceof Permanent || o instanceof StackObject) {
|
||||
o.getColor().setColor(setColor);
|
||||
o.getColor(game).setColor(setColor);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -78,22 +78,22 @@ public class SearchLibraryPutOnLibraryEffect extends SearchEffect {
|
|||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Player controller = game.getPlayer(source.getControllerId());
|
||||
MageObject sourceObject = game.getObject(source.getSourceId());
|
||||
MageObject sourceObject = source.getSourceObject(game);
|
||||
if (controller == null || sourceObject == null) {
|
||||
return false;
|
||||
}
|
||||
if (controller.searchLibrary(target, game)) {
|
||||
List<Card> cards = new ArrayList<>();
|
||||
for (UUID cardId: (List<UUID>)target.getTargets()) {
|
||||
for (UUID cardId: target.getTargets()) {
|
||||
Card card = controller.getLibrary().remove(cardId, game);
|
||||
if (card != null) {
|
||||
cards.add(card);
|
||||
}
|
||||
}
|
||||
Cards foundCards = new CardsImpl();
|
||||
foundCards.addAll(cards);
|
||||
foundCards.addAll(target.getTargets());
|
||||
if (reveal) {
|
||||
controller.revealCards(sourceObject.getName(), foundCards, game);
|
||||
controller.revealCards(sourceObject.getIdName(), foundCards, game);
|
||||
}
|
||||
if (forceShuffle) {
|
||||
controller.shuffleLibrary(game);
|
||||
|
|
@ -117,7 +117,7 @@ public class SearchLibraryPutOnLibraryEffect extends SearchEffect {
|
|||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Search your library for a ").append(target.getTargetName());
|
||||
if (reveal) {
|
||||
sb.append("and reveal that card. Shuffle");
|
||||
sb.append(" and reveal that card. Shuffle");
|
||||
} else {
|
||||
sb.append(", then shuffle");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ public class AffinityForArtifactsAbility extends SimpleStaticAbility implements
|
|||
|
||||
@Override
|
||||
public String getRule() {
|
||||
return "Affinity for artifacts";
|
||||
return "affinity for artifacts <i>(This spell costs {1} less to cast for each artifact you control.)</i>";
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -32,10 +32,8 @@ import mage.abilities.SpellAbility;
|
|||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.costs.AdjustingSourceCosts;
|
||||
import mage.abilities.effects.common.AffinityEffect;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.Zone;
|
||||
import mage.filter.common.FilterControlledPermanent;
|
||||
import mage.filter.predicate.mageobject.CardTypePredicate;
|
||||
import mage.filter.predicate.mageobject.SubtypePredicate;
|
||||
import mage.game.Game;
|
||||
import mage.util.CardUtil;
|
||||
|
|
@ -50,12 +48,14 @@ public class AffinityForLandTypeAbility extends SimpleStaticAbility implements A
|
|||
private final FilterControlledPermanent filter;
|
||||
|
||||
String text;
|
||||
String landType;
|
||||
|
||||
public AffinityForLandTypeAbility(String landType, String text) {
|
||||
super(Zone.OUTSIDE, new AffinityEffect(getFilter(landType)));
|
||||
this.filter = getFilter(landType);
|
||||
setRuleAtTheTop(true);
|
||||
this.text = text;
|
||||
this.landType = landType;
|
||||
}
|
||||
|
||||
private static FilterControlledPermanent getFilter(String landType) {
|
||||
|
|
@ -78,7 +78,7 @@ public class AffinityForLandTypeAbility extends SimpleStaticAbility implements A
|
|||
|
||||
@Override
|
||||
public String getRule() {
|
||||
return "Affinity for " + text;
|
||||
return "Affinity for " + text + " <i>(This spell costs 1 less to cast for each " + landType + " you control.)</i>";
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -90,4 +90,4 @@ public class AffinityForLandTypeAbility extends SimpleStaticAbility implements A
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -207,7 +207,7 @@ class ConvokeEffect extends OneShotEffect {
|
|||
String manaName;
|
||||
if (!perm.isTapped() && perm.tap(game)) {
|
||||
ManaPool manaPool = controller.getManaPool();
|
||||
Choice chooseManaType = buildChoice(perm.getColor(), unpaid.getMana());
|
||||
Choice chooseManaType = buildChoice(perm.getColor(game), unpaid.getMana());
|
||||
if (chooseManaType.getChoices().size() > 0) {
|
||||
if (chooseManaType.getChoices().size() > 1) {
|
||||
chooseManaType.getChoices().add("Colorless");
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ import mage.constants.Zone;
|
|||
import mage.abilities.ActivatedAbilityImpl;
|
||||
import mage.abilities.costs.Cost;
|
||||
import mage.abilities.costs.common.DiscardSourceCost;
|
||||
import mage.abilities.costs.mana.ManaCosts;
|
||||
import mage.abilities.costs.mana.ManaCost;
|
||||
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
|
||||
import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect;
|
||||
import mage.filter.FilterCard;
|
||||
|
|
@ -76,11 +76,11 @@ public class CyclingAbility extends ActivatedAbilityImpl {
|
|||
@Override
|
||||
public String getRule() {
|
||||
StringBuilder rule = new StringBuilder(this.text);
|
||||
if(cost instanceof ManaCosts){
|
||||
if(cost instanceof ManaCost){
|
||||
rule.append(" ");
|
||||
}
|
||||
else{
|
||||
rule.append(" - ");
|
||||
rule.append("—");
|
||||
}
|
||||
rule.append(cost.getText()).append(" <i>(").append(super.getRule(true)).append(")</i>");
|
||||
return rule.toString();
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ public class DashAbility extends StaticAbility implements AlternativeSourceCosts
|
|||
new GainAbilitySourceEffect(HasteAbility.getInstance(), Duration.Custom, false),
|
||||
DashedCondition.getInstance(), false, "","");
|
||||
Effect effect = new ReturnToHandTargetEffect();
|
||||
effect.setText("return the dashed creature from the battlefield to its owner's hand");
|
||||
effect.setTargetPointer(new FixedTarget(card.getId()));
|
||||
ability.addEffect(new CreateDelayedTriggeredAbilityEffect(new AtTheBeginOfNextEndStepDelayedTriggeredAbility(effect), false));
|
||||
addSubAbility(ability);
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ class FearEffect extends RestrictionEffect implements MageSingleton {
|
|||
|
||||
@Override
|
||||
public boolean canBeBlocked(Permanent attacker, Permanent blocker, Ability source, Game game) {
|
||||
if (blocker.getCardType().contains(CardType.ARTIFACT) || blocker.getColor().isBlack()) {
|
||||
if (blocker.getCardType().contains(CardType.ARTIFACT) || blocker.getColor(game).isBlack()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -83,17 +83,19 @@ public class HeroicAbility extends TriggeredAbilityImpl {
|
|||
private boolean checkSpell(Spell spell, Game game) {
|
||||
if (spell != null) {
|
||||
SpellAbility sa = spell.getSpellAbility();
|
||||
for (Target target : sa.getTargets()) {
|
||||
if (!target.isNotTarget() && target.getTargets().contains(this.getSourceId())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (Effect effect : sa.getEffects()) {
|
||||
for (UUID targetId : effect.getTargetPointer().getTargets(game, sa)) {
|
||||
if (targetId.equals(this.getSourceId())) {
|
||||
for(UUID modeId :sa.getModes().getSelectedModes()) {
|
||||
for (Target target : sa.getModes().get(modeId).getTargets()) {
|
||||
if (!target.isNotTarget() && target.getTargets().contains(this.getSourceId())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (Effect effect : sa.getModes().get(modeId).getEffects()) {
|
||||
for (UUID targetId : effect.getTargetPointer().getTargets(game, sa)) {
|
||||
if (targetId.equals(this.getSourceId())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ class IntimidateEffect extends RestrictionEffect implements MageSingleton {
|
|||
if (blocker.getCardType().contains(CardType.ARTIFACT) && (blocker.getCardType().contains(CardType.CREATURE))) {
|
||||
result = true;
|
||||
}
|
||||
if (attacker.getColor().shares(blocker.getColor())) {
|
||||
if (attacker.getColor(game).shares(blocker.getColor(game))) {
|
||||
result = true;
|
||||
}
|
||||
return result;
|
||||
|
|
|
|||
|
|
@ -219,7 +219,7 @@ public class MorphAbility extends StaticAbility implements AlternativeSourceCost
|
|||
}
|
||||
}
|
||||
// change spell colors
|
||||
ObjectColor spellColor = spell.getColor();
|
||||
ObjectColor spellColor = spell.getColor(game);
|
||||
spellColor.setBlack(false);
|
||||
spellColor.setRed(false);
|
||||
spellColor.setGreen(false);
|
||||
|
|
@ -297,7 +297,7 @@ public class MorphAbility extends StaticAbility implements AlternativeSourceCost
|
|||
mageObject.getPower().initValue(2);
|
||||
mageObject.getToughness().initValue(2);
|
||||
mageObject.getAbilities().clear();
|
||||
mageObject.getColor().setColor(new ObjectColor());
|
||||
mageObject.getColor(null).setColor(new ObjectColor());
|
||||
mageObject.setName("");
|
||||
mageObject.getCardType().clear();
|
||||
mageObject.getCardType().add(CardType.CREATURE);
|
||||
|
|
|
|||
|
|
@ -35,10 +35,11 @@ import mage.abilities.effects.ReplacementEffectImpl;
|
|||
import mage.abilities.effects.common.ReturnSourceFromGraveyardToBattlefieldEffect;
|
||||
import mage.constants.Duration;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.Zone;
|
||||
import mage.counters.CounterType;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.EntersTheBattlefieldEvent;
|
||||
import mage.game.events.GameEvent;
|
||||
import mage.game.events.ZoneChangeEvent;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.target.targetpointer.FixedTarget;
|
||||
|
||||
|
|
@ -68,9 +69,11 @@ public class PersistAbility extends DiesTriggeredAbility {
|
|||
@Override
|
||||
public boolean checkTrigger(GameEvent event, Game game) {
|
||||
if (super.checkTrigger(event, game)) {
|
||||
Permanent p = (Permanent) game.getLastKnownInformation(event.getTargetId(), Zone.BATTLEFIELD);
|
||||
if (p.getCounters().getCount(CounterType.M1M1) == 0) {
|
||||
game.getState().setValue("persist" + getSourceId().toString(), new FixedTarget(p.getId()));
|
||||
Permanent permanent = ((ZoneChangeEvent) event).getTarget();
|
||||
if (permanent.getCounters().getCount(CounterType.M1M1) == 0) {
|
||||
FixedTarget fixedTarget = new FixedTarget(permanent.getId());
|
||||
fixedTarget.init(game, this);
|
||||
game.getState().setValue("persist" + getSourceId().toString(), fixedTarget);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -109,7 +112,7 @@ class PersistEffect extends OneShotEffect {
|
|||
class PersistReplacementEffect extends ReplacementEffectImpl {
|
||||
|
||||
PersistReplacementEffect() {
|
||||
super(Duration.OneUse, Outcome.UnboostCreature, false);
|
||||
super(Duration.Custom, Outcome.UnboostCreature, false);
|
||||
selfScope = true;
|
||||
staticText = "return it to the battlefield under its owner's control with a -1/-1 counter on it";
|
||||
}
|
||||
|
|
@ -129,7 +132,7 @@ class PersistReplacementEffect extends ReplacementEffectImpl {
|
|||
if (permanent != null) {
|
||||
permanent.addCounters(CounterType.M1M1.createInstance(), game);
|
||||
}
|
||||
used = true;
|
||||
discard();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -142,7 +145,9 @@ class PersistReplacementEffect extends ReplacementEffectImpl {
|
|||
public boolean applies(GameEvent event, Ability source, Game game) {
|
||||
if (event.getTargetId().equals(source.getSourceId())) {
|
||||
Object fixedTarget = game.getState().getValue("persist" + source.getSourceId().toString());
|
||||
if (fixedTarget instanceof FixedTarget && ((FixedTarget) fixedTarget).getFirst(game, source).equals(source.getSourceId())) {
|
||||
if (fixedTarget instanceof FixedTarget && ((FixedTarget) fixedTarget).getTarget().equals(source.getSourceId()) &&
|
||||
((FixedTarget) fixedTarget).getZoneChangeCounter() + 1 == game.getState().getZoneChangeCounter(source.getSourceId())) {
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ public class TransformAbility extends SimpleStaticAbility {
|
|||
}
|
||||
|
||||
permanent.setName(sourceCard.getName());
|
||||
permanent.getColor().setColor(sourceCard.getColor());
|
||||
permanent.getColor(game).setColor(sourceCard.getColor(game));
|
||||
permanent.getManaCost().clear();
|
||||
permanent.getManaCost().add(sourceCard.getManaCost());
|
||||
permanent.getCardType().clear();
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import java.util.List;
|
|||
import java.util.UUID;
|
||||
import mage.MageObject;
|
||||
import mage.Mana;
|
||||
import mage.ObjectColor;
|
||||
import mage.abilities.Abilities;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.SpellAbility;
|
||||
|
|
@ -134,5 +135,5 @@ public interface Card extends MageObject {
|
|||
*/
|
||||
Card getMainCard();
|
||||
|
||||
void setZone(Zone zone, Game game);
|
||||
void setZone(Zone zone, Game game);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ import java.util.UUID;
|
|||
import mage.MageObject;
|
||||
import mage.MageObjectImpl;
|
||||
import mage.Mana;
|
||||
import mage.ObjectColor;
|
||||
import mage.abilities.Abilities;
|
||||
import mage.abilities.AbilitiesImpl;
|
||||
import mage.abilities.Ability;
|
||||
|
|
@ -59,6 +60,7 @@ import static mage.constants.Zone.PICK;
|
|||
import static mage.constants.Zone.STACK;
|
||||
import mage.counters.Counter;
|
||||
import mage.counters.Counters;
|
||||
import mage.game.CardAttribute;
|
||||
import mage.game.CardState;
|
||||
import mage.game.Game;
|
||||
import mage.game.command.Commander;
|
||||
|
|
@ -763,5 +765,15 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
|
|||
public void setSpellAbility(SpellAbility ability) {
|
||||
spellAbility = ability;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ObjectColor getColor(Game game) {
|
||||
if (game != null) {
|
||||
CardAttribute cardAttribute = game.getState().getCardAttribute(getId());
|
||||
if (cardAttribute != null) {
|
||||
return cardAttribute.getColor();
|
||||
}
|
||||
}
|
||||
return super.getColor(game); //To change body of generated methods, choose Tools | Templates.
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -132,11 +132,11 @@ public class CardInfo {
|
|||
this.secondSideName = secondSide.getName();
|
||||
}
|
||||
|
||||
this.blue = card.getColor().isBlue();
|
||||
this.black = card.getColor().isBlack();
|
||||
this.green = card.getColor().isGreen();
|
||||
this.red = card.getColor().isRed();
|
||||
this.white = card.getColor().isWhite();
|
||||
this.blue = card.getColor(null).isBlue();
|
||||
this.black = card.getColor(null).isBlack();
|
||||
this.green = card.getColor(null).isGreen();
|
||||
this.red = card.getColor(null).isRed();
|
||||
this.white = card.getColor(null).isWhite();
|
||||
|
||||
this.setTypes(card.getCardType());
|
||||
this.setSubtypes(card.getSubtype());
|
||||
|
|
|
|||
|
|
@ -39,14 +39,20 @@ public enum PlayerAction {
|
|||
PASS_PRIORITY_UNTIL_NEXT_TURN,
|
||||
PASS_PRIORITY_UNTIL_STACK_RESOLVED,
|
||||
PASS_PRIORITY_CANCEL_ALL_ACTIONS,
|
||||
ROLLBACK_TURNS,
|
||||
UNDO,
|
||||
CONCEDE,
|
||||
MANA_AUTO_PAYMENT_ON,
|
||||
MANA_AUTO_PAYMENT_OFF,
|
||||
MANA_AUTO_PAYMENT_RESTRICTED_ON,
|
||||
MANA_AUTO_PAYMENT_RESTRICTED_OFF,
|
||||
RESET_AUTO_SELECT_REPLACEMENT_EFFECTS,
|
||||
REVOKE_PERMISSIONS_TO_SEE_HAND_CARDS,
|
||||
REQUEST_PERMISSION_TO_SEE_HAND_CARDS,
|
||||
REQUEST_PERMISSION_TO_ROLLBACK_TURN,
|
||||
ADD_PERMISSION_TO_SEE_HAND_CARDS,
|
||||
ADD_PERMISSION_TO_ROLLBACK_TURN,
|
||||
DENY_PERMISSON_TO_ROLLBACK_TURN,
|
||||
PERMISSION_REQUESTS_ALLOWED_ON,
|
||||
PERMISSION_REQUESTS_ALLOWED_OFF
|
||||
}
|
||||
|
|
@ -1,9 +1,30 @@
|
|||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification, are
|
||||
* permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this list of
|
||||
* conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||
* of conditions and the following disclaimer in the documentation and/or other materials
|
||||
* provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* The views and conclusions contained in the software and documentation are those of the
|
||||
* authors and should not be interpreted as representing official policies, either expressed
|
||||
* or implied, of BetaSteward_at_googlemail.com.
|
||||
*/
|
||||
|
||||
package mage.filter.common;
|
||||
|
||||
import mage.constants.CardType;
|
||||
|
|
@ -15,7 +36,6 @@ import mage.filter.predicate.mageobject.CardTypePredicate;
|
|||
*
|
||||
* @author LevelX2
|
||||
*/
|
||||
|
||||
public class FilterArtifactOrEnchantmentPermanent extends FilterPermanent {
|
||||
|
||||
public FilterArtifactOrEnchantmentPermanent() {
|
||||
|
|
|
|||
48
Mage/src/mage/filter/common/FilterArtifactSpell.java
Normal file
48
Mage/src/mage/filter/common/FilterArtifactSpell.java
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification, are
|
||||
* permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this list of
|
||||
* conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||
* of conditions and the following disclaimer in the documentation and/or other materials
|
||||
* provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* The views and conclusions contained in the software and documentation are those of the
|
||||
* authors and should not be interpreted as representing official policies, either expressed
|
||||
* or implied, of BetaSteward_at_googlemail.com.
|
||||
*/
|
||||
package mage.filter.common;
|
||||
|
||||
import mage.constants.CardType;
|
||||
import mage.filter.FilterSpell;
|
||||
import mage.filter.predicate.mageobject.CardTypePredicate;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Jgod
|
||||
*/
|
||||
public class FilterArtifactSpell extends FilterSpell {
|
||||
|
||||
public FilterArtifactSpell() {
|
||||
this("artifact spell");
|
||||
}
|
||||
|
||||
public FilterArtifactSpell(String name) {
|
||||
super(name);
|
||||
this.add(new CardTypePredicate(CardType.ARTIFACT));
|
||||
}
|
||||
}
|
||||
|
|
@ -30,6 +30,7 @@ package mage.filter.predicate.mageobject;
|
|||
import mage.MageObject;
|
||||
import mage.abilities.Abilities;
|
||||
import mage.abilities.Ability;
|
||||
import mage.cards.Card;
|
||||
import mage.filter.predicate.Predicate;
|
||||
import mage.game.Game;
|
||||
|
||||
|
|
@ -47,9 +48,15 @@ public class AbilityPredicate implements Predicate<MageObject> {
|
|||
|
||||
@Override
|
||||
public boolean apply(MageObject input, Game game) {
|
||||
Abilities<Ability> abilities = input.getAbilities();
|
||||
for (int i = 0; i < abilities.size(); i++) {
|
||||
if (abilityClass.equals(abilities.get(i).getClass())) {
|
||||
Abilities<Ability> abilities;
|
||||
if (input instanceof Card){
|
||||
abilities = ((Card)input).getAbilities(game);
|
||||
} else {
|
||||
abilities = input.getAbilities();
|
||||
}
|
||||
|
||||
for (Ability ability : abilities) {
|
||||
if (abilityClass.equals(ability.getClass())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ public class ColorPredicate implements Predicate<MageObject> {
|
|||
|
||||
@Override
|
||||
public boolean apply(MageObject input, Game game) {
|
||||
return input.getColor().contains(color);
|
||||
return input.getColor(game).contains(color);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ public class ColorlessPredicate implements Predicate<MageObject> {
|
|||
|
||||
@Override
|
||||
public boolean apply(MageObject input, Game game) {
|
||||
return input.getColor().isColorless();
|
||||
return input.getColor(game).isColorless();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ public class MonocoloredPredicate implements Predicate<MageObject> {
|
|||
|
||||
@Override
|
||||
public boolean apply(MageObject input, Game game) {
|
||||
return 1 == input.getColor().getColorCount();
|
||||
return 1 == input.getColor(game).getColorCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ public class MulticoloredPredicate implements Predicate<MageObject> {
|
|||
|
||||
@Override
|
||||
public boolean apply(MageObject input, Game game) {
|
||||
return 1 < input.getColor().getColorCount();
|
||||
return 1 < input.getColor(game).getColorCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ public class SharesColorWithSourcePredicate implements ObjectSourcePlayerPredica
|
|||
public boolean apply(ObjectSourcePlayer<MageObject> input, Game game) {
|
||||
MageObject sourceObject = game.getObject(input.getSourceId());
|
||||
if (sourceObject != null) {
|
||||
return input.getObject().getColor().shares(sourceObject.getColor());
|
||||
return input.getObject().getColor(game).shares(sourceObject.getColor(game));
|
||||
}
|
||||
return false;
|
||||
|
||||
|
|
|
|||
|
|
@ -48,7 +48,8 @@ public class UnblockedPredicate implements Predicate<Permanent> {
|
|||
if ((game.getPhase().getStep().getType() == PhaseStep.DECLARE_BLOCKERS
|
||||
&& game.getStep().getStepPart() == Step.StepPart.PRIORITY)
|
||||
|| game.getPhase().getStep().getType() == PhaseStep.FIRST_COMBAT_DAMAGE
|
||||
|| game.getPhase().getStep().getType() == PhaseStep.COMBAT_DAMAGE) {
|
||||
|| game.getPhase().getStep().getType() == PhaseStep.COMBAT_DAMAGE
|
||||
|| game.getPhase().getStep().getType() == PhaseStep.END_COMBAT) {
|
||||
CombatGroup combatGroup = game.getCombat().findGroup(input.getId());
|
||||
if (combatGroup != null) {
|
||||
return combatGroup.getBlockers().isEmpty();
|
||||
|
|
|
|||
29
Mage/src/mage/game/CardAttribute.java
Normal file
29
Mage/src/mage/game/CardAttribute.java
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package mage.game;
|
||||
|
||||
import java.io.Serializable;
|
||||
import mage.ObjectColor;
|
||||
import mage.cards.Card;
|
||||
|
||||
/**
|
||||
* This class saves changed attributes of cards (e.g. in graveyard, exile or player hands or libraries).
|
||||
*
|
||||
* @author LevelX2
|
||||
*/
|
||||
public class CardAttribute implements Serializable {
|
||||
|
||||
protected ObjectColor color;
|
||||
|
||||
public CardAttribute(Card card) {
|
||||
color = card.getColor(null).copy();
|
||||
}
|
||||
|
||||
public ObjectColor getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -208,9 +208,7 @@ public interface Game extends MageItem, Serializable {
|
|||
*/
|
||||
PreventionEffectData preventDamage(GameEvent event, Ability source, Game game, boolean preventAllDamage);
|
||||
|
||||
//game play methods
|
||||
void start(UUID choosingPlayerId);
|
||||
void start(UUID choosingPlayerId, GameOptions options);
|
||||
void resume();
|
||||
void pause();
|
||||
boolean isPaused();
|
||||
|
|
@ -226,7 +224,8 @@ public interface Game extends MageItem, Serializable {
|
|||
void timerTimeout(UUID playerId);
|
||||
void idleTimeout(UUID playerId);
|
||||
void concede(UUID playerId);
|
||||
void setManaPoolMode(UUID playerId, boolean autoPayment);
|
||||
void setManaPaymentMode(UUID playerId, boolean autoPayment);
|
||||
void setManaPaymentModeRestricted(UUID playerId, boolean autoPaymentRestricted);
|
||||
void undo(UUID playerId);
|
||||
void emptyManaPools();
|
||||
void addEffect(ContinuousEffect continuousEffect, Ability source);
|
||||
|
|
@ -293,5 +292,10 @@ public interface Game extends MageItem, Serializable {
|
|||
int getPriorityTime();
|
||||
void setPriorityTime(int priorityTime);
|
||||
UUID getStartingPlayerId();
|
||||
|
||||
void saveRollBackGameState();
|
||||
boolean canRollbackTurns(int turnsToRollback);
|
||||
void rollbackTurns(int turnsToRollback);
|
||||
boolean executingRollback();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ public abstract class GameCommanderImpl extends GameImpl {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void init(UUID choosingPlayerId, GameOptions gameOptions) {
|
||||
protected void init(UUID choosingPlayerId) {
|
||||
Ability ability = new SimpleStaticAbility(Zone.COMMAND, new InfoEffect("Commander effects"));
|
||||
//Move commander to command zone
|
||||
for (UUID playerId: state.getPlayerList(startingPlayerId)) {
|
||||
|
|
@ -83,9 +83,10 @@ public abstract class GameCommanderImpl extends GameImpl {
|
|||
if (player != null){
|
||||
if (player.getSideboard().size() > 0){
|
||||
Card commander = getCard((UUID)player.getSideboard().toArray()[0]);
|
||||
if (commander != null) {
|
||||
if (commander != null) {
|
||||
player.setCommanderId(commander.getId());
|
||||
commander.moveToZone(Zone.COMMAND, null, this, true);
|
||||
commander.getAbilities().setControllerId(player.getId());
|
||||
ability.addEffect(new CommanderReplacementEffect(commander.getId(), alsoHand, alsoLibrary));
|
||||
ability.addEffect(new CommanderCostModification(commander.getId()));
|
||||
ability.addEffect(new CommanderManaReplacementEffect(player.getId(), CardUtil.getColorIdentity(commander)));
|
||||
|
|
@ -100,7 +101,7 @@ public abstract class GameCommanderImpl extends GameImpl {
|
|||
|
||||
}
|
||||
this.getState().addAbility(ability, null);
|
||||
super.init(choosingPlayerId, gameOptions);
|
||||
super.init(choosingPlayerId);
|
||||
if (startingPlayerSkipsDraw) {
|
||||
state.getTurnMods().add(new TurnMod(startingPlayerId, PhaseStep.DRAW));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -116,6 +116,7 @@ import mage.players.Players;
|
|||
import mage.target.Target;
|
||||
import mage.target.TargetPermanent;
|
||||
import mage.target.TargetPlayer;
|
||||
import mage.util.GameLog;
|
||||
import mage.util.functions.ApplyToPermanent;
|
||||
import mage.watchers.Watchers;
|
||||
import mage.watchers.common.BlockedAttackerWatcher;
|
||||
|
|
@ -129,6 +130,8 @@ import org.apache.log4j.Logger;
|
|||
|
||||
public abstract class GameImpl implements Game, Serializable {
|
||||
|
||||
private static final int ROLLBACK_TURNS_MAX = 4;
|
||||
|
||||
private static final transient Logger logger = Logger.getLogger(GameImpl.class);
|
||||
|
||||
private static final FilterPermanent filterAura = new FilterPermanent();
|
||||
|
|
@ -155,7 +158,8 @@ public abstract class GameImpl implements Game, Serializable {
|
|||
private transient Object customData;
|
||||
protected boolean simulation = false;
|
||||
|
||||
protected final UUID id;
|
||||
protected final UUID id;
|
||||
|
||||
protected boolean ready;
|
||||
protected transient TableEventSource tableEventSource = new TableEventSource();
|
||||
protected transient PlayerQueryEventSource playerQueryEventSource = new PlayerQueryEventSource();
|
||||
|
|
@ -170,6 +174,9 @@ public abstract class GameImpl implements Game, Serializable {
|
|||
protected GameState state;
|
||||
private transient Stack<Integer> savedStates = new Stack<>();
|
||||
protected transient GameStates gameStates = new GameStates();
|
||||
// game states to allow player roll back
|
||||
protected transient Map<Integer, GameState> gameStatesRollBack = new HashMap<>();
|
||||
protected boolean executingRollback;
|
||||
|
||||
protected Date startTime;
|
||||
protected Date endTime;
|
||||
|
|
@ -206,7 +213,7 @@ public abstract class GameImpl implements Game, Serializable {
|
|||
this.attackOption = attackOption;
|
||||
this.state = new GameState();
|
||||
this.startLife = startLife;
|
||||
// this.actions = new LinkedList<MageAction>();
|
||||
this.executingRollback = false;
|
||||
}
|
||||
|
||||
public GameImpl(final GameImpl game) {
|
||||
|
|
@ -232,7 +239,6 @@ public abstract class GameImpl implements Game, Serializable {
|
|||
copyCount++;
|
||||
copyTime += (System.currentTimeMillis() - t1);
|
||||
}
|
||||
// this.actions = new LinkedList<MageAction>();
|
||||
this.stateCheckRequired = game.stateCheckRequired;
|
||||
this.scorePlayer = game.scorePlayer;
|
||||
this.scopeRelevant = game.scopeRelevant;
|
||||
|
|
@ -268,7 +274,10 @@ public abstract class GameImpl implements Game, Serializable {
|
|||
|
||||
@Override
|
||||
public GameOptions getOptions() {
|
||||
return gameOptions;
|
||||
if (gameOptions != null) {
|
||||
return gameOptions;
|
||||
}
|
||||
return new GameOptions(); // happens during the first game updates
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -610,23 +619,17 @@ public abstract class GameImpl implements Game, Serializable {
|
|||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(UUID choosingPlayerId) {
|
||||
start(choosingPlayerId, this.gameOptions != null ? gameOptions : GameOptions.getDefault());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanUp() {
|
||||
gameCards.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(UUID choosingPlayerId, GameOptions options) {
|
||||
public void start(UUID choosingPlayerId) {
|
||||
startTime = new Date();
|
||||
this.gameOptions = options;
|
||||
if (state.getPlayers().values().iterator().hasNext()) {
|
||||
scorePlayer = state.getPlayers().values().iterator().next();
|
||||
init(choosingPlayerId, options);
|
||||
init(choosingPlayerId);
|
||||
play(startingPlayerId);
|
||||
}
|
||||
}
|
||||
|
|
@ -731,13 +734,26 @@ public abstract class GameImpl implements Game, Serializable {
|
|||
}
|
||||
|
||||
private boolean playTurn(Player player) {
|
||||
this.logStartOfTurn(player);
|
||||
if (checkStopOnTurnOption()) {
|
||||
return false;
|
||||
}
|
||||
state.setActivePlayerId(player.getId());
|
||||
player.becomesActivePlayer();
|
||||
state.getTurn().play(this, player.getId());
|
||||
do {
|
||||
if (executingRollback) {
|
||||
executingRollback = false;
|
||||
player = getPlayer(state.getActivePlayerId());
|
||||
for (Player playerObject: getPlayers().values()) {
|
||||
if (playerObject.isInGame()) {
|
||||
playerObject.abortReset();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
state.setActivePlayerId(player.getId());
|
||||
saveRollBackGameState();
|
||||
}
|
||||
this.logStartOfTurn(player);
|
||||
if (checkStopOnTurnOption()) {
|
||||
return false;
|
||||
}
|
||||
state.getTurn().play(this, player);
|
||||
} while (executingRollback);
|
||||
|
||||
if (isPaused() || gameOver(null)) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -778,7 +794,7 @@ public abstract class GameImpl implements Game, Serializable {
|
|||
return false;
|
||||
}
|
||||
|
||||
protected void init(UUID choosingPlayerId, GameOptions gameOptions) {
|
||||
protected void init(UUID choosingPlayerId) {
|
||||
for (Player player: state.getPlayers().values()) {
|
||||
player.beginTurn(this);
|
||||
// init only if match is with timer (>0) and time left was not set yet (== MAX_VALUE).
|
||||
|
|
@ -1108,12 +1124,20 @@ public abstract class GameImpl implements Game, Serializable {
|
|||
|
||||
|
||||
@Override
|
||||
public synchronized void setManaPoolMode(UUID playerId, boolean autoPayment) {
|
||||
public synchronized void setManaPaymentMode(UUID playerId, boolean autoPayment) {
|
||||
Player player = state.getPlayer(playerId);
|
||||
if (player != null) {
|
||||
player.getManaPool().setAutoPayment(autoPayment);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void setManaPaymentModeRestricted(UUID playerId, boolean autoPaymentRestricted) {
|
||||
Player player = state.getPlayer(playerId);
|
||||
if (player != null) {
|
||||
player.getManaPool().setAutoPaymentRestricted(autoPaymentRestricted);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playPriority(UUID activePlayerId, boolean resuming) {
|
||||
|
|
@ -1151,6 +1175,9 @@ public abstract class GameImpl implements Game, Serializable {
|
|||
}
|
||||
// resetPassed should be called if player performs any action
|
||||
if (player.priority(this)) {
|
||||
if(executingRollback()) {
|
||||
return;
|
||||
}
|
||||
applyEffects();
|
||||
}
|
||||
if (isPaused()) {
|
||||
|
|
@ -2058,6 +2085,11 @@ public abstract class GameImpl implements Game, Serializable {
|
|||
Permanent attachedTo = getPermanent(perm.getAttachedTo());
|
||||
if (attachedTo != null) {
|
||||
attachedTo.removeAttachment(perm.getId(), this);
|
||||
} else {
|
||||
Player attachedToPlayer = getPlayer(perm.getAttachedTo());
|
||||
if (attachedToPlayer != null) {
|
||||
attachedToPlayer.removeAttachment(perm, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
// check if it's a creature and must be removed from combat
|
||||
|
|
@ -2100,12 +2132,9 @@ public abstract class GameImpl implements Game, Serializable {
|
|||
}
|
||||
}
|
||||
|
||||
// Update players in range of
|
||||
for (Player leftPlayer :this.getPlayers().values()) {
|
||||
if (leftPlayer.isInGame()) {
|
||||
leftPlayer.otherPlayerLeftGame(this);
|
||||
}
|
||||
}
|
||||
// 801.2c The particular players within each player‘s range of influence are determined as each turn begins.
|
||||
// So no update of range if influence yet
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -2564,6 +2593,51 @@ public abstract class GameImpl implements Game, Serializable {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveRollBackGameState() {
|
||||
if (gameOptions.rollbackTurnsAllowed) {
|
||||
int toDelete = getTurnNum()- ROLLBACK_TURNS_MAX;
|
||||
if (toDelete > 0 && gameStatesRollBack.containsKey(toDelete)) {
|
||||
gameStatesRollBack.remove(toDelete);
|
||||
}
|
||||
gameStatesRollBack.put(getTurnNum(), state.copy());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRollbackTurns(int turnsToRollback) {
|
||||
int turnToGoTo = getTurnNum() - turnsToRollback;
|
||||
return turnToGoTo > 0 && gameStatesRollBack.containsKey(turnToGoTo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void rollbackTurns(int turnsToRollback) {
|
||||
if (gameOptions.rollbackTurnsAllowed) {
|
||||
int turnToGoTo = getTurnNum() - turnsToRollback;
|
||||
if (turnToGoTo < 1 || !gameStatesRollBack.containsKey(turnToGoTo)) {
|
||||
informPlayers(GameLog.getPlayerRequestColoredText("Player request: It's not possible to rollback " + turnsToRollback +" turn(s)"));
|
||||
} else {
|
||||
GameState restore = gameStatesRollBack.get(turnToGoTo);
|
||||
if (restore != null) {
|
||||
informPlayers(GameLog.getPlayerRequestColoredText("Player request: Rolling back to start of turn " + restore.getTurnNum()));
|
||||
for (Player playerObject: getPlayers().values()) {
|
||||
if (playerObject.isHuman() && playerObject.isInGame()) {
|
||||
playerObject.abort();
|
||||
}
|
||||
}
|
||||
state.restoreForRollBack(restore);
|
||||
// because restore uses the objects without copy each copy the state again
|
||||
gameStatesRollBack.put(getTurnNum(), state.copy());
|
||||
executingRollback = true;
|
||||
fireUpdatePlayersEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean executingRollback() {
|
||||
return executingRollback;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,4 +37,9 @@ public class GameOptions implements Serializable {
|
|||
* If true, library won't be shuffled at the beginning of the game
|
||||
*/
|
||||
public boolean skipInitShuffling = false;
|
||||
|
||||
/**
|
||||
* If true, players can roll back turn if all players agree
|
||||
*/
|
||||
public boolean rollbackTurnsAllowed = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,19 +77,19 @@ public class GameState implements Serializable, Copyable<GameState> {
|
|||
|
||||
private final Players players;
|
||||
private final PlayerList playerList;
|
||||
private final Turn turn;
|
||||
private UUID choosingPlayerId; // player that makes a choice at game start
|
||||
|
||||
// revealed cards <Name, <Cards>>, will be reset if all players pass priority
|
||||
private final Revealed revealed;
|
||||
private final Map<UUID, LookedAt> lookedAt = new HashMap<>();
|
||||
private final DelayedTriggeredAbilities delayed;
|
||||
private final SpecialActions specialActions;
|
||||
private final TurnMods turnMods;
|
||||
private final Watchers watchers;
|
||||
|
||||
|
||||
private DelayedTriggeredAbilities delayed;
|
||||
private SpecialActions specialActions;
|
||||
private Watchers watchers;
|
||||
private Turn turn;
|
||||
private TurnMods turnMods;
|
||||
private UUID activePlayerId; // playerId which turn it is
|
||||
private UUID priorityPlayerId; // player that has currently priority
|
||||
private UUID choosingPlayerId; // player that makes a choice at game start
|
||||
private SpellStack stack;
|
||||
private Command command;
|
||||
private Exile exile;
|
||||
|
|
@ -109,6 +109,7 @@ public class GameState implements Serializable, Copyable<GameState> {
|
|||
private Map<UUID, Zone> zones = new HashMap<>();
|
||||
private List<GameEvent> simultaneousEvents = new ArrayList<>();
|
||||
private Map<UUID, CardState> cardState = new HashMap<>();
|
||||
private Map<UUID, CardAttribute> cardAttribute = new HashMap<>();
|
||||
private Map<UUID, Integer> zoneChangeCounter = new HashMap<>();
|
||||
private Map<UUID, Card> copiedCards = new HashMap<>();
|
||||
private int permanentOrderNumber;
|
||||
|
|
@ -134,21 +135,24 @@ public class GameState implements Serializable, Copyable<GameState> {
|
|||
public GameState(final GameState state) {
|
||||
this.players = state.players.copy();
|
||||
this.playerList = state.playerList.copy();
|
||||
this.choosingPlayerId = state.choosingPlayerId;
|
||||
this.revealed = state.revealed.copy();
|
||||
this.lookedAt.putAll(state.lookedAt);
|
||||
this.gameOver = state.gameOver;
|
||||
this.paused = state.paused;
|
||||
|
||||
this.activePlayerId = state.activePlayerId;
|
||||
this.priorityPlayerId = state.priorityPlayerId;
|
||||
this.choosingPlayerId = state.choosingPlayerId;
|
||||
this.turn = state.turn.copy();
|
||||
|
||||
this.stack = state.stack.copy();
|
||||
this.command = state.command.copy();
|
||||
this.exile = state.exile.copy();
|
||||
this.revealed = state.revealed.copy();
|
||||
this.lookedAt.putAll(state.lookedAt);
|
||||
this.battlefield = state.battlefield.copy();
|
||||
this.turnNum = state.turnNum;
|
||||
this.stepNum = state.stepNum;
|
||||
this.extraTurn = state.extraTurn;
|
||||
this.legendaryRuleActive = state.legendaryRuleActive;
|
||||
this.gameOver = state.gameOver;
|
||||
this.effects = state.effects.copy();
|
||||
for (TriggeredAbility trigger: state.triggered) {
|
||||
this.triggered.add(trigger.copy());
|
||||
|
|
@ -163,7 +167,6 @@ public class GameState implements Serializable, Copyable<GameState> {
|
|||
this.values.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
this.zones.putAll(state.zones);
|
||||
this.paused = state.paused;
|
||||
this.simultaneousEvents.addAll(state.simultaneousEvents);
|
||||
for (Map.Entry<UUID, CardState> entry: state.cardState.entrySet()) {
|
||||
cardState.put(entry.getKey(), entry.getValue().copy());
|
||||
|
|
@ -172,6 +175,43 @@ public class GameState implements Serializable, Copyable<GameState> {
|
|||
this.copiedCards.putAll(state.copiedCards);
|
||||
this.permanentOrderNumber = state.permanentOrderNumber;
|
||||
}
|
||||
|
||||
public void restoreForRollBack(GameState state) {
|
||||
restore(state);
|
||||
this.turn = state.turn;
|
||||
}
|
||||
|
||||
public void restore(GameState state) {
|
||||
this.activePlayerId = state.activePlayerId;
|
||||
this.priorityPlayerId = state.priorityPlayerId;
|
||||
this.stack = state.stack;
|
||||
this.command = state.command;
|
||||
this.exile = state.exile;
|
||||
this.battlefield = state.battlefield;
|
||||
this.turnNum = state.turnNum;
|
||||
this.stepNum = state.stepNum;
|
||||
this.extraTurn = state.extraTurn;
|
||||
this.legendaryRuleActive = state.legendaryRuleActive;
|
||||
this.effects = state.effects;
|
||||
this.triggered = state.triggered;
|
||||
this.triggers = state.triggers;
|
||||
this.delayed = state.delayed;
|
||||
this.specialActions = state.specialActions;
|
||||
this.combat = state.combat;
|
||||
this.turnMods = state.turnMods;
|
||||
this.watchers = state.watchers;
|
||||
this.values = state.values;
|
||||
for (Player copyPlayer: state.players.values()) {
|
||||
Player origPlayer = players.get(copyPlayer.getId());
|
||||
origPlayer.restore(copyPlayer);
|
||||
}
|
||||
this.zones = state.zones;
|
||||
this.simultaneousEvents = state.simultaneousEvents;
|
||||
this.cardState = state.cardState;
|
||||
this.zoneChangeCounter = state.zoneChangeCounter;
|
||||
this.copiedCards = state.copiedCards;
|
||||
this.permanentOrderNumber = state.permanentOrderNumber;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GameState copy() {
|
||||
|
|
@ -558,28 +598,6 @@ public class GameState implements Serializable, Copyable<GameState> {
|
|||
zones.put(id, zone);
|
||||
}
|
||||
|
||||
public void restore(GameState state) {
|
||||
this.stack = state.stack;
|
||||
this.command = state.command;
|
||||
this.effects = state.effects;
|
||||
this.triggers = state.triggers;
|
||||
this.triggered = state.triggered;
|
||||
this.combat = state.combat;
|
||||
this.exile = state.exile;
|
||||
this.battlefield = state.battlefield;
|
||||
this.zones = state.zones;
|
||||
this.values = state.values;
|
||||
for (Player copyPlayer: state.players.values()) {
|
||||
Player origPlayer = players.get(copyPlayer.getId());
|
||||
origPlayer.restore(copyPlayer);
|
||||
}
|
||||
this.simultaneousEvents = state.simultaneousEvents;
|
||||
this.cardState = state.cardState;
|
||||
this.zoneChangeCounter = state.zoneChangeCounter;
|
||||
this.copiedCards = state.copiedCards;
|
||||
this.permanentOrderNumber = state.permanentOrderNumber;
|
||||
}
|
||||
|
||||
public void addSimultaneousEvent(GameEvent event, Game game) {
|
||||
simultaneousEvents.add(event);
|
||||
}
|
||||
|
|
@ -814,6 +832,7 @@ public class GameState implements Serializable, Copyable<GameState> {
|
|||
for (CardState state: cardState.values()) {
|
||||
state.clearAbilities();
|
||||
}
|
||||
cardAttribute.clear();
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
|
|
@ -840,6 +859,7 @@ public class GameState implements Serializable, Copyable<GameState> {
|
|||
values.clear();
|
||||
zones.clear();
|
||||
simultaneousEvents.clear();
|
||||
copiedCards.clear();
|
||||
permanentOrderNumber = 0;
|
||||
}
|
||||
|
||||
|
|
@ -874,11 +894,23 @@ public class GameState implements Serializable, Copyable<GameState> {
|
|||
public CardState getCardState(UUID cardId) {
|
||||
if (!cardState.containsKey(cardId)) {
|
||||
cardState.put(cardId, new CardState());
|
||||
// cardState.putIfAbsent(cardId, new CardState());
|
||||
}
|
||||
return cardState.get(cardId);
|
||||
}
|
||||
|
||||
public CardAttribute getCardAttribute(UUID cardId) {
|
||||
return cardAttribute.get(cardId);
|
||||
}
|
||||
|
||||
public CardAttribute getCreateCardAttribute(Card card) {
|
||||
CardAttribute cardAtt = cardAttribute.get(card.getId());
|
||||
if (cardAtt == null) {
|
||||
cardAtt = new CardAttribute(card);
|
||||
cardAttribute.put(card.getId(), cardAtt);
|
||||
}
|
||||
return cardAtt;
|
||||
}
|
||||
|
||||
public void addWatcher(Watcher watcher) {
|
||||
this.watchers.add(watcher);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,8 +42,12 @@ public class GameStates implements Serializable {
|
|||
|
||||
private static final transient Logger logger = Logger.getLogger(GameStates.class);
|
||||
|
||||
// private List<byte[]> states = new LinkedList<byte[]>();
|
||||
private final List<GameState> states = new LinkedList<>();
|
||||
// private final List<byte[]> states;
|
||||
private final List<GameState> states;
|
||||
|
||||
public GameStates() {
|
||||
this.states = new LinkedList<>();
|
||||
}
|
||||
|
||||
public void save(GameState gameState) {
|
||||
// states.add(new Copier<GameState>().copyCompressed(gameState));
|
||||
|
|
@ -60,8 +64,8 @@ public class GameStates implements Serializable {
|
|||
while (states.size() > index + 1) {
|
||||
states.remove(states.size() - 1);
|
||||
}
|
||||
// return new Copier<GameState>().uncompressCopy(states.get(index));
|
||||
logger.trace("Rolling back state: " + index);
|
||||
// return new Copier<GameState>().uncompressCopy(states.get(index));
|
||||
return states.get(index);
|
||||
}
|
||||
return null;
|
||||
|
|
@ -78,7 +82,7 @@ public class GameStates implements Serializable {
|
|||
|
||||
public GameState get(int index) {
|
||||
if (index < states.size()) {
|
||||
// return new Copier<GameState>().uncompressCopy(states.get(index));
|
||||
// return new Copier<GameState>().uncompressCopy(states.get(index));
|
||||
return states.get(index);
|
||||
}
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ public abstract class GameTinyLeadersImpl extends GameImpl{
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void init(UUID choosingPlayerId, GameOptions gameOptions) {
|
||||
protected void init(UUID choosingPlayerId) {
|
||||
Ability ability = new SimpleStaticAbility(Zone.COMMAND, new InfoEffect("Commander effects"));
|
||||
//Move tiny leader to command zone
|
||||
for (UUID playerId: state.getPlayerList(startingPlayerId)) {
|
||||
|
|
@ -101,7 +101,7 @@ public abstract class GameTinyLeadersImpl extends GameImpl{
|
|||
|
||||
}
|
||||
this.getState().addAbility(ability, null);
|
||||
super.init(choosingPlayerId, gameOptions);
|
||||
super.init(choosingPlayerId);
|
||||
if (startingPlayerSkipsDraw) {
|
||||
state.getTurnMods().add(new TurnMod(startingPlayerId, PhaseStep.DRAW));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -149,8 +149,8 @@ public class Commander implements CommandObject{
|
|||
}
|
||||
|
||||
@Override
|
||||
public ObjectColor getColor() {
|
||||
return card.getColor();
|
||||
public ObjectColor getColor(Game game) {
|
||||
return card.getColor(game);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@ public class Emblem implements CommandObject {
|
|||
}
|
||||
|
||||
@Override
|
||||
public ObjectColor getColor() {
|
||||
public ObjectColor getColor(Game game) {
|
||||
return emptyColor;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ public class MatchOptions implements Serializable {
|
|||
protected List<String> playerTypes = new ArrayList<>();
|
||||
protected String password;
|
||||
protected SkillLevel skillLevel;
|
||||
protected boolean rollbackTurnsAllowed;
|
||||
|
||||
/**
|
||||
* Time each player has during the game to play using his\her priority.
|
||||
|
|
@ -159,4 +160,12 @@ public class MatchOptions implements Serializable {
|
|||
public void setSkillLevel(SkillLevel skillLevel) {
|
||||
this.skillLevel = skillLevel;
|
||||
}
|
||||
|
||||
public boolean isRollbackTurnsAllowed() {
|
||||
return rollbackTurnsAllowed;
|
||||
}
|
||||
|
||||
public void setRollbackTurnsAllowed(boolean rollbackTurnsAllowed) {
|
||||
this.rollbackTurnsAllowed = rollbackTurnsAllowed;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@
|
|||
|
||||
package mage.game.match;
|
||||
|
||||
import java.io.Serializable;
|
||||
import mage.cards.Card;
|
||||
import mage.cards.decks.Deck;
|
||||
import mage.players.Player;
|
||||
|
|
@ -36,7 +37,10 @@ import mage.players.Player;
|
|||
*
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
*/
|
||||
public class MatchPlayer {
|
||||
public class MatchPlayer implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 42L;
|
||||
|
||||
private int wins;
|
||||
private boolean matchWinner;
|
||||
|
||||
|
|
@ -45,7 +49,7 @@ public class MatchPlayer {
|
|||
private final String name;
|
||||
|
||||
private boolean quit;
|
||||
private final boolean timerTimeout;
|
||||
//private final boolean timerTimeout;
|
||||
private boolean doneSideboarding;
|
||||
private int priorityTimeLeft;
|
||||
|
||||
|
|
@ -56,7 +60,7 @@ public class MatchPlayer {
|
|||
this.wins = 0;
|
||||
this.doneSideboarding = true;
|
||||
this.quit = false;
|
||||
this.timerTimeout = false;
|
||||
//this.timerTimeout = false;
|
||||
this.name = player.getName();
|
||||
this.matchWinner = false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ public class PermanentCard extends PermanentImpl {
|
|||
this.abilities.setSourceId(objectId);
|
||||
this.cardType.clear();
|
||||
this.cardType.addAll(card.getCardType());
|
||||
this.color = card.getColor().copy();
|
||||
this.color = card.getColor(null).copy();
|
||||
this.manaCost = card.getManaCost().copy();
|
||||
this.power = card.getPower().copy();
|
||||
this.toughness = card.getToughness().copy();
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ package mage.game.permanent;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
|
|
@ -39,6 +38,7 @@ import java.util.Map;
|
|||
import java.util.UUID;
|
||||
import mage.MageObject;
|
||||
import mage.MageObjectReference;
|
||||
import mage.ObjectColor;
|
||||
import mage.abilities.Abilities;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.effects.ContinuousEffect;
|
||||
|
|
@ -1360,5 +1360,11 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
public void setCreateOrder(int createOrder) {
|
||||
this.createOrder = createOrder;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ObjectColor getColor(Game game) {
|
||||
return color;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ public class PermanentToken extends PermanentImpl {
|
|||
this.getManaCost().add(cost.copy());
|
||||
}
|
||||
this.cardType = token.getCardType();
|
||||
this.color = token.getColor().copy();
|
||||
this.color = token.getColor(game).copy();
|
||||
this.power.initValue(token.getPower().getValue());
|
||||
this.toughness.initValue(token.getToughness().getValue());
|
||||
this.supertype = token.getSupertype();
|
||||
|
|
|
|||
|
|
@ -37,7 +37,12 @@ import mage.MageInt;
|
|||
public class GolemToken extends Token {
|
||||
|
||||
public GolemToken() {
|
||||
this("SOM");
|
||||
}
|
||||
|
||||
public GolemToken(String setCode) {
|
||||
super("Golem", "a 3/3 colorless Golem artifact creature token");
|
||||
setOriginalExpansionSetCode(setCode);
|
||||
cardType.add(CardType.ARTIFACT);
|
||||
cardType.add(CardType.CREATURE);
|
||||
subtype.add("Golem");
|
||||
|
|
|
|||
|
|
@ -37,39 +37,33 @@ import mage.Mana;
|
|||
import mage.ObjectColor;
|
||||
import mage.abilities.Abilities;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.Mode;
|
||||
import mage.abilities.SpellAbility;
|
||||
import mage.abilities.costs.AlternativeSourceCosts;
|
||||
import mage.abilities.costs.Cost;
|
||||
import mage.abilities.costs.mana.ManaCost;
|
||||
import mage.abilities.costs.mana.ManaCosts;
|
||||
import mage.abilities.dynamicvalue.common.StaticValue;
|
||||
import mage.abilities.keyword.BestowAbility;
|
||||
import mage.abilities.keyword.MorphAbility;
|
||||
import mage.cards.Card;
|
||||
import mage.cards.SplitCard;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.Rarity;
|
||||
import mage.constants.SpellAbilityType;
|
||||
import mage.constants.Zone;
|
||||
import mage.counters.Counter;
|
||||
import mage.counters.Counters;
|
||||
import mage.filter.FilterPermanent;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.ZoneChangeEvent;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.game.permanent.PermanentCard;
|
||||
import mage.players.Player;
|
||||
import mage.target.Target;
|
||||
import mage.target.TargetAmount;
|
||||
import mage.util.GameLog;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
*/
|
||||
public class Spell implements StackObject, Card {
|
||||
public class Spell extends StackObjImpl implements Card {
|
||||
|
||||
private final List<Card> spellCards = new ArrayList<>();
|
||||
private final List<SpellAbility> spellAbilities = new ArrayList<>();
|
||||
|
|
@ -87,7 +81,7 @@ public class Spell implements StackObject, Card {
|
|||
|
||||
public Spell(Card card, SpellAbility ability, UUID controllerId, Zone fromZone) {
|
||||
this.card = card;
|
||||
this.color = card.getColor().copy();
|
||||
this.color = card.getColor(null).copy();
|
||||
id = ability.getId();
|
||||
this.ability = ability;
|
||||
this.ability.setControllerId(controllerId);
|
||||
|
|
@ -313,213 +307,8 @@ public class Spell implements StackObject, Card {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Choose new targets for the spell
|
||||
*
|
||||
* @param game
|
||||
* @param playerId Player UUID who changes the targets.
|
||||
* @return
|
||||
*/
|
||||
public boolean chooseNewTargets(Game game, UUID playerId) {
|
||||
return chooseNewTargets(game, playerId, false, false, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 114.6. Some effects allow a player to change the target(s) of a spell or
|
||||
* ability, and other effects allow a player to choose new targets for a
|
||||
* spell or ability.
|
||||
*
|
||||
* 114.6a If an effect allows a player to "change the
|
||||
* target(s)" of a spell or ability, each target can be changed only to
|
||||
* another legal target. If a target can't be changed to another legal
|
||||
* target, the original target is unchanged, even if the original target is
|
||||
* itself illegal by then. If all the targets aren't changed to other legal
|
||||
* targets, none of them are changed.
|
||||
*
|
||||
* 114.6b If an effect allows a player to "change a target" of a
|
||||
* spell or ability, the process described in rule 114.6a
|
||||
* is followed, except that only one of those targets may be changed
|
||||
* (rather than all of them or none of them).
|
||||
*
|
||||
* 114.6c If an effect allows a
|
||||
* player to "change any targets" of a spell or ability, the process
|
||||
* described in rule 114.6a is followed, except that any number of those
|
||||
* targets may be changed (rather than all of them or none of them).
|
||||
*
|
||||
* 114.6d If an effect allows a player to "choose new targets" for a spell or
|
||||
* ability, the player may leave any number of the targets unchanged, even
|
||||
* if those targets would be illegal. If the player chooses to change some
|
||||
* or all of the targets, the new targets must be legal and must not cause
|
||||
* any unchanged targets to become illegal.
|
||||
*
|
||||
* 114.6e When changing targets or
|
||||
* choosing new targets for a spell or ability, only the final set of
|
||||
* targets is evaluated to determine whether the change is legal.
|
||||
*
|
||||
* Example: Arc Trail is a sorcery that reads "Arc Trail deals 2 damage to
|
||||
* target creature or player and 1 damage to another target creature or
|
||||
* player." The current targets of Arc Trail are Runeclaw Bear and Llanowar
|
||||
* Elves, in that order. You cast Redirect, an instant that reads "You may
|
||||
* choose new targets for target spell," targeting Arc Trail. You can change
|
||||
* the first target to Llanowar Elves and change the second target to
|
||||
* Runeclaw Bear.
|
||||
*
|
||||
* 114.7. Modal spells and abilities may have different targeting
|
||||
* requirements for each mode. An effect that allows a player to change the
|
||||
* target(s) of a modal spell or ability, or to choose new targets for a
|
||||
* modal spell or ability, doesn't allow that player to change its mode.
|
||||
* (See rule 700.2.)
|
||||
*
|
||||
* 706.10c Some effects copy a spell or ability and state that its
|
||||
* controller may choose new targets for the copy. The player may leave any
|
||||
* number of the targets unchanged, even if those targets would be illegal.
|
||||
* If the player chooses to change some or all of the targets, the new
|
||||
* targets must be legal. Once the player has decided what the copy's
|
||||
* targets will be, the copy is put onto the stack with those targets.
|
||||
*
|
||||
* @param game
|
||||
* @param playerId - player that can/has to change the taregt of the spell
|
||||
* @param forceChange - does only work for targets with maximum of one targetId
|
||||
* @param onlyOneTarget - 114.6b one target must be changed to another target
|
||||
* @param filterNewTarget restriction for the new target, if null nothing is cheched
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public boolean chooseNewTargets(Game game, UUID playerId, boolean forceChange, boolean onlyOneTarget, FilterPermanent filterNewTarget) {
|
||||
Player player = game.getPlayer(playerId);
|
||||
if (player != null) {
|
||||
StringBuilder newTargetDescription = new StringBuilder();
|
||||
// Fused split spells or spells where "Splice on Arcane" was used can have more than one ability
|
||||
for (SpellAbility spellAbility : spellAbilities) {
|
||||
// Some spells can have more than one mode
|
||||
for (UUID modeId : spellAbility.getModes().getSelectedModes()) {
|
||||
Mode mode = spellAbility.getModes().get(modeId);
|
||||
for (Target target : mode.getTargets()) {
|
||||
Target newTarget = chooseNewTarget(player, spellAbility, mode, target, forceChange, filterNewTarget, game);
|
||||
// clear the old target and copy all targets from new target
|
||||
target.clearChosen();
|
||||
for (UUID targetId : newTarget.getTargets()) {
|
||||
target.addTarget(targetId, newTarget.getTargetAmount(targetId), spellAbility, game, false);
|
||||
}
|
||||
|
||||
}
|
||||
newTargetDescription.append(getSpellAbility().getTargetDescription(mode.getTargets(), game));
|
||||
}
|
||||
|
||||
}
|
||||
if (newTargetDescription.length() > 0 && !game.isSimulation()) {
|
||||
game.informPlayers(this.getName() + " is now " + newTargetDescription.toString());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the change of one target instance of a mode
|
||||
*
|
||||
* @param player - player that can choose the new target
|
||||
* @param spellAbility
|
||||
* @param mode
|
||||
* @param target
|
||||
* @param forceChange
|
||||
* @param game
|
||||
* @return
|
||||
*/
|
||||
private Target chooseNewTarget(Player player, SpellAbility spellAbility, Mode mode, Target target, boolean forceChange, FilterPermanent filterNewTarget, Game game) {
|
||||
Target newTarget = target.copy();
|
||||
newTarget.clearChosen();
|
||||
for (UUID targetId : target.getTargets()) {
|
||||
String targetNames = getNamesOftargets(targetId, game);
|
||||
// change the target?
|
||||
if (targetNames != null
|
||||
&& (forceChange || player.chooseUse(mode.getEffects().get(0).getOutcome(), "Change this target: " + targetNames + "?", game))) {
|
||||
// choose exactly one other target
|
||||
if (forceChange && target.possibleTargets(this.getSourceId(), getControllerId(), game).size() > 1) { // controller of spell must be used (e.g. TargetOpponent)
|
||||
int iteration = 0;
|
||||
do {
|
||||
if (iteration > 0 && !game.isSimulation()) {
|
||||
game.informPlayer(player, "You may only select exactly one target that must be different from the origin target!");
|
||||
}
|
||||
iteration++;
|
||||
newTarget.clearChosen();
|
||||
// TODO: Distinction between "spell controller" and "player that can change the target" - here player is used for both
|
||||
newTarget.chooseTarget(mode.getEffects().get(0).getOutcome(), player.getId(), spellAbility, game);
|
||||
// check target restriction
|
||||
if (newTarget.getFirstTarget() != null && filterNewTarget != null) {
|
||||
Permanent newTargetPermanent = game.getPermanent(newTarget.getFirstTarget());
|
||||
if (newTargetPermanent == null || !filterNewTarget.match(newTargetPermanent, game)) {
|
||||
game.informPlayer(player, "Target does not fullfil the target requirements (" + filterNewTarget.getMessage() +")");
|
||||
newTarget.clearChosen();
|
||||
}
|
||||
}
|
||||
} while (player.isInGame() && (targetId.equals(newTarget.getFirstTarget()) || newTarget.getTargets().size() != 1));
|
||||
// choose a new target
|
||||
} else {
|
||||
// build a target definition with exactly one possible target to select that replaces old target
|
||||
Target tempTarget = target.copy();
|
||||
if (target instanceof TargetAmount) {
|
||||
((TargetAmount)tempTarget).setAmountDefinition(new StaticValue(target.getTargetAmount(targetId)));
|
||||
}
|
||||
tempTarget.setMinNumberOfTargets(1);
|
||||
tempTarget.setMaxNumberOfTargets(1);
|
||||
boolean again;
|
||||
do {
|
||||
again = false;
|
||||
tempTarget.clearChosen();
|
||||
if (!tempTarget.chooseTarget(mode.getEffects().get(0).getOutcome(), player.getId(), spellAbility, game)) {
|
||||
if (player.chooseUse(Outcome.Benefit, "No target object selected. Reset to original target?", game)) {
|
||||
// use previous target no target was selected
|
||||
newTarget.addTarget(targetId, target.getTargetAmount(targetId), spellAbility, game, false);
|
||||
} else {
|
||||
again = true;
|
||||
}
|
||||
} else {
|
||||
// if possible add the alternate Target - it may not be included in the old definition nor in the already selected targets of the new definition
|
||||
if (newTarget.getTargets().contains(tempTarget.getFirstTarget()) || target.getTargets().contains(tempTarget.getFirstTarget())) {
|
||||
if(player.isHuman()) {
|
||||
game.informPlayer(player, "This target was already selected from origin spell. You can only keep this target!");
|
||||
again = true;
|
||||
} else {
|
||||
newTarget.addTarget(targetId, target.getTargetAmount(targetId), spellAbility, game, false);
|
||||
}
|
||||
} else if (newTarget.getFirstTarget() != null && filterNewTarget != null) {
|
||||
Permanent newTargetPermanent = game.getPermanent(newTarget.getFirstTarget());
|
||||
if (newTargetPermanent == null || !filterNewTarget.match(newTargetPermanent, game)) {
|
||||
game.informPlayer(player, "This target does not fullfil the target requirements (" + filterNewTarget.getMessage() +")");
|
||||
again = true;
|
||||
}
|
||||
} else {
|
||||
// valid target was selected, add it to the new target definition
|
||||
newTarget.addTarget(tempTarget.getFirstTarget(), target.getTargetAmount(targetId), spellAbility, game, false);
|
||||
}
|
||||
}
|
||||
} while (again && player.isInGame());
|
||||
}
|
||||
}
|
||||
// keep the target
|
||||
else {
|
||||
newTarget.addTarget(targetId, target.getTargetAmount(targetId), spellAbility, game, false);
|
||||
}
|
||||
}
|
||||
return newTarget;
|
||||
}
|
||||
|
||||
|
||||
private String getNamesOftargets(UUID targetId, Game game) {
|
||||
MageObject object = game.getObject(targetId);
|
||||
String name = null;
|
||||
if (object == null) {
|
||||
Player targetPlayer = game.getPlayer(targetId);
|
||||
if (targetPlayer != null) {
|
||||
name = targetPlayer.getLogName();
|
||||
}
|
||||
} else {
|
||||
name = object.getName();
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void counter(UUID sourceId, Game game) {
|
||||
|
|
@ -554,7 +343,13 @@ public class Spell implements StackObject, Card {
|
|||
|
||||
@Override
|
||||
public String getIdName() {
|
||||
return getName() + " ["+getId().toString().substring(0,3) +"]";
|
||||
String idName;
|
||||
if (card != null) {
|
||||
idName = card.getId().toString().substring(0,3);
|
||||
} else {
|
||||
idName = getId().toString().substring(0,3);
|
||||
}
|
||||
return getName() + " ["+idName+"]";
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -636,7 +431,7 @@ public class Spell implements StackObject, Card {
|
|||
}
|
||||
|
||||
@Override
|
||||
public ObjectColor getColor() {
|
||||
public ObjectColor getColor(Game game) {
|
||||
return color;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -54,15 +54,10 @@ import mage.target.Targets;
|
|||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import mage.abilities.dynamicvalue.common.StaticValue;
|
||||
import mage.cards.Card;
|
||||
import mage.constants.AbilityWord;
|
||||
import mage.constants.Outcome;
|
||||
import mage.filter.FilterPermanent;
|
||||
import mage.game.events.GameEvent;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.players.Player;
|
||||
import mage.target.TargetAmount;
|
||||
import mage.util.GameLog;
|
||||
import mage.watchers.Watcher;
|
||||
|
||||
|
|
@ -70,7 +65,7 @@ import mage.watchers.Watcher;
|
|||
*
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
*/
|
||||
public class StackAbility implements StackObject, Ability {
|
||||
public class StackAbility extends StackObjImpl implements Ability {
|
||||
|
||||
private static List<CardType> emptyCardType = new ArrayList<>();
|
||||
private static List<String> emptyString = new ArrayList<>();
|
||||
|
|
@ -180,7 +175,7 @@ public class StackAbility implements StackObject, Ability {
|
|||
}
|
||||
|
||||
@Override
|
||||
public ObjectColor getColor() {
|
||||
public ObjectColor getColor(Game game) {
|
||||
return emptyColor;
|
||||
}
|
||||
|
||||
|
|
@ -551,197 +546,9 @@ public class StackAbility implements StackObject, Ability {
|
|||
throw new UnsupportedOperationException("Not supported.");
|
||||
}
|
||||
|
||||
/**
|
||||
* 114.6. Some effects allow a player to change the target(s) of a spell or
|
||||
* ability, and other effects allow a player to choose new targets for a
|
||||
* spell or ability.
|
||||
*
|
||||
* 114.6a If an effect allows a player to "change the
|
||||
* target(s)" of a spell or ability, each target can be changed only to
|
||||
* another legal target. If a target can't be changed to another legal
|
||||
* target, the original target is unchanged, even if the original target is
|
||||
* itself illegal by then. If all the targets aren't changed to other legal
|
||||
* targets, none of them are changed.
|
||||
*
|
||||
* 114.6b If an effect allows a player to "change a target" of a
|
||||
* spell or ability, the process described in rule 114.6a
|
||||
* is followed, except that only one of those targets may be changed
|
||||
* (rather than all of them or none of them).
|
||||
*
|
||||
* 114.6c If an effect allows a
|
||||
* player to "change any targets" of a spell or ability, the process
|
||||
* described in rule 114.6a is followed, except that any number of those
|
||||
* targets may be changed (rather than all of them or none of them).
|
||||
*
|
||||
* 114.6d If an effect allows a player to "choose new targets" for a spell or
|
||||
* ability, the player may leave any number of the targets unchanged, even
|
||||
* if those targets would be illegal. If the player chooses to change some
|
||||
* or all of the targets, the new targets must be legal and must not cause
|
||||
* any unchanged targets to become illegal.
|
||||
*
|
||||
* 114.6e When changing targets or
|
||||
* choosing new targets for a spell or ability, only the final set of
|
||||
* targets is evaluated to determine whether the change is legal.
|
||||
*
|
||||
* Example: Arc Trail is a sorcery that reads "Arc Trail deals 2 damage to
|
||||
* target creature or player and 1 damage to another target creature or
|
||||
* player." The current targets of Arc Trail are Runeclaw Bear and Llanowar
|
||||
* Elves, in that order. You cast Redirect, an instant that reads "You may
|
||||
* choose new targets for target spell," targeting Arc Trail. You can change
|
||||
* the first target to Llanowar Elves and change the second target to
|
||||
* Runeclaw Bear.
|
||||
*
|
||||
* 114.7. Modal spells and abilities may have different targeting
|
||||
* requirements for each mode. An effect that allows a player to change the
|
||||
* target(s) of a modal spell or ability, or to choose new targets for a
|
||||
* modal spell or ability, doesn't allow that player to change its mode.
|
||||
* (See rule 700.2.)
|
||||
*
|
||||
* 706.10c Some effects copy a spell or ability and state that its
|
||||
* controller may choose new targets for the copy. The player may leave any
|
||||
* number of the targets unchanged, even if those targets would be illegal.
|
||||
* If the player chooses to change some or all of the targets, the new
|
||||
* targets must be legal. Once the player has decided what the copy's
|
||||
* targets will be, the copy is put onto the stack with those targets.
|
||||
*
|
||||
* @param game
|
||||
* @param playerId - player that can/has to change the target of the ability
|
||||
* @param forceChange - does only work for targets with maximum of one targetId
|
||||
* @param onlyOneTarget - 114.6b one target must be changed to another target
|
||||
* @param filterNewTarget restriction for the new target, if null nothing is cheched
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public boolean chooseNewTargets(Game game, UUID playerId, boolean forceChange, boolean onlyOneTarget, FilterPermanent filterNewTarget) {
|
||||
Player player = game.getPlayer(playerId);
|
||||
if (player != null) {
|
||||
StringBuilder newTargetDescription = new StringBuilder();
|
||||
// Some abilities can have more than one mode
|
||||
for (UUID modeId : ability.getModes().getSelectedModes()) {
|
||||
Mode mode = ability.getModes().get(modeId);
|
||||
for (Target target : mode.getTargets()) {
|
||||
Target newTarget = chooseNewTarget(player, getStackAbility(), mode, target, forceChange, filterNewTarget, game);
|
||||
// clear the old target and copy all targets from new target
|
||||
target.clearChosen();
|
||||
for (UUID targetId : newTarget.getTargets()) {
|
||||
target.addTarget(targetId, newTarget.getTargetAmount(targetId), ability, game, false);
|
||||
}
|
||||
|
||||
}
|
||||
newTargetDescription.append(((AbilityImpl)ability).getTargetDescription(mode.getTargets(), game));
|
||||
}
|
||||
|
||||
if (newTargetDescription.length() > 0 && !game.isSimulation()) {
|
||||
game.informPlayers(this.getName() + " is now " + newTargetDescription.toString());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
public String getTargetDescription(Targets targets, Game game) {
|
||||
return getAbilities().get(0).getTargetDescription(targets, game);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the change of one target instance of a mode
|
||||
*
|
||||
* @param player - player that can choose the new target
|
||||
* @param ability
|
||||
* @param mode
|
||||
* @param target
|
||||
* @param forceChange
|
||||
* @param game
|
||||
* @return
|
||||
*/
|
||||
private Target chooseNewTarget(Player player, Ability ability, Mode mode, Target target, boolean forceChange, FilterPermanent filterNewTarget, Game game) {
|
||||
Target newTarget = target.copy();
|
||||
newTarget.clearChosen();
|
||||
for (UUID targetId : target.getTargets()) {
|
||||
String targetNames = getNamesOfTargets(targetId, game);
|
||||
// change the target?
|
||||
if (targetNames != null
|
||||
&& (forceChange || player.chooseUse(mode.getEffects().get(0).getOutcome(), "Change this target: " + targetNames + "?", game))) {
|
||||
// choose exactly one other target
|
||||
if (forceChange && target.possibleTargets(this.getSourceId(), getControllerId(), game).size() > 1) { // controller of ability must be used (e.g. TargetOpponent)
|
||||
int iteration = 0;
|
||||
do {
|
||||
if (iteration > 0 && !game.isSimulation()) {
|
||||
game.informPlayer(player, "You may only select exactly one target that must be different from the origin target!");
|
||||
}
|
||||
iteration++;
|
||||
newTarget.clearChosen();
|
||||
// TODO: Distinction between "ability controller" and "player that can change the target" - here player is used for both
|
||||
newTarget.chooseTarget(mode.getEffects().get(0).getOutcome(), player.getId(), ability, game);
|
||||
// check target restriction
|
||||
if (newTarget.getFirstTarget() != null && filterNewTarget != null) {
|
||||
Permanent newTargetPermanent = game.getPermanent(newTarget.getFirstTarget());
|
||||
if (newTargetPermanent == null || !filterNewTarget.match(newTargetPermanent, game)) {
|
||||
game.informPlayer(player, "Target does not fullfil the target requirements (" + filterNewTarget.getMessage() +")");
|
||||
newTarget.clearChosen();
|
||||
}
|
||||
}
|
||||
} while (player.isInGame() && (targetId.equals(newTarget.getFirstTarget()) || newTarget.getTargets().size() != 1));
|
||||
// choose a new target
|
||||
} else {
|
||||
// build a target definition with exactly one possible target to select that replaces old target
|
||||
Target tempTarget = target.copy();
|
||||
if (target instanceof TargetAmount) {
|
||||
((TargetAmount)tempTarget).setAmountDefinition(new StaticValue(target.getTargetAmount(targetId)));
|
||||
}
|
||||
tempTarget.setMinNumberOfTargets(1);
|
||||
tempTarget.setMaxNumberOfTargets(1);
|
||||
boolean again;
|
||||
do {
|
||||
again = false;
|
||||
tempTarget.clearChosen();
|
||||
if (!tempTarget.chooseTarget(mode.getEffects().get(0).getOutcome(), player.getId(), ability, game)) {
|
||||
if (player.chooseUse(Outcome.Benefit, "No target object selected. Reset to original target?", game)) {
|
||||
// use previous target no target was selected
|
||||
newTarget.addTarget(targetId, target.getTargetAmount(targetId), ability, game, false);
|
||||
} else {
|
||||
again = true;
|
||||
}
|
||||
} else {
|
||||
// if possible add the alternate Target - it may not be included in the old definition nor in the already selected targets of the new definition
|
||||
if (newTarget.getTargets().contains(tempTarget.getFirstTarget()) || target.getTargets().contains(tempTarget.getFirstTarget())) {
|
||||
if (player.isHuman()) {
|
||||
game.informPlayer(player, "This target was already selected from origin ability. You can only keep this target!");
|
||||
again = true;
|
||||
} else {
|
||||
newTarget.addTarget(targetId, target.getTargetAmount(targetId), ability, game, false);
|
||||
}
|
||||
} else if (newTarget.getFirstTarget() != null && filterNewTarget != null) {
|
||||
Permanent newTargetPermanent = game.getPermanent(newTarget.getFirstTarget());
|
||||
if (newTargetPermanent == null || !filterNewTarget.match(newTargetPermanent, game)) {
|
||||
game.informPlayer(player, "This target does not fullfil the target requirements (" + filterNewTarget.getMessage() +")");
|
||||
again = true;
|
||||
}
|
||||
} else {
|
||||
// valid target was selected, add it to the new target definition
|
||||
newTarget.addTarget(tempTarget.getFirstTarget(), target.getTargetAmount(targetId), ability, game, false);
|
||||
}
|
||||
}
|
||||
} while (again && player.isInGame());
|
||||
}
|
||||
}
|
||||
// keep the target
|
||||
else {
|
||||
newTarget.addTarget(targetId, target.getTargetAmount(targetId), ability, game, false);
|
||||
}
|
||||
}
|
||||
return newTarget;
|
||||
}
|
||||
|
||||
|
||||
private String getNamesOfTargets(UUID targetId, Game game) {
|
||||
MageObject object = game.getObject(targetId);
|
||||
String targetNames = null;
|
||||
if (object == null) {
|
||||
Player targetPlayer = game.getPlayer(targetId);
|
||||
if (targetPlayer != null) {
|
||||
targetNames = targetPlayer.getLogName();
|
||||
}
|
||||
} else {
|
||||
targetNames = object.getName();
|
||||
}
|
||||
return targetNames;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
260
Mage/src/mage/game/stack/StackObjImpl.java
Normal file
260
Mage/src/mage/game/stack/StackObjImpl.java
Normal file
|
|
@ -0,0 +1,260 @@
|
|||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package mage.game.stack;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import mage.MageObject;
|
||||
import mage.abilities.Abilities;
|
||||
import mage.abilities.AbilitiesImpl;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.Mode;
|
||||
import mage.abilities.dynamicvalue.common.StaticValue;
|
||||
import mage.constants.Outcome;
|
||||
import mage.filter.FilterPermanent;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.players.Player;
|
||||
import mage.target.Target;
|
||||
import mage.target.TargetAmount;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author LevelX2
|
||||
*/
|
||||
public abstract class StackObjImpl implements StackObject {
|
||||
|
||||
/**
|
||||
* Choose new targets for a stack Object
|
||||
*
|
||||
* @param game
|
||||
* @param playerId Player UUID who changes the targets.
|
||||
* @return
|
||||
*/
|
||||
public boolean chooseNewTargets(Game game, UUID playerId) {
|
||||
return chooseNewTargets(game, playerId, false, false, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 114.6. Some effects allow a player to change the target(s) of a spell or
|
||||
* ability, and other effects allow a player to choose new targets for a
|
||||
* spell or ability.
|
||||
*
|
||||
* 114.6a If an effect allows a player to "change the
|
||||
* target(s)" of a spell or ability, each target can be changed only to
|
||||
* another legal target. If a target can't be changed to another legal
|
||||
* target, the original target is unchanged, even if the original target is
|
||||
* itself illegal by then. If all the targets aren't changed to other legal
|
||||
* targets, none of them are changed.
|
||||
*
|
||||
* 114.6b If an effect allows a player to "change a target" of a
|
||||
* spell or ability, the process described in rule 114.6a
|
||||
* is followed, except that only one of those targets may be changed
|
||||
* (rather than all of them or none of them).
|
||||
*
|
||||
* 114.6c If an effect allows a
|
||||
* player to "change any targets" of a spell or ability, the process
|
||||
* described in rule 114.6a is followed, except that any number of those
|
||||
* targets may be changed (rather than all of them or none of them).
|
||||
*
|
||||
* 114.6d If an effect allows a player to "choose new targets" for a spell or
|
||||
* ability, the player may leave any number of the targets unchanged, even
|
||||
* if those targets would be illegal. If the player chooses to change some
|
||||
* or all of the targets, the new targets must be legal and must not cause
|
||||
* any unchanged targets to become illegal.
|
||||
*
|
||||
* 114.6e When changing targets or
|
||||
* choosing new targets for a spell or ability, only the final set of
|
||||
* targets is evaluated to determine whether the change is legal.
|
||||
*
|
||||
* Example: Arc Trail is a sorcery that reads "Arc Trail deals 2 damage to
|
||||
* target creature or player and 1 damage to another target creature or
|
||||
* player." The current targets of Arc Trail are Runeclaw Bear and Llanowar
|
||||
* Elves, in that order. You cast Redirect, an instant that reads "You may
|
||||
* choose new targets for target spell," targeting Arc Trail. You can change
|
||||
* the first target to Llanowar Elves and change the second target to
|
||||
* Runeclaw Bear.
|
||||
*
|
||||
* 114.7. Modal spells and abilities may have different targeting
|
||||
* requirements for each mode. An effect that allows a player to change the
|
||||
* target(s) of a modal spell or ability, or to choose new targets for a
|
||||
* modal spell or ability, doesn't allow that player to change its mode.
|
||||
* (See rule 700.2.)
|
||||
*
|
||||
* 706.10c Some effects copy a spell or ability and state that its
|
||||
* controller may choose new targets for the copy. The player may leave any
|
||||
* number of the targets unchanged, even if those targets would be illegal.
|
||||
* If the player chooses to change some or all of the targets, the new
|
||||
* targets must be legal. Once the player has decided what the copy's
|
||||
* targets will be, the copy is put onto the stack with those targets.
|
||||
*
|
||||
* @param game
|
||||
* @param targetControllerId - player that can/has to change the target of the spell
|
||||
* @param forceChange - does only work for targets with maximum of one targetId
|
||||
* @param onlyOneTarget - 114.6b one target must be changed to another target
|
||||
* @param filterNewTarget restriction for the new target, if null nothing is cheched
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public boolean chooseNewTargets(Game game, UUID targetControllerId, boolean forceChange, boolean onlyOneTarget, FilterPermanent filterNewTarget) {
|
||||
Player targetController = game.getPlayer(targetControllerId);
|
||||
if (targetController != null) {
|
||||
StringBuilder newTargetDescription = new StringBuilder();
|
||||
// Fused split spells or spells where "Splice on Arcane" was used can have more than one ability
|
||||
Abilities<Ability> objectAbilities = new AbilitiesImpl<>();
|
||||
if (this instanceof Spell) {
|
||||
objectAbilities.addAll(((Spell)this).getSpellAbilities());
|
||||
} else {
|
||||
objectAbilities.addAll(getAbilities());
|
||||
}
|
||||
for (Ability ability : objectAbilities) {
|
||||
// Some spells can have more than one mode
|
||||
for (UUID modeId : ability.getModes().getSelectedModes()) {
|
||||
Mode mode = ability.getModes().get(modeId);
|
||||
for (Target target : mode.getTargets()) {
|
||||
Target newTarget = chooseNewTarget(targetController, ability, mode, target, forceChange, filterNewTarget, game);
|
||||
// clear the old target and copy all targets from new target
|
||||
target.clearChosen();
|
||||
for (UUID targetId : newTarget.getTargets()) {
|
||||
target.addTarget(targetId, newTarget.getTargetAmount(targetId), ability, game, false);
|
||||
}
|
||||
|
||||
}
|
||||
newTargetDescription.append(ability.getTargetDescription(mode.getTargets(), game));
|
||||
}
|
||||
|
||||
}
|
||||
if (newTargetDescription.length() > 0 && !game.isSimulation()) {
|
||||
game.informPlayers(this.getLogName() + " is now " + newTargetDescription.toString());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the change of one target instance of a mode
|
||||
*
|
||||
* @param targetController - player that can choose the new target
|
||||
* @param ability
|
||||
* @param mode
|
||||
* @param target
|
||||
* @param forceChange
|
||||
* @param game
|
||||
* @return
|
||||
*/
|
||||
private Target chooseNewTarget(Player targetController, Ability ability, Mode mode, Target target, boolean forceChange, FilterPermanent filterNewTarget, Game game) {
|
||||
Target newTarget = target.copy();
|
||||
if (!targetController.getId().equals(getControllerId())) {
|
||||
newTarget.setTargetController(targetController.getId()); // target controller for the change is different from spell controller
|
||||
newTarget.setAbilityController(getControllerId());
|
||||
}
|
||||
newTarget.clearChosen();
|
||||
for (UUID targetId : target.getTargets()) {
|
||||
String targetNames = getNamesOftargets(targetId, game);
|
||||
// change the target?
|
||||
if (targetNames != null
|
||||
&& (forceChange || targetController.chooseUse(mode.getEffects().get(0).getOutcome(), "Change this target: " + targetNames + "?", game))) {
|
||||
Set<UUID> possibleTargets = target.possibleTargets(this.getSourceId(), getControllerId(), game);
|
||||
// choose exactly one other target - already targeted objects are not counted
|
||||
if (forceChange && possibleTargets != null && possibleTargets.size() > 1) { // controller of spell must be used (e.g. TargetOpponent)
|
||||
int iteration = 0;
|
||||
do {
|
||||
if (iteration > 0 && !game.isSimulation()) {
|
||||
game.informPlayer(targetController, "You may only select exactly one target that must be different from the origin target!");
|
||||
}
|
||||
iteration++;
|
||||
newTarget.clearChosen();
|
||||
|
||||
newTarget.chooseTarget(mode.getEffects().get(0).getOutcome(), getControllerId(), ability, game);
|
||||
// check target restriction
|
||||
if (newTarget.getFirstTarget() != null && filterNewTarget != null) {
|
||||
Permanent newTargetPermanent = game.getPermanent(newTarget.getFirstTarget());
|
||||
if (newTargetPermanent == null || !filterNewTarget.match(newTargetPermanent, game)) {
|
||||
game.informPlayer(targetController, "Target does not fullfil the target requirements (" + filterNewTarget.getMessage() +")");
|
||||
newTarget.clearChosen();
|
||||
}
|
||||
}
|
||||
} while (targetController.isInGame() && (targetId.equals(newTarget.getFirstTarget()) || newTarget.getTargets().size() != 1));
|
||||
// choose a new target
|
||||
} else {
|
||||
// build a target definition with exactly one possible target to select that replaces old target
|
||||
Target tempTarget = target.copy();
|
||||
if (target instanceof TargetAmount) {
|
||||
((TargetAmount)tempTarget).setAmountDefinition(new StaticValue(target.getTargetAmount(targetId)));
|
||||
}
|
||||
tempTarget.setMinNumberOfTargets(1);
|
||||
tempTarget.setMaxNumberOfTargets(1);
|
||||
if (!targetController.getId().equals(getControllerId())) {
|
||||
tempTarget.setTargetController(targetController.getId());
|
||||
tempTarget.setAbilityController(getControllerId());
|
||||
}
|
||||
boolean again;
|
||||
do {
|
||||
again = false;
|
||||
tempTarget.clearChosen();
|
||||
if (!tempTarget.chooseTarget(mode.getEffects().get(0).getOutcome(), getControllerId(), ability, game)) {
|
||||
if (targetController.chooseUse(Outcome.Benefit, "No target object selected. Reset to original target?", game)) {
|
||||
// use previous target no target was selected
|
||||
newTarget.addTarget(targetId, target.getTargetAmount(targetId), ability, game, false);
|
||||
} else {
|
||||
again = true;
|
||||
}
|
||||
} else {
|
||||
// if possible add the alternate Target - it may not be included in the old definition nor in the already selected targets of the new definition
|
||||
if (newTarget.getTargets().contains(tempTarget.getFirstTarget()) || target.getTargets().contains(tempTarget.getFirstTarget())) {
|
||||
if(targetController.isHuman()) {
|
||||
game.informPlayer(targetController, "This target was already selected from origin spell. You can only keep this target!");
|
||||
again = true;
|
||||
} else {
|
||||
newTarget.addTarget(targetId, target.getTargetAmount(targetId), ability, game, false);
|
||||
}
|
||||
} else if (!target.canTarget(getControllerId(), tempTarget.getFirstTarget(), ability, game)) {
|
||||
if(targetController.isHuman()) {
|
||||
game.informPlayer(targetController, "This target is not valid!");
|
||||
again = true;
|
||||
} else {
|
||||
// keep the old
|
||||
newTarget.addTarget(targetId, target.getTargetAmount(targetId), ability, game, false);
|
||||
}
|
||||
} else if (newTarget.getFirstTarget() != null && filterNewTarget != null) {
|
||||
Permanent newTargetPermanent = game.getPermanent(newTarget.getFirstTarget());
|
||||
if (newTargetPermanent == null || !filterNewTarget.match(newTargetPermanent, game)) {
|
||||
game.informPlayer(targetController, "This target does not fullfil the target requirements (" + filterNewTarget.getMessage() +")");
|
||||
again = true;
|
||||
}
|
||||
} else {
|
||||
// valid target was selected, add it to the new target definition
|
||||
newTarget.addTarget(tempTarget.getFirstTarget(), target.getTargetAmount(targetId), ability, game, false);
|
||||
}
|
||||
}
|
||||
} while (again && targetController.isInGame());
|
||||
}
|
||||
}
|
||||
// keep the target
|
||||
else {
|
||||
newTarget.addTarget(targetId, target.getTargetAmount(targetId), ability, game, false);
|
||||
}
|
||||
}
|
||||
return newTarget;
|
||||
}
|
||||
|
||||
|
||||
private String getNamesOftargets(UUID targetId, Game game) {
|
||||
MageObject object = game.getObject(targetId);
|
||||
String name = null;
|
||||
if (object == null) {
|
||||
Player targetPlayer = game.getPlayer(targetId);
|
||||
if (targetPlayer != null) {
|
||||
name = targetPlayer.getLogName();
|
||||
}
|
||||
} else {
|
||||
name = object.getName();
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -113,6 +113,9 @@ public abstract class Phase implements Serializable {
|
|||
currentStep = step;
|
||||
if (!game.getState().getTurnMods().skipStep(activePlayerId, getStep().getType())) {
|
||||
playStep(game);
|
||||
if (game.executingRollback()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (!game.isSimulation() && checkStopOnStepOption(game)) {
|
||||
return false;
|
||||
|
|
@ -201,6 +204,9 @@ public abstract class Phase implements Serializable {
|
|||
prePriority(game, activePlayerId);
|
||||
if (!game.isPaused() && !game.gameOver(null)) {
|
||||
currentStep.priority(game, activePlayerId, false);
|
||||
if(game.executingRollback()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!game.isPaused() && !game.gameOver(null)) {
|
||||
postPriority(game, activePlayerId);
|
||||
|
|
|
|||
|
|
@ -117,30 +117,34 @@ public class Turn implements Serializable {
|
|||
return null;
|
||||
}
|
||||
|
||||
public void play(Game game, UUID activePlayerId) {
|
||||
public void play(Game game, Player activePlayer) {
|
||||
activePlayer.becomesActivePlayer();
|
||||
this.setDeclareAttackersStepStarted(false);
|
||||
if (game.isPaused() || game.gameOver(null)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (game.getState().getTurnMods().skipTurn(activePlayerId)) {
|
||||
if (game.getState().getTurnMods().skipTurn(activePlayer.getId())) {
|
||||
return;
|
||||
}
|
||||
|
||||
checkTurnIsControlledByOtherPlayer(game, activePlayerId);
|
||||
checkTurnIsControlledByOtherPlayer(game, activePlayer.getId());
|
||||
|
||||
this.activePlayerId = activePlayerId;
|
||||
this.activePlayerId = activePlayer.getId();
|
||||
resetCounts();
|
||||
game.getPlayer(activePlayerId).beginTurn(game);
|
||||
game.getPlayer(activePlayer.getId()).beginTurn(game);
|
||||
for (Phase phase: phases) {
|
||||
if (game.isPaused() || game.gameOver(null)) {
|
||||
return;
|
||||
}
|
||||
if (!isEndTurnRequested() || phase.getType().equals(TurnPhase.END)) {
|
||||
currentPhase = phase;
|
||||
game.fireEvent(new GameEvent(GameEvent.EventType.PHASE_CHANGED, activePlayerId, null, activePlayerId));
|
||||
if (!game.getState().getTurnMods().skipPhase(activePlayerId, currentPhase.getType())) {
|
||||
if (phase.play(game, activePlayerId)) {
|
||||
game.fireEvent(new GameEvent(GameEvent.EventType.PHASE_CHANGED, activePlayer.getId(), null, activePlayer.getId()));
|
||||
if (!game.getState().getTurnMods().skipPhase(activePlayer.getId(), currentPhase.getType())) {
|
||||
if (phase.play(game, activePlayer.getId())) {
|
||||
if(game.executingRollback()) {
|
||||
return;
|
||||
}
|
||||
//20091005 - 500.4/703.4n
|
||||
game.emptyManaPools();
|
||||
game.saveState(false);
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ public class ManaPool implements Serializable {
|
|||
private final List<ManaPoolItem> manaItems = new ArrayList<>();
|
||||
|
||||
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 final Set<ManaType> doNotEmptyManaTypes = new HashSet<>();
|
||||
|
|
@ -68,6 +69,7 @@ public class ManaPool implements Serializable {
|
|||
public ManaPool(UUID playerId) {
|
||||
this.playerId = playerId;
|
||||
autoPayment = true;
|
||||
autoPaymentRestricted = true;
|
||||
unlockedManaType = null;
|
||||
}
|
||||
|
||||
|
|
@ -77,6 +79,7 @@ public class ManaPool implements Serializable {
|
|||
manaItems.add(item.copy());
|
||||
}
|
||||
this.autoPayment = pool.autoPayment;
|
||||
this.autoPaymentRestricted = pool.autoPaymentRestricted;
|
||||
this.unlockedManaType = pool.unlockedManaType;
|
||||
this.doNotEmptyManaTypes.addAll(pool.doNotEmptyManaTypes);
|
||||
}
|
||||
|
|
@ -101,11 +104,25 @@ public class ManaPool implements Serializable {
|
|||
return get(ManaType.BLACK);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param manaType the mana type that should be paid
|
||||
* @param ability
|
||||
* @param filter
|
||||
* @param game
|
||||
* @return
|
||||
*/
|
||||
public boolean pay(ManaType manaType, Ability ability, Filter filter, Game game) {
|
||||
if (!autoPayment && !manaType.equals(unlockedManaType)) {
|
||||
// if manual payment and the needed mana type was not unlocked, nothing will be paid
|
||||
return false;
|
||||
}
|
||||
if (autoPayment && autoPaymentRestricted && !wasManaAddedBeyondStock() && !manaType.equals(unlockedManaType)) {
|
||||
// if automatic restricted payment and there is laready mana in the pool
|
||||
// and the needed mana type was not unlocked, nothing will be paid
|
||||
return false;
|
||||
}
|
||||
|
||||
if (getConditional(manaType, ability, filter, game) > 0) {
|
||||
removeConditional(manaType, ability, game);
|
||||
lockManaType(); // pay only one mana if mana payment is set to manually
|
||||
|
|
@ -118,6 +135,10 @@ public class ManaPool implements Serializable {
|
|||
continue;
|
||||
}
|
||||
}
|
||||
if (!manaType.equals(unlockedManaType) && autoPayment && autoPaymentRestricted && mana.count() == mana.getStock()) {
|
||||
// no mana added beyond the stock so don't auto pay this
|
||||
continue;
|
||||
}
|
||||
boolean spendAnyMana = spendAnyMana(ability, game);
|
||||
if (mana.get(manaType) > 0 || (spendAnyMana && mana.count() > 0)) {
|
||||
GameEvent event = new GameEvent(GameEvent.EventType.MANA_PAYED, ability.getId(), mana.getSourceId(), ability.getControllerId(), 0, mana.getFlag());
|
||||
|
|
@ -128,6 +149,9 @@ public class ManaPool implements Serializable {
|
|||
} else {
|
||||
mana.remove(manaType);
|
||||
}
|
||||
if (mana.count() == 0) { // so no items with count 0 stay in list
|
||||
manaItems.remove(mana);
|
||||
}
|
||||
lockManaType(); // pay only one mana if mana payment is set to manually
|
||||
return true;
|
||||
}
|
||||
|
|
@ -416,6 +440,14 @@ public class ManaPool implements Serializable {
|
|||
this.autoPayment = autoPayment;
|
||||
}
|
||||
|
||||
public void setAutoPaymentRestricted(boolean autoPaymentRestricted) {
|
||||
this.autoPaymentRestricted = autoPaymentRestricted;
|
||||
}
|
||||
|
||||
public boolean isAutoPaymentRestricted() {
|
||||
return autoPaymentRestricted;
|
||||
}
|
||||
|
||||
public ManaType getUnlockedManaType() {
|
||||
return unlockedManaType;
|
||||
}
|
||||
|
|
@ -428,4 +460,18 @@ public class ManaPool implements Serializable {
|
|||
this.unlockedManaType = null;
|
||||
}
|
||||
|
||||
public void setStock() {
|
||||
for (ManaPoolItem mana : manaItems) {
|
||||
mana.setStock(mana.count());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean wasManaAddedBeyondStock() {
|
||||
for (ManaPoolItem mana : manaItems) {
|
||||
if (mana.getStock() < mana.count()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ public class ManaPoolItem implements Serializable {
|
|||
private UUID originalId; // originalId of the mana producing ability
|
||||
private boolean flag = false;
|
||||
private Duration duration;
|
||||
private int stock; // amount the item had at the start of casting something
|
||||
|
||||
public ManaPoolItem() {}
|
||||
|
||||
|
|
@ -91,6 +92,7 @@ public class ManaPoolItem implements Serializable {
|
|||
this.originalId = item.originalId;
|
||||
this.flag = item.flag;
|
||||
this.duration = item.duration;
|
||||
this.stock = item.stock;
|
||||
}
|
||||
|
||||
public ManaPoolItem copy() {
|
||||
|
|
@ -207,8 +209,9 @@ public class ManaPoolItem implements Serializable {
|
|||
}
|
||||
|
||||
public void removeAny() {
|
||||
int oldCount = count();
|
||||
if (black > 0) {
|
||||
black--;
|
||||
black--;
|
||||
} else if (blue > 0) {
|
||||
blue--;
|
||||
} else if (green > 0) {
|
||||
|
|
@ -219,10 +222,14 @@ public class ManaPoolItem implements Serializable {
|
|||
white--;
|
||||
} else if (colorless > 0) {
|
||||
colorless--;
|
||||
}
|
||||
}
|
||||
if (stock == oldCount && oldCount > count()) {
|
||||
stock--;
|
||||
}
|
||||
}
|
||||
|
||||
public void remove(ManaType manaType) {
|
||||
int oldCount = count();
|
||||
switch(manaType) {
|
||||
case BLACK:
|
||||
if (black > 0) {
|
||||
|
|
@ -255,6 +262,9 @@ public class ManaPoolItem implements Serializable {
|
|||
}
|
||||
break;
|
||||
}
|
||||
if (stock == oldCount && oldCount > count()) {
|
||||
stock--;
|
||||
}
|
||||
}
|
||||
|
||||
public void clear(ManaType manaType) {
|
||||
|
|
@ -310,5 +320,13 @@ public class ManaPoolItem implements Serializable {
|
|||
public void setDuration(Duration duration) {
|
||||
this.duration = duration;
|
||||
}
|
||||
|
||||
public int getStock() {
|
||||
return stock;
|
||||
}
|
||||
|
||||
public void setStock(int stock) {
|
||||
this.stock = stock;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ import mage.abilities.costs.AlternativeSourceCosts;
|
|||
import mage.abilities.costs.VariableCost;
|
||||
import mage.abilities.costs.mana.ManaCost;
|
||||
import mage.abilities.costs.mana.ManaCosts;
|
||||
import mage.abilities.mana.ManaOptions;
|
||||
import mage.cards.Card;
|
||||
import mage.cards.Cards;
|
||||
import mage.cards.decks.Deck;
|
||||
|
|
@ -280,6 +281,7 @@ public interface Player extends MageItem, Copyable<Player> {
|
|||
void leave();
|
||||
void concede(Game game);
|
||||
void abort();
|
||||
void abortReset();
|
||||
void skip();
|
||||
|
||||
// priority, undo, ...
|
||||
|
|
@ -376,8 +378,10 @@ public interface Player extends MageItem, Copyable<Player> {
|
|||
void phasing(Game game);
|
||||
void untap(Game game);
|
||||
|
||||
ManaOptions getManaAvailable(Game game);
|
||||
List<Ability> getPlayable(Game game, boolean hidden);
|
||||
List<Ability> getPlayableOptions(Ability ability, Game game);
|
||||
|
||||
|
||||
Set<UUID> getPlayableInHand(Game game);
|
||||
LinkedHashMap<UUID, ActivatedAbility> getUseableActivatedAbilities(MageObject object, Zone zone, Game game);
|
||||
|
|
@ -385,7 +389,7 @@ public interface Player extends MageItem, Copyable<Player> {
|
|||
void addCounters(Counter counter, Game game);
|
||||
List<UUID> getAttachments();
|
||||
boolean addAttachment(UUID permanentId, Game game);
|
||||
boolean removeAttachment(UUID permanentId, Game game);
|
||||
boolean removeAttachment(Permanent permanent, Game game);
|
||||
|
||||
/**
|
||||
* Signals that the player becomes active player in this turn.
|
||||
|
|
|
|||
|
|
@ -130,7 +130,6 @@ import mage.target.common.TargetCardInLibrary;
|
|||
import mage.target.common.TargetDiscard;
|
||||
import mage.util.CardUtil;
|
||||
import mage.util.GameLog;
|
||||
import mage.watchers.common.BloodthirstWatcher;
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
|
|
@ -370,8 +369,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
this.canPaySacrificeCost = player.canPaySacrificeCost();
|
||||
this.loseByZeroOrLessLife = player.canLoseByZeroOrLessLife();
|
||||
this.canPlayCardsFromGraveyard = player.canPlayCardsFromGraveyard();
|
||||
this.alternativeSourceCosts.addAll(player.getAlternativeSourceCosts());
|
||||
this.storedBookmark = player.getStoredBookmark();
|
||||
this.alternativeSourceCosts.addAll(player.getAlternativeSourceCosts());
|
||||
|
||||
this.topCardRevealed = player.isTopCardRevealed();
|
||||
this.playersUnderYourControl.clear();
|
||||
|
|
@ -386,7 +384,9 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
this.castSourceIdWithAlternateMana = player.getCastSourceIdWithAlternateMana();
|
||||
this.castSourceIdManaCosts = player.getCastSourceIdManaCosts();
|
||||
|
||||
this.usersAllowedToSeeHandCards.addAll(player.getUsersAllowedToSeeHandCards());
|
||||
// Don't restore!
|
||||
// this.storedBookmark
|
||||
// this.usersAllowedToSeeHandCards
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -573,6 +573,10 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
playersUnderYourControl.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* returns true if the player has the control itself - false if the player is controlled by another player
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public boolean isGameUnderControl() {
|
||||
return isGameUnderControl;
|
||||
|
|
@ -690,7 +694,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
@Override
|
||||
public Cards discard(int amount, boolean random, Ability source, Game game) {
|
||||
Cards discardedCards = new CardsImpl();
|
||||
if (amount >= this.getHand().size()) {
|
||||
if (this.getHand().size() == 1) {
|
||||
discardedCards.addAll(this.getHand());
|
||||
while (this.getHand().size() > 0) {
|
||||
discard(this.getHand().get(this.getHand().iterator().next(), game), source, game);
|
||||
|
|
@ -772,15 +776,12 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAttachment(UUID permanentId, Game game) {
|
||||
if (this.attachments.contains(permanentId)) {
|
||||
Permanent aura = game.getPermanent(permanentId);
|
||||
if (aura != null) {
|
||||
if (!game.replaceEvent(new GameEvent(GameEvent.EventType.UNATTACH, playerId, permanentId, aura.getControllerId()))) {
|
||||
this.attachments.remove(permanentId);
|
||||
aura.attachTo(null, game);
|
||||
}
|
||||
game.fireEvent(new GameEvent(GameEvent.EventType.UNATTACHED, playerId, permanentId, aura.getControllerId()));
|
||||
public boolean removeAttachment(Permanent attachment, Game game) {
|
||||
if (this.attachments.contains(attachment.getId())) {
|
||||
if (!game.replaceEvent(new GameEvent(GameEvent.EventType.UNATTACH, playerId, attachment.getId(), attachment.getControllerId()))) {
|
||||
this.attachments.remove(attachment.getId());
|
||||
attachment.attachTo(null, game);
|
||||
game.fireEvent(new GameEvent(GameEvent.EventType.UNATTACHED, playerId, attachment.getId(), attachment.getControllerId()));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -795,7 +796,13 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
Permanent attachedTo = game.getPermanent(permanent.getAttachedTo());
|
||||
if (attachedTo != null) {
|
||||
attachedTo.removeAttachment(permanent.getId(), game);
|
||||
} else {
|
||||
Player attachedToPlayer = game.getPlayer(permanent.getAttachedTo());
|
||||
if (attachedToPlayer != null) {
|
||||
attachedToPlayer.removeAttachment(permanent, game);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if (permanent.getPairedCard() != null) {
|
||||
Permanent pairedCard = game.getPermanent(permanent.getPairedCard());
|
||||
|
|
@ -1148,7 +1155,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
return true;
|
||||
}
|
||||
}
|
||||
game.restoreState(bookmark, source.getRule());
|
||||
game.restoreState(bookmark, source.getRule()); // why restore is needed here?
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -1853,7 +1860,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
passedAllTurns = false;
|
||||
passedUntilEndOfTurn = true;
|
||||
passedUntilStackResolved = false;
|
||||
skippedAtLeastOnce = !game.getTurn().getStepType().equals(PhaseStep.END_TURN);
|
||||
skippedAtLeastOnce = !PhaseStep.END_TURN.equals(game.getTurn().getStepType());
|
||||
this.skip();
|
||||
break;
|
||||
case PASS_PRIORITY_UNTIL_NEXT_TURN: // F4
|
||||
|
|
@ -2141,7 +2148,8 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
return blockers;
|
||||
}
|
||||
|
||||
protected ManaOptions getManaAvailable(Game game) {
|
||||
@Override
|
||||
public ManaOptions getManaAvailable(Game game) {
|
||||
ManaOptions available = new ManaOptions();
|
||||
|
||||
List<Abilities<ManaAbility>> sourceWithoutCosts = new ArrayList<>();
|
||||
|
|
@ -2199,7 +2207,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
}
|
||||
|
||||
// returns only mana producers that require mana payment
|
||||
protected List<Permanent> getAvailableManaProducersWithCost(Game game) {
|
||||
public List<Permanent> getAvailableManaProducersWithCost(Game game) {
|
||||
List<Permanent> result = new ArrayList<>();
|
||||
for (Permanent permanent : game.getBattlefield().getAllActivePermanents(playerId)) {
|
||||
for (ManaAbility ability : permanent.getAbilities().getManaAbilities(Zone.BATTLEFIELD)) {
|
||||
|
|
@ -2374,7 +2382,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
|
||||
if (hidden) {
|
||||
for (Card card : hand.getUniqueCards(game)) {
|
||||
for (Ability ability : card.getAbilities()) { // gets this activated ability from hand? (Morph?)
|
||||
for (Ability ability : card.getAbilities(game)) { // gets this activated ability from hand? (Morph?)
|
||||
if (ability.getZone().match(Zone.HAND)) {
|
||||
if (ability instanceof ActivatedAbility) {
|
||||
if (!(ability instanceof PlayLandAbility)
|
||||
|
|
@ -2454,7 +2462,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
for (CommandObject commandObject : game.getState().getCommand()) {
|
||||
for (ActivatedAbility ability : commandObject.getAbilities().getActivatedAbilities(Zone.COMMAND)) {
|
||||
if (ability.getControllerId().equals(getId())
|
||||
&& ability.getAbilityType().equals(AbilityType.ACTIVATED)
|
||||
&& ability instanceof ActivatedAbility
|
||||
&& canPlay(ability, availableMana, game.getObject(ability.getSourceId()), game)) {
|
||||
playableActivated.put(ability.toString(), ability);
|
||||
}
|
||||
|
|
@ -2901,6 +2909,8 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
|
||||
@Override
|
||||
public boolean moveCardsToGraveyardWithInfo(List<Card> allCards, Ability source, Game game, Zone fromZone) {
|
||||
boolean result = true;
|
||||
UUID sourceId = source == null ? null : source.getSourceId();
|
||||
while (!allCards.isEmpty()) {
|
||||
// identify cards from one owner
|
||||
Cards cards = new CardsImpl();
|
||||
|
|
@ -2915,7 +2925,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
cards.add(card);
|
||||
}
|
||||
}
|
||||
// move cards ot graveyard in order the owner decides
|
||||
// move cards to graveyard in order the owner decides
|
||||
if (!cards.isEmpty()) {
|
||||
Player choosingPlayer = this;
|
||||
if (ownerId != this.getId()) {
|
||||
|
|
@ -2937,22 +2947,21 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
Card card = cards.get(targetObjectId, game);
|
||||
cards.remove(targetObjectId);
|
||||
if (card != null) {
|
||||
choosingPlayer.moveCardToGraveyardWithInfo(card, source.getSourceId(), game, fromZone);
|
||||
result &= choosingPlayer.moveCardToGraveyardWithInfo(card, sourceId, game, fromZone);
|
||||
}
|
||||
target.clearChosen();
|
||||
}
|
||||
if (cards.size() == 1) {
|
||||
choosingPlayer.moveCardToGraveyardWithInfo(cards.getCards(game).iterator().next(), source == null ? null : source.getSourceId(), game, fromZone);
|
||||
result &= choosingPlayer.moveCardToGraveyardWithInfo(cards.getCards(game).iterator().next(), sourceId, game, fromZone);
|
||||
}
|
||||
} else {
|
||||
for (Card card : cards.getCards(game)) {
|
||||
choosingPlayer.moveCardToGraveyardWithInfo(card, source.getSourceId(), game, fromZone);
|
||||
result &= choosingPlayer.moveCardToGraveyardWithInfo(card, sourceId, game, fromZone);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -3123,4 +3132,8 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
return matchPlayer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void abortReset() {
|
||||
abort = false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,14 +13,20 @@ public class UserData implements Serializable {
|
|||
protected int avatarId;
|
||||
protected boolean showAbilityPickerForced;
|
||||
protected boolean allowRequestShowHandCards;
|
||||
protected boolean confirmEmptyManaPool;
|
||||
protected UserSkipPrioritySteps userSkipPrioritySteps;
|
||||
protected String flagName;
|
||||
|
||||
public UserData(UserGroup userGroup, int avatarId, boolean showAbilityPickerForced, boolean allowRequestShowHandCards, UserSkipPrioritySteps userSkipPrioritySteps) {
|
||||
public UserData(UserGroup userGroup, int avatarId, boolean showAbilityPickerForced,
|
||||
boolean allowRequestShowHandCards, boolean confirmEmptyManaPool, UserSkipPrioritySteps userSkipPrioritySteps,
|
||||
String flagName) {
|
||||
this.groupId = userGroup.getGroupId();
|
||||
this.avatarId = avatarId;
|
||||
this.showAbilityPickerForced = showAbilityPickerForced;
|
||||
this.allowRequestShowHandCards = allowRequestShowHandCards;
|
||||
this.userSkipPrioritySteps = userSkipPrioritySteps;
|
||||
this.confirmEmptyManaPool = confirmEmptyManaPool;
|
||||
this.flagName = flagName;
|
||||
}
|
||||
|
||||
public void setGroupId(int groupId) {
|
||||
|
|
@ -66,5 +72,17 @@ public class UserData implements Serializable {
|
|||
public void setUserSkipPrioritySteps(UserSkipPrioritySteps userSkipPrioritySteps) {
|
||||
this.userSkipPrioritySteps = userSkipPrioritySteps;
|
||||
}
|
||||
|
||||
public boolean confirmEmptyManaPool() {
|
||||
return confirmEmptyManaPool;
|
||||
}
|
||||
|
||||
public void setConfirmEmptyManaPool(boolean confirmEmptyManaPool) {
|
||||
this.confirmEmptyManaPool = confirmEmptyManaPool;
|
||||
}
|
||||
|
||||
public String getFlagName() {
|
||||
return flagName;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ import java.io.Serializable;
|
|||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import mage.players.Player;
|
||||
|
||||
/**
|
||||
*
|
||||
|
|
@ -108,4 +109,7 @@ public interface Target extends Serializable {
|
|||
// some targets are choosen from players that are not the controller of the ability (e.g. Pandemonium)
|
||||
void setTargetController(UUID playerId);
|
||||
UUID getTargetController();
|
||||
void setAbilityController(UUID playerId);
|
||||
UUID getAbilityController();
|
||||
Player getTargetController(Game game, UUID playerId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -117,10 +117,9 @@ public abstract class TargetAmount extends TargetImpl {
|
|||
if (!amountWasSet) {
|
||||
setAmount(source, game);
|
||||
}
|
||||
Player player = game.getPlayer(playerId);
|
||||
chosen = remainingAmount == 0;
|
||||
while (remainingAmount > 0) {
|
||||
if (!player.chooseTargetAmount(outcome, this, source, game)) {
|
||||
if (!getTargetController(game, playerId).chooseTargetAmount(outcome, this, source, game)) {
|
||||
return chosen;
|
||||
}
|
||||
chosen = remainingAmount == 0;
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ public abstract class TargetImpl implements Target {
|
|||
protected boolean notTarget = false;
|
||||
protected boolean atRandom = false;
|
||||
protected UUID targetController = null; // if null the ability controller is the targetController
|
||||
protected UUID abilityController = null; // only used if target controller != ability controller
|
||||
|
||||
@Override
|
||||
public abstract TargetImpl copy();
|
||||
|
|
@ -86,6 +87,7 @@ public abstract class TargetImpl implements Target {
|
|||
this.atRandom = target.atRandom;
|
||||
this.notTarget = target.notTarget;
|
||||
this.targetController = target.targetController;
|
||||
this.abilityController = target.abilityController;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -110,6 +112,12 @@ public abstract class TargetImpl implements Target {
|
|||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
String suffix = "";
|
||||
if (targetController != null) {
|
||||
// Hint for the selecting player that the targets must be valid from the point of the ability controller
|
||||
// e.g. select opponent text may be misleading otherwise
|
||||
suffix = " (target controlling!)";
|
||||
}
|
||||
if (getMaxNumberOfTargets() != 1) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Select ").append(targetName);
|
||||
|
|
@ -118,12 +126,13 @@ public abstract class TargetImpl implements Target {
|
|||
} else {
|
||||
sb.append(" (").append(targets.size()).append(")");
|
||||
}
|
||||
sb.append(suffix);
|
||||
return sb.toString();
|
||||
}
|
||||
if (targetName.startsWith("another") || targetName.startsWith("a ") || targetName.startsWith("an ")) {
|
||||
return "Select " + targetName;
|
||||
return "Select " + targetName + suffix;
|
||||
}
|
||||
return "Select a " + targetName;
|
||||
return "Select a " + targetName + suffix;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -298,7 +307,6 @@ public abstract class TargetImpl implements Target {
|
|||
|
||||
@Override
|
||||
public boolean chooseTarget(Outcome outcome, UUID playerId, Ability source, Game game) {
|
||||
Player player = game.getPlayer(playerId);
|
||||
while (!isChosen() && !doneChosing()) {
|
||||
chosen = targets.size() >= getNumberOfTargets();
|
||||
if (isRandom()) {
|
||||
|
|
@ -316,7 +324,7 @@ public abstract class TargetImpl implements Target {
|
|||
return chosen;
|
||||
}
|
||||
} else {
|
||||
if (!player.chooseTarget(outcome, this, source, game)) {
|
||||
if (!getTargetController(game, playerId).chooseTarget(outcome, this, source, game)) {
|
||||
return chosen;
|
||||
}
|
||||
}
|
||||
|
|
@ -433,5 +441,23 @@ public abstract class TargetImpl implements Target {
|
|||
return targetController;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAbilityController(UUID playerId) {
|
||||
this.abilityController = playerId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getAbilityController() {
|
||||
return abilityController;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Player getTargetController(Game game, UUID playerId) {
|
||||
if (getTargetController() != null) {
|
||||
return game.getPlayer(getTargetController());
|
||||
} else {
|
||||
return game.getPlayer(playerId);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ import java.util.zip.GZIPOutputStream;
|
|||
/**
|
||||
*
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
* @param <T>
|
||||
*/
|
||||
public class Copier<T> {
|
||||
|
||||
|
|
@ -95,8 +96,7 @@ public class Copier<T> {
|
|||
|
||||
public T uncompressCopy(byte[] buffer) {
|
||||
T copy = null;
|
||||
try {
|
||||
ObjectInputStream in = new CopierObjectInputStream(loader, new GZIPInputStream(new ByteArrayInputStream(buffer)));
|
||||
try (ObjectInputStream in = new CopierObjectInputStream(loader, new GZIPInputStream(new ByteArrayInputStream(buffer)))) {
|
||||
copy = (T) in.readObject();
|
||||
}
|
||||
catch(IOException e) {
|
||||
|
|
|
|||
|
|
@ -38,6 +38,8 @@ import mage.ObjectColor;
|
|||
public class GameLog {
|
||||
|
||||
static final String LOG_COLOR_PLAYER = "#20B2AA"; // LightSeaGreen
|
||||
static final String LOG_COLOR_PLAYER_REQUEST = "#D2691E"; // Chocolate
|
||||
static final String LOG_COLOR_PLAYER_CONFIRM = "#D2691E"; // Chocolate
|
||||
static final String LOG_COLOR_GREEN = "#90EE90"; // LightGreen
|
||||
static final String LOG_COLOR_RED = "#FF6347"; // Tomato
|
||||
static final String LOG_COLOR_BLUE = "#87CEFA"; // LightSkyBlue
|
||||
|
|
@ -54,7 +56,7 @@ public class GameLog {
|
|||
}
|
||||
|
||||
public static String getColoredObjectName(MageObject mageObject) {
|
||||
return "<font color=\'" + getColorName(mageObject.getColor()) + "\'>" + mageObject.getName() + " ["+mageObject.getId().toString().substring(0,3) + "]</font>";
|
||||
return "<font color=\'" + getColorName(mageObject.getColor(null)) + "\'>" + mageObject.getName() + " ["+mageObject.getId().toString().substring(0,3) + "]</font>";
|
||||
}
|
||||
|
||||
public static String getNeutralColoredText(String text) {
|
||||
|
|
@ -64,6 +66,18 @@ public class GameLog {
|
|||
public static String getColoredPlayerName(String name) {
|
||||
return "<font color=\'" + LOG_COLOR_PLAYER + "\'>" + name + "</font>";
|
||||
}
|
||||
|
||||
public static String getPlayerRequestColoredText(String name) {
|
||||
return "<font color=\'" + LOG_COLOR_PLAYER_REQUEST + "\'>" + name + "</font>";
|
||||
}
|
||||
|
||||
public static String getPlayerConfirmColoredText(String name) {
|
||||
return "<font color=\'" + LOG_COLOR_PLAYER_CONFIRM + "\'>" + name + "</font>";
|
||||
}
|
||||
|
||||
public static String getSmallSecondLineText(String text) {
|
||||
return "<div style='font-size:11pt'>" + text + "</div>";
|
||||
}
|
||||
|
||||
private static String getColorName(ObjectColor objectColor) {
|
||||
if (objectColor.isMulticolored()) {
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ public class CopyTokenFunction implements Function<Token, Card> {
|
|||
}
|
||||
|
||||
target.setName(sourceObj.getName());
|
||||
target.getColor().setColor(sourceObj.getColor());
|
||||
target.getColor(null).setColor(sourceObj.getColor(null));
|
||||
target.getManaCost().clear();
|
||||
target.getManaCost().add(sourceObj.getManaCost());
|
||||
target.getCardType().clear();
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ public class TraceUtil {
|
|||
for (UUID blockerId : group.getBlockers()) {
|
||||
Permanent blocker = game.getPermanent(blockerId);
|
||||
if (blocker != null && !blocker.getCardType().contains(CardType.ARTIFACT)
|
||||
&& !attacker.getColor().shares(blocker.getColor())) {
|
||||
&& !attacker.getColor(game).shares(blocker.getColor(game))) {
|
||||
log.warn("Found creature with intimidate blocked by non artifact not sharing color creature");
|
||||
traceCombat(game, attacker, blocker);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ public abstract class Watcher implements Serializable {
|
|||
public boolean conditionMet() {
|
||||
return condition;
|
||||
}
|
||||
|
||||
|
||||
public void reset() {
|
||||
condition = false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
package mage.watchers.common;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.WatcherScope;
|
||||
import mage.game.Game;
|
||||
|
|
@ -13,12 +16,15 @@ import mage.watchers.Watcher;
|
|||
*/
|
||||
public class LandfallWatcher extends Watcher {
|
||||
|
||||
Set<UUID> playerPlayedLand = new HashSet<>();
|
||||
|
||||
public LandfallWatcher() {
|
||||
super("LandPlayed", WatcherScope.PLAYER);
|
||||
super("LandPlayed", WatcherScope.GAME);
|
||||
}
|
||||
|
||||
public LandfallWatcher(final LandfallWatcher watcher) {
|
||||
super(watcher);
|
||||
playerPlayedLand.addAll(playerPlayedLand);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -28,15 +34,21 @@ public class LandfallWatcher extends Watcher {
|
|||
|
||||
@Override
|
||||
public void watch(GameEvent event, Game game) {
|
||||
if (condition == true) { //no need to check - condition has already occured
|
||||
return;
|
||||
}
|
||||
if (event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD) {
|
||||
Permanent permanent = game.getPermanent(event.getTargetId());
|
||||
if (permanent.getCardType().contains(CardType.LAND) && permanent.getControllerId().equals(this.controllerId)) {
|
||||
condition = true;
|
||||
if (permanent.getCardType().contains(CardType.LAND) && !playerPlayedLand.contains(event.getPlayerId())) {
|
||||
playerPlayedLand.add(event.getPlayerId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
playerPlayedLand.clear();
|
||||
super.reset();
|
||||
}
|
||||
|
||||
public boolean landPlayed(UUID playerId) {
|
||||
return playerPlayedLand.contains(playerId);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,9 +68,9 @@ public class PlayerLostLifeWatcher extends Watcher {
|
|||
if (playerId != null) {
|
||||
Integer amount = amountOfLifeLostThisTurn.get(playerId);
|
||||
if (amount == null) {
|
||||
amount = Integer.valueOf(event.getAmount());
|
||||
amount = event.getAmount();
|
||||
} else {
|
||||
amount = Integer.valueOf(amount + event.getAmount());
|
||||
amount = amount + event.getAmount();
|
||||
}
|
||||
amountOfLifeLostThisTurn.put(playerId, amount);
|
||||
}
|
||||
|
|
@ -80,7 +80,7 @@ public class PlayerLostLifeWatcher extends Watcher {
|
|||
public int getLiveLost(UUID playerId) {
|
||||
Integer amount = amountOfLifeLostThisTurn.get(playerId);
|
||||
if (amount != null) {
|
||||
return amount.intValue();
|
||||
return amount;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -88,7 +88,7 @@ public class PlayerLostLifeWatcher extends Watcher {
|
|||
public int getLiveLostLastTurn(UUID playerId) {
|
||||
Integer amount = amountOfLifeLostLastTurn.get(playerId);
|
||||
if (amount != null) {
|
||||
return amount.intValue();
|
||||
return amount;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue