mirror of
https://github.com/correl/mage.git
synced 2024-12-24 11:50:45 +00:00
Fixed some problems concerning applying layeres effects in timestamp order. Tests now always successful.
1.Timestamps not distinct. 2. Timestamps not updated when attachments are attached (mainly equipments).
This commit is contained in:
parent
564eeae669
commit
2e60801df6
3 changed files with 183 additions and 53 deletions
|
@ -0,0 +1,47 @@
|
|||
package org.mage.test.cards.abilities.lose;
|
||||
|
||||
import mage.Constants;
|
||||
import mage.abilities.keyword.FlyingAbility;
|
||||
import mage.game.permanent.Permanent;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author LevelX2
|
||||
*/
|
||||
public class LoseAbilityByEquipmentTest extends CardTestPlayerBase {
|
||||
|
||||
/**
|
||||
* Tests that gaining flying by and after that losing flying by eqipments results in not have flying
|
||||
*/
|
||||
@Test
|
||||
public void testGainVsLoseByEquipmentAbility() {
|
||||
addCard(Constants.Zone.BATTLEFIELD, playerA, "Silvercoat Lion");
|
||||
addCard(Constants.Zone.BATTLEFIELD, playerA, "Plains", 3);
|
||||
addCard(Constants.Zone.BATTLEFIELD, playerA, "Island", 4);
|
||||
|
||||
addCard(Constants.Zone.HAND, playerA, "Magebane Armor"); // loses Flying
|
||||
addCard(Constants.Zone.HAND, playerA, "Cobbled Wings"); // gives Flying
|
||||
|
||||
castSpell(1, Constants.PhaseStep.PRECOMBAT_MAIN, playerA, "Magebane Armor");
|
||||
castSpell(3, Constants.PhaseStep.PRECOMBAT_MAIN, playerA, "Cobbled Wings");
|
||||
activateAbility(3, Constants.PhaseStep.PRECOMBAT_MAIN, playerA, "Equip {1}", "Silvercoat Lion"); // give Flying
|
||||
activateAbility(3, Constants.PhaseStep.POSTCOMBAT_MAIN, playerA, "Equip {2}", "Silvercoat Lion"); // lose Flying
|
||||
|
||||
setStopAt(3, Constants.PhaseStep.END_TURN);
|
||||
execute();
|
||||
|
||||
assertLife(playerA, 20);
|
||||
Permanent silvercoatLion = getPermanent("Silvercoat Lion", playerA.getId());
|
||||
Assert.assertNotNull(silvercoatLion);
|
||||
Assert.assertEquals("Silvercoat Lion equipments", 2, silvercoatLion.getAttachments().size());
|
||||
Assert.assertEquals("Silvercoat Lion power",4, silvercoatLion.getPower().getValue());
|
||||
Assert.assertEquals("Silvercoat Lion toughness",6, silvercoatLion.getToughness().getValue());
|
||||
|
||||
// should NOT have flying
|
||||
Assert.assertFalse("Silvercoat Lion has flying but shouldn't have",silvercoatLion.getAbilities().contains(FlyingAbility.getInstance()));
|
||||
}
|
||||
|
||||
}
|
|
@ -28,6 +28,8 @@
|
|||
|
||||
package mage.abilities.effects;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
import mage.Constants.AsThoughEffectType;
|
||||
import mage.Constants.Duration;
|
||||
import mage.Constants.Layer;
|
||||
|
@ -39,16 +41,14 @@ import mage.game.events.GameEvent;
|
|||
import mage.game.permanent.Permanent;
|
||||
import mage.players.Player;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
*/
|
||||
public class ContinuousEffects implements Serializable {
|
||||
|
||||
private Date lastSetTimestamp;
|
||||
|
||||
//transient Continuous effects
|
||||
private ContinuousEffectsList<ContinuousEffect> layeredEffects = new ContinuousEffectsList<ContinuousEffect>();
|
||||
private ContinuousEffectsList<ReplacementEffect> replacementEffects = new ContinuousEffectsList<ReplacementEffect>();
|
||||
|
@ -90,6 +90,7 @@ public class ContinuousEffects implements Serializable {
|
|||
sources.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
collectAllEffects();
|
||||
lastSetTimestamp = effect.lastSetTimestamp;
|
||||
}
|
||||
|
||||
private void collectAllEffects() {
|
||||
|
@ -116,18 +117,24 @@ public class ContinuousEffects implements Serializable {
|
|||
|
||||
public Ability getAbility(UUID effectId) {
|
||||
Ability ability = layeredEffects.getAbility(effectId);
|
||||
if (ability == null)
|
||||
if (ability == null) {
|
||||
ability = replacementEffects.getAbility(effectId);
|
||||
if (ability == null)
|
||||
}
|
||||
if (ability == null) {
|
||||
ability = preventionEffects.getAbility(effectId);
|
||||
if (ability == null)
|
||||
}
|
||||
if (ability == null) {
|
||||
ability = requirementEffects.getAbility(effectId);
|
||||
if (ability == null)
|
||||
}
|
||||
if (ability == null) {
|
||||
ability = restrictionEffects.getAbility(effectId);
|
||||
if (ability == null)
|
||||
}
|
||||
if (ability == null) {
|
||||
ability = asThoughEffects.getAbility(effectId);
|
||||
if (ability == null)
|
||||
}
|
||||
if (ability == null) {
|
||||
ability = costModificationEffects.getAbility(effectId);
|
||||
}
|
||||
return ability;
|
||||
}
|
||||
|
||||
|
@ -159,8 +166,9 @@ public class ContinuousEffects implements Serializable {
|
|||
case WhileOnStack:
|
||||
case WhileInGraveyard:
|
||||
Ability ability = layeredEffects.getAbility(effect.getId());
|
||||
if (ability.isInUseableZone(game, null, false))
|
||||
if (ability.isInUseableZone(game, null, false)) {
|
||||
layerEffects.add(effect);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
layerEffects.add(effect);
|
||||
|
@ -183,18 +191,27 @@ public class ContinuousEffects implements Serializable {
|
|||
for (ContinuousEffect continuousEffect : layerEffects) {
|
||||
// check if it's new, then set timestamp
|
||||
if (!previous.contains(continuousEffect)) {
|
||||
continuousEffect.setTimestamp();
|
||||
setUniqueTimesstamp(continuousEffect);
|
||||
}
|
||||
}
|
||||
previous.clear();
|
||||
previous.addAll(layerEffects);
|
||||
}
|
||||
|
||||
public void setUniqueTimesstamp(ContinuousEffect effect) {
|
||||
do {
|
||||
effect.setTimestamp();
|
||||
}
|
||||
while (effect.getTimestamp().equals(lastSetTimestamp)); // prevent to set the same timestamp so logical order is saved
|
||||
lastSetTimestamp = effect.getTimestamp();
|
||||
}
|
||||
|
||||
private List<ContinuousEffect> filterLayeredEffects(List<ContinuousEffect> effects, Layer layer) {
|
||||
List<ContinuousEffect> layerEffects = new ArrayList<ContinuousEffect>();
|
||||
for (ContinuousEffect effect: effects) {
|
||||
if (effect.hasLayer(layer))
|
||||
if (effect.hasLayer(layer)) {
|
||||
layerEffects.add(effect);
|
||||
}
|
||||
}
|
||||
return layerEffects;
|
||||
}
|
||||
|
@ -204,8 +221,9 @@ public class ContinuousEffects implements Serializable {
|
|||
for (RequirementEffect effect: requirementEffects) {
|
||||
Ability ability = requirementEffects.getAbility(effect.getId());
|
||||
if (!(ability instanceof StaticAbility) || ability.isInUseableZone(game, null, false)) {
|
||||
if (effect.applies(permanent, ability, game))
|
||||
if (effect.applies(permanent, ability, game)) {
|
||||
effects.add(effect);
|
||||
}
|
||||
}
|
||||
}
|
||||
return effects;
|
||||
|
@ -216,8 +234,9 @@ public class ContinuousEffects implements Serializable {
|
|||
for (RestrictionEffect effect: restrictionEffects) {
|
||||
Ability ability = restrictionEffects.getAbility(effect.getId());
|
||||
if (!(ability instanceof StaticAbility) || ability.isInUseableZone(game, permanent, false)) {
|
||||
if (effect.applies(permanent, ability, game))
|
||||
if (effect.applies(permanent, ability, game)) {
|
||||
effects.add(effect);
|
||||
}
|
||||
}
|
||||
}
|
||||
return effects;
|
||||
|
@ -231,10 +250,12 @@ public class ContinuousEffects implements Serializable {
|
|||
*/
|
||||
private List<ReplacementEffect> getApplicableReplacementEffects(GameEvent event, Game game) {
|
||||
List<ReplacementEffect> replaceEffects = new ArrayList<ReplacementEffect>();
|
||||
if (planeswalkerRedirectionEffect.applies(event, null, game))
|
||||
if (planeswalkerRedirectionEffect.applies(event, null, game)) {
|
||||
replaceEffects.add(planeswalkerRedirectionEffect);
|
||||
if(auraReplacementEffect.applies(event, null, game))
|
||||
}
|
||||
if(auraReplacementEffect.applies(event, null, game)){
|
||||
replaceEffects.add(auraReplacementEffect);
|
||||
}
|
||||
//get all applicable transient Replacement effects
|
||||
for (ReplacementEffect effect: replacementEffects) {
|
||||
Ability ability = replacementEffects.getAbility(effect.getId());
|
||||
|
@ -339,11 +360,13 @@ public class ContinuousEffects implements Serializable {
|
|||
List<ReplacementEffect> rEffects = getApplicableReplacementEffects(event, game);
|
||||
for (Iterator<ReplacementEffect> i = rEffects.iterator(); i.hasNext();) {
|
||||
ReplacementEffect entry = i.next();
|
||||
if (consumed.contains(entry.getId()))
|
||||
if (consumed.contains(entry.getId())) {
|
||||
i.remove();
|
||||
}
|
||||
}
|
||||
if (rEffects.isEmpty())
|
||||
if (rEffects.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
int index;
|
||||
if (rEffects.size() == 1) {
|
||||
index = 0;
|
||||
|
@ -355,8 +378,9 @@ public class ContinuousEffects implements Serializable {
|
|||
}
|
||||
ReplacementEffect rEffect = rEffects.get(index);
|
||||
caught = rEffect.replaceEvent(event, this.getAbility(rEffect.getId()), game);
|
||||
if (caught)
|
||||
if (caught) {
|
||||
break;
|
||||
}
|
||||
consumed.add(rEffect.getId());
|
||||
game.applyEffects();
|
||||
} while (true);
|
||||
|
|
|
@ -28,23 +28,44 @@
|
|||
|
||||
package mage.game.permanent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import mage.Constants;
|
||||
import mage.Constants.AsThoughEffectType;
|
||||
import mage.Constants.CardType;
|
||||
import mage.Constants.Zone;
|
||||
import mage.MageObject;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.effects.ContinuousEffect;
|
||||
import mage.abilities.effects.Effect;
|
||||
import mage.abilities.effects.RestrictionEffect;
|
||||
import mage.abilities.keyword.*;
|
||||
import mage.abilities.keyword.DeathtouchAbility;
|
||||
import mage.abilities.keyword.DefenderAbility;
|
||||
import mage.abilities.keyword.HasteAbility;
|
||||
import mage.abilities.keyword.HexproofAbility;
|
||||
import mage.abilities.keyword.InfectAbility;
|
||||
import mage.abilities.keyword.LifelinkAbility;
|
||||
import mage.abilities.keyword.ProtectionAbility;
|
||||
import mage.abilities.keyword.ShroudAbility;
|
||||
import mage.abilities.keyword.WitherAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.counters.Counter;
|
||||
import mage.counters.CounterType;
|
||||
import mage.counters.Counters;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.*;
|
||||
import mage.game.events.DamageCreatureEvent;
|
||||
import mage.game.events.DamagePlaneswalkerEvent;
|
||||
import mage.game.events.DamagedCreatureEvent;
|
||||
import mage.game.events.DamagedPlaneswalkerEvent;
|
||||
import mage.game.events.GameEvent;
|
||||
import mage.game.events.GameEvent.EventType;
|
||||
import mage.players.Player;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
|
@ -144,10 +165,12 @@ public abstract class PermanentImpl<T extends PermanentImpl<T>> extends CardImpl
|
|||
public void reset(Game game) {
|
||||
this.beforeResetControllerId = this.controllerId;
|
||||
this.controllerId = originalControllerId;
|
||||
if (!controllerId.equals(beforeResetControllerId))
|
||||
if (!controllerId.equals(beforeResetControllerId)) {
|
||||
controllerChanged = true;
|
||||
else
|
||||
}
|
||||
else {
|
||||
controllerChanged = false;
|
||||
}
|
||||
this.maxBlocks = 1;
|
||||
this.minBlockedBy = 1;
|
||||
this.copy = false;
|
||||
|
@ -174,22 +197,22 @@ public abstract class PermanentImpl<T extends PermanentImpl<T>> extends CardImpl
|
|||
@Override
|
||||
public void addAbility(Ability ability, Game game) {
|
||||
if (!abilities.containsKey(ability.getId())) {
|
||||
Ability copy = ability.copy();
|
||||
copy.setControllerId(controllerId);
|
||||
copy.setSourceId(objectId);
|
||||
game.getState().addAbility(copy, this);
|
||||
abilities.add(copy);
|
||||
Ability copyAbility = ability.copy();
|
||||
copyAbility.setControllerId(controllerId);
|
||||
copyAbility.setSourceId(objectId);
|
||||
game.getState().addAbility(copyAbility, this);
|
||||
abilities.add(copyAbility);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAbility(Ability ability, UUID sourceId, Game game) {
|
||||
if (!abilities.containsKey(ability.getId())) {
|
||||
Ability copy = ability.copy();
|
||||
copy.setControllerId(controllerId);
|
||||
copy.setSourceId(objectId);
|
||||
game.getState().addAbility(copy, sourceId, this);
|
||||
abilities.add(copy);
|
||||
Ability copyAbility = ability.copy();
|
||||
copyAbility.setControllerId(controllerId);
|
||||
copyAbility.setSourceId(objectId);
|
||||
game.getState().addAbility(copyAbility, sourceId, this);
|
||||
abilities.add(copyAbility);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -240,8 +263,9 @@ public abstract class PermanentImpl<T extends PermanentImpl<T>> extends CardImpl
|
|||
counters.removeCounter(name, amount);
|
||||
GameEvent event = GameEvent.getEvent(EventType.COUNTER_REMOVED, objectId, controllerId);
|
||||
event.setData(name);
|
||||
for (int i = 0; i < amount; i++)
|
||||
for (int i = 0; i < amount; i++) {
|
||||
game.fireEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -560,6 +584,26 @@ public abstract class PermanentImpl<T extends PermanentImpl<T>> extends CardImpl
|
|||
}
|
||||
}
|
||||
this.attachedTo = permanentId;
|
||||
/*
|
||||
* 20121001 613.6. Within a layer or sublayer, determining which order effects are applied in is
|
||||
* usually done using a timestamp system. An effect with an earlier timestamp is
|
||||
* applied before an effect with a later timestamp
|
||||
* 20121001 613.6d If an Aura, Equipment, or Fortification becomes attached to an object or player,
|
||||
* the Aura, Equipment, or Fortification receives a new timestamp at that time.
|
||||
*/
|
||||
for (Iterator<Ability> it = this.getAbilities().iterator(); it.hasNext();) {
|
||||
Ability ability = it.next();
|
||||
for (Iterator<Effect> ite = ability.getEffects(game, Constants.EffectType.CONTINUOUS).iterator(); ite.hasNext();) {
|
||||
ContinuousEffect effect = (ContinuousEffect) ite.next();
|
||||
game.getContinuousEffects().setUniqueTimesstamp(effect);
|
||||
// It's important is to update timestamp of the copied effect in ContinuousEffects because it does the action
|
||||
for (ContinuousEffect conEffect: game.getContinuousEffects().getLayeredEffects(game)) {
|
||||
if (conEffect.getId().equals(effect.getId())) {
|
||||
game.getContinuousEffects().setUniqueTimesstamp(conEffect);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -690,9 +734,9 @@ public abstract class PermanentImpl<T extends PermanentImpl<T>> extends CardImpl
|
|||
if (source != null && hasProtectionFrom(source, game)) {
|
||||
GameEvent preventEvent = new GameEvent(GameEvent.EventType.PREVENT_DAMAGE, this.objectId, sourceId, this.controllerId, event.getAmount(), false);
|
||||
if (!game.replaceEvent(preventEvent)) {
|
||||
int damage = event.getAmount();
|
||||
int preventedDamage = event.getAmount();
|
||||
event.setAmount(0);
|
||||
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.PREVENTED_DAMAGE, this.objectId, sourceId, this.controllerId, damage));
|
||||
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.PREVENTED_DAMAGE, this.objectId, sourceId, this.controllerId, preventedDamage));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
@ -715,13 +759,17 @@ public abstract class PermanentImpl<T extends PermanentImpl<T>> extends CardImpl
|
|||
@Override
|
||||
public boolean canBeTargetedBy(MageObject source, UUID sourceControllerId, Game game) {
|
||||
if (source != null) {
|
||||
if (abilities.containsKey(ShroudAbility.getInstance().getId()))
|
||||
if (abilities.containsKey(ShroudAbility.getInstance().getId())) {
|
||||
return false;
|
||||
if (abilities.containsKey(HexproofAbility.getInstance().getId()))
|
||||
if (game.getOpponents(controllerId).contains(sourceControllerId))
|
||||
}
|
||||
if (abilities.containsKey(HexproofAbility.getInstance().getId())) {
|
||||
if (game.getOpponents(controllerId).contains(sourceControllerId)) {
|
||||
return false;
|
||||
if (hasProtectionFrom(source, game))
|
||||
}
|
||||
}
|
||||
if (hasProtectionFrom(source, game)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -730,8 +778,9 @@ public abstract class PermanentImpl<T extends PermanentImpl<T>> extends CardImpl
|
|||
@Override
|
||||
public boolean hasProtectionFrom(MageObject source, Game game) {
|
||||
for (ProtectionAbility ability : abilities.getProtectionAbilities()) {
|
||||
if (!ability.canTarget(source, game))
|
||||
if (!ability.canTarget(source, game)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -801,51 +850,61 @@ public abstract class PermanentImpl<T extends PermanentImpl<T>> extends CardImpl
|
|||
|
||||
@Override
|
||||
public boolean canAttack(Game game) {
|
||||
if (tapped)
|
||||
if (tapped) {
|
||||
return false;
|
||||
if (hasSummoningSickness())
|
||||
}
|
||||
if (hasSummoningSickness()) {
|
||||
return false;
|
||||
}
|
||||
//20101001 - 508.1c
|
||||
for (RestrictionEffect effect : game.getContinuousEffects().getApplicableRestrictionEffects(this, game)) {
|
||||
if (!effect.canAttack(game))
|
||||
if (!effect.canAttack(game)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (abilities.containsKey(DefenderAbility.getInstance().getId()) && !game.getContinuousEffects().asThough(this.objectId, AsThoughEffectType.ATTACK, game))
|
||||
if (abilities.containsKey(DefenderAbility.getInstance().getId()) && !game.getContinuousEffects().asThough(this.objectId, AsThoughEffectType.ATTACK, game)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canBlock(UUID attackerId, Game game) {
|
||||
if (tapped)
|
||||
if (tapped) {
|
||||
return false;
|
||||
}
|
||||
Permanent attacker = game.getPermanent(attackerId);
|
||||
//20101001 - 509.1b
|
||||
for (RestrictionEffect effect : game.getContinuousEffects().getApplicableRestrictionEffects(this, game)) {
|
||||
if (!effect.canBlock(attacker, this, game.getContinuousEffects().getAbility(effect.getId()), game))
|
||||
if (!effect.canBlock(attacker, this, game.getContinuousEffects().getAbility(effect.getId()), game)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// check also attacker's restriction effects
|
||||
for (RestrictionEffect effect : game.getContinuousEffects().getApplicableRestrictionEffects(attacker, game)) {
|
||||
/*if (!effect.canBlock(attacker, this, game))
|
||||
return false;*/
|
||||
if (!effect.canBeBlocked(attacker, this, game.getContinuousEffects().getAbility(effect.getId()), game))
|
||||
if (!effect.canBeBlocked(attacker, this, game.getContinuousEffects().getAbility(effect.getId()), game)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (attacker != null && attacker.hasProtectionFrom(this, game))
|
||||
if (attacker != null && attacker.hasProtectionFrom(this, game)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canBlockAny(Game game) {
|
||||
if (tapped)
|
||||
if (tapped) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//20101001 - 509.1b
|
||||
for (RestrictionEffect effect : game.getContinuousEffects().getApplicableRestrictionEffects(this, game)) {
|
||||
if (!effect.canBlock(null, this, game.getContinuousEffects().getAbility(effect.getId()), game))
|
||||
if (!effect.canBlock(null, this, game.getContinuousEffects().getAbility(effect.getId()), game)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
Loading…
Reference in a new issue