changes to the way abilities are added to cards

This commit is contained in:
betasteward 2015-03-23 13:04:09 -04:00
parent 65390e09a6
commit 72ff6f27b3
91 changed files with 2003 additions and 217 deletions

View file

@ -361,6 +361,19 @@ public interface Ability extends Controllable, Serializable {
boolean canChooseTarget(Game game);
/**
* Gets the list of sub-abilities associated with this ability.
* @return
*/
List<Ability> getSubAbilities();
/**
* Adds a sub-ability to this ability.
*
* @param ability The {@link Ability} to add.
*/
void addSubAbility(Ability ability);
List<Watcher> getWatchers();
void addWatcher(Watcher watcher);

View file

@ -86,6 +86,7 @@ public abstract class AbilityImpl implements Ability {
private static final transient Logger logger = Logger.getLogger(AbilityImpl.class);
private static final List<Watcher> emptyWatchers = new ArrayList<>();
private static final List<Ability> emptyAbilities = new ArrayList<>();
protected UUID id;
protected UUID originalId;
@ -110,6 +111,7 @@ public abstract class AbilityImpl implements Ability {
protected boolean worksFaceDown = false;
protected MageObject sourceObject;
protected List<Watcher> watchers = null;
protected List<Ability> subAbilities = null;
public AbilityImpl(AbilityType abilityType, Zone zone) {
this.id = UUID.randomUUID();
@ -145,6 +147,12 @@ public abstract class AbilityImpl implements Ability {
watchers.add(watcher.copy());
}
}
if (ability.subAbilities != null) {
this.subAbilities = new ArrayList<>();
for (Ability subAbility: ability.subAbilities) {
subAbilities.add(subAbility.copy());
}
}
this.modes = ability.modes.copy();
this.ruleAtTheTop = ability.ruleAtTheTop;
this.ruleVisible = ability.ruleVisible;
@ -562,6 +570,11 @@ public abstract class AbilityImpl implements Ability {
watcher.setControllerId(controllerId);
}
}
if (subAbilities != null) {
for (Ability subAbility: subAbilities) {
subAbility.setControllerId(controllerId);
}
}
}
@ -579,6 +592,11 @@ public abstract class AbilityImpl implements Ability {
this.sourceId = sourceId;
}
}
if (subAbilities != null) {
for (Ability subAbility: subAbilities) {
subAbility.setSourceId(sourceId);
}
}
if (watchers != null) {
for (Watcher watcher: watchers) {
watcher.setSourceId(sourceId);
@ -660,6 +678,23 @@ public abstract class AbilityImpl implements Ability {
watchers.add(watcher);
}
@Override
public List<Ability> getSubAbilities() {
if (subAbilities != null)
return subAbilities;
else
return emptyAbilities;
}
@Override
public void addSubAbility(Ability ability) {
if (subAbilities == null)
subAbilities = new ArrayList<>();
ability.setSourceId(this.sourceId);
ability.setControllerId(this.controllerId);
subAbilities.add(ability);
}
@Override
public boolean isUsesStack() {
return usesStack;

View file

@ -107,8 +107,7 @@ public class GainAbilitySourceEffect extends ContinuousEffectImpl implements Sou
}
if (card != null) {
// add ability to card only once
card.addAbility(ability);
discard();
game.getState().addOtherAbility(card, ability);
return true;
}
} else {

View file

@ -127,7 +127,7 @@ public class GainAbilityTargetEffect extends ContinuousEffectImpl {
for (UUID cardId : targetPointer.getTargets(game, source)) {
Card card = game.getCard(cardId);
if (card != null) {
card.addAbility(ability);
game.getState().addOtherAbility(card, ability);
affectedTargets++;
}
}

View file

@ -116,7 +116,7 @@ public class BestowAbility extends SpellAbility {
this.addEffect(new AttachEffect(Outcome.BoostCreature));
Ability ability = new SimpleStaticAbility(Zone.BATTLEFIELD, new BestowTypeChangingEffect());
ability.setRuleVisible(false);
card.addAbility(ability);
addSubAbility(ability);
}
public BestowAbility(final BestowAbility ability) {

View file

@ -105,12 +105,12 @@ public class ChampionAbility extends StaticAbility {
Ability ability1 = new EntersBattlefieldTriggeredAbility(
new SacrificeSourceUnlessPaysEffect(new ChampionExileCost(filter, new StringBuilder(card.getName()).append(" championed permanents").toString())),false);
ability1.setRuleVisible(false);
card.addAbility(ability1);
addSubAbility(ability1);
// When this permanent leaves the battlefield, return the exiled card to the battlefield under its owner's control.
Ability ability2 = new LeavesBattlefieldTriggeredAbility(new ReturnFromExileForSourceEffect(Zone.BATTLEFIELD), false);
ability2.setRuleVisible(false);
card.addAbility(ability2);
addSubAbility(ability2);
}
public ChampionAbility(final ChampionAbility ability) {

View file

@ -86,7 +86,7 @@ public class ConspireAbility extends StaticAbility implements OptionalAdditional
public ConspireAbility(Card card) {
super(Zone.STACK, null);
setRuleAtTheTop(false);
card.addAbility(new ConspireTriggeredAbility());
addSubAbility(new ConspireTriggeredAbility());
}
public ConspireAbility(final ConspireAbility ability) {

View file

@ -79,7 +79,7 @@ public class DashAbility extends StaticAbility implements AlternativeSourceCosts
Effect effect = new ReturnToHandTargetEffect();
effect.setTargetPointer(new FixedTarget(card.getId()));
ability.addEffect(new CreateDelayedTriggeredAbilityEffect(new AtTheBeginOfNextEndStepDelayedTriggeredAbility(effect), false));
card.addAbility(ability);
addSubAbility(ability);
}

View file

@ -72,7 +72,7 @@ public class EvokeAbility extends StaticAbility implements AlternativeSourceCost
this.addEvokeCost(manaString);
Ability ability = new ConditionalTriggeredAbility(new EntersBattlefieldTriggeredAbility(new SacrificeSourceEffect()), EvokedCondition.getInstance(), "Sacrifice {this} when it enters the battlefield and was evoked.");
ability.setRuleVisible(false);
card.addAbility(ability);
addSubAbility(ability);
}

View file

@ -28,7 +28,7 @@ public class FadingAbility extends EntersBattlefieldAbility {
super(new AddCountersSourceEffect(CounterType.FADE.createInstance(fadeCounter)), "with");
Ability ability = new BeginningOfUpkeepTriggeredAbility(new FadingEffect(), TargetController.YOU, false);
ability.setRuleVisible(false);
card.addAbility(ability);
addSubAbility(ability);
StringBuilder sb = new StringBuilder("Fading ");
sb.append(fadeCounter);
sb.append(" <i>(This permanent enters the battlefield with ")

View file

@ -73,7 +73,7 @@ public class GraftAbility extends TriggeredAbilityImpl {
sb.append(theCardtype.toString().toLowerCase(Locale.ENGLISH)).append(" ");
}
this.cardtype = sb.toString().trim();
card.addAbility(new GraftStaticAbility(amount));
addSubAbility(new GraftStaticAbility(amount));
}
public GraftAbility(GraftAbility ability) {

View file

@ -67,7 +67,7 @@ public class HauntAbility extends TriggeredAbilityImpl {
public HauntAbility(Card card, Effect effect) {
super(Zone.ALL, effect , false);
card.addAbility(new HauntExileAbility());
addSubAbility(new HauntExileAbility());
}
public HauntAbility(final HauntAbility ability) {

View file

@ -68,11 +68,11 @@ public class HideawayAbility extends StaticAbility {
super(Zone.BATTLEFIELD, new EntersBattlefieldEffect(new TapSourceEffect(true)));
Ability ability = new EntersBattlefieldTriggeredAbility(new HideawayExileEffect(), false);
ability.setRuleVisible(false);
card.addAbility(ability);
addSubAbility(ability);
// Allow controller to look at face down card
ability = new SimpleStaticAbility(Zone.BATTLEFIELD, new HideawayLookAtFaceDownCardEffect());
ability.setRuleVisible(false);
card.addAbility(ability);
addSubAbility(ability);
}
public HideawayAbility(final HideawayAbility ability) {

View file

@ -134,9 +134,11 @@ public class LevelerCardBuilder {
*
* @param card
* @param levelAbilities
* @return list of levelAbilities to add to card
*/
public static void construct(Card card, LevelAbility... levelAbilities) {
public static List<Ability> construct(LevelAbility... levelAbilities) {
LevelerCardBuilder builder = new LevelerCardBuilder();
List<Ability> abilities = new ArrayList<>();
for (LevelAbility levelAbility : levelAbilities) {
// set main params
@ -153,22 +155,11 @@ public class LevelerCardBuilder {
builder.addAbility(addedAbility);
}
// build static abilities and add them to card
for (Ability simpleStaticAbility : builder.build()) {
card.addAbility(simpleStaticAbility);
}
// build static abilities and add them to list
abilities.addAll(builder.build());
}
// set max level counters (for ai)
if (card instanceof LevelerCard) {
int maxValue = 0;
for (LevelAbility levelAbility : levelAbilities) {
if (levelAbility.getLevel1() > maxValue) {
maxValue = levelAbility.getLevel1();
}
}
((LevelerCard) card).setMaxLevelCounters(maxValue);
}
return abilities;
}
public static class LevelAbility {

View file

@ -41,7 +41,7 @@ public class MadnessAbility extends StaticAbility {
@SuppressWarnings("unchecked")
public MadnessAbility(Card card, ManaCosts madnessCost) {
super(Zone.HAND, new MadnessReplacementEffect((ManaCosts<ManaCost>)madnessCost));
card.addAbility(new MadnessTriggeredAbility((ManaCosts<ManaCost>)madnessCost));
addSubAbility(new MadnessTriggeredAbility((ManaCosts<ManaCost>)madnessCost));
rule = "Madness " + madnessCost.getText() + " <i>(If you discard this card, you may cast it for its madness cost instead of putting it into your graveyard.)<i/>";
}

View file

@ -59,9 +59,9 @@ public class ModularAbility extends DiesTriggeredAbility {
if (sunburst) {
Ability ability = new SunburstAbility(card);
ability.setRuleVisible(false);
card.addAbility(ability);
addSubAbility(ability);
} else {
card.addAbility(new ModularStaticAbility(amount));
addSubAbility(new ModularStaticAbility(amount));
}
}

View file

@ -148,7 +148,7 @@ public class MorphAbility extends StaticAbility implements AlternativeSourceCost
Ability ability = new SimpleStaticAbility(Zone.BATTLEFIELD, new BecomesFaceDownCreatureEffect(morphCosts, (megamorph ? FaceDownType.MEGAMORPHED :FaceDownType.MORPHED)));
ability.setWorksFaceDown(true);
ability.setRuleVisible(false);
card.addAbility(ability);
addSubAbility(ability);
}

View file

@ -96,7 +96,7 @@ public class ReboundAbility extends TriggeredAbilityImpl {
((ZoneChangeEvent) event).getToZone() == Zone.STACK) {
Card card = (Card) game.getObject(event.getTargetId());
if (card.getAbilities().contains(this)) {
if (card.getAbilities(game).contains(this)) {
this.installReboundEffect = true;
}
}

View file

@ -66,7 +66,7 @@ public class ReplicateAbility extends StaticAbility implements OptionalAdditiona
this.additionalCost = new OptionalAdditionalCostImpl(keywordText, reminderTextMana, new ManaCostsImpl(manaString));
this.additionalCost.setRepeatable(true);
setRuleAtTheTop(true);
card.addAbility(new ReplicateTriggeredAbility());
addSubAbility(new ReplicateTriggeredAbility());
}
public ReplicateAbility(final ReplicateAbility ability) {

View file

@ -179,8 +179,8 @@ public class SuspendAbility extends ActivatedAbilityImpl {
if (card.getManaCost().isEmpty()) {
setRuleAtTheTop(true);
}
card.addAbility(new SuspendBeginningOfUpkeepTriggeredAbility());
card.addAbility(new SuspendPlayCardAbility(card.getCardType().contains(CardType.CREATURE)));
addSubAbility(new SuspendBeginningOfUpkeepTriggeredAbility());
addSubAbility(new SuspendPlayCardAbility(card.getCardType().contains(CardType.CREATURE)));
}
ruleText = sb.toString();
}
@ -196,18 +196,18 @@ public class SuspendAbility extends ActivatedAbilityImpl {
SuspendAbility ability = new SuspendAbility(0, null, card, false);
ability.setSourceId(card.getId());
ability.setControllerId(card.getOwnerId());
game.getState().addOtherAbility(card.getId(), ability);
game.getState().addOtherAbility(card, ability);
SuspendBeginningOfUpkeepTriggeredAbility ability1 = new SuspendBeginningOfUpkeepTriggeredAbility();
ability1.setSourceId(card.getId());
ability1.setControllerId(card.getOwnerId());
game.getState().addOtherAbility(card.getId(), ability1);
game.getState().addOtherAbility(card, ability1);
game.getState().addAbility(ability1, source.getSourceId(), card);
SuspendPlayCardAbility ability2 = new SuspendPlayCardAbility(card.getCardType().contains(CardType.CREATURE));
ability2.setSourceId(card.getId());
ability2.setControllerId(card.getOwnerId());
game.getState().addOtherAbility(card.getId(), ability2);
game.getState().addOtherAbility(card, ability2);
game.getState().addAbility(ability2, source.getSourceId(), card);
}

View file

@ -33,6 +33,7 @@ import java.util.List;
import java.util.UUID;
import mage.MageObject;
import mage.Mana;
import mage.abilities.Abilities;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.constants.Rarity;
@ -47,7 +48,7 @@ public interface Card extends MageObject {
int getCardNumber();
Rarity getRarity();
void setOwnerId(UUID ownerId);
void addAbility(Ability ability);
public Abilities<Ability> getAbilities(Game game);
void setSpellAbility(SpellAbility ability);
SpellAbility getSpellAbility();
List<String> getRules(); // gets base card rules

View file

@ -36,10 +36,11 @@ import java.util.UUID;
import mage.MageObject;
import mage.MageObjectImpl;
import mage.Mana;
import mage.abilities.Abilities;
import mage.abilities.AbilitiesImpl;
import mage.abilities.Ability;
import mage.abilities.PlayLandAbility;
import mage.abilities.SpellAbility;
import mage.abilities.keyword.MorphAbility;
import mage.abilities.mana.ManaAbility;
import mage.constants.CardType;
import mage.constants.ColoredManaSymbol;
@ -226,11 +227,11 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
for (String data : cardState.getInfo().values()) {
rules.add(data);
}
for (Ability ability: cardState.getAbilities()) {
rules.add(ability.getRule());
}
}
}
// for (Ability ability: state.getAbilities()) {
// rules.add(ability.getRule());
// }
return rules;
} catch (Exception e) {
logger.error("Exception in rules generation for card: " + this.getName(), e);
@ -238,12 +239,48 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
return rulesError;
}
/**
* Gets all base abilities - does not include additional abilities added by
* other cards or effects
* @return A list of {@link Ability} - this collection is modifiable
*/
@Override
public void addAbility(Ability ability) {
ability.setSourceId(this.getId());
abilities.add(ability);
public Abilities<Ability> getAbilities() {
return super.getAbilities();
}
/**
* Gets all current abilities - includes additional abilities added by
* other cards or effects
* @param game
* @return A list of {@link Ability} - this collection is not modifiable
*/
@Override
public Abilities<Ability> getAbilities(Game game) {
Abilities<Ability> otherAbilities = game.getState().getAllOtherAbilities(objectId);
if (otherAbilities == null) {
return abilities;
}
Abilities<Ability> all = new AbilitiesImpl<>();
all.addAll(abilities);
all.addAll(otherAbilities);
return all;
}
protected void addAbility(Ability ability) {
ability.setSourceId(this.getId());
abilities.add(ability);
for (Ability subAbility: ability.getSubAbilities()) {
abilities.add(subAbility);
}
}
protected void addAbilities(List<Ability> abilities) {
for (Ability ability: abilities) {
addAbility(ability);
}
}
protected void addAbility(Ability ability, Watcher watcher) {
addAbility(ability);
ability.addWatcher(watcher);

View file

@ -53,7 +53,7 @@ public abstract class LevelerCard extends CardImpl {
return maxLevelCounters;
}
public void setMaxLevelCounters(int maxLevelCounters) {
protected void setMaxLevelCounters(int maxLevelCounters) {
this.maxLevelCounters = maxLevelCounters;
}

View file

@ -145,6 +145,19 @@ public abstract class SplitCard extends CardImpl {
return allAbilites;
}
@Override
public Abilities<Ability> getAbilities(Game game) {
Abilities<Ability> allAbilites = new AbilitiesImpl<>();
for (Ability ability : super.getAbilities(game)) {
if (ability instanceof SpellAbility && !((SpellAbility)ability).getSpellAbilityType().equals(SpellAbilityType.SPLIT)) {
allAbilites.add(ability);
}
}
allAbilites.addAll(leftHalfCard.getAbilities(game));
allAbilites.addAll(rightHalfCard.getAbilities(game));
return allAbilites;
}
@Override
public List<String> getRules() {
List<String> rules = new ArrayList<>();

View file

@ -2,6 +2,9 @@ package mage.game;
import java.util.HashMap;
import java.util.Map;
import mage.abilities.Abilities;
import mage.abilities.AbilitiesImpl;
import mage.abilities.Ability;
import mage.counters.Counters;
/**
*
@ -12,8 +15,10 @@ public class CardState {
protected boolean faceDown;
protected Map<String, String> info;
protected Counters counters;
protected Abilities<Ability> abilities;
private static final Map<String, String> emptyInfo = new HashMap<>();
private static final Abilities<Ability> emptyAbilities = new AbilitiesImpl<>();
public CardState() {
counters = new Counters();
@ -26,6 +31,12 @@ public class CardState {
info.putAll(state.info);
}
counters = state.counters.copy();
if (state.abilities != null) {
abilities = new AbilitiesImpl<>();
for (Ability ability: state.abilities) {
abilities.add(ability.copy());
}
}
}
public CardState copy() {
@ -62,10 +73,37 @@ public class CardState {
return info;
}
public Abilities<Ability> getAbilities() {
if (abilities == null) {
return emptyAbilities;
}
return abilities;
}
public void addAbility(Ability ability) {
if (abilities == null) {
abilities = new AbilitiesImpl<>();
}
abilities.add(ability);
for (Ability sub: ability.getSubAbilities()) {
abilities.add(sub);
}
}
public void clearAbilities() {
if (abilities != null) {
for (Ability ability: abilities) {
ability.setSourceId(null);
ability.setControllerId(null);
}
abilities = null;
}
}
public void clear() {
counters.clear();
info = null;
clearAbilities();
}
}

View file

@ -58,7 +58,6 @@ import mage.watchers.Watchers;
import java.io.Serializable;
import java.util.*;
import org.apache.log4j.Logger;
/**
*
@ -80,7 +79,6 @@ public class GameState implements Serializable, Copyable<GameState> {
private final Map<UUID, LookedAt> lookedAt = new HashMap<>();
private final DelayedTriggeredAbilities delayed;
private final SpecialActions specialActions;
private final Map<UUID, Abilities<Ability>> otherAbilities = new HashMap<>();
private final TurnMods turnMods;
private final Watchers watchers;
@ -156,9 +154,6 @@ public class GameState implements Serializable, Copyable<GameState> {
this.values.put(entry.getKey(), entry.getValue());
}
this.zones.putAll(state.zones);
for (Map.Entry<UUID, Abilities<Ability>> entry: state.otherAbilities.entrySet()) {
otherAbilities.put(entry.getKey(), entry.getValue().copy());
}
this.paused = state.paused;
this.simultaneousEvents.addAll(state.simultaneousEvents);
for (Map.Entry<UUID, CardState> entry: state.cardState.entrySet()) {
@ -601,6 +596,9 @@ public class GameState implements Serializable, Copyable<GameState> {
watcher.setSourceId(attachedTo.getId());
watchers.add(watcher);
}
for (Ability sub: ability.getSubAbilities()) {
addAbility(sub, sourceId, attachedTo);
}
}
public void addCommandObject(CommandObject commandObject) {
@ -678,41 +676,32 @@ public class GameState implements Serializable, Copyable<GameState> {
* @return
*/
public Abilities<ActivatedAbility> getActivatedOtherAbilities(UUID objectId, Zone zone) {
if (otherAbilities.containsKey(objectId)) {
return otherAbilities.get(objectId).getActivatedAbilities(zone);
if (cardState.containsKey(objectId)) {
return cardState.get(objectId).getAbilities().getActivatedAbilities(zone);
}
return null;
}
public Abilities<Ability> getAllOtherAbilities(UUID objectId) {
if (otherAbilities.containsKey(objectId)) {
return otherAbilities.get(objectId);
if (cardState.containsKey(objectId)) {
return cardState.get(objectId).getAbilities();
}
return null;
}
public void addOtherAbility(UUID objectId, Ability ability) {
if (!otherAbilities.containsKey(objectId)) {
otherAbilities.put(objectId, new AbilitiesImpl(ability));
} else {
otherAbilities.get(objectId).add(ability);
}
}
/**
* Adds the ability to continuous or triggered abilities
* @param ability
* @param card
*/
public void addOtherAbility(Ability ability, Card card) {
addOtherAbility(card.getId(), ability);
addAbility(ability, card.getId(), card);
}
private void resetOtherAbilities() {
otherAbilities.clear();
public void addOtherAbility(Card attachedTo, Ability ability) {
ability.setSourceId(attachedTo.getId());
ability.setControllerId(attachedTo.getOwnerId());
if (!cardState.containsKey(attachedTo.getId())) {
cardState.put(attachedTo.getId(), new CardState());
}
cardState.get(attachedTo.getId()).addAbility(ability);
addAbility(ability, attachedTo.getId(), attachedTo);
}
/**
@ -734,7 +723,9 @@ public class GameState implements Serializable, Copyable<GameState> {
triggers.removeAllGainedAbilities();
getContinuousEffects().removeAllTemporaryEffects();
this.setLegendaryRuleActive(true);
this.resetOtherAbilities();
for (CardState state: cardState.values()) {
state.clearAbilities();
}
}
public void clear() {
@ -754,7 +745,6 @@ public class GameState implements Serializable, Copyable<GameState> {
legendaryRuleActive = true;
gameOver = false;
specialActions.clear();
otherAbilities.clear();
cardState.clear();
combat.clear();
turnMods.clear();

View file

@ -125,7 +125,6 @@ public interface Permanent extends Card, Controllable {
String getValue();
@Deprecated
@Override
void addAbility(Ability ability);
@Deprecated
void addAbility(Ability ability, Game game);

View file

@ -38,6 +38,8 @@ import java.util.Map;
import java.util.UUID;
import mage.MageObject;
import mage.MageObjectReference;
import mage.abilities.Abilities;
import mage.abilities.AbilitiesImpl;
import mage.abilities.Ability;
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.Effect;
@ -244,6 +246,16 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
}
}
@Override
public Abilities<Ability> getAbilities() {
return abilities;
}
@Override
public Abilities<Ability> getAbilities(Game game) {
return abilities;
}
@Override
@Deprecated
public void addAbility(Ability ability) {

View file

@ -601,6 +601,11 @@ public class Spell implements StackObject, Card {
return card.getAbilities();
}
@Override
public Abilities<Ability> getAbilities(Game game) {
return card.getAbilities(game);
}
@Override
public boolean hasAbility(UUID abilityId, Game game) {
return card.hasAbility(abilityId, game);
@ -674,7 +679,6 @@ public class Spell implements StackObject, Card {
spellAbilities.add(spellAbility);
}
@Override
public void addAbility(Ability ability) {}
@Override

View file

@ -487,6 +487,16 @@ public class StackAbility implements StackObject, Ability {
throw new UnsupportedOperationException("Not supported.");
}
@Override
public List<Ability> getSubAbilities() {
return this.ability.getSubAbilities();
}
@Override
public void addSubAbility(Ability ability) {
throw new UnsupportedOperationException("Not supported.");
}
@Override
public MageObject getSourceObject(Game game) {
throw new UnsupportedOperationException("Not supported."); //To change body of generated methods, choose Tools | Templates.