Fixed Issue#35. All tests pass now.

This commit is contained in:
magenoxx 2012-08-22 22:36:19 +04:00
parent aa071912cb
commit c61e4f2b32
13 changed files with 230 additions and 18 deletions

View file

@ -33,16 +33,23 @@ import mage.Constants.CardType;
import mage.Constants.Rarity;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldAbility;
import mage.abilities.Mode;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.common.continious.GainAbilityControlledEffect;
import mage.abilities.effects.common.counter.AddCountersSourceEffect;
import mage.abilities.effects.Effect;
import mage.abilities.effects.Effects;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.abilities.effects.common.counter.AddCountersTargetEffect;
import mage.abilities.keyword.FlyingAbility;
import mage.cards.CardImpl;
import mage.counters.CounterType;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.mageobject.SubtypePredicate;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.game.stack.Spell;
import mage.target.targetpointer.FixedTarget;
import java.util.UUID;
@ -71,10 +78,8 @@ public class DearlyDeparted extends CardImpl<DearlyDeparted> {
this.addAbility(FlyingAbility.getInstance());
// As long as Dearly Departed is in your graveyard, each Human creature you control enters the battlefield with an additional +1/+1 counter on it.
Ability ability = new EntersBattlefieldAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance(1)));
ContinuousEffect effect = new GainAbilityControlledEffect(ability, Constants.Duration.WhileInGraveyard, filterHuman);
effect.overrideRuleText(ruleText);
this.addAbility(new SimpleStaticAbility(Constants.Zone.GRAVEYARD, effect));
this.addAbility(new SimpleStaticAbility(Constants.Zone.GRAVEYARD,
new EntersBattlefieldEffect(new AddCountersTargetEffect(CounterType.P1P1.createInstance(1)), ruleText)));
}
public DearlyDeparted(final DearlyDeparted card) {
@ -86,3 +91,80 @@ public class DearlyDeparted extends CardImpl<DearlyDeparted> {
return new DearlyDeparted(this);
}
}
class EntersBattlefieldEffect extends ReplacementEffectImpl<EntersBattlefieldEffect> {
protected Effects baseEffects = new Effects();
protected String text;
public static final String SOURCE_CAST_SPELL_ABILITY = "sourceCastSpellAbility";
public EntersBattlefieldEffect(Effect baseEffect) {
this(baseEffect, "");
}
public EntersBattlefieldEffect(Effect baseEffect, String text) {
super(Constants.Duration.OneUse, baseEffect.getOutcome());
this.baseEffects.add(baseEffect);
this.text = text;
}
public EntersBattlefieldEffect(EntersBattlefieldEffect effect) {
super(effect);
this.baseEffects = effect.baseEffects.copy();
this.text = effect.text;
}
public void addEffect(Effect effect) {
baseEffects.add(effect);
}
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
if (event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD) {
Permanent permanent = game.getPermanent(event.getTargetId());
if (permanent != null && permanent.getControllerId().equals(source.getControllerId()) && permanent.hasSubtype("Human")) {
setValue("target", permanent);
return true;
}
}
return false;
}
@Override
public boolean apply(Game game, Ability source) {
return false;
}
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
Spell spell = game.getStack().getSpell(event.getSourceId());
for (Effect effect: baseEffects) {
Object target = getValue("target");
if (target != null && target instanceof Permanent) {
effect.setTargetPointer(new FixedTarget(((Permanent)target).getId()));
if (effect instanceof ContinuousEffect) {
game.addEffect((ContinuousEffect) effect, source);
}
else {
if (spell != null) {
effect.setValue(SOURCE_CAST_SPELL_ABILITY, spell.getSpellAbility());
}
effect.apply(game, source);
}
}
}
return false;
}
@Override
public String getText(Mode mode) {
return (text == null || text.isEmpty()) ? baseEffects.getText(mode) : text;
}
@Override
public EntersBattlefieldEffect copy() {
return new EntersBattlefieldEffect(this);
}
}

View file

@ -44,6 +44,7 @@ import mage.cards.Card;
import mage.choices.Choice;
import mage.choices.Choices;
import mage.game.Game;
import mage.game.permanent.PermanentCard;
import mage.target.Target;
import mage.target.Targets;
import org.apache.log4j.Logger;
@ -478,6 +479,19 @@ public abstract class AbilityImpl<T extends AbilityImpl<T>> implements Ability {
}
}
MageObject object = game.getObject(getSourceId());
if (object != null && !object.getAbilities().contains(this)) {
boolean found = false;
// unfortunately we need to handle double faced cards separately and only this way
if (object instanceof PermanentCard && ((PermanentCard)object).canTransform()) {
PermanentCard permanent = (PermanentCard)object;
found = permanent.getSecondCardFace().getAbilities().contains(this) || permanent.getCard().getAbilities().contains(this);
}
if (!found) {
return false;
}
}
// check against current state
Zone test = game.getState().getZone(sourceId);
return test != null && zone.match(test);

View file

@ -65,6 +65,8 @@ public class ContinuousEffects implements Serializable {
private final AuraReplacementEffect auraReplacementEffect;
private List<ContinuousEffect> previous = new ArrayList<ContinuousEffect>();
private Map<Effect, UUID> sources = new HashMap<Effect, UUID>();
public ContinuousEffects() {
applyCounters = new ApplyCountersEffect();
@ -84,6 +86,9 @@ public class ContinuousEffects implements Serializable {
restrictionEffects = effect.restrictionEffects.copy();
asThoughEffects = effect.asThoughEffects.copy();
costModificationEffects = effect.costModificationEffects.copy();
for (Map.Entry<Effect, UUID> entry : effect.sources.entrySet()) {
sources.put(entry.getKey(), entry.getValue());
}
collectAllEffects();
}
@ -384,9 +389,12 @@ public class ContinuousEffects implements Serializable {
}
layer = filterLayeredEffects(layerEffects, Layer.AbilityAddingRemovingEffects_6);
for (ContinuousEffect effect: layer) {
effect.apply(Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, layeredEffects.getAbility(effect.getId()), game);
layerEffects = getLayeredEffects(game);
if (layerEffects.contains(effect)) {
effect.apply(Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, layeredEffects.getAbility(effect.getId()), game);
}
}
layerEffects = getLayeredEffects(game);
layer = filterLayeredEffects(layerEffects, Layer.PTChangingEffects_7);
for (ContinuousEffect effect: layer) {
effect.apply(Layer.PTChangingEffects_7, SubLayer.SetPT_7b, layeredEffects.getAbility(effect.getId()), game);
@ -408,6 +416,11 @@ public class ContinuousEffects implements Serializable {
}
}
public void addEffect(ContinuousEffect effect, UUID sourceId, Ability source) {
addEffect(effect, source);
sources.put(effect, sourceId);
}
public void addEffect(ContinuousEffect effect, Ability source) {
switch (effect.getEffectType()) {
case REPLACEMENT:
@ -460,6 +473,42 @@ public class ContinuousEffects implements Serializable {
for (ContinuousEffectsList effectsList : allEffectsLists) {
effectsList.clear();
}
sources.clear();
}
/**
* Removes all granted effects
*/
public void removeGainedEffects() {
for (Map.Entry<Effect, UUID> source : sources.entrySet()) {
for (ContinuousEffectsList effectsList : allEffectsLists) {
Ability ability = effectsList.getAbility(source.getKey().getId());
if (ability != null && !ability.getSourceId().equals(source.getValue())) {
effectsList.removeEffect((ContinuousEffect) source.getKey());
}
}
}
}
/**
* Removes effects granted by sourceId
*
* @param sourceId
*/
public List<Effect> removeGainedEffectsForSource(UUID sourceId) {
List<Effect> effects = new ArrayList<Effect>();
for (Map.Entry<Effect, UUID> source : sources.entrySet()) {
if (sourceId.equals(source.getValue())) {
for (ContinuousEffectsList effectsList : allEffectsLists) {
Ability ability = effectsList.getAbility(source.getKey().getId());
if (ability != null && !ability.getSourceId().equals(source.getValue())) {
effectsList.removeEffect((ContinuousEffect) source.getKey());
effects.add(source.getKey());
}
}
}
}
return effects;
}
}

View file

@ -108,6 +108,18 @@ public class ContinuousEffectsList<T extends ContinuousEffect> extends ArrayList
return abilityMap.get(effectId);
}
public void removeEffect(T effect) {
for (Iterator<T> i = this.iterator(); i.hasNext();) {
T entry = i.next();
if (entry.equals(effect)) {
i.remove();
if (abilityMap.containsKey(effect.getId())) {
abilityMap.remove(effect.getId());
}
}
}
}
@Override
public void clear() {
super.clear();

View file

@ -106,7 +106,7 @@ public class GainAbilityControlledEffect extends ContinuousEffectImpl<GainAbilit
if (!this.affectedObjectsSet || objects.contains(perm.getId())) {
if (!(excludeSource && perm.getId().equals(source.getSourceId()))) {
for (Ability abilityToAdd : ability) {
perm.addAbility(abilityToAdd, game);
perm.addAbility(abilityToAdd, source.getSourceId(), game);
}
}
}

View file

@ -65,7 +65,7 @@ public class LoseAllAbilitiesTargetEffect extends ContinuousEffectImpl {
for (UUID permanentId : targetPointer.getTargets(game, source)) {
Permanent permanent = game.getPermanent(permanentId);
if (permanent != null) {
permanent.getAbilities().clear();
permanent.removeAllAbilities(source.getSourceId(), game);
affectedTargets++;
}
}

View file

@ -399,7 +399,7 @@ public abstract class CardImpl<T extends CardImpl<T>> extends MageObjectImpl<T>
PermanentCard permanent = new PermanentCard(this, controllerId);
game.addPermanent(permanent);
game.setZone(objectId, Zone.BATTLEFIELD);
game.applyEffects();
//game.applyEffects(); // magenoxx: this causes bugs
permanent.entersBattlefield(sourceId, game);
game.applyEffects();
game.fireEvent(new ZoneChangeEvent(permanent, controllerId, fromZone, Zone.BATTLEFIELD));

View file

@ -123,6 +123,7 @@ public interface Game extends MageItem, Serializable {
public Player getLosingPlayer();
public void setStateCheckRequired();
public boolean getStateCheckRequired();
public void resetForSourceId(UUID sourceId);
//client event methods
public void addTableEventListener(Listener<TableEvent> listener);

View file

@ -1440,6 +1440,21 @@ public abstract class GameImpl<T extends GameImpl<T>> implements Game, Serializa
shortLivingLKI.clear();
}
@Override
public void resetForSourceId(UUID sourceId) {
// make sure that all effects don't touch this card once it returns back to battlefield
// e.g. this prevents effects affect creature with undying return from graveyard
for (ContinuousEffect effect : getContinuousEffects().getLayeredEffects(this)) {
if (effect.getAffectedObjects().contains(sourceId)) {
effect.getAffectedObjects().remove(sourceId);
}
if (effect.getAffectedObjects().contains(sourceId)) {
effect.getAffectedObjects().remove(sourceId);
}
}
getContinuousEffects().removeGainedEffectsForSource(sourceId);
}
@Override
public void cheat(UUID ownerId, Map<Zone, String> commands) {
if (commands != null) {

View file

@ -365,6 +365,10 @@ public class GameState implements Serializable, Copyable<GameState> {
effects.addEffect(effect, source);
}
public void addEffect(ContinuousEffect effect, UUID sourceId, Ability source) {
effects.addEffect(effect, sourceId, source);
}
// public void addMessage(String message) {
// this.messages.add(message);
// }
@ -440,6 +444,7 @@ public class GameState implements Serializable, Copyable<GameState> {
}
}
@Deprecated
public void addAbility(Ability ability, MageObject attachedTo) {
if (ability instanceof StaticAbility) {
if (ability instanceof KickerAbility) {
@ -458,6 +463,25 @@ public class GameState implements Serializable, Copyable<GameState> {
}
}
public void addAbility(Ability ability, UUID sourceId, MageObject attachedTo) {
if (ability instanceof StaticAbility) {
if (ability instanceof KickerAbility) {
return;
}
for (Mode mode: ability.getModes().values()) {
for (Effect effect: mode.getEffects()) {
if (effect instanceof ContinuousEffect) {
addEffect((ContinuousEffect)effect, sourceId, ability);
}
}
}
}
else if (ability instanceof TriggeredAbility) {
// TODO: add sources for triggers - the same way as in addEffect: sources
this.triggers.add((TriggeredAbility)ability, attachedTo);
}
}
public void addEmblem(Emblem emblem) {
getCommand().add(emblem);
for (Ability ability: emblem.getAbilities()) {

View file

@ -114,7 +114,11 @@ public interface Permanent extends Card, Controllable {
@Deprecated
@Override
public void addAbility(Ability ability);
@Deprecated
public void addAbility(Ability ability, Game game);
public void addAbility(Ability ability, UUID sourceId, Game game);
public void removeAllAbilities(UUID sourceId, Game game);
public void setLoyaltyUsed(boolean used);
public boolean isLoyaltyUsed();

View file

@ -29,7 +29,6 @@
package mage.game.permanent;
import mage.Constants.Zone;
import mage.abilities.effects.ContinuousEffect;
import mage.cards.Card;
import mage.cards.LevelerCard;
import mage.game.Game;
@ -174,11 +173,7 @@ public class PermanentCard extends PermanentImpl<PermanentCard> {
ZoneChangeEvent event = new ZoneChangeEvent(this, sourceId, controllerId, fromZone, toZone);
if (!game.replaceEvent(event)) {
if (event.getFromZone().equals(Zone.BATTLEFIELD)) {
for (ContinuousEffect effect : game.getContinuousEffects().getLayeredEffects(game)) {
if (effect.getAffectedObjects().contains(getId())) {
effect.getAffectedObjects().remove(getId());
}
}
game.resetForSourceId(getId());
}
Player owner = game.getPlayer(ownerId);
game.rememberLKI(objectId, Zone.BATTLEFIELD, this);

View file

@ -182,6 +182,22 @@ public abstract class PermanentImpl<T extends PermanentImpl<T>> extends CardImpl
}
}
@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);
}
}
@Override
public void removeAllAbilities(UUID sourceId, Game game) {
getAbilities().clear();
}
@Override
public Counters getCounters() {
return counters;