mirror of
https://github.com/correl/mage.git
synced 2024-11-14 19:19:32 +00:00
Ability refactor: new code to search abilities in cards and permanents;
This commit is contained in:
parent
978118148b
commit
8af43dc13a
31 changed files with 85 additions and 47 deletions
|
@ -1321,7 +1321,7 @@ public class ComputerPlayer extends PlayerImpl implements Player {
|
|||
if (ability != null && ability.canActivate(playerId, game).canActivate()
|
||||
&& !game.getContinuousEffects().preventedByRuleModification(GameEvent.getEvent(GameEvent.EventType.CAST_SPELL, ability.getSourceId(), ability.getSourceId(), playerId), ability, game, true)) {
|
||||
if (card.getCardType().contains(CardType.INSTANT)
|
||||
|| card.hasAbility(FlashAbility.getInstance().getId(), game)) {
|
||||
|| card.hasAbility(FlashAbility.getInstance(), game)) {
|
||||
playableInstant.add(card);
|
||||
} else {
|
||||
playableNonInstant.add(card);
|
||||
|
|
|
@ -82,7 +82,7 @@ class ChaosphereEffect extends RestrictionEffect {
|
|||
if (attacker == null) {
|
||||
return true;
|
||||
}
|
||||
return attacker.hasAbility(FlyingAbility.getInstance().getId(), game);
|
||||
return attacker.hasAbility(FlyingAbility.getInstance(), game);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -71,7 +71,7 @@ class CrimsonRocTriggeredAbility extends TriggeredAbilityImpl {
|
|||
Permanent permanent = game.getPermanent(event.getTargetId());
|
||||
return permanent != null
|
||||
&& permanent.isCreature()
|
||||
&& !permanent.getAbilities(game).contains(FlyingAbility.getInstance());
|
||||
&& !permanent.hasAbility(FlyingAbility.getInstance(), game);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -66,7 +66,7 @@ class DenseCanopyCantBlockEffect extends RestrictionEffect {
|
|||
if (attacker == null) {
|
||||
return true;
|
||||
}
|
||||
return attacker.hasAbility(FlyingAbility.getInstance().getId(), game);
|
||||
return attacker.hasAbility(FlyingAbility.getInstance(), game);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -75,6 +75,6 @@ enum MomentumRumblerCondition implements Condition {
|
|||
if (permanent == null) {
|
||||
return false;
|
||||
}
|
||||
return hasAbility == permanent.getAbilities(game).containsKey(FirstStrikeAbility.getInstance().getId());
|
||||
return hasAbility == permanent.hasAbility(FirstStrikeAbility.getInstance(), game);
|
||||
}
|
||||
}
|
|
@ -82,7 +82,7 @@ class QuartzwoodCrasherTriggeredAbility extends TriggeredAbilityImpl {
|
|||
&& ((DamagedPlayerEvent) event).isCombatDamage()) {
|
||||
Permanent creature = game.getPermanent(event.getSourceId());
|
||||
if (creature != null && creature.isControlledBy(controllerId)
|
||||
&& creature.hasAbility(TrampleAbility.getInstance().getId(), game)
|
||||
&& creature.hasAbility(TrampleAbility.getInstance(), game)
|
||||
&& !damagedPlayerIds.contains(event.getTargetId())) {
|
||||
damagedPlayerIds.add(event.getTargetId());
|
||||
this.getEffects().setTargetPointer(new FixedTarget(event.getTargetId(), game));
|
||||
|
@ -143,7 +143,7 @@ class QuartzwoodCrasherWatcher extends Watcher {
|
|||
return;
|
||||
}
|
||||
Permanent creature = game.getPermanent(event.getSourceId());
|
||||
if (creature == null || !creature.getAbilities(game).containsKey(TrampleAbility.getInstance().getId())) {
|
||||
if (creature == null || !creature.hasAbility(TrampleAbility.getInstance(), game)) {
|
||||
return;
|
||||
}
|
||||
damageMap
|
||||
|
|
|
@ -88,7 +88,7 @@ class ReturnFromExtinctionTarget extends TargetCardInYourGraveyard {
|
|||
return false;
|
||||
}
|
||||
for (Card card : player.getGraveyard().getCards(filter, sourceId, sourceControllerId, game)) {
|
||||
if (card.isAllCreatureTypes() || card.getAbilities(game).contains(ChangelingAbility.getInstance())) {
|
||||
if (card.isAllCreatureTypes() || card.hasAbility(ChangelingAbility.getInstance(), game)) {
|
||||
if (!subTypes.isEmpty()) {
|
||||
return true;
|
||||
} else {
|
||||
|
|
|
@ -80,8 +80,8 @@ class SidarKondoOfJamuraaCantBlockCreaturesSourceEffect extends RestrictionEffec
|
|||
|
||||
@Override
|
||||
public boolean applies(Permanent permanent, Ability source, Game game) {
|
||||
if (permanent.hasAbility(FlyingAbility.getInstance().getId(), game)
|
||||
|| permanent.hasAbility(ReachAbility.getInstance().getId(), game)) {
|
||||
if (permanent.hasAbility(FlyingAbility.getInstance(), game)
|
||||
|| permanent.hasAbility(ReachAbility.getInstance(), game)) {
|
||||
return false;
|
||||
}
|
||||
return game.getOpponents(source.getControllerId()).contains(permanent.getControllerId());
|
||||
|
|
|
@ -61,7 +61,7 @@ enum SlingbowTrapCondition implements Condition {
|
|||
for (UUID attackingCreatureId : game.getCombat().getAttackers()) {
|
||||
Permanent attackingCreature = game.getPermanent(attackingCreatureId);
|
||||
if (attackingCreature != null) {
|
||||
if (attackingCreature.getColor(game).isBlack() && attackingCreature.hasAbility(FlyingAbility.getInstance().getId(), game)) {
|
||||
if (attackingCreature.getColor(game).isBlack() && attackingCreature.hasAbility(FlyingAbility.getInstance(), game)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -91,7 +91,7 @@ public final class StormtideLeviathan extends CardImpl {
|
|||
// land abilities are intrinsic, so add them here, not in layer 6
|
||||
if (!land.hasSubtype(SubType.ISLAND, game)) {
|
||||
land.getSubtype(game).add(SubType.ISLAND);
|
||||
if (!land.getAbilities(game).contains(new BlueManaAbility())) {
|
||||
if (!land.getAbilities(game).containsClass(BlueManaAbility.class)) {
|
||||
land.addAbility(new BlueManaAbility(), source.getSourceId(), game);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -150,7 +150,7 @@ class TaigamOjutaiMasterGainReboundEffect extends ContinuousEffectImpl {
|
|||
}
|
||||
|
||||
private void addReboundAbility(Card card, Ability source, Game game) {
|
||||
boolean found = card.getAbilities(game).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);
|
||||
|
|
|
@ -113,7 +113,6 @@ public class CommandersCastTest extends CardTestCommander4Players {
|
|||
waitStackResolved(5, PhaseStep.POSTCOMBAT_MAIN);
|
||||
checkPermanentCount("after cast 2", 5, PhaseStep.POSTCOMBAT_MAIN, playerA, "Academy Ruins", 1);
|
||||
|
||||
// showBattlefield("end battlefield", 5, PhaseStep.END_TURN, playerA);
|
||||
setStopAt(5, PhaseStep.END_TURN);
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
|
|
|
@ -40,7 +40,7 @@ public class WonderTest extends CardTestPlayerBase {
|
|||
// check no flying in graveyard
|
||||
for (Card card : playerA.getGraveyard().getCards(currentGame)) {
|
||||
if (card.getName().equals("Runeclaw Bear")) {
|
||||
Assert.assertFalse(card.getAbilities().contains(FlyingAbility.getInstance()));
|
||||
Assert.assertFalse(card.hasAbility(FlyingAbility.getInstance(), currentGame));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue