Merge pull request #6585 from magefree/abilities_refactor

Abilities refactor and some fixes
This commit is contained in:
Oleg Agafonov 2020-05-28 22:17:21 +02:00 committed by GitHub
commit 37b197eab7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
104 changed files with 613 additions and 307 deletions

View file

@ -1267,7 +1267,7 @@ public class ComputerPlayer extends PlayerImpl implements Player {
//play a land that will allow us to play an unplayable
for (Mana mana : unplayable.keySet()) {
for (Card card : lands) {
for (ActivatedManaAbilityImpl ability : card.getAbilities().getActivatedManaAbilities(Zone.BATTLEFIELD)) {
for (ActivatedManaAbilityImpl ability : card.getAbilities(game).getActivatedManaAbilities(Zone.BATTLEFIELD)) {
for (Mana netMana : ability.getNetMana(game)) {
if (netMana.enough(mana)) {
this.playLand(card, game, false);
@ -1281,7 +1281,7 @@ public class ComputerPlayer extends PlayerImpl implements Player {
//play a land that will get us closer to playing an unplayable
for (Mana mana : unplayable.keySet()) {
for (Card card : lands) {
for (ActivatedManaAbilityImpl ability : card.getAbilities().getActivatedManaAbilities(Zone.BATTLEFIELD)) {
for (ActivatedManaAbilityImpl ability : card.getAbilities(game).getActivatedManaAbilities(Zone.BATTLEFIELD)) {
for (Mana netMana : ability.getNetMana(game)) {
if (mana.contains(netMana)) {
this.playLand(card, game, false);
@ -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);
@ -1362,7 +1362,7 @@ public class ComputerPlayer extends PlayerImpl implements Player {
}
}
for (Card card : graveyard.getCards(game)) {
for (ActivatedAbility ability : card.getAbilities().getActivatedAbilities(Zone.GRAVEYARD)) {
for (ActivatedAbility ability : card.getAbilities(game).getActivatedAbilities(Zone.GRAVEYARD)) {
if (ability.canActivate(playerId, game).canActivate()) {
ManaOptions abilityOptions = ability.getManaCosts().getOptions();
if (abilityOptions.isEmpty()) {

View file

@ -225,9 +225,7 @@ class AnimateDeadChangeAbilityEffect extends ContinuousEffectImpl implements Sou
abilityToRemove = ability;
}
}
if (abilityToRemove != null) {
permanent.getAbilities().remove(abilityToRemove);
}
permanent.removeAbility(abilityToRemove, source.getSourceId(), game);
permanent.addAbility(newAbility, source.getSourceId(), game);
return true;
}

View file

@ -83,7 +83,7 @@ class AnjeFalkenrathTriggeredAbility extends TriggeredAbilityImpl {
if (card == null) {
return false;
}
return card.getAbilities(game).stream().anyMatch(ability -> ability instanceof MadnessAbility);
return card.getAbilities(game).containsClass(MadnessAbility.class);
}
@Override

View file

@ -63,7 +63,7 @@ class ArtisanOfFormsApplyToPermanent extends ApplyToPermanent {
@Override
public boolean apply(Game game, Permanent permanent, Ability source, UUID copyToObjectId) {
permanent.addAbility(ArtisanOfForms.createAbility(), game);
permanent.addAbility(ArtisanOfForms.createAbility(), source.getSourceId(), game);
return true;
}
}

View file

@ -1,6 +1,8 @@
package mage.cards.b;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
@ -70,7 +72,13 @@ class BloodSunEffect extends ContinuousEffectImpl {
for (Permanent permanent : game.getState().getBattlefield().getActivePermanents(StaticFilters.FILTER_LANDS, player.getId(), source.getSourceId(), game)) {
switch (layer) {
case AbilityAddingRemovingEffects_6:
permanent.getAbilities().removeIf(ability -> ability.getAbilityType() != AbilityType.MANA);
List<Ability> toRemove = new ArrayList<>();
permanent.getAbilities().forEach(a -> {
if (a.getAbilityType() != AbilityType.MANA) {
toRemove.add(a);
}
});
permanent.removeAbilities(toRemove, source.getSourceId(), game);
break;
}
}

View file

@ -122,8 +122,8 @@ class BludgeonBrawlGainAbilityEffect extends ContinuousEffectImpl {
Permanent permanent = game.getPermanent(permanentId);
if (permanent != null) {
int convertedManaCost = permanent.getConvertedManaCost();
permanent.addAbility(new EquipAbility(Outcome.BoostCreature, new GenericManaCost(convertedManaCost)), game);
permanent.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new BoostEquippedEffect(convertedManaCost, 0)), game);
permanent.addAbility(new EquipAbility(Outcome.BoostCreature, new GenericManaCost(convertedManaCost)), source.getSourceId(), game);
permanent.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new BoostEquippedEffect(convertedManaCost, 0)), source.getSourceId(), game);
}
}
return true;

View file

@ -133,17 +133,14 @@ class BronzehideLionContinuousEffect extends ContinuousEffectImpl {
if (game.getState().getZoneChangeCounter(source.getSourceId()) > zoneChangeCounter) {
discard();
}
MageObject sourceObject = game.getPermanent(source.getSourceId());
Permanent sourceObject = game.getPermanent(source.getSourceId());
if (sourceObject == null) {
sourceObject = game.getPermanentEntering(source.getSourceId());
}
if (sourceObject == null) {
return false;
}
if (!(sourceObject instanceof Permanent)) {
return true;
}
Permanent lion = (Permanent) sourceObject;
Permanent lion = sourceObject;
switch (layer) {
case TypeChangingEffects_4:
lion.getCardType().clear();
@ -158,7 +155,7 @@ class BronzehideLionContinuousEffect extends ContinuousEffectImpl {
toRemove.add(ability);
}
}
lion.getAbilities(game).removeAll(toRemove);
lion.removeAbilities(toRemove, source.getSourceId(), game);
lion.getSpellAbility().getTargets().clear();
lion.getSpellAbility().getEffects().clear();

View file

@ -89,7 +89,7 @@ class GainReboundEffect extends ContinuousEffectImpl {
private void addReboundAbility(Card card, Game game) {
if (CastThroughTime.filter.match(card, 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);

View file

@ -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

View file

@ -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

View file

@ -56,8 +56,8 @@ class DacksDuplicateApplyToPermanent extends ApplyToPermanent {
* 29/05/2014 The ability of Dack's Duplicate doesn't target the
* creature.
*/
permanent.addAbility(new DethroneAbility(), game);
permanent.addAbility(HasteAbility.getInstance(), game);
permanent.addAbility(new DethroneAbility(), source.getSourceId(), game);
permanent.addAbility(HasteAbility.getInstance(), source.getSourceId(), game);
return true;
}

View file

@ -240,9 +240,7 @@ class DanceOfTheDeadChangeAbilityEffect extends ContinuousEffectImpl implements
abilityToRemove = ability;
}
}
if (abilityToRemove != null) {
permanent.getAbilities().remove(abilityToRemove);
}
permanent.removeAbility(abilityToRemove, source.getSourceId(), game);
permanent.addAbility(newAbility, source.getSourceId(), game);
return true;
}

View file

@ -22,8 +22,12 @@ public final class DeadlyRecluse extends CardImpl {
this.subtype.add(SubType.SPIDER);
this.power = new MageInt(1);
this.toughness = new MageInt(2);
this.toughness = new MageInt(2);
// Reach (This creature can block creatures with flying.)
this.addAbility(ReachAbility.getInstance());
// Deathtouch (Any amount of damage this deals to a creature is enough to destroy it.)
this.addAbility(DeathtouchAbility.getInstance());
}

View file

@ -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

View file

@ -69,7 +69,7 @@ class EvilTwinApplyToPermanent extends ApplyToPermanent {
Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new DestroyTargetEffect(), new ManaCostsImpl("{U}{B}"));
ability.addCost(new TapSourceCost());
ability.addTarget(new TargetCreaturePermanent(filter));
permanent.addAbility(ability, game);
permanent.addAbility(ability, source.getSourceId(), game);
return true;
}

View file

@ -112,7 +112,7 @@ class ExcavatorEffect extends ContinuousEffectImpl implements SourceEffect {
if (permanent != null) {
for(Ability ability : abilities)
{
permanent.addAbility(ability, source.getSourceId(), game, false);
permanent.addAbility(ability, source.getSourceId(), game);
}
}
}

View file

@ -51,8 +51,6 @@ enum FlameSweepPredicate implements ObjectPlayerPredicate<ObjectPlayer<Permanent
Permanent object = input.getObject();
UUID playerId = input.getPlayerId();
return !(object.isControlledBy(playerId)
&& object.getAbilities(game).stream().anyMatch(
ability -> ability.getClass().equals(FlyingAbility.class)
));
&& object.getAbilities(game).containsClass(FlyingAbility.class));
}
}

View file

@ -66,7 +66,7 @@ class GargoyleSentinelEffect extends ContinuousEffectImpl {
switch (layer) {
case AbilityAddingRemovingEffects_6:
if (sublayer == SubLayer.NA) {
permanent.getAbilities().removeIf(entry -> entry.getId().equals(DefenderAbility.getInstance().getId()));
permanent.removeAbility(DefenderAbility.getInstance(), source.getSourceId(), game);
permanent.getAbilities().add(FlyingAbility.getInstance());
}
break;

View file

@ -100,7 +100,7 @@ class GravityWellEffect extends ContinuousEffectImpl {
switch (layer) {
case AbilityAddingRemovingEffects_6:
if (sublayer == SubLayer.NA) {
permanent.getAbilities().removeIf(entry -> entry.getId().equals(FlyingAbility.getInstance().getId()));
permanent.removeAbility(FlyingAbility.getInstance(), source.getSourceId(), game);
}
break;
}

View file

@ -100,7 +100,7 @@ class HisokasGuardGainAbilityTargetEffect extends ContinuousEffectImpl {
if (hisokasGuard != null && !hisokasGuard.getConnectedCards("HisokasGuard").isEmpty()) {
Permanent guardedCreature = game.getPermanent(hisokasGuard.getConnectedCards("HisokasGuard").get(0));
if (guardedCreature != null && hisokasGuard.isTapped()) {
guardedCreature.addAbility(ability, game);
guardedCreature.addAbility(ability, source.getSourceId(), game);
return true;
} else {
// if guard isn't tapped, the effect is no more valid

View file

@ -66,7 +66,7 @@ class MartyrdomGainAbilityTargetEffect extends ContinuousEffectImpl {
if (permanent != null) {
ActivatedAbilityImpl ability = new MartyrdomActivatedAbility(source.getControllerId());
ability.setMayActivate(TargetController.ANY);
permanent.addAbility(ability, source.getSourceId(), game, false);
permanent.addAbility(ability, source.getSourceId(), game);
return true;
}
return false;

View file

@ -149,7 +149,7 @@ class MeliraSylvokOutcastEffect3 extends ContinuousEffectImpl {
Set<UUID> opponents = game.getOpponents(source.getControllerId());
for (Permanent perm : game.getBattlefield().getActivePermanents(filter, source.getControllerId(), game)) {
if (opponents.contains(perm.getControllerId())) {
perm.getAbilities().remove(InfectAbility.getInstance());
perm.removeAbility(InfectAbility.getInstance(), source.getSourceId(), game);
}
}
return true;

View file

@ -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);
}
}

View file

@ -1,8 +1,5 @@
package mage.cards.m;
import java.util.Objects;
import java.util.UUID;
import mage.MageObject;
import mage.abilities.Abilities;
import mage.abilities.Ability;
@ -20,8 +17,10 @@ import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.Predicate;
import mage.game.Game;
import java.util.Objects;
import java.util.UUID;
/**
*
* @author anonymous
*/
public final class MuragandaPetroglyphs extends CardImpl {
@ -54,6 +53,12 @@ public final class MuragandaPetroglyphs extends CardImpl {
class NoAbilityPredicate implements Predicate<MageObject> {
// Muraganda Petroglyphs gives a bonus only to creatures that have no rules text at all. This includes true vanilla
// creatures (such as Grizzly Bears), face-down creatures, many tokens, and creatures that have lost their abilities
// (due to Ovinize, for example). Any ability of any kind, whether or not the ability functions in the on the
// battlefield zone, including things like Cycling {2} means the creature doesnt get the bonus.
// (2007-05-01)
@Override
public boolean apply(MageObject input, Game game) {
boolean isFaceDown = false;
@ -65,8 +70,20 @@ class NoAbilityPredicate implements Predicate<MageObject> {
abilities = input.getAbilities();
}
if (isFaceDown) {
// Some Auras and Equipment grant abilities to creatures, meaning the affected creature would no longer
// get the +2/+2 bonus. For example, Flight grants flying to the enchanted creature. Other Auras and
// Equipment do not, meaning the affected creature would continue to get the +2/+2 bonus. For example,
// Dehydration states something now true about the enchanted creature, but doesnt give it any abilities.
// Auras and Equipment that grant abilities will use the words gains or has, and theyll list a keyword
// ability or an ability in quotation marks.
// (2007-05-01)
for (Ability ability : abilities) {
if (!ability.getSourceId().equals(input.getId()) && !ability.getClass().equals(JohanVigilanceAbility.class)) {
if (ability.getWorksFaceDown()) {
// inner face down abilities like turn up and becomes creature
continue;
}
if (!Objects.equals(ability.getClass(), SpellAbility.class) && !ability.getClass().equals(JohanVigilanceAbility.class)) {
return false;
}
}

View file

@ -72,7 +72,7 @@ class ProteanThaumaturgeApplyToPermanent extends ApplyToPermanent {
@Override
public boolean apply(Game game, Permanent permanent, Ability source, UUID copyToObjectId) {
permanent.addAbility(ProteanThaumaturge.createAbility(), game);
permanent.addAbility(ProteanThaumaturge.createAbility(), source.getSourceId(), game);
return true;
}
}

View file

@ -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

View file

@ -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 {

View file

@ -64,7 +64,7 @@ class SakashimaTheImpostorApplier extends ApplyToPermanent {
permanent.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD,
new CreateDelayedTriggeredAbilityEffect(new AtTheBeginOfNextEndStepDelayedTriggeredAbility(new ReturnToHandSourceEffect(true)), false),
new ManaCostsImpl("{2}{U}{U}")
), game);
), source.getSourceId(), game);
return true;
}

View file

@ -78,7 +78,7 @@ class ShieldsOfVelisVelGainEffect extends ContinuousEffectImpl {
for (Iterator<MageObjectReference> it = affectedObjectList.iterator(); it.hasNext();) {
Permanent permanent = it.next().getPermanent(game);
if (permanent != null) {
permanent.addAbility(ChangelingAbility.getInstance(), source.getSourceId(), game, false);
permanent.addAbility(ChangelingAbility.getInstance(), source.getSourceId(), game);
} else {
it.remove();
}

View file

@ -67,7 +67,7 @@ class ShoalSerpentEffect extends ContinuousEffectImpl {
switch (layer) {
case AbilityAddingRemovingEffects_6:
if (sublayer == SubLayer.NA) {
permanent.getAbilities().removeIf(entry -> entry.getId().equals(DefenderAbility.getInstance().getId()));
permanent.removeAbility(DefenderAbility.getInstance(), source.getSourceId(), game);
}
break;
}

View file

@ -1,4 +1,3 @@
package mage.cards.s;
import java.util.UUID;
@ -34,8 +33,6 @@ public final class Showstopper extends CardImpl {
public Showstopper (UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{1}{B}{R}");
// Until end of turn, creatures you control gain "When this creature dies, it deals 2 damage to target creature an opponent controls."
TriggeredAbility ability = new DiesTriggeredAbility(new DamageTargetEffect(2, "it"), false);
Target target = new TargetCreaturePermanent(filter2);

View file

@ -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());

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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);

View file

@ -111,7 +111,7 @@ class TestamentOfFaithBecomesCreatureSourceEffect extends ContinuousEffectImpl i
if (sublayer == SubLayer.NA) {
if (!token.getAbilities().isEmpty()) {
for (Ability ability: token.getAbilities()) {
permanent.addAbility(ability, source.getSourceId(), game, false);
permanent.addAbility(ability, source.getSourceId(), game);
}
}
}

View file

@ -63,7 +63,7 @@ public final class UncheckedGrowth extends CardImpl {
for (UUID permanentId : targetPointer.getTargets(game, source)) {
Permanent permanent = game.getPermanent(permanentId);
if (permanent != null && permanent.hasSubtype(SubType.SPIRIT, game)) {
permanent.addAbility(TrampleAbility.getInstance(), game);
permanent.addAbility(TrampleAbility.getInstance(), source.getSourceId(), game);
affectedTargets++;
}
}

View file

@ -77,7 +77,7 @@ class UnstableShapeshifterEffect extends OneShotEffect {
if (targetCreature != null && permanent != null) {
Permanent blueprintPermanent = game.copyPermanent(Duration.Custom, targetCreature, permanent.getId(), source, new EmptyApplyToPermanent());
blueprintPermanent.addAbility(new EntersBattlefieldAllTriggeredAbility(Zone.BATTLEFIELD,
new UnstableShapeshifterEffect(), filterAnotherCreature, false, SetTargetPointer.PERMANENT, ""), game);
new UnstableShapeshifterEffect(), filterAnotherCreature, false, SetTargetPointer.PERMANENT, ""), source.getSourceId(), game);
return true;
}
return false;

View file

@ -86,7 +86,7 @@ class VraskaTheUnseenGainAbilityEffect extends ContinuousEffectImpl {
public boolean apply(Game game, Ability source) {
Permanent permanent = game.getPermanent(source.getSourceId());
if (permanent != null) {
permanent.addAbility(ability, game);
permanent.addAbility(ability, source.getSourceId(), game);
return true;
}
return false;

View file

@ -68,7 +68,7 @@ class WishfulMerfolkEffect extends ContinuousEffectImpl {
switch (layer) {
case AbilityAddingRemovingEffects_6:
if (sublayer == SubLayer.NA) {
permanent.getAbilities().removeIf(entry -> entry.getId().equals(DefenderAbility.getInstance().getId()));
permanent.removeAbility(DefenderAbility.getInstance(), source.getSourceId(), game);
}
break;
case TypeChangingEffects_4:

View file

@ -274,9 +274,12 @@ public class ManifestTest extends CardTestPlayerBase {
// Check if a Megamorph card is manifested and turned face up by their megamorph ability
// it gets the +1/+1 counter.
// 701.33c
// If a card with morph is manifested, its controller may turn that card face up using
// either the procedure described in rule 702.36e to turn a face-down permanent with morph face up
// or the procedure described above to turn a manifested permanent face up.
@Test
public void testManifestMegamorph() {
public void testManifestMegamorph_TurnUpByMegamorphCost() {
addCard(Zone.BATTLEFIELD, playerB, "Swamp", 2);
addCard(Zone.BATTLEFIELD, playerB, "Forest", 6);
// {1}{B}, {T}, Sacrifice another creature: Manifest the top card of your library.
@ -295,6 +298,7 @@ public class ManifestTest extends CardTestPlayerBase {
activateAbility(2, PhaseStep.POSTCOMBAT_MAIN, playerB, "{5}{G}: Turn");
setStrictChooseMode(true);
setStopAt(2, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
@ -310,7 +314,45 @@ public class ManifestTest extends CardTestPlayerBase {
assertPowerToughness(playerB, "Aerie Bowmasters", 4, 5); // 3/4 and the +1/+1 counter from Megamorph
Permanent aerie = getPermanent("Aerie Bowmasters", playerB);
Assert.assertTrue("Aerie Bowmasters has to be green", aerie != null && aerie.getColor(currentGame).isGreen());
}
@Test
public void testManifestMegamorph_TurnUpBySimpleCost() {
addCard(Zone.BATTLEFIELD, playerB, "Swamp", 2);
addCard(Zone.BATTLEFIELD, playerB, "Forest", 4);
// {1}{B}, {T}, Sacrifice another creature: Manifest the top card of your library.
addCard(Zone.BATTLEFIELD, playerB, "Qarsi High Priest", 1);
addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 1);
// {2}{G}{G}
// Reach (This creature can block creatures with flying.)
// Megamorph {5}{G}
addCard(Zone.LIBRARY, playerB, "Aerie Bowmasters", 1);
addCard(Zone.LIBRARY, playerB, "Mountain", 1);
skipInitShuffling();
activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerB, "{1}{B}, {T}, Sacrifice another creature");
setChoice(playerB, "Silvercoat Lion");
activateAbility(2, PhaseStep.POSTCOMBAT_MAIN, playerB, "{2}{G}{G}: Turn");
setStrictChooseMode(true);
setStopAt(2, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
// no life gain
assertLife(playerA, 20);
assertLife(playerB, 20);
assertGraveyardCount(playerB, "Silvercoat Lion", 1);
assertPermanentCount(playerB, EmptyNames.FACE_DOWN_CREATURE.toString(), 0);
assertPermanentCount(playerB, "Aerie Bowmasters", 1);
assertPowerToughness(playerB, "Aerie Bowmasters", 3, 4); // 3/4 without counter (megamorph not used)
Permanent aerie = getPermanent("Aerie Bowmasters", playerB);
Assert.assertTrue("Aerie Bowmasters has to be green", aerie != null && aerie.getColor(currentGame).isGreen());
}
/**

View file

@ -0,0 +1,70 @@
package org.mage.test.cards.abilities.other;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.abilities.keyword.VigilanceAbility;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import mage.game.permanent.Permanent;
import org.junit.Assert;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
* @author JayDi85
*/
public class GainAbilitiesTest extends CardTestPlayerBase {
@Test
public void test_AttachmentSingleton() {
// {2}{W}
// Enchanted creature gets +2/+2.
// Enchanted creature has vigilance as long as you control a black or green permanent.
addCard(Zone.HAND, playerA, "Abzan Runemark@attach", 2);
addCard(Zone.BATTLEFIELD, playerA, "Plains", 6);
//
addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears@bear", 1); // 2/2
// attach all
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "@attach.1", "@bear");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "@attach.2", "@bear");
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
checkAbility("after", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "@bear", VigilanceAbility.class, true);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertAllCommandsUsed();
Permanent permanent = getPermanent("Balduvian Bears");
Assert.assertEquals("must have only 1 singleton ability instance from two attachments",
1, permanent.getAbilities(currentGame).stream().filter(a -> a instanceof VigilanceAbility).count());
}
@Test
public void test_AttachmentUnique() {
// {R}
// Enchanted creature has "{R}, {T}, Discard a card: Draw a card."
addCard(Zone.HAND, playerA, "Epiphany Storm@attach", 2);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2);
//
addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears@bear", 1); // 2/2
// attach all
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "@attach.1", "@bear");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "@attach.2", "@bear");
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
//checkAbility("after", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "@bear", VigilanceAbility.class, true);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertAllCommandsUsed();
Permanent permanent = getPermanent("Balduvian Bears");
Assert.assertEquals("must have 2 dynamic ability instances from two attachments",
2, permanent.getAbilities(currentGame).stream().filter(
a -> a.getEffects().stream().anyMatch(e -> e instanceof DrawCardSourceControllerEffect)
).count());
}
}

View file

@ -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();

View file

@ -14,20 +14,17 @@ import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
*
* @author LevelX2
*/
public class LandTypeChangingEffectsTest extends CardTestPlayerBase {
/**
*
* Playing a commander game. Opponent had a Magus of the Moon, and I later
* dropped a Chromatic Lantern.
*
* <p>
* I was not allowed to use the Chromatic Lantern's ability. Since layers
* are tricky I asked on the Judge's chat to confirm and the user "luma"
* said it should work on this scenario.
*
*/
@Test
public void testMagusOfTheMoonAndChromaticLantern() {
@ -42,8 +39,10 @@ public class LandTypeChangingEffectsTest extends CardTestPlayerBase {
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Chromatic Lantern");
setStrictChooseMode(true);
setStopAt(2, PhaseStep.BEGIN_COMBAT);
execute();
assertAllCommandsUsed();
assertPermanentCount(playerB, "Chromatic Lantern", 1);
@ -66,8 +65,11 @@ public class LandTypeChangingEffectsTest extends CardTestPlayerBase {
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Chromatic Lantern");
castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Magus of the Moon");
setStrictChooseMode(true);
setStopAt(3, PhaseStep.BEGIN_COMBAT);
execute();
assertAllCommandsUsed();
assertPermanentCount(playerB, "Chromatic Lantern", 1);
assertPermanentCount(playerA, "Magus of the Moon", 1);
@ -97,8 +99,11 @@ public class LandTypeChangingEffectsTest extends CardTestPlayerBase {
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Aquitect's Will", "Forbidding Watchtower");
activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerB, "{1}{W}:");
setStrictChooseMode(true);
setStopAt(2, PhaseStep.BEGIN_COMBAT);
execute();
assertAllCommandsUsed();
assertGraveyardCount(playerA, "Aquitect's Will", 1);
@ -128,8 +133,10 @@ public class LandTypeChangingEffectsTest extends CardTestPlayerBase {
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, bloodmoon);
playLand(1, PhaseStep.POSTCOMBAT_MAIN, playerA, urborgtoy);
setStrictChooseMode(true);
setStopAt(2, PhaseStep.PRECOMBAT_MAIN);
execute();
assertAllCommandsUsed();
assertPermanentCount(playerA, bloodmoon, 1);
assertPermanentCount(playerA, urborgtoy, 1);
@ -157,8 +164,10 @@ public class LandTypeChangingEffectsTest extends CardTestPlayerBase {
playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, urborgtoy);
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, bloodmoon);
setStrictChooseMode(true);
setStopAt(2, PhaseStep.PRECOMBAT_MAIN);
execute();
assertAllCommandsUsed();
assertPermanentCount(playerA, bloodmoon, 1);
assertPermanentCount(playerA, urborgtoy, 1);
@ -176,7 +185,7 @@ public class LandTypeChangingEffectsTest extends CardTestPlayerBase {
In terms of time-stamp order, Urborg was down first, then Kormus Bell, then Quicksilver.
When I put a flood counter on a basic swamp, it would become a 0/0 instead of a 1/1 and die.
*/
@Test
public void testCormusBellAfterUrborg() {
// Land - Legendary
@ -198,14 +207,14 @@ public class LandTypeChangingEffectsTest extends CardTestPlayerBase {
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Kormus Bell");
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Quicksilver Fountain");
addTarget(playerA, "Mountain");
setStrictChooseMode(true);
setStopAt(3, PhaseStep.POSTCOMBAT_MAIN);
execute();
assertAllCommandsUsed();
assertPermanentCount(playerA, urborgtoy, 1);
assertPermanentCount(playerA, "Kormus Bell", 1);
assertPermanentCount(playerB, "Quicksilver Fountain", 1);
@ -245,8 +254,10 @@ public class LandTypeChangingEffectsTest extends CardTestPlayerBase {
addCard(Zone.BATTLEFIELD, playerA, "Stormtide Leviathan"); // all lands are islands in addition to their other types
addCard(Zone.BATTLEFIELD, playerA, "Darksteel Citadel"); // land has indestructible ability
setStrictChooseMode(true);
setStopAt(3, PhaseStep.POSTCOMBAT_MAIN);
execute();
assertAllCommandsUsed();
Permanent darksteel = getPermanent("Darksteel Citadel", playerA.getId());
Assert.assertNotNull(darksteel);

View file

@ -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));
}
}
}

View file

@ -1,4 +1,3 @@
package org.mage.test.cards.planeswalker;
import mage.abilities.keyword.IndestructibleAbility;
@ -11,7 +10,6 @@ import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
*
* @author LevelX2
*/
public class GideonTest extends CardTestPlayerBase {
@ -116,16 +114,23 @@ public class GideonTest extends CardTestPlayerBase {
// Equip {2}
addCard(Zone.BATTLEFIELD, playerB, "Stitcher's Graft", 1);
// transform
attack(2, playerB, "Kytheon, Hero of Akros");
attack(2, playerB, "Silvercoat Lion");
attack(2, playerB, "Pillarfield Ox");
checkPermanentCount("after transform", 2, PhaseStep.POSTCOMBAT_MAIN, playerB, "Gideon, Battle-Forged", 1);
// become creature and equip
activateAbility(4, PhaseStep.PRECOMBAT_MAIN, playerB, "0: Until ");
waitStackResolved(4, PhaseStep.PRECOMBAT_MAIN);
activateAbility(4, PhaseStep.PRECOMBAT_MAIN, playerB, "Equip {2}", "Gideon, Battle-Forged");
attack(4, playerB, "Gideon, Battle-Forged"); // 7 damage
setStrictChooseMode(true);
setStopAt(5, PhaseStep.PRECOMBAT_MAIN);
execute();
assertAllCommandsUsed();
assertPermanentCount(playerB, "Silvercoat Lion", 1);
assertLife(playerA, 7);

View file

@ -1,5 +1,6 @@
package org.mage.test.cards.single.fut;
import mage.abilities.keyword.HasteAbility;
import mage.constants.EmptyNames;
import mage.constants.PhaseStep;
import mage.constants.Zone;
@ -31,8 +32,10 @@ public class MuragandaPetroglyphsTest extends CardTestPlayerBase {
addCard(Zone.BATTLEFIELD, playerA, "Muraganda Petroglyphs", 1);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.PRECOMBAT_MAIN);
execute();
assertAllCommandsUsed();
assertPowerToughness(playerA, "Grizzly Bears", 4, 4, Filter.ComparisonScope.Any);
assertPowerToughness(playerB, "Grizzly Bears", 4, 4, Filter.ComparisonScope.Any);
@ -41,15 +44,20 @@ public class MuragandaPetroglyphsTest extends CardTestPlayerBase {
@Test
public void faceDownCreaturesTest() {
// Morph {4}{G}
addCard(Zone.HAND, playerA, "Pine Walker");
addCard(Zone.BATTLEFIELD, playerA, "Forest", 3);
//
// Creatures with no abilities get +2/+2.
addCard(Zone.BATTLEFIELD, playerA, "Muraganda Petroglyphs", 1);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Pine Walker");
setChoice(playerA, "Yes"); // cast it face down as 2/2 creature
setStrictChooseMode(true);
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertAllCommandsUsed();
assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1);
assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 4, 4);
@ -57,21 +65,26 @@ public class MuragandaPetroglyphsTest extends CardTestPlayerBase {
@Test
public void faceDownGainedAbilityTest() {
// Morph {4}{G}
addCard(Zone.HAND, playerA, "Pine Walker");
addCard(Zone.BATTLEFIELD, playerA, "Forest", 5);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1);
addCard(Zone.BATTLEFIELD, playerA, "Mass Hysteria"); // All creatures have haste.
// Creatures with no abilities get +2/+2.
addCard(Zone.BATTLEFIELD, playerA, "Muraganda Petroglyphs", 1);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Pine Walker");
setChoice(playerA, "Yes"); // cast it face down as 2/2 creature
setStrictChooseMode(true);
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertAllCommandsUsed();
assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1);
assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 2);
//assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 2); // no boost (permanent have haste)
assertAbility(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), HasteAbility.getInstance(), true);
}
@Test
@ -83,8 +96,10 @@ public class MuragandaPetroglyphsTest extends CardTestPlayerBase {
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Raise the Alarm");
setStrictChooseMode(true);
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertAllCommandsUsed();
assertPowerToughness(playerA, "Soldier", 3, 3);
}
@ -99,8 +114,10 @@ public class MuragandaPetroglyphsTest extends CardTestPlayerBase {
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Ovinize", "Goblin Guide");
setStrictChooseMode(true);
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertAllCommandsUsed();
assertPowerToughness(playerB, "Goblin Guide", 2, 3);
}
@ -110,8 +127,10 @@ public class MuragandaPetroglyphsTest extends CardTestPlayerBase {
addCard(Zone.BATTLEFIELD, playerA, "Hundroog", 1); // Cycling {3}, 4/7
addCard(Zone.BATTLEFIELD, playerA, "Muraganda Petroglyphs", 1);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertAllCommandsUsed();
assertPowerToughness(playerA, "Hundroog", 4, 7);
}
@ -126,11 +145,12 @@ public class MuragandaPetroglyphsTest extends CardTestPlayerBase {
addCard(Zone.BATTLEFIELD, playerA, "Muraganda Petroglyphs", 1);
addCard(Zone.HAND, playerA, "Vastwood Zendikon");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Vastwood Zendikon", "Forest");
setStrictChooseMode(true);
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertAllCommandsUsed();
assertPowerToughness(playerA, "Forest", 6, 4);
@ -160,8 +180,10 @@ public class MuragandaPetroglyphsTest extends CardTestPlayerBase {
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Dehydration", "Runeclaw Bear");
setStrictChooseMode(true);
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertAllCommandsUsed();
assertPowerToughness(playerA, "Grizzly Bears", 4, 2);
@ -179,11 +201,14 @@ public class MuragandaPetroglyphsTest extends CardTestPlayerBase {
addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears", 1);
addCard(Zone.HAND, playerA, "Shadow Slice"); // {4}{B}
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Shadow Slice");
setChoice(playerA, "Grizzly Bears");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Shadow Slice", playerB);
setChoice(playerA, "Yes"); // do cipher
addTarget(playerA, "Grizzly Bears");
setStrictChooseMode(true);
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertAllCommandsUsed();
assertPowerToughness(playerA, "Grizzly Bears", 2, 2);
}

View file

@ -1,4 +1,3 @@
package org.mage.test.cards.triggers.dies;
import mage.constants.PhaseStep;
@ -7,7 +6,6 @@ import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
*
* @author LevelX2
*/
@ -16,10 +14,9 @@ public class ShowstopperTest extends CardTestPlayerBase {
/**
* Tests that the dies triggered ability of silvercoat lion (gained by Showstopper)
* triggers as he dies from Lightning Bolt
*
*/
@Test
public void testDiesTriggeredAbility() {
public void test_OneTrigger() {
// Showstopper Instant {1}{B}{R}
// Until end of turn, creatures you control gain "When this creature dies, it deals 2 damage to target creature an opponent controls."
addCard(Zone.HAND, playerA, "Showstopper");
@ -30,10 +27,14 @@ public class ShowstopperTest extends CardTestPlayerBase {
addCard(Zone.BATTLEFIELD, playerB, "Ornithopter", 1);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Showstopper");
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Lightning Bolt", "Silvercoat Lion");
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Lightning Bolt", "Silvercoat Lion");
addTarget(playerA, "Ornithopter");
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertLife(playerA, 20);
assertLife(playerB, 20);
@ -43,12 +44,13 @@ public class ShowstopperTest extends CardTestPlayerBase {
assertGraveyardCount(playerB, "Ornithopter", 1);
}
/**
* Test if Showstopper is called twice
*/
@Test
public void testTwoDiesTriggeredAbilities() {
public void test_TwoTriggers() {
// Showstopper Instant {1}{B}{R}
// Until end of turn, creatures you control gain "When this creature dies, it deals 2 damage to target creature an opponent controls."
addCard(Zone.HAND, playerA, "Showstopper", 2);
@ -62,12 +64,16 @@ public class ShowstopperTest extends CardTestPlayerBase {
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Showstopper");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Showstopper");
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Lightning Bolt", "Silvercoat Lion");
setChoice(playerA, "When {this} dies"); // choose from two triggers
addTarget(playerA, "Ornithopter");
addTarget(playerA, "Grizzly Bears");
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertLife(playerA, 20);
assertLife(playerB, 20);
@ -79,4 +85,65 @@ public class ShowstopperTest extends CardTestPlayerBase {
assertGraveyardCount(playerB, "Ornithopter", 1);
}
@Test
public void test_TwoTriggersAndCopies() {
// Showstopper Instant {1}{B}{R}
// Until end of turn, creatures you control gain "When this creature dies, it deals 2 damage to target creature an opponent controls."
addCard(Zone.HAND, playerA, "Showstopper", 2);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3);
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3);
addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 1);
addCard(Zone.HAND, playerB, "Lightning Bolt");
addCard(Zone.BATTLEFIELD, playerB, "Mountain", 1);
addCard(Zone.BATTLEFIELD, playerB, "Ornithopter", 1);
addCard(Zone.BATTLEFIELD, playerB, "Grizzly Bears", 1);
addCard(Zone.BATTLEFIELD, playerB, "Alchemist's Apprentice", 1);
addCard(Zone.BATTLEFIELD, playerB, "Augmenting Automaton", 1);
//
// When you next cast an instant spell, cast a sorcery spell, or activate a loyalty ability this turn, copy that spell or ability twice.
// You may choose new targets for the copies.
addCard(Zone.HAND, playerA, "Repeated Reverberation", 1); // {2}{R}{R}
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4);
addCard(Zone.BATTLEFIELD, playerA, "Island", 4);
// first spell
activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {B}", 2);
activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Showstopper");
// prepare copy
activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}", 4);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Repeated Reverberation");
// second spell with 2x copy
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Showstopper");
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Lightning Bolt", "Silvercoat Lion");
setChoice(playerA, "When {this} dies"); // choose from 4 triggers
setChoice(playerA, "When {this} dies"); // choose from 4 triggers
setChoice(playerA, "When {this} dies"); // choose from 4 triggers
addTarget(playerA, "Ornithopter");
addTarget(playerA, "Grizzly Bears");
addTarget(playerA, "Alchemist's Apprentice");
addTarget(playerA, "Augmenting Automaton");
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertLife(playerA, 20);
assertLife(playerB, 20);
assertGraveyardCount(playerA, "Showstopper", 2);
assertGraveyardCount(playerA, "Silvercoat Lion", 1);
assertGraveyardCount(playerB, "Grizzly Bears", 1);
assertGraveyardCount(playerB, "Ornithopter", 1);
assertGraveyardCount(playerB, "Alchemist's Apprentice", 1);
assertGraveyardCount(playerB, "Augmenting Automaton", 1);
}
}

View file

@ -40,7 +40,7 @@ public class SerializationTest extends CardTestPlayerBase {
currentGame.addPermanent(permanent);
// mark damage from infected ability
permanent.addAbility(InfectAbility.getInstance(), currentGame);
permanent.addAbility(InfectAbility.getInstance(), null, currentGame);
permanent.markDamage(1, permanent.getId(), currentGame, false, false);
// test compress (it uses default java serialization)

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

@ -2,9 +2,12 @@
package mage.abilities;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.function.Predicate;
import mage.abilities.keyword.ProtectionAbility;
import mage.abilities.mana.ActivatedManaAbilityImpl;
import mage.constants.Zone;
@ -255,7 +258,8 @@ public interface Abilities<T extends Ability> extends List<T>, Serializable {
boolean containsAll(Abilities<T> abilities);
/**
* Searches this set of abilities for the existence of the give class
* Searches this set of abilities for the existence of the given class
* Warning, it doesn't work with inherited classes (e.g. it's not equal to instanceOf command)
*
* @param classObject
* @return True if the passed in class is also in this set of abilities.
@ -271,4 +275,13 @@ public interface Abilities<T extends Ability> extends List<T>, Serializable {
Abilities<T> copy();
String getValue();
@Deprecated // use permanent.removeAbility instead
boolean remove(Object o);
@Deprecated // use permanent.removeAbility instead
boolean removeAll(Collection<?> c);
@Deprecated // use permanent.removeAbility instead
boolean removeIf(Predicate<? super T> filter);
}

View file

@ -232,7 +232,8 @@ public class AbilitiesImpl<T extends Ability> extends ArrayList<T> implements Ab
if (ability.getId().equals(test.getId())) {
return true;
}
if (ability.getOriginalId().equals(test.getId())) {
if (ability.getOriginalId().equals(test.getOriginalId())) {
// on ability resolve: engine creates ability's copy and generates newId(), so you must use originalId to find that ability in card later
return true;
}
if (ability instanceof MageSingleton && test instanceof MageSingleton && ability.getRule().equals(test.getRule())) {
@ -243,7 +244,7 @@ public class AbilitiesImpl<T extends Ability> extends ArrayList<T> implements Ab
}
@Override
public boolean containsRule(T ability) {
public boolean containsRule(T ability) { // TODO: remove
return stream().anyMatch(rule -> rule.getRule().equals(ability.getRule()));
}
@ -262,7 +263,7 @@ public class AbilitiesImpl<T extends Ability> extends ArrayList<T> implements Ab
}
@Override
public boolean containsKey(UUID abilityId) {
public boolean containsKey(UUID abilityId) { // TODO: remove
return stream().anyMatch(ability -> abilityId.equals(ability.getId()));
}

View file

@ -522,4 +522,11 @@ public interface Ability extends Controllable, Serializable {
Ability addCustomOutcome(Outcome customOutcome);
Outcome getCustomOutcome();
/**
* For mtg's instances search, see rules example in 112.10b
* @param ability
* @return
*/
boolean isSameInstance(Ability ability);
}

View file

@ -938,6 +938,10 @@ public abstract class AbilityImpl implements Ability {
@Override
public boolean hasSourceObjectAbility(Game game, MageObject source, GameEvent event) {
// if source object have this ability
// uses for ability.isInUseableZone
// replacement and other continues effects can be without source, but active (must return true)
MageObject object = source;
// for singleton abilities like Flying we can't rely on abilities' source because it's only once in continuous effects
// so will use the sourceId of the object itself that came as a parameter if it is not null
@ -949,16 +953,10 @@ public abstract class AbilityImpl implements Ability {
}
if (object != null) {
if (object instanceof Permanent) {
if (!((Permanent) object).getAbilities(game).contains(this)) {
return false;
}
return ((Permanent) object).isPhasedIn();
} else if (object instanceof Card) {
return ((Card) object).getAbilities(game).contains(this);
} else if (!object.getAbilities().contains(this)) { // not sure which object it can still be
// check if it's an ability that is temporary gained to a card
Abilities<Ability> otherAbilities = game.getState().getAllOtherAbilities(this.getSourceId());
return otherAbilities != null && otherAbilities.contains(this);
return object.hasAbility(this, game) && ((Permanent) object).isPhasedIn();
} else {
// cards and other objects
return object.hasAbility(this, game);
}
}
return true;
@ -1264,4 +1262,17 @@ public abstract class AbilityImpl implements Ability {
public Outcome getCustomOutcome() {
return this.customOutcome;
}
@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(true).equals(ability.getRule(true)));
}
}

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

@ -130,7 +130,8 @@ class LicidContinuousEffect extends ContinuousEffectImpl {
}
}
}
licid.getAbilities(game).removeAll(toRemove);
licid.removeAbilities(toRemove, source.getSourceId(), game);
Ability ability = new EnchantAbility("creature");
ability.setRuleAtTheTop(true);
licid.addAbility(ability, source.getSourceId(), game);

View file

@ -40,6 +40,7 @@ public class TurnFaceUpAbility extends SpecialAction {
this.usesStack = false;
this.abilityType = AbilityType.SPECIAL_ACTION;
this.setRuleVisible(false); // will be made visible only to controller in CardView
this.setWorksFaceDown(true);
}
public TurnFaceUpAbility(final TurnFaceUpAbility ability) {

View file

@ -17,7 +17,7 @@ public enum BuybackCondition implements Condition {
public boolean apply(Game game, Ability source) {
Card card = game.getCard(source.getSourceId());
if (card != null) {
return card.getAbilities().stream()
return card.getAbilities(game).stream()
.filter(a -> a instanceof BuybackAbility)
.anyMatch(a -> ((BuybackAbility) a).isBuybackActivated(game));
}

View file

@ -19,9 +19,9 @@ public enum DashedCondition implements Condition {
public boolean apply(Game game, Ability source) {
Card card = game.getCard(source.getSourceId());
if (card != null) {
return card.getAbilities().stream()
return card.getAbilities(game).stream()
.filter(a -> a instanceof DashAbility)
.anyMatch(d -> ((DashAbility)d).isActivated(source, game));
.anyMatch(d -> ((DashAbility) d).isActivated(source, game));
}
return false;

View file

@ -22,7 +22,7 @@ public enum EvokedCondition implements Condition {
public boolean apply(Game game, Ability source) {
Card card = game.getCard(source.getSourceId());
if (card != null) {
return card.getAbilities().stream()
return card.getAbilities(game).stream()
.filter(ab -> ab instanceof EvokeAbility)
.anyMatch(evoke -> ((EvokeAbility) evoke).isActivated(source, game));
}

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

@ -57,7 +57,7 @@ public class ContinuousEffects implements Serializable {
// private final PlaneswalkerRedirectionEffect planeswalkerRedirectionEffect;
private final AuraReplacementEffect auraReplacementEffect;
private final List<ContinuousEffect> previous = new ArrayList<>();
private final Map<String, List<ContinuousEffect>> lastEffectsListOnLayer = new HashMap<>(); // helps to find out new effect timestamps
// note all effect/abilities that were only added temporary
private final Map<ContinuousEffect, Set<Ability>> temporaryEffects = new HashMap<>();
@ -178,6 +178,16 @@ public class ContinuousEffects implements Serializable {
}
public synchronized List<ContinuousEffect> getLayeredEffects(Game game) {
return getLayeredEffects(game, "main");
}
/**
* Return effects list ordered by timestamps (timestamps are automaticity generates from new/old lists on same layer)
*
* @param timestampGroupName workaround to fix broken timestamps on effect's add/remove between different layers
* @return effects list ordered by timestamp
*/
public synchronized List<ContinuousEffect> getLayeredEffects(Game game, String timestampGroupName) {
List<ContinuousEffect> layerEffects = new ArrayList<>();
for (ContinuousEffect effect : layeredEffects) {
switch (effect.getDuration()) {
@ -202,9 +212,14 @@ public class ContinuousEffects implements Serializable {
}
}
updateTimestamps(layerEffects);
updateTimestamps(timestampGroupName, layerEffects);
layerEffects.sort(Comparator.comparingLong(ContinuousEffect::getOrder));
/* debug effects apply order:
if (game.getStep() != null) System.out.println("layr - " + game.getTurnNum() + "." + game.getStep().getType() + ": layers " + layerEffects.size()
+ " - " + layerEffects.stream().map(l -> l.getClass().getSimpleName()).collect(Collectors.joining(", "))
+ " - " + callName);
//*/
Collections.sort(layerEffects, Comparator.comparingLong(ContinuousEffect::getOrder));
return layerEffects;
}
@ -215,17 +230,23 @@ public class ContinuousEffects implements Serializable {
* Ability.#isInUseableZone(Game, boolean) method in
* #getLayeredEffects(Game).
*
* It must be called with different timestamp group name (otherwise sort order will be changed for add/remove effects, see Urborg and Bloodmoon test)
*
* @param layerEffects
*/
private synchronized void updateTimestamps(List<ContinuousEffect> layerEffects) {
private synchronized void updateTimestamps(String timestampGroupName, List<ContinuousEffect> layerEffects) {
if (!lastEffectsListOnLayer.containsKey(timestampGroupName)) {
lastEffectsListOnLayer.put(timestampGroupName, new ArrayList<>());
}
List<ContinuousEffect> prevs = lastEffectsListOnLayer.get(timestampGroupName);
for (ContinuousEffect continuousEffect : layerEffects) {
// check if it's new, then set order
if (!previous.contains(continuousEffect)) {
if (!prevs.contains(continuousEffect)) {
setOrder(continuousEffect);
}
}
previous.clear();
previous.addAll(layerEffects);
prevs.clear();
prevs.addAll(layerEffects);
}
public void setOrder(ContinuousEffect effect) {
@ -425,12 +446,12 @@ public class ContinuousEffects implements Serializable {
return false;
}
boolean exists = true;
if (!object.getAbilities().contains(ability)) {
if (!object.hasAbility(ability, game)) {
exists = false;
if (object instanceof PermanentCard) {
PermanentCard permanent = (PermanentCard) object;
if (permanent.isTransformable() && event.getType() == GameEvent.EventType.TRANSFORMED) {
exists = permanent.getCard().getAbilities().contains(ability);
exists = permanent.getCard().hasAbility(ability, game);
}
}
} else if (object instanceof PermanentCard) {
@ -903,7 +924,7 @@ public class ContinuousEffects implements Serializable {
//20091005 - 613
public synchronized void apply(Game game) {
removeInactiveEffects(game);
List<ContinuousEffect> activeLayerEffects = getLayeredEffects(game);
List<ContinuousEffect> activeLayerEffects = getLayeredEffects(game); // main call
List<ContinuousEffect> layer = filterLayeredEffects(activeLayerEffects, Layer.CopyEffects_1);
for (ContinuousEffect effect : layer) {
@ -914,7 +935,7 @@ public class ContinuousEffects implements Serializable {
}
//Reload layerEffect if copy effects were applied
if (!layer.isEmpty()) {
activeLayerEffects = getLayeredEffects(game);
activeLayerEffects = getLayeredEffects(game, "layer_1");
}
layer = filterLayeredEffects(activeLayerEffects, Layer.ControlChangingEffects_2);
@ -936,16 +957,16 @@ public class ContinuousEffects implements Serializable {
game.getBattlefield().resetPermanentsControl();
}
applyLayer(activeLayerEffects, Layer.TextChangingEffects_3, game);
applyLayer(activeLayerEffects, Layer.TypeChangingEffects_4, game);
applyLayer(activeLayerEffects, Layer.ColorChangingEffects_5, game);
applyLayer(activeLayerEffects, Layer.TextChangingEffects_3, game, "layer_3");
applyLayer(activeLayerEffects, Layer.TypeChangingEffects_4, game, "layer_4");
applyLayer(activeLayerEffects, Layer.ColorChangingEffects_5, game, "layer_5");
Map<ContinuousEffect, List<Ability>> appliedEffectAbilities = new HashMap<>();
boolean done = false;
Map<ContinuousEffect, Set<UUID>> waitingEffects = new LinkedHashMap<>();
Set<UUID> appliedEffects = new HashSet<>();
applyCounters.apply(Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, null, game);
activeLayerEffects = getLayeredEffects(game);
activeLayerEffects = getLayeredEffects(game, "layer_6");
while (!done) { // loop needed if a added effect adds again an effect (e.g. Level 5- of Joraga Treespeaker)
done = true;
@ -1000,7 +1021,7 @@ public class ContinuousEffects implements Serializable {
effect.apply(Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, ability, game);
done = false;
// list must be updated after each applied effect (eg. if "Turn to Frog" removes abilities)
activeLayerEffects = getLayeredEffects(game);
activeLayerEffects = getLayeredEffects(game, "apply");
}
appliedEffects.add(effect.getId());
@ -1027,7 +1048,7 @@ public class ContinuousEffects implements Serializable {
entry.getKey().apply(Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, ability, game);
done = false;
// list must be updated after each applied effect (eg. if "Turn to Frog" removes abilities)
activeLayerEffects = getLayeredEffects(game);
activeLayerEffects = getLayeredEffects(game, "apply");
}
appliedEffects.add(entry.getKey().getId());
iterator.remove();
@ -1083,10 +1104,10 @@ public class ContinuousEffects implements Serializable {
private boolean abilityActive(Ability ability, Game game) {
MageObject object = game.getObject(ability.getSourceId());
return object != null && object.hasAbility(ability.getId(), game);
return object != null && object.hasAbility(ability, game);
}
private void applyLayer(List<ContinuousEffect> activeLayerEffects, Layer currentLayer, Game game) {
private void applyLayer(List<ContinuousEffect> activeLayerEffects, Layer currentLayer, Game game, String timestampGroupName) {
List<ContinuousEffect> layer = filterLayeredEffects(activeLayerEffects, currentLayer);
// layer is a list of all effects at the current layer
if (!layer.isEmpty()) {
@ -1109,7 +1130,7 @@ public class ContinuousEffects implements Serializable {
applyContinuousEffect(effect, currentLayer, game);
// add it to the applied effects list
appliedEffects.add(effect.getId());
layer = getLayeredEffects(game);
layer = getLayeredEffects(game, timestampGroupName);
// check waiting effects to see if it has anything to check
if (!waitingEffects.isEmpty()) {
@ -1120,7 +1141,7 @@ public class ContinuousEffects implements Serializable {
applyContinuousEffect(entry.getKey(), currentLayer, game);
// add it to the applied effects list
appliedEffects.add(entry.getKey().getId());
layer = getLayeredEffects(game);
layer = getLayeredEffects(game, timestampGroupName);
}
}
}
@ -1131,7 +1152,7 @@ public class ContinuousEffects implements Serializable {
applyContinuousEffect(entry.getKey(), currentLayer, game);
// add it to the applied effects list
appliedEffects.add(entry.getKey().getId());
layer = getLayeredEffects(game);
layer = getLayeredEffects(game, timestampGroupName);
}
}
}
@ -1154,7 +1175,7 @@ public class ContinuousEffects implements Serializable {
if (!(effect instanceof BecomesFaceDownCreatureEffect)
&& (effect != null && !effect.getDuration().equals(Duration.Custom))) { // Custom effects do not depend on the creating permanent
if (card != null) {
return card.getAbilities(game).contains(ability);
return card.hasAbility(ability, game);
}
}

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

@ -115,11 +115,11 @@ public class CopyEffect extends ContinuousEffectImpl {
permanent.removeAllAbilities(source.getSourceId(), game);
if (copyFromObject instanceof Permanent) {
for (Ability ability : ((Permanent) copyFromObject).getAbilities(game)) {
permanent.addAbility(ability, getSourceId(), game, false); // no new Id so consumed replacement effects are known while new continuousEffects.apply happen.
permanent.addAbility(ability, getSourceId(), game);
}
} else {
for (Ability ability : copyFromObject.getAbilities()) {
permanent.addAbility(ability, getSourceId(), game, false); // no new Id so consumed replacement effects are known while new continuousEffects.apply happen.
permanent.addAbility(ability, getSourceId(), game);
}
}

View file

@ -41,7 +41,7 @@ public class CopyTokenEffect extends ContinuousEffectImpl {
}
permanent.getAbilities().clear();
for (Ability ability : token.getAbilities()) {
permanent.addAbility(ability, game);
permanent.addAbility(ability, source.getSourceId(), game);
}
permanent.getPower().setValue(token.getPower().getValue());
permanent.getToughness().setValue(token.getToughness().getValue());

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

@ -1,11 +1,5 @@
package mage.abilities.effects.common.continuous;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import mage.MageObjectReference;
import mage.ObjectColor;
import mage.abilities.Ability;
@ -13,23 +7,20 @@ import mage.abilities.common.TurnFaceUpAbility;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.abilities.keyword.MorphAbility;
import mage.cards.Card;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.Layer;
import mage.constants.Outcome;
import mage.constants.SubLayer;
import mage.constants.*;
import mage.filter.FilterPermanent;
import mage.game.Game;
import mage.game.permanent.Permanent;
import java.util.*;
/**
*
* @author LevelX2
*/
public class BecomesFaceDownCreatureAllEffect extends ContinuousEffectImpl implements SourceEffect {
protected Map<UUID,Ability> turnFaceUpAbilityMap = new HashMap<>();
protected Map<UUID, Ability> turnFaceUpAbilityMap = new HashMap<>();
protected FilterPermanent filter;
public BecomesFaceDownCreatureAllEffect(FilterPermanent filter) {
@ -40,7 +31,7 @@ public class BecomesFaceDownCreatureAllEffect extends ContinuousEffectImpl imple
public BecomesFaceDownCreatureAllEffect(final BecomesFaceDownCreatureAllEffect effect) {
super(effect);
for (Map.Entry<UUID,Ability> entry: effect.turnFaceUpAbilityMap.entrySet()) {
for (Map.Entry<UUID, Ability> entry : effect.turnFaceUpAbilityMap.entrySet()) {
this.turnFaceUpAbilityMap.put(entry.getKey(), entry.getValue());
}
this.filter = effect.filter.copy();
@ -54,16 +45,16 @@ public class BecomesFaceDownCreatureAllEffect extends ContinuousEffectImpl imple
@Override
public void init(Ability source, Game game) {
super.init(source, game);
for (Permanent perm: game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source.getSourceId(), game)) {
for (Permanent perm : game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source.getSourceId(), game)) {
if (!perm.isFaceDown(game) && !perm.isTransformable()) {
affectedObjectList.add(new MageObjectReference(perm, game));
perm.setFaceDown(true, game);
// check for Morph
Card card = game.getCard(perm.getId());
if (card != null) {
for (Ability ability: card.getAbilities()) {
for (Ability ability : card.getAbilities()) {
if (ability instanceof MorphAbility) {
this.turnFaceUpAbilityMap.put(card.getId(), new TurnFaceUpAbility(((MorphAbility)ability).getMorphCosts()));
this.turnFaceUpAbilityMap.put(card.getId(), new TurnFaceUpAbility(((MorphAbility) ability).getMorphCosts()));
}
}
}
@ -74,7 +65,7 @@ public class BecomesFaceDownCreatureAllEffect extends ContinuousEffectImpl imple
@Override
public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) {
boolean targetExists = false;
for (MageObjectReference mor: affectedObjectList) {
for (MageObjectReference mor : affectedObjectList) {
Permanent permanent = mor.getPermanent(game);
if (permanent != null && permanent.isFaceDown(game)) {
targetExists = true;
@ -92,27 +83,35 @@ public class BecomesFaceDownCreatureAllEffect extends ContinuousEffectImpl imple
break;
case AbilityAddingRemovingEffects_6:
Card card = game.getCard(permanent.getId()); //
List<Ability> abilities = new ArrayList<>();
List<Ability> abilitiesToRemove = new ArrayList<>();
for (Ability ability : permanent.getAbilities()) {
// keep gained abilities from other sources, removes only own (card text)
if (card != null && !card.getAbilities().contains(ability)) {
// gained abilities from other sources won't be removed
continue;
}
// TODO: Add flag "works also face down" to ability and use it to control ability removement instead of instanceof check
// 701.33c
// If a card with morph is manifested, its controller may turn that card face up using
// either the procedure described in rule 702.36e to turn a face-down permanent with morph face up
// or the procedure described above to turn a manifested permanent face up.
//
// so keep all tune face up abilities and other face down compatible
if (ability.getWorksFaceDown()) {
ability.setRuleVisible(false);
continue;
}
if (!ability.getRuleVisible() && !ability.getEffects().isEmpty()) {
if (ability.getEffects().get(0) instanceof BecomesFaceDownCreatureAllEffect) {
continue;
}
}
abilities.add(ability);
abilitiesToRemove.add(ability);
}
permanent.getAbilities().removeAll(abilities);
permanent.removeAbilities(abilitiesToRemove, source.getSourceId(), game);
if (turnFaceUpAbilityMap.containsKey(permanent.getId())) {
permanent.addAbility(turnFaceUpAbilityMap.get(permanent.getId()), game);
permanent.addAbility(turnFaceUpAbilityMap.get(permanent.getId()), source.getSourceId(), game);
}
break;
case PTChangingEffects_7:

View file

@ -1,8 +1,5 @@
package mage.abilities.effects.common.continuous;
import java.util.ArrayList;
import java.util.List;
import mage.MageObjectReference;
import mage.ObjectColor;
import mage.abilities.Ability;
@ -12,14 +9,13 @@ import mage.abilities.costs.Costs;
import mage.abilities.costs.CostsImpl;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.cards.Card;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.Layer;
import mage.constants.Outcome;
import mage.constants.SubLayer;
import mage.constants.*;
import mage.game.Game;
import mage.game.permanent.Permanent;
import java.util.ArrayList;
import java.util.List;
/**
* This effect lets the card be a 2/2 face-down creature, with no text, no name,
* no subtypes, and no mana cost, if it's face down on the battlefield. And it
@ -149,21 +145,31 @@ public class BecomesFaceDownCreatureEffect extends ContinuousEffectImpl implemen
Card card = game.getCard(permanent.getId()); //
List<Ability> abilitiesToRemove = new ArrayList<>();
for (Ability ability : permanent.getAbilities()) {
// keep gained abilities from other sources, removes only own (card text)
if (card != null && !card.getAbilities().contains(ability)) {
// gained abilities from other sources won't be removed
continue;
}
// 701.33c
// If a card with morph is manifested, its controller may turn that card face up using
// either the procedure described in rule 702.36e to turn a face-down permanent with morph face up
// or the procedure described above to turn a manifested permanent face up.
//
// so keep all tune face up abilities and other face down compatible
if (ability.getWorksFaceDown()) {
ability.setRuleVisible(false);
continue;
} else if (!ability.getRuleVisible() && !ability.getEffects().isEmpty()) {
}
if (!ability.getRuleVisible() && !ability.getEffects().isEmpty()) {
if (ability.getEffects().get(0) instanceof BecomesFaceDownCreatureEffect) {
continue;
}
}
abilitiesToRemove.add(ability);
}
permanent.getAbilities().removeAll(abilitiesToRemove);
permanent.removeAbilities(abilitiesToRemove, source.getSourceId(), game);
if (turnFaceUpAbility != null) {
permanent.addAbility(turnFaceUpAbility, source.getSourceId(), game);
}

View file

@ -45,9 +45,7 @@ public class CreaturesCantGetOrHaveAbilityEffect extends ContinuousEffectImpl {
if (controller != null) {
for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source.getSourceId(), game)) {
if (permanent != null) {
while (permanent.getAbilities().remove(ability)) {
// repeat as long as ability can be removed
}
permanent.removeAbility(ability, source.getSourceId(), game);
}
}
return true;

View file

@ -87,7 +87,7 @@ public class GainAbilityAllEffect extends ContinuousEffectImpl {
for (Iterator<MageObjectReference> it = affectedObjectList.iterator(); it.hasNext(); ) { // filter may not be used again, because object can have changed filter relevant attributes but still geets boost
Permanent permanent = it.next().getPermanentOrLKIBattlefield(game); //LKI is neccessary for "dies triggered abilities" to work given to permanets (e.g. Showstopper)
if (permanent != null) {
permanent.addAbility(ability, source.getSourceId(), game, false);
permanent.addAbility(ability, source.getSourceId(), game);
} else {
it.remove(); // no longer on the battlefield, remove reference to object
if (affectedObjectList.isEmpty()) {
@ -99,7 +99,7 @@ public class GainAbilityAllEffect extends ContinuousEffectImpl {
setRuntimeData(source, game);
for (Permanent perm : game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source.getSourceId(), game)) {
if (!(excludeSource && perm.getId().equals(source.getSourceId())) && selectedByRuntimeData(perm, source, game)) {
perm.addAbility(ability, source.getSourceId(), game, false);
perm.addAbility(ability, source.getSourceId(), game);
}
}
// still as long as the prev. permanent is known to the LKI (e.g. Mikaeus, the Unhallowed) so gained dies triggered ability will trigger
@ -109,7 +109,7 @@ public class GainAbilityAllEffect extends ContinuousEffectImpl {
Permanent perm = (Permanent) mageObject;
if (!(excludeSource && perm.getId().equals(source.getSourceId())) && selectedByRuntimeData(perm, source, game)) {
if (filter.match(perm, source.getSourceId(), source.getControllerId(), game)) {
perm.addAbility(ability, source.getSourceId(), game, false);
perm.addAbility(ability, source.getSourceId(), game);
}
}
}

View file

@ -88,7 +88,7 @@ public class GainAbilityAttachedEffect extends ContinuousEffectImpl {
}
}
if (permanent != null) {
permanent.addAbility(ability, source.getSourceId(), game, false);
permanent.addAbility(ability, source.getSourceId(), game);
}
return true;
}

View file

@ -87,7 +87,7 @@ public class GainAbilityControlledEffect extends ContinuousEffectImpl {
Permanent perm = it.next().getPermanentOrLKIBattlefield(game); //LKI is neccessary for "dies triggered abilities" to work given to permanets (e.g. Showstopper)
if (perm != null) {
for (Ability abilityToAdd : ability) {
perm.addAbility(abilityToAdd, source.getSourceId(), game, false);
perm.addAbility(abilityToAdd, source.getSourceId(), game);
}
} else {
it.remove();
@ -100,7 +100,7 @@ public class GainAbilityControlledEffect extends ContinuousEffectImpl {
for (Permanent perm : game.getBattlefield().getAllActivePermanents(filter, source.getControllerId(), game)) {
if (!(excludeSource && perm.getId().equals(source.getSourceId()))) {
for (Ability abilityToAdd : ability) {
perm.addAbility(abilityToAdd, source.getSourceId(), game, false);
perm.addAbility(abilityToAdd, source.getSourceId(), game);
}
}
}
@ -112,7 +112,7 @@ public class GainAbilityControlledEffect extends ContinuousEffectImpl {
if (!(excludeSource && perm.getId().equals(source.getSourceId()))) {
if (filter.match(perm, source.getSourceId(), source.getControllerId(), game)) {
for (Ability abilityToAdd : ability) {
perm.addAbility(abilityToAdd, source.getSourceId(), game, false);
perm.addAbility(abilityToAdd, source.getSourceId(), game);
}
}
}

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

@ -43,7 +43,7 @@ public class GainAbilityPairedEffect extends ContinuousEffectImpl {
Permanent paired = permanent.getPairedCard().getPermanent(game);
if (paired != null && paired.getPairedCard() != null && paired.getPairedCard().equals(new MageObjectReference(permanent, game))) {
permanent.addAbility(ability, source.getSourceId(), game);
paired.addAbility(ability, source.getSourceId(), game, false);
paired.addAbility(ability, source.getSourceId(), game);
return true;
} else {

View file

@ -103,7 +103,7 @@ public class GainAbilitySourceEffect extends ContinuousEffectImpl implements Sou
permanent = game.getPermanent(source.getSourceId());
}
if (permanent != null) {
permanent.addAbility(ability, source.getSourceId(), game, false);
permanent.addAbility(ability, source.getSourceId(), game);
return true;
}
}

View file

@ -112,7 +112,7 @@ public class GainAbilityTargetEffect extends ContinuousEffectImpl {
for (UUID permanentId : targetPointer.getTargets(game, source)) {
Permanent permanent = game.getPermanentOrLKIBattlefield(permanentId);
if (permanent != null) {
permanent.addAbility(ability, source.getSourceId(), game, false);
permanent.addAbility(ability, source.getSourceId(), game);
affectedTargets++;
}
}

View file

@ -38,7 +38,7 @@ public class GainProtectionFromTypeTargetEffect extends GainAbilityTargetEffect
public boolean apply(Game game, Ability source) {
Permanent creature = game.getPermanent(source.getFirstTarget());
if (creature != null) {
creature.addAbility(ability, game);
creature.addAbility(ability, source.getSourceId(), game);
return true;
}
return false;

View file

@ -85,9 +85,7 @@ public class LoseAbilityAllEffect extends ContinuousEffectImpl {
for (Iterator<MageObjectReference> it = affectedObjectList.iterator(); it.hasNext();) { // filter may not be used again, because object can have changed filter relevant attributes but still geets boost
Permanent perm = it.next().getPermanentOrLKIBattlefield(game); //LKI is neccessary for "dies triggered abilities" to work given to permanets (e.g. Showstopper)
if (perm != null) {
for (Ability ability : ability) {
perm.getAbilities().removeIf(entry -> entry.getId().equals(ability.getId()));
}
perm.removeAbilities(ability, source.getSourceId(), game);
} else {
it.remove();
if (affectedObjectList.isEmpty()) {
@ -99,9 +97,7 @@ public class LoseAbilityAllEffect extends ContinuousEffectImpl {
for (Permanent perm : game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source.getSourceId(), game)) {
if (!(excludeSource && perm.getId().equals(source.getSourceId()))) {
System.out.println(game.getTurn() + ", " + game.getPhase() + ": " + "remove from size " + perm.getAbilities().size());
for (Ability ability : ability) {
perm.getAbilities().removeIf(entry -> entry.getId().equals(ability.getId()));
}
perm.removeAbilities(ability, source.getSourceId(), game);
}
}
// still as long as the prev. permanent is known to the LKI (e.g. Mikaeus, the Unhallowed) so gained dies triggered ability will trigger
@ -111,9 +107,7 @@ public class LoseAbilityAllEffect extends ContinuousEffectImpl {
Permanent perm = (Permanent) mageObject;
if (!(excludeSource && perm.getId().equals(source.getSourceId()))) {
if (filter.match(perm, source.getSourceId(), source.getControllerId(), game)) {
for (Ability ability : ability) {
perm.getAbilities().removeIf(entry -> entry.getId().equals(ability.getId()));
}
perm.removeAbilities(ability, source.getSourceId(), game);
}
}
}

View file

@ -43,12 +43,7 @@ public class LoseAbilityAttachedEffect extends ContinuousEffectImpl {
if (equipment != null && equipment.getAttachedTo() != null) {
Permanent creature = game.getPermanent(equipment.getAttachedTo());
if (creature != null) {
while (creature.getAbilities().contains(ability)) {
if (!creature.getAbilities().remove(ability)) {
// Something went wrong - ability has an other id?
logger.warn("ability" + ability.getRule() + "couldn't be removed.");
}
}
creature.removeAbility(ability, source.getSourceId(), game);
}
}
return true;

View file

@ -63,13 +63,9 @@ public class LoseAbilityOrAnotherAbilityTargetEffect extends LoseAbilityTargetEf
if (player.choose(outcome, chooseAbility, game)) {
String chosenAbility = chooseAbility.getChoice();
if (chosenAbility.equals(ability.getRule())) {
while (permanent.getAbilities().contains(ability)) {
permanent.getAbilities().remove(ability);
}
permanent.removeAbility(ability, source.getSourceId(), game);
} else if (chosenAbility.equals(ability2.getRule())) {
while (permanent.getAbilities().contains(ability2)) {
permanent.getAbilities().remove(ability2);
}
permanent.removeAbility(ability2, source.getSourceId(), game);
}
} else {
return false;

View file

@ -56,10 +56,7 @@ public class LoseAbilitySourceEffect extends ContinuousEffectImpl {
public boolean apply(Game game, Ability source) {
Permanent permanent = game.getPermanent(source.getSourceId());
if (permanent != null) {
// 112.10
while (permanent.getAbilities().remove(ability)) {
}
permanent.removeAbility(ability, source.getSourceId(), game);
}
return true;
}

View file

@ -45,18 +45,7 @@ public class LoseAbilityTargetEffect extends ContinuousEffectImpl {
public boolean apply(Game game, Ability source) {
Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source));
if (permanent != null) {
if (ability instanceof MageSingleton) {
while (permanent.getAbilities().contains(ability)) {
permanent.getAbilities().remove(ability);
}
} else {
for (Iterator<Ability> iter = permanent.getAbilities().iterator(); iter.hasNext();) {
Ability ab = iter.next();
if (ab.getClass().equals(ability.getClass())) {
iter.remove();
}
}
}
permanent.removeAbility(ability, source.getSourceId(), game);
}
return true;
}

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

@ -38,7 +38,7 @@ public class TransformAbility extends SimpleStaticAbility {
return "";
}
public static void transform(Permanent permanent, Card sourceCard, Game game) {
public static void transform(Permanent permanent, Card sourceCard, Game game, Ability source) {
if (sourceCard == null) {
return;
@ -63,7 +63,7 @@ public class TransformAbility extends SimpleStaticAbility {
permanent.setExpansionSetCode(sourceCard.getExpansionSetCode());
permanent.getAbilities().clear();
for (Ability ability : sourceCard.getAbilities()) {
permanent.addAbility(ability, game);
permanent.addAbility(ability, source == null ? null : source.getSourceId(), game);
}
permanent.getPower().modifyBaseValue(sourceCard.getPower().getValue());
permanent.getToughness().modifyBaseValue(sourceCard.getToughness().getValue());
@ -105,7 +105,7 @@ class TransformEffect extends ContinuousEffectImpl {
return false;
}
TransformAbility.transform(permanent, card, game);
TransformAbility.transform(permanent, card, game, source);
return true;

View file

@ -26,6 +26,10 @@ public interface Card extends MageObject {
void setOwnerId(UUID ownerId);
/**
* For cards: return all basic and dynamic abilities
* For permanents: return all basic and dynamic abilities
*/
Abilities<Ability> getAbilities(Game game);
void setSpellAbility(SpellAbility ability);

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

@ -1647,7 +1647,7 @@ public abstract class GameImpl implements Game, Serializable {
}
newBluePrint.assignNewId();
if (copyFromPermanent.isTransformed()) {
TransformAbility.transform(newBluePrint, newBluePrint.getSecondCardFace(), this);
TransformAbility.transform(newBluePrint, newBluePrint.getSecondCardFace(), this, source);
}
}
if (applier != null) {

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

@ -327,16 +327,16 @@ public class Battlefield implements Serializable {
}
}
public List<Permanent> getPhasedIn(UUID controllerId) {
public List<Permanent> getPhasedIn(Game game, UUID controllerId) {
return field.values()
.stream()
.filter(perm -> perm.getAbilities().containsKey(PhasingAbility.getInstance().getId())
.filter(perm -> perm.hasAbility(PhasingAbility.getInstance(), game)
&& perm.isPhasedIn()
&& perm.isControlledBy(controllerId))
.collect(Collectors.toList());
}
public List<Permanent> getPhasedOut(UUID controllerId) {
public List<Permanent> getPhasedOut(Game game, UUID controllerId) {
return field.values()
.stream()
.filter(perm -> !perm.isPhasedIn() && perm.isControlledBy(controllerId))

View file

@ -154,15 +154,14 @@ public interface Permanent extends Card, Controllable {
String getValue(GameState state);
@Deprecated
void addAbility(Ability ability, Game game);
void addAbility(Ability ability, UUID sourceId, Game game);
void addAbility(Ability ability, UUID sourceId, Game game, boolean createNewId);
void removeAllAbilities(UUID sourceId, Game game);
void removeAbility(Ability abilityToRemove, UUID sourceId, Game game);
void removeAbilities(List<Ability> abilitiesToRemove, UUID sourceId, Game game);
void addLoyaltyUsed();
boolean canLoyaltyBeUsed(Game game);

View file

@ -53,7 +53,7 @@ public class PermanentCard extends PermanentImpl {
if (game.getState().getValue(TransformAbility.VALUE_KEY_ENTER_TRANSFORMED + getId()) != null) {
game.getState().setValue(TransformAbility.VALUE_KEY_ENTER_TRANSFORMED + getId(), null);
setTransformed(true);
TransformAbility.transform(this, getSecondCardFace(), game);
TransformAbility.transform(this, getSecondCardFace(), game, null);
}
}
}

View file

@ -351,62 +351,61 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
@Override
public Abilities<Ability> getAbilities() {
return abilities;
return super.getAbilities();
}
@Override
public Abilities<Ability> getAbilities(Game game) {
return abilities;
}
/**
* @param ability
* @param game
*/
@Override
public void addAbility(Ability ability, Game game) {
if (!abilities.containsKey(ability.getId())) {
Ability copyAbility = ability.copy();
copyAbility.setControllerId(controllerId);
copyAbility.setSourceId(objectId);
if (game != null) {
game.getState().addAbility(copyAbility, this);
}
abilities.add(copyAbility);
}
return super.getAbilities(game);
}
@Override
public void addAbility(Ability ability, UUID sourceId, Game game) {
addAbility(ability, sourceId, game, true);
}
@Override
public void addAbility(Ability ability, UUID sourceId, Game game, boolean createNewId) {
// singleton abilities -- only one instance
// other abilities -- any amount of instances
if (!abilities.containsKey(ability.getId())) {
Ability copyAbility = ability.copy();
if (createNewId) {
copyAbility.newId(); // needed so that source can get an ability multiple times (e.g. Raging Ravine)
}
copyAbility.newId(); // needed so that source can get an ability multiple times (e.g. Raging Ravine)
copyAbility.setControllerId(controllerId);
copyAbility.setSourceId(objectId);
// triggered abilities must be added to the state().triggers
// still as long as the prev. permanent is known to the LKI (e.g. Showstopper) so gained dies triggered ability will trigger
game.getState().addAbility(copyAbility, sourceId, this);
abilities.add(copyAbility);
} else if (!createNewId) {
// triggered abilities must be added to the state().triggerdAbilities
// still as long as the prev. permanent is known to the LKI (e.g. Showstopper) so gained dies triggered ability will trigger
if (!game.getBattlefield().containsPermanent(this.getId())) {
Ability copyAbility = ability.copy();
copyAbility.setControllerId(controllerId);
copyAbility.setSourceId(objectId);
game.getState().addAbility(copyAbility, sourceId, this);
}
}
}
@Override
public void removeAllAbilities(UUID sourceId, Game game) {
getAbilities().clear();
// TODO: what about triggered abilities? See addAbility above -- triggers adds to GameState
abilities.clear();
}
@Override
public void removeAbility(Ability abilityToRemove, UUID sourceId, Game game) {
if (abilityToRemove == null) {
return;
}
// 112.10b Effects that remove an ability remove all instances of it.
List<Ability> toRemove = new ArrayList<>();
abilities.forEach(a -> {
if (a.isSameInstance(abilityToRemove)) {
toRemove.add(a);
}
});
// TODO: what about triggered abilities? See addAbility above -- triggers adds to GameState
toRemove.forEach(r -> abilities.remove(r));
}
@Override
public void removeAbilities(List<Ability> abilitiesToRemove, UUID sourceId, Game game){
if (abilitiesToRemove == null) {
return;
}
abilitiesToRemove.forEach(a -> removeAbility(a, sourceId, game));
}
@Override
@ -728,7 +727,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
game.fireEvent(new GameEvent(EventType.GAINED_CONTROL, objectId, objectId, controllerId));
return true;
} else if (isCopy()) {// Because the previous copied abilities can be from another controller chnage controller in any case for abilities
} else if (isCopy()) {// Because the previous copied abilities can be from another controller - change controller in any case for abilities
this.getAbilities(game).setControllerId(controllerId);
game.getContinuousEffects().setController(objectId, controllerId);
}

View file

@ -59,7 +59,7 @@ public class PermanentToken extends PermanentImpl {
} else {
// first time -> create ContinuousEffects only once
for (Ability ability : token.getAbilities()) {
this.addAbility(ability, game);
this.addAbility(ability, null, game);
}
}
this.abilities.setControllerId(this.controllerId);

Some files were not shown because too many files have changed in this diff Show more