Merge branch 'master' into Network_Upgrade

Conflicts:
	Mage.Client/src/main/java/mage/client/chat/ChatPanel.java
	Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.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/Session.java
This commit is contained in:
betasteward 2015-06-20 23:00:34 -04:00
commit f4aff4a121
894 changed files with 23817 additions and 4981 deletions

View file

@ -7,7 +7,7 @@
<parent>
<groupId>org.mage</groupId>
<artifactId>mage-root</artifactId>
<version>1.4.0</version>
<version>1.4.1</version>
</parent>
<artifactId>mage</artifactId>

View file

@ -69,7 +69,11 @@ public class MageObjectReference implements Comparable<MageObjectReference>, Ser
if (mageObject != null) {
this.zoneChangeCounter = mageObject.getZoneChangeCounter(game);
} else {
throw new IllegalArgumentException("The provided sourceId is not connected to an object in the game");
if (game.getPlayerList().contains(sourceId)) {
this.zoneChangeCounter = 0;
} else {
throw new IllegalArgumentException("The provided sourceId is not connected to an object in the game");
}
}
}

View file

@ -247,7 +247,7 @@ public abstract class AbilityImpl implements Ability {
return false;
}
game.applyEffects();
/* 20130201 - 601.2b
* If the spell is modal the player announces the mode choice (see rule 700.2).
*/
@ -299,7 +299,7 @@ public abstract class AbilityImpl implements Ability {
// A player can't apply two alternative methods of casting or two alternative costs to a single spell.
if (!activateAlternateOrAdditionalCosts(sourceObject, noMana, controller, game)){
if (getAbilityType().equals(AbilityType.SPELL)
&& ((SpellAbility) this).getSpellAbilityType().equals(SpellAbilityType.LAND_ALTERNATE)) {
&& ((SpellAbility) this).getSpellAbilityType().equals(SpellAbilityType.FACE_DOWN_CREATURE)) {
return false;
}
}
@ -329,7 +329,7 @@ public abstract class AbilityImpl implements Ability {
// and/or zones become the target of a spell trigger at this point; they'll wait to be put on
// the stack until the spell has finished being cast.)
if (sourceObject != null && !this.getAbilityType().equals(AbilityType.TRIGGERED)) { // triggered abilities check this already TriggeredAbilities.checkTriggers()
if (sourceObject != null && !this.getAbilityType().equals(AbilityType.TRIGGERED)) { // triggered abilities check this already in playerImpl.triggerAbility
sourceObject.adjustTargets(this, game);
}
if (getTargets().size() > 0 && getTargets().chooseTargets(getEffects().get(0).getOutcome(), this.controllerId, this, game) == false) {

View file

@ -79,10 +79,10 @@ public abstract class TriggeredAbilityImpl extends AbilityImpl implements Trigge
}
// TODO: Implement for all TriggeredAbilities so this default method can be removed
@Override
/*@Override
public boolean checkEventType(GameEvent event, Game game) {
return true;
}
}*/
@Override
public boolean resolve(Game game) {

View file

@ -0,0 +1,33 @@
/*
* 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.abilities.common;
import mage.abilities.StaticAbility;
import mage.abilities.effects.common.combat.AttacksIfAbleSourceEffect;
import mage.constants.Duration;
import mage.constants.Zone;
/**
*
* @author LevelX2
*/
public class AttacksEachCombatStaticAbility extends StaticAbility {
public AttacksEachCombatStaticAbility() {
super(Zone.BATTLEFIELD, new AttacksIfAbleSourceEffect(Duration.WhileOnBattlefield, true));
}
public AttacksEachCombatStaticAbility(AttacksEachCombatStaticAbility ability) {
super(ability);
}
@Override
public AttacksEachCombatStaticAbility copy() {
return new AttacksEachCombatStaticAbility(this);
}
}

View file

@ -32,6 +32,7 @@ import mage.constants.Duration;
import mage.constants.Zone;
import mage.abilities.StaticAbility;
import mage.abilities.effects.common.combat.AttacksIfAbleSourceEffect;
import mage.watchers.common.AttackedThisTurnWatcher;
/**
*
@ -41,6 +42,7 @@ public class AttacksEachTurnStaticAbility extends StaticAbility {
public AttacksEachTurnStaticAbility() {
super(Zone.BATTLEFIELD, new AttacksIfAbleSourceEffect(Duration.WhileOnBattlefield));
addWatcher(new AttackedThisTurnWatcher());
}
public AttacksEachTurnStaticAbility(AttacksEachTurnStaticAbility ability) {

View file

@ -0,0 +1,48 @@
/*
* 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.abilities.common;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.Effect;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
/**
*
* @author LevelX2
*/
public class EndOfCombatTriggeredAbility extends TriggeredAbilityImpl {
public EndOfCombatTriggeredAbility(Effect effect, boolean optional) {
super(Zone.BATTLEFIELD, effect, optional);
}
public EndOfCombatTriggeredAbility(final EndOfCombatTriggeredAbility ability) {
super(ability);
}
@Override
public EndOfCombatTriggeredAbility copy() {
return new EndOfCombatTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.END_COMBAT_STEP_PRE;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
return true;
}
@Override
public String getRule() {
return "At the end of combat, " + super.getRule();
}
}

View file

@ -13,7 +13,7 @@ import java.util.UUID;
* @author Loki
*/
public class CardsInOpponentGraveCondition implements Condition {
private int value;
private final int value;
public CardsInOpponentGraveCondition(int value) {
this.value = value;

View file

@ -59,7 +59,7 @@ public class KickedCondition implements Condition {
if (card != null) {
for (Ability ability: card.getAbilities()) {
if (ability instanceof KickerAbility) {
if(((KickerAbility) ability).isKicked(game)) {
if(((KickerAbility) ability).isKicked(game, source, "")) {
return true;
}
}

View file

@ -24,17 +24,9 @@ public class KickedCostCondition implements Condition {
public boolean apply(Game game, Ability source) {
Card card = game.getCard(source.getSourceId());
if (card != null) {
KickerAbility kickerAbility = null;
for (Ability ability: card.getAbilities()) {
if (ability instanceof KickerAbility) {
kickerAbility = (KickerAbility) ability;
}
}
if (kickerAbility != null) {
for (OptionalAdditionalCost cost: kickerAbility.getKickerCosts()) {
if (cost.getText(true).equals(kickerCostText)) {
return cost.isActivated();
}
return ((KickerAbility) ability).isKicked(game, source, kickerCostText);
}
}
}

View file

@ -28,31 +28,29 @@
package mage.abilities.condition.common;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.condition.Condition;
import mage.constants.TargetController;
import mage.filter.FilterPermanent;
import mage.filter.predicate.permanent.ControllerPredicate;
import mage.filter.predicate.permanent.ControllerIdPredicate;
import mage.game.Game;
/**
*
* Checks if one opponent (each opponent is checked on its own) fulfills
* the defined condition of controlling the defined number of permanents.
*
* @author LevelX2
*/
public class OpponentControlsPermanentCondition implements Condition {
public static enum CountType { MORE_THAN, FEWER_THAN, EQUAL_TO };
private FilterPermanent filter;
private Condition condition;
private CountType type;
private int count;
/**
* Applies a filter and delegates creation to
* {@link #ControlsPermanent(mage.filter.FilterPermanent, mage.abilities.condition.common.ControlsPermanent.CountType, int)}
* with {@link CountType#MORE_THAN}, and 0.
*
* @param filter
*/
public OpponentControlsPermanentCondition(FilterPermanent filter) {
@ -74,46 +72,33 @@ public class OpponentControlsPermanentCondition implements Condition {
this.count = count;
}
/**
* Applies a filter, a {@link CountType}, and count to permanents on the
* battlefield and calls the decorated condition to see if it
* {@link #apply(mage.game.Game, mage.abilities.Ability) applies}
* as well. This will force both conditions to apply for this to be true.
*
* @param filter
* @param type
* @param count
* @param conditionToDecorate
*/
public OpponentControlsPermanentCondition ( FilterPermanent filter, CountType type, int count, Condition conditionToDecorate ) {
this(filter, type, count);
this.condition = conditionToDecorate;
}
@Override
public boolean apply(Game game, Ability source) {
boolean conditionApplies = false;
FilterPermanent localFilter = filter.copy();
localFilter.add(new ControllerPredicate(TargetController.OPPONENT));
switch ( this.type ) {
case FEWER_THAN:
conditionApplies = game.getBattlefield().count(localFilter, source.getSourceId(), source.getControllerId(), game) < this.count;
break;
case MORE_THAN:
conditionApplies = game.getBattlefield().count(localFilter, source.getSourceId(), source.getControllerId(), game) > this.count;
break;
case EQUAL_TO:
conditionApplies = game.getBattlefield().count(localFilter, source.getSourceId(), source.getControllerId(), game) == this.count;
break;
boolean conditionApplies = false;
for(UUID opponentId :game.getOpponents(source.getControllerId())) {
FilterPermanent localFilter = filter.copy();
localFilter.add(new ControllerIdPredicate(opponentId));
switch ( this.type ) {
case FEWER_THAN:
if (game.getBattlefield().count(localFilter, source.getSourceId(), source.getControllerId(), game) < this.count) {
conditionApplies = true;
break;
}
case MORE_THAN:
if (game.getBattlefield().count(localFilter, source.getSourceId(), source.getControllerId(), game) > this.count) {
conditionApplies = true;
break;
}
break;
case EQUAL_TO:
if (game.getBattlefield().count(localFilter, source.getSourceId(), source.getControllerId(), game) == this.count) {
conditionApplies = true;
break;
}
break;
}
}
//If a decorated condition exists, check it as well and apply them together.
if ( this.condition != null ) {
conditionApplies = conditionApplies && this.condition.apply(game, source);
}
return conditionApplies;
}

View file

@ -0,0 +1,37 @@
/*
* 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.abilities.condition.common;
import mage.abilities.Ability;
import mage.abilities.condition.Condition;
import mage.game.Game;
import mage.watchers.common.DamageDoneWatcher;
/**
*
* @author LevelX2
*/
public class SourceDealtDamageCondition implements Condition {
private final int value;
public SourceDealtDamageCondition(int value) {
this.value = value;
}
@Override
public boolean apply(Game game, Ability source) {
DamageDoneWatcher watcher = (DamageDoneWatcher) game.getState().getWatchers().get("DamageDone");
return watcher != null && watcher.damageDone(source.getSourceId(), source.getSourceObjectZoneChangeCounter(), game) >= value;
}
@Override
public String toString() {
return "{this} has dealt " + value + " or more damage this turn" ;
}
}

View file

@ -0,0 +1,53 @@
/*
* 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.abilities.condition.common;
import mage.abilities.Ability;
import mage.abilities.condition.Condition;
import mage.constants.CardType;
import mage.filter.FilterCard;
import mage.filter.predicate.Predicates;
import mage.filter.predicate.mageobject.CardTypePredicate;
import mage.game.Game;
import mage.players.Player;
/**
*
* @author LevelX2
*/
public class SpellMasteryCondition implements Condition {
private static final FilterCard filter = new FilterCard();
static {
filter.add(Predicates.or(new CardTypePredicate(CardType.INSTANT), new CardTypePredicate(CardType.SORCERY)));
}
private static SpellMasteryCondition fInstance = null;
public static SpellMasteryCondition getInstance() {
if (fInstance == null) {
fInstance = new SpellMasteryCondition();
}
return fInstance;
}
private SpellMasteryCondition() {}
@Override
public boolean apply(Game game, Ability source) {
Player player = game.getPlayer(source.getControllerId());
return player != null && player.getGraveyard().count(filter, game) >= 2;
}
@Override
public String toString() {
return "there are two or more instant and/or sorcery cards in your graveyard";
}
}

View file

@ -1,5 +1,6 @@
package mage.abilities.decorator;
import mage.MageObject;
import mage.abilities.TriggeredAbility;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.condition.Condition;
@ -74,4 +75,21 @@ public class ConditionalTriggeredAbility extends TriggeredAbilityImpl {
return ability.getEffects();
}
@Override
public MageObject getSourceObjectIfItStillExists(Game game) {
return ability.getSourceObjectIfItStillExists(game);
}
@Override
public MageObject getSourceObject(Game game) {
return ability.getSourceObject(game);
}
@Override
public int getSourceObjectZoneChangeCounter() {
return ability.getSourceObjectZoneChangeCounter();
}
}

View file

@ -0,0 +1,34 @@
package mage.abilities.dynamicvalue.common;
import mage.abilities.Ability;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.effects.Effect;
import mage.game.Game;
import mage.players.Player;
/**
*
* @author cbrianhill
*/
public class CardsInTargetPlayerHandCount implements DynamicValue {
@Override
public int calculate(Game game, Ability sourceAbility, Effect effect) {
Player player = game.getPlayer(effect.getTargetPointer().getFirst(game, sourceAbility));
if (player != null) {
return player.getHand().size();
}
return 0;
}
@Override
public DynamicValue copy() {
return new CardsInTargetPlayerHandCount();
}
@Override
public String getMessage() {
return "cards in target player's hand";
}
}

View file

@ -50,7 +50,7 @@ public class MultikickerCount implements DynamicValue {
if (card != null) {
for (Ability ability: card.getAbilities()) {
if (ability instanceof KickerAbility) {
count += ((KickerAbility) ability).getKickedCounter(game);
count += ((KickerAbility) ability).getKickedCounter(game, source);
}
}
}

View file

@ -61,4 +61,7 @@ public interface ContinuousEffect extends Effect {
void newId();
@Override
ContinuousEffect copy();
boolean isTemporary();
void setTemporary(boolean temporary);
}

View file

@ -68,7 +68,8 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu
protected boolean discarded = false; // for manual effect discard
protected boolean affectedObjectsSet = false;
protected List<MageObjectReference> affectedObjectList = new ArrayList<>();
protected boolean temporary = false;
// until your next turn
protected int startingTurn;
protected UUID startingControllerId;
@ -96,6 +97,7 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu
this.discarded = effect.discarded;
this.affectedObjectsSet = effect.affectedObjectsSet;
this.affectedObjectList.addAll(effect.affectedObjectList);
this.temporary = effect.temporary;
this.startingTurn = effect.startingTurn;
this.startingControllerId = effect.startingControllerId;
}
@ -234,4 +236,18 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu
return affectedObjectList;
}
/**
* Returns the status if the effect is temporary added to the ContinuousEffects
* @return
*/
@Override
public boolean isTemporary() {
return temporary;
}
@Override
public void setTemporary(boolean temporary) {
this.temporary = temporary;
}
}

View file

@ -939,6 +939,7 @@ public class ContinuousEffects implements Serializable {
*/
public void addEffect(ContinuousEffect effect, UUID sourceId, Ability source) {
if (!(source instanceof MageSingleton)) { // because MageSingletons may never be removed by removing the temporary effecs they are not added to the temporaryEffects to prevent this
effect.setTemporary(true);
Set abilities = temporaryEffects.get(effect);
if (abilities == null) {
abilities = new HashSet<>();

View file

@ -95,21 +95,14 @@ public class EntersBattlefieldEffect extends ReplacementEffectImpl {
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
if (event.getType() == EventType.ENTERS_THE_BATTLEFIELD) {
if (event.getTargetId().equals(source.getSourceId())) {
if (condition == null || condition.apply(game, source)) {
return true;
}
if (event.getTargetId().equals(source.getSourceId())) {
if (condition == null || condition.apply(game, source)) {
return true;
}
}
return false;
}
@Override
public boolean apply(Game game, Ability source) {
return false;
}
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
if (optional) {

View file

@ -35,6 +35,7 @@ import mage.constants.Outcome;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.util.CardUtil;
/**
*
@ -64,10 +65,10 @@ public class ChooseColorEffect extends OneShotEffect {
}
}
if (!game.isSimulation()) {
game.informPlayers(new StringBuilder(permanent.getLogName()).append(": ").append(controller.getLogName()).append(" has chosen ").append(choice.getChoice()).toString());
game.informPlayers(permanent.getLogName()+": "+controller.getLogName()+" has chosen "+choice.getChoice());
}
game.getState().setValue(source.getSourceId() + "_color", choice.getColor());
permanent.addInfo("chosen color", "<font color = 'blue'>Chosen color: " + choice.getColor().getDescription() + "</font>", game);
permanent.addInfo("chosen color", CardUtil.addToolTipMarkTags("Chosen color: " + choice.getChoice()), game);
return true;
}
return false;

View file

@ -40,6 +40,7 @@ import mage.constants.Duration;
import mage.constants.Layer;
import mage.constants.Outcome;
import mage.constants.SubLayer;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.game.permanent.PermanentCard;
@ -56,6 +57,7 @@ public class CopyEffect extends ContinuousEffectImpl {
* Object we copy from
*/
private MageObject target;
private UUID sourceId;
private ApplyToPermanent applier;
@ -65,7 +67,7 @@ public class CopyEffect extends ContinuousEffectImpl {
public CopyEffect(Duration duration, MageObject target, UUID sourceId) {
super(duration, Layer.CopyEffects_1, SubLayer.NA, Outcome.BecomeCreature);
this.target = target;
this.target = target;
this.sourceId = sourceId;
}
@ -79,23 +81,24 @@ public class CopyEffect extends ContinuousEffectImpl {
@Override
public void init(Ability source, Game game) {
super.init(source, game);
if (affectedObjectsSet) {
affectedObjectList.add(new MageObjectReference(sourceId, game));
if (!(target instanceof Permanent) && (target instanceof Card)) {
this.target = new PermanentCard((Card)target, source.getControllerId(), game);
}
affectedObjectList.add(new MageObjectReference(getSourceId(), game));
}
@Override
public boolean apply(Game game, Ability source) {
Permanent permanent;
if (affectedObjectsSet) {
permanent = affectedObjectList.get(0).getPermanent(game);
} else {
permanent = game.getPermanent(this.sourceId);
}
Permanent permanent = affectedObjectList.get(0).getPermanent(game);
if (permanent == null) {
discard();
return false;
permanent = (Permanent) game.getLastKnownInformation(getSourceId(), Zone.BATTLEFIELD, source.getSourceObjectZoneChangeCounter());
// As long as the permanent is still in the LKI continue to copy to get triggered abilities to TriggeredAbilites for dies events.
if (permanent == null) {
discard();
return false;
}
}
permanent.setCopy(true);
permanent.setName(target.getName());
permanent.getColor(game).setColor(target.getColor(game));
permanent.getManaCost().clear();
@ -134,9 +137,7 @@ public class CopyEffect extends ContinuousEffectImpl {
} else if (target instanceof PermanentToken || target instanceof Card) {
permanent.setCardNumber(((Card) target).getCardNumber());
permanent.setExpansionSetCode(((Card) target).getExpansionSetCode());
}
permanent.setCopy(true);
}
return true;
}

View file

@ -69,8 +69,9 @@ public class CopyTargetSpellEffect extends OneShotEffect {
if (activateMessage.startsWith(" casts ")) {
activateMessage = activateMessage.substring(6);
}
if (!game.isSimulation())
game.informPlayers(player.getLogName() + " copies " + activateMessage);
if (!game.isSimulation()) {
game.informPlayers(player.getLogName() + activateMessage);
}
return true;
}
return false;

View file

@ -0,0 +1,55 @@
/*
* 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.abilities.effects.common;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.keyword.TransformAbility;
import mage.cards.Card;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.game.Game;
import mage.players.Player;
/**
*
* @author LevelX2
*/
public class ExileAndReturnTransformedSourceEffect extends OneShotEffect {
public static enum Gender { MALE, FEMAL };
public ExileAndReturnTransformedSourceEffect(Gender gender) {
super(Outcome.Benefit);
this.staticText = "exile {this}, then return " + (gender.equals(Gender.MALE) ? "him":"her")
+ " to the battlefield transformed under" + (gender.equals(Gender.MALE) ? "his":"her")+ " owner's control";
}
public ExileAndReturnTransformedSourceEffect(final ExileAndReturnTransformedSourceEffect effect) {
super(effect);
}
@Override
public ExileAndReturnTransformedSourceEffect copy() {
return new ExileAndReturnTransformedSourceEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
MageObject sourceObject = source.getSourceObjectIfItStillExists(game);
Player controller = game.getPlayer(source.getControllerId());
if (sourceObject != null && controller != null) {
Card card = (Card) sourceObject;
if (controller.moveCards(card, Zone.BATTLEFIELD, Zone.EXILED, source, game)) {
game.getState().setValue(TransformAbility.VALUE_KEY_ENTER_TRANSFORMED + source.getSourceId(), Boolean.TRUE);
controller.putOntoBattlefieldWithInfo(card, game, Zone.EXILED, source.getSourceId());
}
}
return true;
}
}

View file

@ -35,6 +35,7 @@ import mage.cards.Card;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.stack.Spell;
import mage.players.Player;
/**
@ -58,12 +59,15 @@ public class ShuffleSpellEffect extends OneShotEffect implements MageSingleton {
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller != null) {
Card spellCard = game.getStack().getSpell(source.getSourceId()).getCard();
if (spellCard != null) {
Player owner = game.getPlayer(spellCard.getOwnerId());
if (owner != null) {
controller.moveCardToLibraryWithInfo(spellCard, source.getSourceId(), game, Zone.STACK, true, true);
owner.shuffleLibrary(game);
Spell spell = game.getStack().getSpell(source.getSourceId());
if (spell != null) {
Card spellCard = spell.getCard();
if (spellCard != null) {
Player owner = game.getPlayer(spellCard.getOwnerId());
if (owner != null) {
controller.moveCardToLibraryWithInfo(spellCard, source.getSourceId(), game, Zone.STACK, true, true);
owner.shuffleLibrary(game);
}
}
}
return true;

View file

@ -33,6 +33,7 @@ import mage.abilities.Ability;
import mage.abilities.effects.RequirementEffect;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.watchers.common.AttackedThisTurnWatcher;
/**
*
@ -40,18 +41,25 @@ import mage.game.permanent.Permanent;
*/
public class AttacksIfAbleSourceEffect extends RequirementEffect {
boolean eachCombat;
public AttacksIfAbleSourceEffect(Duration duration) {
this(duration, false);
}
public AttacksIfAbleSourceEffect(Duration duration, boolean eachCombat) {
super(duration);
this.eachCombat = eachCombat;
if (this.duration == Duration.EndOfTurn) {
staticText = "{this} attacks this turn if able";
}
else {
staticText = "{this} attacks each turn if able";
staticText = "{this} attacks " + (eachCombat ? "each combat" :"this turn") + " if able";
} else {
staticText = "{this} attacks each " + (eachCombat ? "combat" :"turn") + " if able";
}
}
public AttacksIfAbleSourceEffect(final AttacksIfAbleSourceEffect effect) {
super(effect);
this.eachCombat = effect.eachCombat;
}
@Override
@ -61,7 +69,14 @@ public class AttacksIfAbleSourceEffect extends RequirementEffect {
@Override
public boolean applies(Permanent permanent, Ability source, Game game) {
return permanent.getId().equals(source.getSourceId());
if (permanent.getId().equals(source.getSourceId())) {
if (eachCombat) {
return true;
}
AttackedThisTurnWatcher watcher = (AttackedThisTurnWatcher)game.getState().getWatchers().get("AttackedThisTurn");
return watcher != null && !watcher.getAttackedThisTurnCreatures().contains(permanent.getId());
}
return false;
}
@Override

View file

@ -59,6 +59,7 @@ public class BecomesBasicLandTargetEffect extends ContinuousEffectImpl {
protected boolean chooseLandType;
protected ArrayList<String> landTypes = new ArrayList();
protected boolean loseOther; // loses all other abilities, card types, and creature types
public BecomesBasicLandTargetEffect(Duration duration) {
this(duration, true, new String[0]);
@ -69,10 +70,15 @@ public class BecomesBasicLandTargetEffect extends ContinuousEffectImpl {
}
public BecomesBasicLandTargetEffect(Duration duration, boolean chooseLandType, String... landNames) {
this(duration, chooseLandType, true, landNames);
}
public BecomesBasicLandTargetEffect(Duration duration, boolean chooseLandType, boolean loseOther, String... landNames) {
super(duration, Outcome.Detriment);
this.landTypes.addAll(Arrays.asList(landNames));
this.chooseLandType = chooseLandType;
this.staticText = setText();
this.loseOther = loseOther;
}
@ -105,6 +111,19 @@ public class BecomesBasicLandTargetEffect extends ContinuousEffectImpl {
this.discard();
}
}
if(!loseOther) {
for (UUID targetPermanent : targetPointer.getTargets(game, source)) {
Permanent land = game.getPermanent(targetPermanent);
if (land != null) {
for(String type : land.getSubtype()) {
if(!landTypes.contains(type)) {
landTypes.add(type);
}
}
}
}
}
}
@Override
@ -116,16 +135,22 @@ public class BecomesBasicLandTargetEffect extends ContinuousEffectImpl {
case AbilityAddingRemovingEffects_6:
land.removeAllAbilities(source.getSourceId(), game);
for (String landType : landTypes) {
if (landType.equals("Swamp")) {
land.addAbility(new BlackManaAbility(), source.getSourceId(), game);
} else if (landType.equals("Mountain")) {
land.addAbility(new RedManaAbility(), source.getSourceId(), game);
} else if (landType.equals("Forest")) {
land.addAbility(new GreenManaAbility(), source.getSourceId(), game);
} else if (landType.equals("Island")) {
land.addAbility(new BlueManaAbility(), source.getSourceId(), game);
} else if (landType.equals("Plains")) {
land.addAbility(new WhiteManaAbility(), source.getSourceId(), game);
switch (landType) {
case "Swamp":
land.addAbility(new BlackManaAbility(), source.getSourceId(), game);
break;
case "Mountain":
land.addAbility(new RedManaAbility(), source.getSourceId(), game);
break;
case "Forest":
land.addAbility(new GreenManaAbility(), source.getSourceId(), game);
break;
case "Island":
land.addAbility(new BlueManaAbility(), source.getSourceId(), game);
break;
case "Plains":
land.addAbility(new WhiteManaAbility(), source.getSourceId(), game);
break;
}
}
break;

View file

@ -80,6 +80,7 @@ public class GainAbilityAllEffect extends ContinuousEffectImpl {
public GainAbilityAllEffect(final GainAbilityAllEffect effect) {
super(effect);
this.ability = effect.ability.copy();
ability.newId(); // This is needed if the effect is copied e.g. by a clone so the ability can be added multiple times to permanents
this.filter = effect.filter.copy();
this.excludeSource = effect.excludeSource;
}

View file

@ -80,6 +80,7 @@ public class GainAbilityAttachedEffect extends ContinuousEffectImpl {
public GainAbilityAttachedEffect(final GainAbilityAttachedEffect effect) {
super(effect);
this.ability = effect.ability.copy();
ability.newId(); // This is needed if the effect is copied e.g. by a clone so the ability can be added multiple times to permanents
this.attachmentType = effect.attachmentType;
this.fixedTarget = effect.fixedTarget;
}

View file

@ -54,6 +54,7 @@ public class GainAbilityPairedEffect extends ContinuousEffectImpl {
public GainAbilityPairedEffect(final GainAbilityPairedEffect effect) {
super(effect);
this.ability = effect.ability.copy();
ability.newId(); // This is needed if the effect is copied e.g. by a clone so the ability can be added multiple times to permanents
}
@Override
@ -68,7 +69,7 @@ public class GainAbilityPairedEffect extends ContinuousEffectImpl {
Permanent paired = game.getPermanent(permanent.getPairedCard());
if (paired != null) {
permanent.addAbility(ability, game);
paired.addAbility(ability, source.getSourceId(), game);
paired.addAbility(ability, source.getSourceId(), game, false);
return true;
}
}

View file

@ -80,6 +80,7 @@ public class GainAbilitySourceEffect extends ContinuousEffectImpl implements Sou
public GainAbilitySourceEffect(final GainAbilitySourceEffect effect) {
super(effect);
this.ability = effect.ability.copy();
ability.newId(); // This is needed if the effect is copied e.g. by a clone so the ability can be added multiple times to permanents
this.onCard = effect.onCard;
}

View file

@ -77,6 +77,7 @@ public class GainAbilityTargetEffect extends ContinuousEffectImpl {
public GainAbilityTargetEffect(final GainAbilityTargetEffect effect) {
super(effect);
this.ability = effect.ability.copy();
ability.newId(); // This is needed if the effect is copied e.g. by a clone so the ability can be added multiple times to permanents
this.onCard = effect.onCard;
this.durationPhaseStep = effect.durationPhaseStep;
this.durationPlayerId = effect.durationPlayerId;

View file

@ -36,8 +36,8 @@ import mage.constants.Duration;
import mage.constants.Layer;
import mage.constants.Outcome;
import mage.constants.SubLayer;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.permanent.Permanent;
/**
*
@ -76,12 +76,16 @@ public class SetPowerToughnessSourceEffect extends ContinuousEffectImpl {
@Override
public boolean apply(Game game, Ability source) {
MageObject mageObject = game.getObject(source.getSourceId());
MageObject mageObject = game.getObject(source.getSourceId());
if (mageObject == null) {
if (duration.equals(Duration.Custom)) {
discard();
}
return false;
} else if (isTemporary()) { // it's somehow w
if (!(mageObject instanceof Permanent)) {
return false;
}
}
if (amount != null) {
int value = amount.calculate(game, source, this);

View file

@ -87,7 +87,7 @@ public class ProliferateEffect extends OneShotEffect {
choices.add(counter.getName());
}
choice.setChoices(choices);
choice.setMessage("Choose a counter to proliferate (" + permanent.getName() + ")");
choice.setMessage("Choose a counter to proliferate (" + permanent.getIdName() + ")");
controller.choose(Outcome.Benefit, choice, game);
for (Counter counter : permanent.getCounters().values()) {
if (counter.getName().equals(choice.getChoice())) {

View file

@ -45,8 +45,8 @@ import mage.target.common.TargetCardInLibrary;
*/
public class SearchLibraryPutInPlayEffect extends SearchEffect {
private boolean tapped;
private boolean forceShuffle;
protected boolean tapped;
protected boolean forceShuffle;
public SearchLibraryPutInPlayEffect(TargetCardInLibrary target) {
this(target, false, true, Outcome.PutCardInPlay);

View file

@ -0,0 +1,119 @@
/*
* 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.abilities.effects.common.search;
import java.util.List;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.effects.SearchEffect;
import mage.cards.Card;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.game.Game;
import mage.players.Player;
import mage.target.common.TargetCardInLibrary;
/**
*
* @author LevelX2
*/
public class SearchLibraryPutInPlayTargetPlayerEffect extends SearchEffect {
protected boolean tapped;
protected boolean forceShuffle;
public SearchLibraryPutInPlayTargetPlayerEffect(TargetCardInLibrary target) {
this(target, false, true, Outcome.PutCardInPlay);
}
public SearchLibraryPutInPlayTargetPlayerEffect(TargetCardInLibrary target, boolean tapped) {
this(target, tapped, true, Outcome.PutCardInPlay);
}
public SearchLibraryPutInPlayTargetPlayerEffect(TargetCardInLibrary target, boolean tapped, boolean forceShuffle) {
this(target, tapped, forceShuffle, Outcome.PutCardInPlay);
}
public SearchLibraryPutInPlayTargetPlayerEffect(TargetCardInLibrary target, boolean tapped, Outcome outcome) {
this(target, tapped, true, outcome);
}
public SearchLibraryPutInPlayTargetPlayerEffect(TargetCardInLibrary target, boolean tapped, boolean forceShuffle, Outcome outcome) {
super(target, outcome);
this.tapped = tapped;
this.forceShuffle = forceShuffle;
setText();
}
public SearchLibraryPutInPlayTargetPlayerEffect(final SearchLibraryPutInPlayTargetPlayerEffect effect) {
super(effect);
this.tapped = effect.tapped;
this.forceShuffle = effect.forceShuffle;
}
@Override
public SearchLibraryPutInPlayTargetPlayerEffect copy() {
return new SearchLibraryPutInPlayTargetPlayerEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player player = game.getPlayer(getTargetPointer().getFirst(game, source));
if (player != null) {
if (player.searchLibrary(target, game)) {
if (target.getTargets().size() > 0) {
for (UUID cardId: target.getTargets()) {
Card card = player.getLibrary().getCard(cardId, game);
if (card != null) {
player.putOntoBattlefieldWithInfo(card, game, Zone.LIBRARY, source.getSourceId(), tapped);
}
}
}
player.shuffleLibrary(game);
return true;
}
if (forceShuffle) {
player.shuffleLibrary(game);
}
}
return false;
}
private void setText() {
StringBuilder sb = new StringBuilder();
sb.append("target player searches his or her library for ");
if (target.getNumberOfTargets() == 0 && target.getMaxNumberOfTargets() > 0) {
if ( target.getMaxNumberOfTargets() == Integer.MAX_VALUE ) {
sb.append("any number of ").append(" ");
}
else {
sb.append("up to ").append(target.getMaxNumberOfTargets()).append(" ");
}
sb.append(target.getTargetName()).append(" and put them onto the battlefield");
}
else {
sb.append("a ").append(target.getTargetName()).append(" and put it onto the battlefield");
}
if (tapped) {
sb.append(" tapped");
}
if (forceShuffle) {
sb.append(". Then that player shuffles his or her library");
}
else {
sb.append(". If that player does, he or she shuffles his or her library");
}
staticText = sb.toString();
}
public List<UUID> getTargets() {
return target.getTargets();
}
}

View file

@ -1,15 +0,0 @@
/*
* 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.abilities.keyword;
/**
*
* @author ludwig.hirth
*/
public class CantBlockAloneAttachedEffect {
}

View file

@ -40,7 +40,6 @@ import mage.constants.Outcome;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.GameEvent.EventType;
import mage.game.events.ZoneChangeEvent;
import mage.game.permanent.Permanent;
import mage.target.common.TargetCreaturePermanent;

View file

@ -28,9 +28,11 @@
package mage.abilities.keyword;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.abilities.StaticAbility;
@ -42,7 +44,7 @@ import mage.abilities.costs.OptionalAdditionalSourceCosts;
import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.costs.mana.VariableManaCost;
import mage.cards.Card;
import mage.constants.AbilityType;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.game.Game;
@ -87,12 +89,12 @@ public class KickerAbility extends StaticAbility implements OptionalAdditionalSo
protected static final String KICKER_REMINDER_MANA = "(You may pay an additional {cost} as you cast this spell.)";
protected static final String KICKER_REMINDER_COST = "(You may {cost} in addition to any other costs as you cast this spell.)";
protected Map<String, Integer> activations = new HashMap<>(); // zoneChangeCounter, activations
protected String keywordText;
protected String reminderText;
protected List<OptionalAdditionalCost> kickerCosts = new LinkedList<>();
private int xManaValue = 0;
// needed to reset kicked status, if card changes zone after casting it
private int zoneChangeCounter = 0;
public KickerAbility(String manaString) {
this(KICKER_KEYWORD, KICKER_REMINDER_MANA);
@ -118,7 +120,7 @@ public class KickerAbility extends StaticAbility implements OptionalAdditionalSo
this.keywordText = ability.keywordText;
this.reminderText = ability.reminderText;
this.xManaValue = ability.xManaValue;
this.zoneChangeCounter = ability.zoneChangeCounter;
this.activations.putAll(ability.activations);
}
@ -143,35 +145,24 @@ public class KickerAbility extends StaticAbility implements OptionalAdditionalSo
for (OptionalAdditionalCost cost: kickerCosts) {
cost.reset();
}
zoneChangeCounter = 0;
}
public int getXManaValue() {
return xManaValue;
}
public int getKickedCounter(Game game) {
if (isKicked(game)) {
int counter = 0;
for (OptionalAdditionalCost cost: kickerCosts) {
counter += cost.getActivateCount();
}
return counter;
public int getKickedCounter(Game game, Ability source) {
String key = getActivationKey(source, "", game);
if (activations.containsKey(key)) {
return activations.get(key);
}
return 0;
}
public boolean isKicked(Game game) {
Card card = game.getCard(sourceId);
// kicked status counts only if card not changed zone since it was kicked
if (card != null && card.getZoneChangeCounter(game) <= zoneChangeCounter +1) {
for (OptionalAdditionalCost cost: kickerCosts) {
if(cost.isActivated()) {
return true;
}
}
} else {
this.resetKicker();
public boolean isKicked(Game game, Ability source, String costText) {
String key = getActivationKey(source, costText, game);
if (activations.containsKey(key)) {
return activations.get(key) > 0;
}
return false;
}
@ -180,19 +171,26 @@ public class KickerAbility extends StaticAbility implements OptionalAdditionalSo
return kickerCosts;
}
private void activateKicker(OptionalAdditionalCost kickerCost, Game game) {
kickerCost.activate();
// remember zone change counter
if (zoneChangeCounter == 0) {
Card card = game.getCard(getSourceId());
if (card != null) {
zoneChangeCounter = card.getZoneChangeCounter(game);
} else {
throw new IllegalArgumentException("Kicker source card not found");
}
private void activateKicker(OptionalAdditionalCost kickerCost, Ability source, Game game) {
int amount = 1;
String key = getActivationKey(source, kickerCost.getText(true), game);
if (activations.containsKey(key)) {
amount += activations.get(key);
}
activations.put(key, amount);
}
private String getActivationKey(Ability source, String costText, Game game) {
int zcc = source.getSourceObjectZoneChangeCounter();
if (source.getSourceObjectZoneChangeCounter() == 0) {
zcc = game.getState().getZoneChangeCounter(source.getSourceId());
}
if (zcc > 0 && (source.getAbilityType().equals(AbilityType.TRIGGERED) || source.getAbilityType().equals(AbilityType.STATIC))) {
--zcc;
}
return String.valueOf(zcc) + ((kickerCosts.size() > 1) ? costText :"");
}
@Override
public void addOptionalAdditionalCosts(Ability ability, Game game) {
if (ability instanceof SpellAbility) {
@ -208,8 +206,8 @@ public class KickerAbility extends StaticAbility implements OptionalAdditionalSo
times = Integer.toString(activatedCount + 1) + (activatedCount == 0 ? " time ":" times ");
}
if (kickerCost.canPay(ability, sourceId, controllerId, game) &&
player.chooseUse(Outcome.Benefit, new StringBuilder("Pay ").append(times).append(kickerCost.getText(false)).append(" ?").toString(), game)) {
this.activateKicker(kickerCost, game);
player.chooseUse(Outcome.Benefit, "Pay " + times + kickerCost.getText(false) + " ?", game)) {
this.activateKicker(kickerCost, ability, game);
for (Iterator it = ((Costs) kickerCost).iterator(); it.hasNext();) {
Cost cost = (Cost) it.next();
if (cost instanceof ManaCostsImpl) {

View file

@ -0,0 +1,38 @@
/*
* 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.abilities.keyword;
import mage.abilities.Ability;
import mage.abilities.StaticAbility;
import mage.abilities.effects.common.combat.CantBeBlockedByMoreThanOneSourceEffect;
import mage.abilities.effects.common.combat.CantBeBlockedByOneEffect;
import mage.constants.Zone;
/**
*
* @author LevelX2
*/
public class MenaceAbility extends StaticAbility {
public MenaceAbility() {
super(Zone.BATTLEFIELD, new CantBeBlockedByOneEffect(2));
}
public MenaceAbility(MenaceAbility ability) {
super(ability);
}
@Override
public Ability copy() {
return new MenaceAbility(this);
}
@Override
public String getRule() {
return "Menace <i>(This creature can't be blocked except by two or more creatures.)</i>";
}
}

View file

@ -38,7 +38,6 @@ import mage.abilities.costs.CostsImpl;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.SpliceCardEffectImpl;
import mage.cards.Card;
import mage.cards.CardsImpl;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.SpellAbilityType;
@ -105,12 +104,12 @@ import mage.players.Player;
public class SpliceOntoArcaneAbility extends SimpleStaticAbility {
private static final String KEYWORD_TEXT = "Splice onto Arcane";
private Costs spliceCosts = new CostsImpl();
private Costs<Cost> spliceCosts = new CostsImpl<>();
private boolean nonManaCosts = false;
public SpliceOntoArcaneAbility(String manaString) {
super(Zone.HAND, new SpliceOntoArcaneEffect());
spliceCosts.add(new ManaCostsImpl(manaString));
spliceCosts.add(new ManaCostsImpl<>(manaString));
}
public SpliceOntoArcaneAbility(Cost cost) {
@ -144,7 +143,6 @@ public class SpliceOntoArcaneAbility extends SimpleStaticAbility {
}
}
class SpliceOntoArcaneEffect extends SpliceCardEffectImpl {
public SpliceOntoArcaneEffect() {
@ -156,8 +154,6 @@ class SpliceOntoArcaneEffect extends SpliceCardEffectImpl {
super(effect);
}
@Override
public SpliceOntoArcaneEffect copy() {
return new SpliceOntoArcaneEffect(this);
@ -175,12 +171,7 @@ class SpliceOntoArcaneEffect extends SpliceCardEffectImpl {
splicedAbility.setSourceId(abilityToModify.getSourceId());
spell.addSpellAbility(splicedAbility);
for (Iterator it = ((SpliceOntoArcaneAbility) source).getSpliceCosts().iterator(); it.hasNext();) {
Cost cost = (Cost) it.next();
if (cost instanceof ManaCostsImpl) {
spell.getSpellAbility().getManaCostsToPay().add((ManaCostsImpl) cost.copy());
} else {
spell.getSpellAbility().getCosts().add(cost.copy());
}
spell.getSpellAbility().getCosts().add(((Cost) it.next()).copy());
}
}
return true;

View file

@ -69,8 +69,8 @@ public class StormAbility extends TriggeredAbilityImpl {
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (event.getSourceId().equals(this.sourceId)) {
StackObject spell = game.getStack().getStackObject(this.sourceId);
if (event.getSourceId().equals(getSourceId())) {
StackObject spell = game.getStack().getStackObject(getSourceId());
if (spell instanceof Spell) {
for (Effect effect : this.getEffects()) {
effect.setValue("StormSpell", spell);
@ -108,7 +108,7 @@ class StormEffect extends OneShotEffect {
Spell spell = (Spell) this.getValue("StormSpell");
if (spell != null) {
if (!game.isSimulation()) {
game.informPlayers("Storm: " + spell.getName() + " will be copied " + stormCount + " time" + (stormCount > 1 ?"s":""));
game.informPlayers("Storm: " + spell.getLogName() + " will be copied " + stormCount + " time" + (stormCount > 1 ?"s":""));
}
for (int i = 0; i < stormCount; i++) {
Spell copy = spell.copySpell();

View file

@ -44,6 +44,9 @@ public class TransformAbility extends SimpleStaticAbility {
public static final String NO_SPELLS_TRANSFORM_RULE = "At the beginning of each upkeep, if no spells were cast last turn, transform {this}.";
public static final String TWO_OR_MORE_SPELLS_TRANSFORM_RULE = "At the beginning of each upkeep, if a player cast two or more spells last turn, transform {this}.";
// this state value controlls if a permanent enters the battlefield already transformed
public static final String VALUE_KEY_ENTER_TRANSFORMED = "EnterTransformed";
public TransformAbility() {
super(Zone.BATTLEFIELD, new TransformEffect());
}

View file

@ -359,6 +359,12 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
break;
case STACK:
StackObject stackObject = game.getStack().getSpell(getId());
if (stackObject == null && (this instanceof SplitCard)) { // handle if half od Split cast is on the stack
stackObject = game.getStack().getSpell(((SplitCard)this).getLeftHalfCard().getId());
if (stackObject == null) {
stackObject = game.getStack().getSpell(((SplitCard)this).getRightHalfCard().getId());
}
}
if (stackObject != null) {
game.getStack().remove(stackObject);
}

View file

@ -58,9 +58,9 @@ public enum CardRepository {
private static final String JDBC_URL = "jdbc:h2:file:./db/cards.h2;AUTO_SERVER=TRUE";
private static final String VERSION_ENTITY_NAME = "card";
// raise this if db structure was changed
private static final long CARD_DB_VERSION = 38;
private static final long CARD_DB_VERSION = 39;
// raise this if new cards were added to the server
private static final long CARD_CONTENT_VERSION = 17;
private static final long CARD_CONTENT_VERSION = 20;
private final Random random = new Random();
private Dao<CardInfo, Object> cardDao;

View file

@ -26,8 +26,8 @@ public enum ExpansionRepository {
private static final String JDBC_URL = "jdbc:h2:file:./db/cards.h2;AUTO_SERVER=TRUE";
private static final String VERSION_ENTITY_NAME = "expansion";
private static final long EXPANSION_DB_VERSION = 4;
private static final long EXPANSION_CONTENT_VERSION = 7;
private static final long EXPANSION_DB_VERSION = 5;
private static final long EXPANSION_CONTENT_VERSION = 8;
private Dao<ExpansionInfo, Object> expansionDao;

View file

@ -7,7 +7,7 @@ package mage.constants;
public enum SpellAbilityType {
BASE("Basic SpellAbility"),
BASE_ALTERNATE("Basic SpellAbility Alternate"), // used for Overload, Flashback to know they must be handled as Alternate casting costs
LAND_ALTERNATE("Basic SpellAbility Alternate Land"), // used for Lands with Morph to cast as Face Down creature
FACE_DOWN_CREATURE("Face down creature"), // used for Lands with Morph to cast as Face Down creature
SPLIT("Split SpellAbility"),
SPLIT_FUSED("Split SpellAbility"),
SPLIT_LEFT("LeftSplit SpellAbility"),

View file

@ -53,6 +53,7 @@ public enum CounterType {
FADE("fade"),
FATE("fate"),
FEATHER("feather"),
FLOOD("flood"),
FUSE("fuse"),
HATCHLING("hatchling"),
HOOFPRINT("hoofprint"),

View file

@ -29,10 +29,7 @@
package mage.filter.common;
import mage.constants.AsThoughEffectType;
import mage.filter.predicate.ObjectPlayer;
import mage.filter.predicate.ObjectPlayerPredicate;
import mage.filter.predicate.Predicate;
import mage.game.Controllable;
import mage.game.Game;
import mage.game.permanent.Permanent;

View file

@ -21,6 +21,14 @@ public class CardAttribute implements Serializable {
public CardAttribute(Card card) {
color = card.getColor(null).copy();
}
public CardAttribute(CardAttribute cardAttribute) {
this.color = cardAttribute.color;
}
public CardAttribute copy() {
return new CardAttribute(this);
}
public ObjectColor getColor() {
return color;

View file

@ -253,7 +253,7 @@ public interface Game extends MageItem, Serializable {
Card copyCard(Card cardToCopy, Ability source, UUID newController);
void addTriggeredAbility(TriggeredAbility ability);
void addDelayedTriggeredAbility(DelayedTriggeredAbility delayedAbility);
UUID addDelayedTriggeredAbility(DelayedTriggeredAbility delayedAbility);
void applyEffects();
boolean checkStateAndTriggered();
void playPriority(UUID activePlayerId, boolean resuming);

View file

@ -56,8 +56,10 @@ import mage.watchers.common.CommanderInfoWatcher;
public abstract class GameCommanderImpl extends GameImpl {
static boolean CHECK_COMMANDER_DAMAGE = true;
private final Map<UUID, Cards> mulliganedCards = new HashMap<>();
private final Set<CommanderInfoWatcher> commanderCombatWatcher = new HashSet<>();
// private final Set<CommanderInfoWatcher> commanderCombatWatcher = new HashSet<>();
protected boolean alsoHand; // replace commander going to hand
protected boolean alsoLibrary; // replace commander going to library
@ -91,9 +93,8 @@ public abstract class GameCommanderImpl extends GameImpl {
ability.addEffect(new CommanderCostModification(commander.getId()));
ability.addEffect(new CommanderManaReplacementEffect(player.getId(), CardUtil.getColorIdentity(commander)));
getState().setValue(commander.getId() + "_castCount", 0);
CommanderInfoWatcher watcher = new CommanderInfoWatcher(commander.getId(), true);
CommanderInfoWatcher watcher = new CommanderInfoWatcher(commander.getId(), CHECK_COMMANDER_DAMAGE);
getState().getWatchers().add(watcher);
this.commanderCombatWatcher.add(watcher);
watcher.addCardInfoToCommander(this);
}
}
@ -185,12 +186,13 @@ public abstract class GameCommanderImpl extends GameImpl {
*/
@Override
protected boolean checkStateBasedActions() {
for (CommanderInfoWatcher damageWatcher: commanderCombatWatcher) {
for (Player player: getPlayers().values()) {
CommanderInfoWatcher damageWatcher = (CommanderInfoWatcher) getState().getWatchers().get("CommanderCombatDamageWatcher", player.getCommanderId());
for(Map.Entry<UUID, Integer> entrySet : damageWatcher.getDamageToPlayer().entrySet()){
if (entrySet.getValue() > 20) {
Player player = getPlayer(entrySet.getKey());
if (player != null && player.isInGame()){
player.lost(this);
Player opponent = getPlayer(entrySet.getKey());
if (opponent != null && player.isInGame()){
opponent.lost(this);
}
}
}

View file

@ -122,6 +122,7 @@ import mage.watchers.Watchers;
import mage.watchers.common.BlockedAttackerWatcher;
import mage.watchers.common.BloodthirstWatcher;
import mage.watchers.common.CastSpellLastTurnWatcher;
import mage.watchers.common.DamageDoneWatcher;
import mage.watchers.common.MorbidWatcher;
import mage.watchers.common.PlayerDamagedBySourceWatcher;
import mage.watchers.common.PlayerLostLifeWatcher;
@ -912,6 +913,7 @@ public abstract class GameImpl implements Game, Serializable {
watchers.add(new SoulbondWatcher());
watchers.add(new PlayerLostLifeWatcher());
watchers.add(new BlockedAttackerWatcher());
watchers.add(new DamageDoneWatcher());
//20100716 - 103.5
for (UUID playerId: state.getPlayerList(startingPlayerId)) {
@ -1144,6 +1146,7 @@ public abstract class GameImpl implements Game, Serializable {
int bookmark = 0;
clearAllBookmarks();
try {
applyEffects();
while (!isPaused() && !gameOver(null) && !this.getTurn().isEndTurnRequested()) {
if (!resuming) {
state.getPlayers().resetPassed();
@ -1383,11 +1386,12 @@ public abstract class GameImpl implements Game, Serializable {
}
@Override
public void addDelayedTriggeredAbility(DelayedTriggeredAbility delayedAbility) {
public UUID addDelayedTriggeredAbility(DelayedTriggeredAbility delayedAbility) {
DelayedTriggeredAbility newAbility = delayedAbility.copy();
newAbility.newId();
newAbility.init(this);
state.addDelayedTriggeredAbility(newAbility);
return newAbility.getId();
}
/**

View file

@ -171,6 +171,9 @@ public class GameState implements Serializable, Copyable<GameState> {
for (Map.Entry<UUID, CardState> entry: state.cardState.entrySet()) {
cardState.put(entry.getKey(), entry.getValue().copy());
}
for (Map.Entry<UUID, CardAttribute> entry: state.cardAttribute.entrySet()) {
cardAttribute.put(entry.getKey(), entry.getValue().copy());
}
this.zoneChangeCounter.putAll(state.zoneChangeCounter);
this.copiedCards.putAll(state.copiedCards);
this.permanentOrderNumber = state.permanentOrderNumber;
@ -208,6 +211,7 @@ public class GameState implements Serializable, Copyable<GameState> {
this.zones = state.zones;
this.simultaneousEvents = state.simultaneousEvents;
this.cardState = state.cardState;
this.cardAttribute = state.cardAttribute;
this.zoneChangeCounter = state.zoneChangeCounter;
this.copiedCards = state.copiedCards;
this.permanentOrderNumber = state.permanentOrderNumber;

View file

@ -247,7 +247,9 @@ public class Combat implements Serializable, Copyable<Combat> {
for (Ability ability : entry.getValue()) {
UUID defenderId = effect.mustAttackDefender(ability, game);
if (defenderId != null) {
defendersForcedToAttack.add(defenderId);
if (defenders.contains(defenderId)) {
defendersForcedToAttack.add(defenderId);
}
}
break;
}

View file

@ -33,6 +33,7 @@ import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.costs.mana.ManaCost;
import mage.abilities.costs.mana.ManaCosts;
import mage.abilities.keyword.TransformAbility;
import mage.cards.Card;
import mage.cards.LevelerCard;
import mage.constants.Zone;
@ -65,6 +66,13 @@ public class PermanentCard extends PermanentImpl {
if (card instanceof LevelerCard) {
maxLevelCounters = ((LevelerCard) card).getMaxLevelCounters();
}
if (canTransform()) {
if (game.getState().getValue(TransformAbility.VALUE_KEY_ENTER_TRANSFORMED + getId()) != null) {
game.getState().setValue(TransformAbility.VALUE_KEY_ENTER_TRANSFORMED + getId(), null);
setTransformed(true);
TransformAbility.transform(this, getSecondCardFace(), game);
}
}
}
public PermanentCard(final PermanentCard permanent) {

View file

@ -372,6 +372,11 @@ public class Spell extends StackObjImpl implements Card {
@Override
public List<CardType> getCardType() {
if (this.getSpellAbility().getSpellAbilityType().equals(SpellAbilityType.FACE_DOWN_CREATURE)) {
List<CardType> cardTypes = new ArrayList<>();
cardTypes.add(CardType.CREATURE);
return cardTypes;
}
if (this.getSpellAbility() instanceof BestowAbility) {
List<CardType> cardTypes = new ArrayList<>();
cardTypes.addAll(card.getCardType());

View file

@ -209,21 +209,20 @@ public abstract class PlayerImpl implements Player, Serializable {
protected Set<UUID> playersUnderYourControl = new HashSet<>();
protected Set<UUID> usersAllowedToSeeHandCards = new HashSet<>();
protected boolean requestsAllowedToSeeHandCards = true;
protected List<UUID> attachments = new ArrayList<>();
protected boolean topCardRevealed = false;
// 800.4i When a player leaves the game, any continuous effects with durations that last until that player's next turn
// or until a specific point in that turn will last until that turn would have begun.
// or until a specific point in that turn will last until that turn would have begun.
// They neither expire immediately nor last indefinitely.
protected boolean reachedNextTurnAfterLeaving = false;
// indicates that the spell with the set sourceId can be cast with an alternate mana costs (can also be no mana costs)
protected UUID castSourceIdWithAlternateMana;
protected ManaCosts castSourceIdManaCosts;
// indicates that the player is in mana payment phase
protected boolean payManaMode = false;
@ -369,7 +368,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.alternativeSourceCosts.addAll(player.getAlternativeSourceCosts());
this.topCardRevealed = player.isTopCardRevealed();
this.playersUnderYourControl.clear();
@ -444,7 +443,7 @@ public abstract class PlayerImpl implements Player, Serializable {
this.payManaMode = false;
this.setLife(game.getLife(), game);
this.setReachedNextTurnAfterLeaving(false);
this.castSourceIdWithAlternateMana = null;
this.castSourceIdManaCosts = null;
}
@ -575,7 +574,7 @@ public abstract class PlayerImpl implements Player, Serializable {
/**
* returns true if the player has the control itself - false if the player is controlled by another player
* @return
* @return
*/
@Override
public boolean isGameUnderControl() {
@ -704,11 +703,14 @@ public abstract class PlayerImpl implements Player, Serializable {
if (random) {
for (int i = 0; i < amount; i++) {
Card card = this.getHand().getRandom(game);
discardedCards.add(card);
discard(card, source, game);
if(card != null) {
discardedCards.add(card);
discard(card, source, game);
}
}
} else {
TargetDiscard target = new TargetDiscard(amount, amount, new FilterCard(CardUtil.numberToText(amount, "a") + " card" + (amount > 1 ? "s" : "")), playerId);
int possibleAmount = Math.min(getHand().size(), amount);
TargetDiscard target = new TargetDiscard(possibleAmount, possibleAmount, new FilterCard(CardUtil.numberToText(possibleAmount, "a") + " card" + (possibleAmount > 1 ? "s" : "")), playerId);
choose(Outcome.Discard, target, source == null ? null : source.getSourceId(), game);
for (UUID cardId : target.getTargets()) {
Card card = this.getHand().get(cardId, game);
@ -802,7 +804,7 @@ public abstract class PlayerImpl implements Player, Serializable {
attachedToPlayer.removeAttachment(permanent, game);
}
}
}
if (permanent.getPairedCard() != null) {
Permanent pairedCard = game.getPermanent(permanent.getPairedCard());
@ -876,7 +878,7 @@ public abstract class PlayerImpl implements Player, Serializable {
while (isInGame() && cards.size() > 1) {
this.choose(Outcome.Neutral, cards, target, game);
UUID targetObjectId = target.getFirstTarget();
cards.remove(targetObjectId);
cards.remove(targetObjectId);
moveObjectToLibrary(targetObjectId, source.getSourceId(), game, true, false);
target.clearChosen();
}
@ -917,7 +919,7 @@ public abstract class PlayerImpl implements Player, Serializable {
return castSourceIdManaCosts;
}
@Override
public boolean isInPayManaMode() {
return payManaMode;
@ -947,7 +949,7 @@ public abstract class PlayerImpl implements Player, Serializable {
spellAbility.getManaCosts().add(alternateCosts.copy());
spellAbility.getManaCostsToPay().clear();
spellAbility.getManaCostsToPay().add(alternateCosts.copy());
}
}
}
setCastSourceIdWithAlternateMana(null, null);
if (spell.activate(game, noMana)) {
@ -987,7 +989,7 @@ public abstract class PlayerImpl implements Player, Serializable {
}
}
if (found) {
SpellAbility spellAbility = new SpellAbility(null, "", game.getState().getZone(card.getId()), SpellAbilityType.LAND_ALTERNATE);
SpellAbility spellAbility = new SpellAbility(null, "", game.getState().getZone(card.getId()), SpellAbilityType.FACE_DOWN_CREATURE);
spellAbility.setControllerId(this.getId());
spellAbility.setSourceId(card.getId());
if (cast(spellAbility, game, false)) {
@ -1528,7 +1530,7 @@ public abstract class PlayerImpl implements Player, Serializable {
@Override
public String getLogName() {
return GameLog.getColoredPlayerName(name);
return GameLog.getColoredPlayerName(name);
}
@Override
@ -1669,11 +1671,11 @@ public abstract class PlayerImpl implements Player, Serializable {
sourceControllerId = ((Spell) source).getControllerId();
} else if (source instanceof Card) {
sourceAbilities = ((Card) source).getAbilities(game);
sourceControllerId = ((Card) source).getOwnerId();
sourceControllerId = ((Card) source).getOwnerId();
} else if (source instanceof CommandObject){
sourceControllerId = ((CommandObject) source).getControllerId();
sourceAbilities = ((CommandObject) source).getAbilities();
}
}
} else {
sourceAbilities = ((Permanent) source).getAbilities(game);
sourceControllerId = ((Permanent) source).getControllerId();
@ -2426,7 +2428,7 @@ public abstract class PlayerImpl implements Player, Serializable {
if (game.getContinuousEffects().asThough(card.getId(), AsThoughEffectType.PLAY_FROM_NON_HAND_ZONE, this.getId(), game)) {
for (Ability ability : card.getAbilities()) {
if (ability.getZone().match(Zone.HAND)) {
ability.setControllerId(this.getId()); // controller must be set for case owner != caster
ability.setControllerId(this.getId()); // controller must be set for case owner != caster
if (ability instanceof ActivatedAbility) {
if (((ActivatedAbility) ability).canActivate(playerId, game)) {
playable.add(ability);
@ -2537,7 +2539,7 @@ public abstract class PlayerImpl implements Player, Serializable {
* @return
*/
private boolean shouldSkipGettingPlayable(Game game) {
if (game.getStep() == null) { // happens at the start of the game
if (game.getStep() == null) { // happens at the start of the game
return true;
}
for (Entry<PhaseStep, Step.StepPart> phaseStep : silentPhaseSteps.entrySet()) {
@ -2838,19 +2840,31 @@ public abstract class PlayerImpl implements Player, Serializable {
@Override
public boolean moveCards(Cards cards, Zone fromZone, Zone toZone, Ability source, Game game) {
ArrayList<Card> cardList = new ArrayList<>();
cardList.addAll(cards.getCards(game));
for (UUID cardId: cards) {
if (fromZone.equals(Zone.BATTLEFIELD)) {
Permanent permanent = game.getPermanent(cardId);
if (permanent != null) {
cardList.add(permanent);
}
} else {
Card card = game.getCard(cardId);
if (card != null) {
cardList.add(card);
}
}
}
return moveCards(cardList, fromZone, toZone, source, game);
}
@Override
public boolean moveCards(Card card, Zone fromZone, Zone toZone, Ability source, Game game) {
ArrayList<Card> cardList = new ArrayList<>();
if (card != null) {
cardList.add(card);
}
return moveCards(cardList, fromZone, toZone, source, game);
return moveCards(cardList, fromZone, toZone, source, game);
}
@Override
public boolean moveCards(List<Card> cards, Zone fromZone, Zone toZone, Ability source, Game game) {
if (cards.isEmpty()) {
@ -2858,13 +2872,13 @@ public abstract class PlayerImpl implements Player, Serializable {
}
game.fireEvent(new ZoneChangeGroupEvent(cards, source == null ? null : source.getSourceId(), this.getId(), fromZone, toZone));
switch(toZone) {
case EXILED:
case EXILED:
boolean result = false;
for(Card card: cards) {
result |= moveCardToExileWithInfo(card, null, "", source == null ? null : source.getSourceId(), game, fromZone, true);
}
return result;
case GRAVEYARD:
return result;
case GRAVEYARD:
return moveCardsToGraveyardWithInfo(cards, source, game, fromZone);
case HAND:
result = false;
@ -2874,9 +2888,9 @@ public abstract class PlayerImpl implements Player, Serializable {
return result;
default:
throw new UnsupportedOperationException("to Zone not supported yet");
}
}
}
@Override
public boolean moveCardToHandWithInfo(Card card, UUID sourceId, Game game, Zone fromZone) {
return this.moveCardToHandWithInfo(card, sourceId, game, fromZone, true);
@ -2898,7 +2912,7 @@ public abstract class PlayerImpl implements Player, Serializable {
default:
sb.append(fromZone != null ? new StringBuilder(" from ").append(fromZone.toString().toLowerCase(Locale.ENGLISH)).append(" ") : "");
break;
}
}
sb.append(card.getOwnerId().equals(this.getId()) ? "into his or her hand" : "into its owner's hand");
game.informPlayers(sb.toString());
}
@ -2906,10 +2920,10 @@ public abstract class PlayerImpl implements Player, Serializable {
}
return result;
}
@Override
public boolean moveCardsToGraveyardWithInfo(List<Card> allCards, Ability source, Game game, Zone fromZone) {
boolean result = true;
boolean result = true;
UUID sourceId = source == null ? null : source.getSourceId();
while (!allCards.isEmpty()) {
// identify cards from one owner
@ -2934,19 +2948,21 @@ public abstract class PlayerImpl implements Player, Serializable {
if (choosingPlayer == null) {
continue;
}
boolean chooseOrder = true;
if (cards.size() > 2) {
chooseOrder = choosingPlayer.chooseUse(Outcome.Neutral, "Would you like to choose the order the cards go to graveyard?", game);
boolean chooseOrder = false;
if (userData.askMoveToGraveOrder()) {
if (cards.size() > 3) {
chooseOrder = choosingPlayer.chooseUse(Outcome.Neutral, "Would you like to choose the order the cards go to graveyard?", game);
}
}
if (chooseOrder) {
TargetCard target = new TargetCard(fromZone, new FilterCard("card to put on the top of your graveyard (last one chosen will be topmost)"));
target.setRequired(true);
while (choosingPlayer.isInGame() && cards.size() > 1) {
choosingPlayer.chooseTarget(Outcome.Neutral, cards, target, source, game);
UUID targetObjectId = target.getFirstTarget();
UUID targetObjectId = target.getFirstTarget();
Card card = cards.get(targetObjectId, game);
cards.remove(targetObjectId);
if (card != null) {
cards.remove(targetObjectId);
if (card != null) {
result &= choosingPlayer.moveCardToGraveyardWithInfo(card, sourceId, game, fromZone);
}
target.clearChosen();
@ -2959,11 +2975,11 @@ public abstract class PlayerImpl implements Player, Serializable {
result &= choosingPlayer.moveCardToGraveyardWithInfo(card, sourceId, game, fromZone);
}
}
}
}
}
return result;
}
@Override
public boolean moveCardToGraveyardWithInfo(Card card, UUID sourceId, Game game, Zone fromZone) {
boolean result = false;
@ -3045,7 +3061,7 @@ public abstract class PlayerImpl implements Player, Serializable {
public boolean putOntoBattlefieldWithInfo(Card card, Game game, Zone fromZone, UUID sourceId, boolean tapped) {
return this.putOntoBattlefieldWithInfo(card, game, fromZone, sourceId, tapped, false);
}
@Override
public boolean putOntoBattlefieldWithInfo(Card card, Game game, Zone fromZone, UUID sourceId, boolean tapped, boolean facedown) {
boolean result = false;
@ -3109,7 +3125,7 @@ public abstract class PlayerImpl implements Player, Serializable {
@Override
public boolean isRequestToShowHandCardsAllowed() {
return userData.allowRequestShowHandCards();
return userData.isAllowRequestShowHandCards();
}
@Override

View file

@ -16,10 +16,11 @@ public class UserData implements Serializable {
protected boolean confirmEmptyManaPool;
protected UserSkipPrioritySteps userSkipPrioritySteps;
protected String flagName;
protected boolean askMoveToGraveOrder;
public UserData(UserGroup userGroup, int avatarId, boolean showAbilityPickerForced,
boolean allowRequestShowHandCards, boolean confirmEmptyManaPool, UserSkipPrioritySteps userSkipPrioritySteps,
String flagName) {
String flagName, boolean askMoveToGraveOrder) {
this.groupId = userGroup.getGroupId();
this.avatarId = avatarId;
this.showAbilityPickerForced = showAbilityPickerForced;
@ -27,6 +28,7 @@ public class UserData implements Serializable {
this.userSkipPrioritySteps = userSkipPrioritySteps;
this.confirmEmptyManaPool = confirmEmptyManaPool;
this.flagName = flagName;
this.askMoveToGraveOrder = askMoveToGraveOrder;
}
public void setGroupId(int groupId) {
@ -49,20 +51,16 @@ public class UserData implements Serializable {
return showAbilityPickerForced;
}
public boolean isAllowRequestShowHandCards() {
return allowRequestShowHandCards;
}
public void setShowAbilityPickerForced(boolean showAbilityPickerForced) {
this.showAbilityPickerForced = showAbilityPickerForced;
}
public void setAllowRequestShowHandCards(boolean allowRequestShowHandCards) {
this.allowRequestShowHandCards = allowRequestShowHandCards;
public boolean isAllowRequestShowHandCards() {
return allowRequestShowHandCards;
}
public boolean allowRequestShowHandCards() {
return allowRequestShowHandCards;
public void setAllowRequestShowHandCards(boolean allowRequestShowHandCards) {
this.allowRequestShowHandCards = allowRequestShowHandCards;
}
public UserSkipPrioritySteps getUserSkipPrioritySteps() {
@ -84,5 +82,13 @@ public class UserData implements Serializable {
public String getFlagName() {
return flagName;
}
public boolean askMoveToGraveOrder() {
return askMoveToGraveOrder;
}
public void setAskMoveToGraveOrder(boolean askMoveToGraveOrder) {
this.askMoveToGraveOrder = askMoveToGraveOrder;
}
}

View file

@ -0,0 +1,53 @@
/*
* 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.watchers.common;
import java.util.HashSet;
import java.util.Set;
import mage.MageObjectReference;
import mage.constants.WatcherScope;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.watchers.Watcher;
/**
*
* @author LevelX2
*/
public class AttackedThisCombatWatcher extends Watcher {
public Set<MageObjectReference> attackedThisTurnCreatures = new HashSet<>();
public AttackedThisCombatWatcher() {
super("AttackedThisCombat", WatcherScope.GAME);
}
public AttackedThisCombatWatcher(final AttackedThisCombatWatcher watcher) {
super(watcher);
this.attackedThisTurnCreatures.addAll(watcher.attackedThisTurnCreatures);
}
@Override
public void watch(GameEvent event, Game game) {
if (event.getType() == GameEvent.EventType.BEGIN_COMBAT_STEP_PRE) {
this.attackedThisTurnCreatures.clear();
}
if (event.getType() == GameEvent.EventType.ATTACKER_DECLARED) {
this.attackedThisTurnCreatures.add(new MageObjectReference(event.getSourceId(),game));
}
}
public Set<MageObjectReference> getAttackedThisTurnCreatures() {
return this.attackedThisTurnCreatures;
}
@Override
public AttackedThisCombatWatcher copy() {
return new AttackedThisCombatWatcher(this);
}
}

View file

@ -40,6 +40,7 @@ import mage.watchers.Watcher;
*/
public class AttackedThisTurnWatcher extends Watcher {
// TODO: use MageObjectReference instead of UUID
public Set<UUID> attackedThisTurnCreatures = new HashSet<>();
public AttackedThisTurnWatcher() {

View file

@ -0,0 +1,67 @@
/*
* 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.watchers.common;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import mage.MageObjectReference;
import mage.constants.WatcherScope;
import mage.game.Game;
import mage.game.events.DamageEvent;
import mage.game.events.GameEvent;
import mage.watchers.Watcher;
/**
*
* @author LevelX2
*/
public class DamageDoneWatcher extends Watcher {
// which object did how much damage during the turn
public Map<MageObjectReference, Integer> damagingObjects = new HashMap<>();
public DamageDoneWatcher() {
super("DamageDone", WatcherScope.GAME);
}
public DamageDoneWatcher(final DamageDoneWatcher watcher) {
super(watcher);
this.damagingObjects.putAll(damagingObjects);
}
@Override
public DamageDoneWatcher copy() {
return new DamageDoneWatcher(this);
}
@Override
public void watch(GameEvent event, Game game) {
switch(event.getType()) {
case DAMAGED_CREATURE:
case DAMAGED_PLANESWALKER:
case DAMAGED_PLAYER:
{
MageObjectReference mor = new MageObjectReference(event.getSourceId(), game);
int count = damagingObjects.containsKey(mor) ? damagingObjects.get(mor) : 0;
damagingObjects.put(mor, count + event.getAmount());
}
}
}
@Override
public void reset() {
super.reset();
damagingObjects.clear();
}
public int damageDone(UUID objectId, int zoneChangeCounter, Game game) {
MageObjectReference mor = new MageObjectReference(objectId, zoneChangeCounter, game);
return damagingObjects.containsKey(mor) ? damagingObjects.get(mor) : 0;
}
}