Ability refactor: new code to search abilities in cards and permanents;

This commit is contained in:
Oleg Agafonov 2020-05-28 22:34:27 +04:00
parent 978118148b
commit 8af43dc13a
31 changed files with 85 additions and 47 deletions

View file

@ -41,9 +41,13 @@ public interface MageObject extends MageItem, Serializable {
Set<SuperType> getSuperType();
/**
* For cards: return basic abilities (without dynamic added)
* For permanents: return all abilities (dynamic ability inserts into permanent)
*/
Abilities<Ability> getAbilities();
boolean hasAbility(UUID abilityId, Game game);
boolean hasAbility(Ability ability, Game game);
ObjectColor getColor(Game game);
@ -180,9 +184,9 @@ public interface MageObject extends MageItem, Serializable {
}
if (this.isCreature() && otherCard.isCreature()) {
if (this.getAbilities().contains(ChangelingAbility.getInstance())
if (this.hasAbility(ChangelingAbility.getInstance(), game)
|| this.isAllCreatureTypes()
|| otherCard.getAbilities().contains(ChangelingAbility.getInstance())
|| otherCard.hasAbility(ChangelingAbility.getInstance(), game)
|| otherCard.isAllCreatureTypes()) {
return true;
}

View file

@ -132,12 +132,12 @@ public abstract class MageObjectImpl implements MageObject {
}
@Override
public boolean hasAbility(UUID abilityId, Game game) {
if (this.getAbilities().containsKey(abilityId)) {
public boolean hasAbility(Ability ability, Game game) {
if (this.getAbilities().contains(ability)) {
return true;
}
Abilities<Ability> otherAbilities = game.getState().getAllOtherAbilities(getId());
return otherAbilities != null && otherAbilities.containsKey(abilityId);
return otherAbilities != null && otherAbilities.contains(ability);
}
@Override

View file

@ -71,7 +71,7 @@ public class SpellAbility extends ActivatedAbilityImpl {
}
return null != game.getContinuousEffects().asThough(sourceId, AsThoughEffectType.CAST_AS_INSTANT, this, playerId, game) // check this first to allow Offering in main phase
|| timing == TimingRule.INSTANT
|| object.hasAbility(FlashAbility.getInstance().getId(), game)
|| object.hasAbility(FlashAbility.getInstance(), game)
|| game.canPlaySorcery(playerId);
}

View file

@ -27,10 +27,10 @@ public enum SuspendedCondition implements Condition {
public boolean apply(Game game, Ability source) {
Card card = game.getCard(source.getSourceId());
if (card != null) {
boolean found = card.getAbilities().stream().anyMatch(ability -> ability instanceof SuspendAbility);
boolean found = card.getAbilities(game).containsClass(SuspendAbility.class);
if (!found) {
found = game.getState().getAllOtherAbilities(source.getSourceId()).stream().anyMatch(ability -> ability instanceof SuspendAbility);
found = game.getState().getAllOtherAbilities(source.getSourceId()).containsClass(SuspendAbility.class);
}
if (found) {

View file

@ -64,7 +64,7 @@ public class GainAbilitySpellsEffect extends ContinuousEffectImpl {
if (stackObject.isControlledBy(source.getControllerId())) {
Card card = game.getCard(stackObject.getSourceId());
if (card != null && filter.match(card, game)) {
if (!card.getAbilities().contains(ability)) {
if (!card.hasAbility(ability, game)) {
game.getState().addOtherAbility(card, ability);
}
}

View file

@ -33,7 +33,7 @@ public class CanBlockOnlyFlyingAttachedEffect extends RestrictionEffect {
if (attacker == null) {
return true;
}
return attacker.getAbilities().contains(FlyingAbility.getInstance());
return attacker.hasAbility(FlyingAbility.getInstance(), game);
}
@Override

View file

@ -33,7 +33,7 @@ public class CanBlockOnlyFlyingEffect extends RestrictionEffect {
if (attacker == null) {
return true;
}
return attacker.getAbilities().contains(FlyingAbility.getInstance());
return attacker.hasAbility(FlyingAbility.getInstance(), game);
}
@Override

View file

@ -51,7 +51,7 @@ public class GainAbilityControlledSpellsEffect extends ContinuousEffectImpl {
if ((stackObject instanceof Spell) && !stackObject.isCopy() && stackObject.isControlledBy(source.getControllerId())) {
Spell spell = (Spell) stackObject;
if (filter.match(spell, game)) {
if (!spell.getAbilities().contains(ability)) {
if (!spell.hasAbility(ability, game)) {
game.getState().addOtherAbility(spell.getCard(), ability);
}
}

View file

@ -213,7 +213,7 @@ public class SuspendAbility extends SpecialAction {
}
MageObject object = game.getObject(sourceId);
return new ActivationStatus(object.isInstant()
|| object.hasAbility(FlashAbility.getInstance().getId(), game)
|| object.hasAbility(FlashAbility.getInstance(), game)
|| null != game.getContinuousEffects().asThough(sourceId,
AsThoughEffectType.CAST_AS_INSTANT, this, playerId, game)
|| game.canPlaySorcery(playerId), null);
@ -356,6 +356,13 @@ class SuspendPlayCardEffect extends OneShotEffect {
}
}
// remove the abilities from the card
// TODO: will not work with Adventure Cards and another auto-generated abilities list
// TODO: is it work after blink or return to hand?
/*
bug example:
Epochrasite bug: It comes out of suspend, is cast and enters the battlefield. THEN if it's returned to
its owner's hand from battlefield, the bounced Epochrasite can't be cast for the rest of the game.
*/
card.getAbilities().removeAll(abilitiesToRemove);
}
// cast the card for free

View file

@ -285,7 +285,7 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
/**
* Gets all current abilities - includes additional abilities added by other
* cards or effects
* cards or effects. Warning, you can't modify that list.
*
* @param game
* @return A list of {@link Ability} - this collection is not modifiable
@ -295,15 +295,23 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
if (game == null) {
return abilities; // deck editor with empty game
}
CardState cardState = game.getState().getCardState(this.getId());
if (!cardState.hasLostAllAbilities() && (cardState.getAbilities() == null || cardState.getAbilities().isEmpty())) {
if (cardState == null) {
return abilities;
}
// collects all abilities
Abilities<Ability> all = new AbilitiesImpl<>();
// basic
if (!cardState.hasLostAllAbilities()) {
all.addAll(abilities);
}
// dynamic
all.addAll(cardState.getAbilities());
return all;
}
@ -314,6 +322,12 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
cardState.getAbilities().clear();
}
@Override
public boolean hasAbility(Ability ability, Game game) {
// getAbilities(game) searches all abilities from base and dynamic lists (other)
return this.getAbilities(game).contains(ability);
}
/**
* Public in order to support adding abilities to SplitCardHalf's
*

View file

@ -171,8 +171,8 @@ public abstract class Designation implements MageObject {
}
@Override
public boolean hasAbility(UUID abilityId, Game game) {
return abilites.containsKey(abilityId);
public boolean hasAbility(Ability ability, Game game) {
return this.getAbilities().contains(ability);
}
@Override

View file

@ -306,7 +306,7 @@ public class Combat implements Serializable, Copyable<Combat> {
|| getAttackers().size() <= 1) {
return;
}
boolean canBand = attacker.getAbilities().containsKey(BandingAbility.getInstance().getId());
boolean canBand = attacker.hasAbility(BandingAbility.getInstance(), game);
List<Ability> bandsWithOther = new ArrayList<>();
for (Ability ability : attacker.getAbilities()) {
if (ability.getClass().equals(BandsWithOtherAbility.class)) {
@ -390,7 +390,7 @@ public class Combat implements Serializable, Copyable<Combat> {
permanent.addBandedCard(creatureId);
attacker.addBandedCard(targetId);
if (canBand) {
if (!permanent.getAbilities().containsKey(BandingAbility.getInstance().getId())) {
if (!permanent.hasAbility(BandingAbility.getInstance(), game)) {
filter.add(new AbilityPredicate(BandingAbility.class));
canBandWithOther = false;
}
@ -1289,7 +1289,8 @@ public class Combat implements Serializable, Copyable<Combat> {
if (attacker != null) {
if (!game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.DECLARE_ATTACKER, defenderId, creatureId, playerId))) {
if (addAttackerToCombat(creatureId, defenderId, game)) {
if (!attacker.getAbilities().containsKey(VigilanceAbility.getInstance().getId()) && !attacker.getAbilities().containsKey(JohanVigilanceAbility.getInstance().getId())) {
if (!attacker.hasAbility(VigilanceAbility.getInstance(), game)
&& !attacker.hasAbility(JohanVigilanceAbility.getInstance(), game)) {
if (!attacker.isTapped()) {
attacker.setTapped(true);
attackersTappedByAttack.add(attacker.getId());

View file

@ -158,12 +158,12 @@ public class Commander implements CommandObject {
}
@Override
public boolean hasAbility(UUID abilityId, Game game) {
if (this.getAbilities().containsKey(abilityId)) {
public boolean hasAbility(Ability ability, Game game) {
if (this.getAbilities().contains(ability)) {
return true;
}
Abilities<Ability> otherAbilities = game.getState().getAllOtherAbilities(getId());
return otherAbilities != null && otherAbilities.containsKey(abilityId);
return otherAbilities != null && otherAbilities.contains(ability);
}
@Override

View file

@ -173,8 +173,8 @@ public class Emblem implements CommandObject {
}
@Override
public boolean hasAbility(UUID abilityId, Game game) {
return abilites.containsKey(abilityId);
public boolean hasAbility(Ability ability, Game game) {
return getAbilities().contains(ability);
}
@Override

View file

@ -182,8 +182,8 @@ public class Plane implements CommandObject {
}
@Override
public boolean hasAbility(UUID abilityId, Game game) {
return abilites.containsKey(abilityId);
public boolean hasAbility(Ability ability, Game game) {
return getAbilities().contains(ability);
}
@Override

View file

@ -120,7 +120,7 @@ class TrailOfTheMageRingsReboundEffect extends ContinuousEffectImpl {
private void addReboundAbility(Card card, Ability source, Game game) {
if (filter.match(card, game)) {
boolean found = card.getAbilities().stream().anyMatch(ability -> ability instanceof ReboundAbility);
boolean found = card.getAbilities(game).containsClass(ReboundAbility.class);
if (!found) {
Ability ability = new ReboundAbility();
game.getState().addOtherAbility(card, ability);

View file

@ -524,8 +524,8 @@ public class Spell extends StackObjImpl implements Card {
}
@Override
public boolean hasAbility(UUID abilityId, Game game) {
return card.hasAbility(abilityId, game);
public boolean hasAbility(Ability ability, Game game) {
return card.hasAbility(ability, game);
}
@Override

View file

@ -178,7 +178,7 @@ public class StackAbility extends StackObjImpl implements Ability {
}
@Override
public boolean hasAbility(UUID abilityId, Game game) {
public boolean hasAbility(Ability ability, Game game) {
return false;
}
@ -672,4 +672,17 @@ public class StackAbility extends StackObjImpl implements Ability {
public Outcome getCustomOutcome() {
return this.ability.getCustomOutcome();
}
@Override
public boolean isSameInstance(Ability ability) {
// same instance (by mtg rules) = same object, ID or class+text (you can't check class only cause it can be different by params/text)
if (ability == null) {
return false;
}
return (this == ability)
|| (this.getId().equals(ability.getId()))
|| (this.getOriginalId().equals(ability.getOriginalId()))
|| (this.getClass() == ability.getClass() && this.getRule().equals(ability.getRule()));
}
}