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:
betasteward 2015-06-12 08:56:26 -04:00
commit 38da157f8c
807 changed files with 13691 additions and 4039 deletions

View file

@ -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();

View file

@ -157,7 +157,7 @@ public abstract class MageObjectImpl implements MageObject {
}
@Override
public ObjectColor getColor() {
public ObjectColor getColor(Game game) {
return color;
}

View file

@ -537,4 +537,6 @@ public interface Ability extends Controllable, Serializable {
*/
MageObject getSourceObjectIfItStillExists(Game game);
String getTargetDescription(Targets targets, Game game);
}

View file

@ -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);
}

View file

@ -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) {

View file

@ -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

View file

@ -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

View file

@ -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> &mdash; Whenever a land enters the battlefield under your control, " + super.getRule();
}
@Override

View file

@ -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

View file

@ -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());
}
}

View file

@ -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 ){

View file

@ -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;

View file

@ -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) {

View file

@ -72,6 +72,7 @@ public class ChooseNewTargetsTargetEffect extends OneShotEffect {
super(effect);
this.forceChange = effect.forceChange;
this.onlyOneTarget = effect.onlyOneTarget;
this.filterNewTarget = effect.filterNewTarget;
}
@Override

View file

@ -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();

View file

@ -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);

View file

@ -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;
}
}

View file

@ -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) {

View file

@ -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();

View file

@ -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;

View file

@ -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;
}

View file

@ -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());
}
}

View file

@ -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;
}

View file

@ -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";
}
}

View file

@ -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";
}
}

View file

@ -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();

View file

@ -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;

View file

@ -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);

View file

@ -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

View file

@ -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:

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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()); //

View file

@ -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()); //

View file

@ -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) {

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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");
}

View file

@ -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

View file

@ -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
}
}
}
}
}

View file

@ -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");

View file

@ -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("&mdash;");
}
rule.append(cost.getText()).append(" <i>(").append(super.getRule(true)).append(")</i>");
return rule.toString();

View file

@ -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);

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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);

View file

@ -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;
}
}

View file

@ -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();

View file

@ -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);
}

View file

@ -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.
}
}

View file

@ -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());

View file

@ -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
}

View file

@ -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() {

View 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));
}
}

View file

@ -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;
}
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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;

View file

@ -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();

View 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;
}
}

View file

@ -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();
}
}

View file

@ -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));
}

View file

@ -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 players 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;
}
}

View file

@ -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;
}

View file

@ -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);
}

View file

@ -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;

View file

@ -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));
}

View file

@ -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

View file

@ -150,7 +150,7 @@ public class Emblem implements CommandObject {
}
@Override
public ObjectColor getColor() {
public ObjectColor getColor(Game game) {
return emptyColor;
}

View file

@ -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;
}
}

View file

@ -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;
}

View file

@ -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();

View file

@ -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;
}
}

View file

@ -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();

View file

@ -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");

View file

@ -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;
}

View file

@ -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;
}
}

View 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;
}
}

View file

@ -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);

View file

@ -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);

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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.

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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);
}

View file

@ -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;

View file

@ -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);
}
}
}

View file

@ -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) {

View file

@ -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()) {

View file

@ -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();

View file

@ -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);
}

View file

@ -92,7 +92,7 @@ public abstract class Watcher implements Serializable {
public boolean conditionMet() {
return condition;
}
public void reset() {
condition = false;
}

View file

@ -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);
}
}

View file

@ -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;
}