diff --git a/Mage.Sets/src/mage/cards/k/KarakykGuardian.java b/Mage.Sets/src/mage/cards/k/KarakykGuardian.java index 6fd569725a6..01151692af5 100644 --- a/Mage.Sets/src/mage/cards/k/KarakykGuardian.java +++ b/Mage.Sets/src/mage/cards/k/KarakykGuardian.java @@ -1,10 +1,8 @@ package mage.cards.k; import mage.MageInt; -import mage.MageObjectReference; -import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.condition.Condition; +import mage.abilities.condition.common.SourceHasntDealtDamageThisGameCondition; import mage.abilities.decorator.ConditionalContinuousEffect; import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; import mage.abilities.keyword.FlyingAbility; @@ -15,14 +13,8 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.WatcherScope; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.permanent.Permanent; -import mage.watchers.Watcher; +import mage.watchers.common.DealtDamageThisGameWatcher; -import java.util.HashSet; -import java.util.Set; import java.util.UUID; /** @@ -49,8 +41,9 @@ public final class KarakykGuardian extends CardImpl { // This creature has hexproof if it hasn't dealt damage yet. this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( new GainAbilitySourceEffect(HexproofAbility.getInstance()), - KarakykGuardianCondition.instance, "{this} has hexproof if it hasn't dealt damage yet" - )), new KarakykGuardianWatcher()); + SourceHasntDealtDamageThisGameCondition.instance, + "{this} has hexproof if it hasn't dealt damage yet" + )).addHint(SourceHasntDealtDamageThisGameCondition.getHint()), new DealtDamageThisGameWatcher()); } private KarakykGuardian(final KarakykGuardian card) { @@ -62,49 +55,3 @@ public final class KarakykGuardian extends CardImpl { return new KarakykGuardian(this); } } - -enum KarakykGuardianCondition implements Condition { - - instance; - - @Override - public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(source.getSourceId()); - KarakykGuardianWatcher watcher = game.getState().getWatcher(KarakykGuardianWatcher.class); - return permanent != null && !watcher.getDamagers().contains(new MageObjectReference(permanent, game)); - } - - @Override - public String toString() { - return "{this} hasn't dealt damage yet"; - } - -} - -class KarakykGuardianWatcher extends Watcher { - - private final Set damagers = new HashSet<>(); - - public KarakykGuardianWatcher() { - super(WatcherScope.GAME); - } - - @Override - public void watch(GameEvent event, Game game) { - switch (event.getType()) { - case DAMAGED_PERMANENT: - case DAMAGED_PLAYER: - break; - default: - return; - } - Permanent permanent = game.getPermanent(event.getSourceId()); - if (permanent != null) { - damagers.add(new MageObjectReference(permanent, game)); - } - } - - public Set getDamagers() { - return damagers; - } -} diff --git a/Mage.Sets/src/mage/cards/p/PalladiaMorsTheRuiner.java b/Mage.Sets/src/mage/cards/p/PalladiaMorsTheRuiner.java index 52a3f52255f..5a650a1ec43 100644 --- a/Mage.Sets/src/mage/cards/p/PalladiaMorsTheRuiner.java +++ b/Mage.Sets/src/mage/cards/p/PalladiaMorsTheRuiner.java @@ -1,13 +1,8 @@ package mage.cards.p; -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; import mage.MageInt; -import mage.MageObjectReference; -import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.condition.Condition; +import mage.abilities.condition.common.SourceHasntDealtDamageThisGameCondition; import mage.abilities.decorator.ConditionalContinuousEffect; import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; import mage.abilities.keyword.FlyingAbility; @@ -19,15 +14,11 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.SuperType; -import mage.constants.WatcherScope; -import mage.constants.Zone; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.permanent.Permanent; -import mage.watchers.Watcher; +import mage.watchers.common.DealtDamageThisGameWatcher; + +import java.util.UUID; /** - * * @author TheElk801 */ public final class PalladiaMorsTheRuiner extends CardImpl { @@ -51,13 +42,11 @@ public final class PalladiaMorsTheRuiner extends CardImpl { this.addAbility(TrampleAbility.getInstance()); // Palladia-Mors, the Ruiner has hexproof if it hasn't dealt damage yet. - this.addAbility(new SimpleStaticAbility( - new ConditionalContinuousEffect( - new GainAbilitySourceEffect(HexproofAbility.getInstance()), - PalladiaMorsTheRuinerCondition.instance, - "{this} has hexproof if it hasn't dealt damage yet" - ) - ), new PalladiaMorsTheRuinerWatcher()); + this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( + new GainAbilitySourceEffect(HexproofAbility.getInstance()), + SourceHasntDealtDamageThisGameCondition.instance, + "{this} has hexproof if it hasn't dealt damage yet" + )).addHint(SourceHasntDealtDamageThisGameCondition.getHint()), new DealtDamageThisGameWatcher()); } private PalladiaMorsTheRuiner(final PalladiaMorsTheRuiner card) { @@ -69,49 +58,3 @@ public final class PalladiaMorsTheRuiner extends CardImpl { return new PalladiaMorsTheRuiner(this); } } - -enum PalladiaMorsTheRuinerCondition implements Condition { - - instance; - - @Override - public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(source.getSourceId()); - PalladiaMorsTheRuinerWatcher watcher = game.getState().getWatcher(PalladiaMorsTheRuinerWatcher.class); - return permanent != null && !watcher.getDamagers().contains(new MageObjectReference(permanent, game)); - } - - @Override - public String toString() { - return "{this} hasn't dealt damage yet"; - } - -} - -class PalladiaMorsTheRuinerWatcher extends Watcher { - - private final Set damagers = new HashSet<>(); - - public PalladiaMorsTheRuinerWatcher() { - super(WatcherScope.GAME); - } - - @Override - public void watch(GameEvent event, Game game) { - switch (event.getType()) { - case DAMAGED_PERMANENT: - case DAMAGED_PLAYER: - break; - default: - return; - } - Permanent permanent = game.getPermanent(event.getSourceId()); - if (permanent != null) { - damagers.add(new MageObjectReference(permanent, game)); - } - } - - public Set getDamagers() { - return damagers; - } -} diff --git a/Mage.Sets/src/mage/cards/r/Ratonhnhaketon.java b/Mage.Sets/src/mage/cards/r/Ratonhnhaketon.java new file mode 100644 index 00000000000..da46c679dc9 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/Ratonhnhaketon.java @@ -0,0 +1,152 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.common.delayed.ReflexiveTriggeredAbility; +import mage.abilities.condition.common.SourceHasntDealtDamageThisGameCondition; +import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.decorator.ConditionalRestrictionEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.combat.CantBeBlockedSourceEffect; +import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; +import mage.abilities.keyword.HexproofAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterCard; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.game.permanent.token.AssassinMenaceToken; +import mage.game.permanent.token.Token; +import mage.players.Player; +import mage.target.common.TargetCardInYourGraveyard; +import mage.util.CardUtil; +import mage.watchers.common.DealtDamageThisGameWatcher; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class Ratonhnhaketon extends CardImpl { + + public Ratonhnhaketon(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{W}{U}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ASSASSIN); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // As long as Ratonhnhaketon hasn't dealt damage yet, it has hexproof and can't be blocked. + Ability ability = new SimpleStaticAbility(new ConditionalContinuousEffect( + new GainAbilitySourceEffect(HexproofAbility.getInstance()), + SourceHasntDealtDamageThisGameCondition.instance, + "as long as {this} hasn't dealt damage yet, it has hexproof" + )); + ability.addEffect(new ConditionalRestrictionEffect( + new CantBeBlockedSourceEffect(), + SourceHasntDealtDamageThisGameCondition.instance, + "and can't be blocked" + )); + this.addAbility(ability.addHint(SourceHasntDealtDamageThisGameCondition.getHint()), new DealtDamageThisGameWatcher()); + + // Whenever Ratonhnhaketon deals combat damage to a player, create a 1/1 black Assassin creature token with menace. When you do, return target Equipment card from your graveyard to the battlefield, then attach it to that token. + this.addAbility(new DealsCombatDamageToAPlayerTriggeredAbility(new RatonhnhaketonTokenEffect())); + } + + private Ratonhnhaketon(final Ratonhnhaketon card) { + super(card); + } + + @Override + public Ratonhnhaketon copy() { + return new Ratonhnhaketon(this); + } +} + +class RatonhnhaketonTokenEffect extends OneShotEffect { + + private static final FilterCard filter = new FilterCard("Equipment card from your graveyard"); + + static { + filter.add(SubType.EQUIPMENT.getPredicate()); + } + + RatonhnhaketonTokenEffect() { + super(Outcome.Benefit); + staticText = "create a 1/1 black Assassin creature token with menace. When you do, return " + + "target Equipment card from your graveyard to the battlefield, then attach it to that token"; + } + + private RatonhnhaketonTokenEffect(final RatonhnhaketonTokenEffect effect) { + super(effect); + } + + @Override + public RatonhnhaketonTokenEffect copy() { + return new RatonhnhaketonTokenEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Token token = new AssassinMenaceToken(); + token.putOntoBattlefield(1, game, source); + for (UUID tokenId : token.getLastAddedTokenIds()) { + Permanent permanent = game.getPermanent(tokenId); + if (permanent == null) { + continue; + } + ReflexiveTriggeredAbility ability = new ReflexiveTriggeredAbility( + new RatonhnhaketonReturnEffect(tokenId), false + ); + ability.addTarget(new TargetCardInYourGraveyard(filter)); + game.fireReflexiveTriggeredAbility(ability, source); + } + return true; + } +} + +class RatonhnhaketonReturnEffect extends OneShotEffect { + + private final UUID tokenId; + + RatonhnhaketonReturnEffect(UUID tokenId) { + super(Outcome.Benefit); + staticText = "return target Equipment card from your graveyard to the battlefield, then attach it to that token"; + this.tokenId = tokenId; + } + + private RatonhnhaketonReturnEffect(final RatonhnhaketonReturnEffect effect) { + super(effect); + this.tokenId = effect.tokenId; + } + + @Override + public RatonhnhaketonReturnEffect copy() { + return new RatonhnhaketonReturnEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + Card card = game.getCard(getTargetPointer().getFirst(game, source)); + if (player == null) { + return false; + } + player.moveCards(card, Zone.BATTLEFIELD, source, game); + Permanent permanent = CardUtil.getPermanentFromCardPutToBattlefield(card, game); + if (permanent == null) { + return false; + } + Optional.ofNullable(tokenId) + .map(game::getPermanent) + .ifPresent(p -> p.addAttachment(permanent.getId(), source, game)); + return true; + } +} diff --git a/Mage.Sets/src/mage/sets/AssassinsCreed.java b/Mage.Sets/src/mage/sets/AssassinsCreed.java index 5925cca943d..56755d60e15 100644 --- a/Mage.Sets/src/mage/sets/AssassinsCreed.java +++ b/Mage.Sets/src/mage/sets/AssassinsCreed.java @@ -237,9 +237,9 @@ public final class AssassinsCreed extends ExpansionSet { cards.add(new SetCardInfo("Poison-Blade Mentor", 288, Rarity.UNCOMMON, mage.cards.p.PoisonBladeMentor.class)); cards.add(new SetCardInfo("Propaganda", 195, Rarity.UNCOMMON, mage.cards.p.Propaganda.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Propaganda", 85, Rarity.UNCOMMON, mage.cards.p.Propaganda.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Ratonhnhake:ton", 150, Rarity.RARE, mage.cards.r.Ratonhnhaketon.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Ratonhnhake:ton", 244, Rarity.RARE, mage.cards.r.Ratonhnhaketon.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Ratonhnhake:ton", 62, Rarity.RARE, mage.cards.r.Ratonhnhakton.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ratonhnhaketon", 150, Rarity.RARE, mage.cards.r.Ratonhnhaketon.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ratonhnhaketon", 244, Rarity.RARE, mage.cards.r.Ratonhnhaketon.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ratonhnhaketon", 62, Rarity.RARE, mage.cards.r.Ratonhnhaketon.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Raven Clan War-Axe", 297, Rarity.RARE, mage.cards.r.RavenClanWarAxe.class)); cards.add(new SetCardInfo("Reconnaissance", 179, Rarity.UNCOMMON, mage.cards.r.Reconnaissance.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Reconnaissance", 82, Rarity.UNCOMMON, mage.cards.r.Reconnaissance.class, NON_FULL_USE_VARIOUS)); diff --git a/Mage/src/main/java/mage/abilities/condition/common/SourceHasntDealtDamageThisGameCondition.java b/Mage/src/main/java/mage/abilities/condition/common/SourceHasntDealtDamageThisGameCondition.java new file mode 100644 index 00000000000..bd34c678443 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/condition/common/SourceHasntDealtDamageThisGameCondition.java @@ -0,0 +1,34 @@ +package mage.abilities.condition.common; + +import mage.abilities.Ability; +import mage.abilities.condition.Condition; +import mage.abilities.hint.ConditionHint; +import mage.abilities.hint.Hint; +import mage.game.Game; +import mage.watchers.common.DealtDamageThisGameWatcher; + +/** + * requires DealtDamageThisGameWatcher + * + * @author TheElk801 + */ +public enum SourceHasntDealtDamageThisGameCondition implements Condition { + instance; + private static final Hint hint = new ConditionHint( + instance, "This creature hasn't dealt damage yet this game" + ); + + public static Hint getHint() { + return hint; + } + + @Override + public boolean apply(Game game, Ability source) { + return DealtDamageThisGameWatcher.checkCreature(game, source); + } + + @Override + public String toString() { + return "{this} hasn't dealt damage yet"; + } +} diff --git a/Mage/src/main/java/mage/watchers/common/DealtDamageThisGameWatcher.java b/Mage/src/main/java/mage/watchers/common/DealtDamageThisGameWatcher.java new file mode 100644 index 00000000000..a537a64c7b5 --- /dev/null +++ b/Mage/src/main/java/mage/watchers/common/DealtDamageThisGameWatcher.java @@ -0,0 +1,52 @@ +package mage.watchers.common; + +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.constants.WatcherScope; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.watchers.Watcher; + +import java.util.HashSet; +import java.util.Set; + +/** + * @author TheElk801 + */ +public class DealtDamageThisGameWatcher extends Watcher { + + private final Set damagers = new HashSet<>(); + + public DealtDamageThisGameWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + switch (event.getType()) { + case BEGINNING_PHASE_PRE: + // keep the stored values from getting too big, especially since it doesn't reset between games + damagers.removeIf(mor -> !mor.zoneCounterIsCurrent(game)); + return; + case DAMAGED_PERMANENT: + case DAMAGED_PLAYER: + break; + default: + return; + } + Permanent permanent = game.getPermanent(event.getSourceId()); + if (permanent != null) { + damagers.add(new MageObjectReference(permanent, game)); + } + } + + public static boolean checkCreature(Game game, Ability source) { + return game + .getState() + .getWatcher(DealtDamageThisGameWatcher.class) + .damagers + .stream() + .noneMatch(mor -> mor.refersTo(source.getSourcePermanentOrLKI(game), game)); + } +}