From c0efada19ac80386f11675968fff69cb233d0b56 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Wed, 13 Jan 2021 19:32:56 -0500 Subject: [PATCH] [KHM] Implemented Toralf, God of Fury / Toralf's Hammer --- .../src/mage/cards/t/ToralfGodOfFury.java | 189 ++++++++++++++++++ Mage.Sets/src/mage/sets/Kaldheim.java | 1 + .../java/mage/game/events/DamagedEvent.java | 12 +- .../mage/game/permanent/PermanentImpl.java | 95 +++++---- 4 files changed, 253 insertions(+), 44 deletions(-) create mode 100644 Mage.Sets/src/mage/cards/t/ToralfGodOfFury.java diff --git a/Mage.Sets/src/mage/cards/t/ToralfGodOfFury.java b/Mage.Sets/src/mage/cards/t/ToralfGodOfFury.java new file mode 100644 index 0000000000..d119553e5c --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/ToralfGodOfFury.java @@ -0,0 +1,189 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.AttachedToMatchesFilterCondition; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.common.UnattachCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.effects.common.TapTargetEffect; +import mage.abilities.effects.common.continuous.BoostEquippedEffect; +import mage.abilities.effects.common.continuous.GainAbilityWithAttachmentEffect; +import mage.abilities.keyword.EquipAbility; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardSetInfo; +import mage.cards.ModalDoubleFacesCard; +import mage.constants.*; +import mage.filter.StaticFilters; +import mage.filter.common.FilterCreaturePlayerOrPlaneswalker; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.MageObjectReferencePredicate; +import mage.game.Game; +import mage.game.events.DamagedEvent; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetAnyTarget; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ToralfGodOfFury extends ModalDoubleFacesCard { + + private static final Condition condition + = new AttachedToMatchesFilterCondition(StaticFilters.FILTER_PERMANENT_LEGENDARY); + + public ToralfGodOfFury(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, + new CardType[]{CardType.CREATURE}, new SubType[]{SubType.GOD}, "{2}{R}{R}", + "", new CardType[]{CardType.ARTIFACT}, new SubType[]{SubType.EQUIPMENT}, "{1}{R}" + ); + + // 1. + // Toralf, God of Fury + // Legendary Creature - God + this.getLeftHalfCard().addSuperType(SuperType.LEGENDARY); + this.getLeftHalfCard().setPT(new MageInt(5), new MageInt(4)); + + // Trample + this.getLeftHalfCard().addAbility(TrampleAbility.getInstance()); + + // Whenever a creature or planeswalker an opponent controls is dealt excess noncombat damage, Toralf, God of Fury deals damage equal to the excess to any target other than that permanent. + this.getLeftHalfCard().addAbility(new ToralfGodOfFuryTriggeredAbility()); + + // 2. + // Toralf's Hammer + // Legendary Artifact - Equipment + this.getRightHalfCard().addSuperType(SuperType.LEGENDARY); + + // Equipped creature has "{1}{R}, {T}, Unattach Toralf's Hammer: It deals 3 damage to any target. Return Toralf's Hammer to its owner's hand." + this.getRightHalfCard().addAbility(new SimpleStaticAbility(new GainAbilityWithAttachmentEffect( + "equipped creature has \"{tap}, Unattach {this}: It deals 3 damage to any target. Return {this} to its owner's hand.\"", + new TapTargetEffect(), new TargetAnyTarget(), new UnattachCost(), new ManaCostsImpl<>("{1]{R}"), new TapSourceCost() + ))); + + // Equipped creature get +3/+0 as long as its legendary. + this.getRightHalfCard().addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( + new BoostEquippedEffect(3, 0), condition, + "equipped creature get +3/+0 as long as it's legendary" + ))); + + // Equip {1}{R} + this.getRightHalfCard().addAbility(new EquipAbility(Outcome.BoostCreature, new ManaCostsImpl<>("{1}{R}"))); + } + + private ToralfGodOfFury(final ToralfGodOfFury card) { + super(card); + } + + @Override + public ToralfGodOfFury copy() { + return new ToralfGodOfFury(this); + } +} + +class ToralfGodOfFuryTriggeredAbility extends TriggeredAbilityImpl { + + ToralfGodOfFuryTriggeredAbility() { + super(Zone.BATTLEFIELD, null); + } + + private ToralfGodOfFuryTriggeredAbility(final ToralfGodOfFuryTriggeredAbility ability) { + super(ability); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + switch (event.getType()) { + case DAMAGED_CREATURE: + case DAMAGED_PLANESWALKER: + return true; + } + return false; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + DamagedEvent dEvent = (DamagedEvent) event; + if (dEvent.getExcess() < 1 + || dEvent.isCombatDamage() + || !game.getOpponents(getControllerId()).contains(game.getControllerId(event.getTargetId()))) { + return false; + } + this.getEffects().clear(); + this.getTargets().clear(); + this.addEffect(new DamageTargetEffect(dEvent.getExcess())); + FilterCreaturePlayerOrPlaneswalker filter = new FilterCreaturePlayerOrPlaneswalker(); + filter.getPermanentFilter().add(Predicates.not(new MageObjectReferencePredicate(new MageObjectReference(event.getTargetId(), game)))); + this.addTarget(new TargetAnyTarget(filter)); + return true; + } + + @Override + public ToralfGodOfFuryTriggeredAbility copy() { + return new ToralfGodOfFuryTriggeredAbility(this); + } + + @Override + public String getRule() { + return "Whenever a creature or planeswalker an opponent controls is dealt excess noncombat damage, " + + "{this} deals damage equal to the excess to any target other than that permanent."; + } +} + +class ToralfsHammerEffect extends OneShotEffect { + + ToralfsHammerEffect() { + super(Outcome.Benefit); + } + + private ToralfsHammerEffect(final ToralfsHammerEffect effect) { + super(effect); + } + + @Override + public ToralfsHammerEffect copy() { + return new ToralfsHammerEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Object object = getValue("attachedPermanent"); + Player player = game.getPlayer(source.getControllerId()); + if (!(object instanceof Permanent) || player == null) { + return false; + } + Permanent permanent = (Permanent) object; + Permanent targetedPermanent = game.getPermanent(source.getFirstTarget()); + if (targetedPermanent == null) { + Player targetedPlayer = game.getPlayer(source.getFirstTarget()); + if (targetedPlayer != null) { + targetedPlayer.damage(3, permanent.getId(), source, game); + } + } else { + targetedPermanent.damage(3, permanent.getId(), source, game); + } + player.moveCards(permanent, Zone.HAND, source, game); + return true; + } + + @Override + public String getText(Mode mode) { + String name = "Toralf's Hammer"; + Object object = getValue("attachedPermanent"); + if (object instanceof Permanent) { + name = ((Permanent) object).getName(); + } + return "It deals 3 damage to any target. Return " + name + " to its owner's hand."; + } +} diff --git a/Mage.Sets/src/mage/sets/Kaldheim.java b/Mage.Sets/src/mage/sets/Kaldheim.java index 07c1ee4cd9..9979e204d2 100644 --- a/Mage.Sets/src/mage/sets/Kaldheim.java +++ b/Mage.Sets/src/mage/sets/Kaldheim.java @@ -169,6 +169,7 @@ public final class Kaldheim extends ExpansionSet { cards.add(new SetCardInfo("The Trickster-God's Heist", 232, Rarity.UNCOMMON, mage.cards.t.TheTricksterGodsHeist.class)); cards.add(new SetCardInfo("The World Tree", 275, Rarity.RARE, mage.cards.t.TheWorldTree.class)); cards.add(new SetCardInfo("Thornmantle Striker", 387, Rarity.UNCOMMON, mage.cards.t.ThornmantleStriker.class)); + cards.add(new SetCardInfo("Toralf, God of Fury", 154, Rarity.MYTHIC, mage.cards.t.ToralfGodOfFury.class)); cards.add(new SetCardInfo("Toski, Bearer of Secrets", 197, Rarity.RARE, mage.cards.t.ToskiBearerOfSecrets.class)); cards.add(new SetCardInfo("Tyvar Kell", 198, Rarity.MYTHIC, mage.cards.t.TyvarKell.class)); cards.add(new SetCardInfo("Undersea Invader", 78, Rarity.COMMON, mage.cards.u.UnderseaInvader.class)); diff --git a/Mage/src/main/java/mage/game/events/DamagedEvent.java b/Mage/src/main/java/mage/game/events/DamagedEvent.java index 6981fc8b92..36098c9610 100644 --- a/Mage/src/main/java/mage/game/events/DamagedEvent.java +++ b/Mage/src/main/java/mage/game/events/DamagedEvent.java @@ -1,20 +1,19 @@ - - package mage.game.events; import java.util.UUID; /** - * * @author BetaSteward_at_googlemail.com */ public abstract class DamagedEvent extends GameEvent { protected boolean combat; + protected int excess; public DamagedEvent(EventType type, UUID targetId, UUID attackerId, UUID playerId, int amount, boolean combat) { super(type, targetId, null, playerId, amount, false); this.combat = combat; + this.excess = 0; this.setSourceId(attackerId); } @@ -26,4 +25,11 @@ public abstract class DamagedEvent extends GameEvent { return flag; } + public void setExcess(int excess) { + this.excess = Math.max(excess, 0); + } + + public int getExcess() { + return excess; + } } diff --git a/Mage/src/main/java/mage/game/permanent/PermanentImpl.java b/Mage/src/main/java/mage/game/permanent/PermanentImpl.java index 87aee9ad46..10f739f436 100644 --- a/Mage/src/main/java/mage/game/permanent/PermanentImpl.java +++ b/Mage/src/main/java/mage/game/permanent/PermanentImpl.java @@ -853,7 +853,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { /** * @param damageAmount - * @param attackerId id of the permanent or player who make damage (source.getSourceId() in most cases) + * @param attackerId id of the permanent or player who make damage (source.getSourceId() in most cases) * @param source * @param game * @param preventable @@ -975,53 +975,64 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { protected int damagePlaneswalker(int damage, UUID attackerId, Ability source, Game game, boolean preventable, boolean combat, boolean markDamage, List appliedEffects) { GameEvent event = new DamagePlaneswalkerEvent(objectId, attackerId, controllerId, damage, preventable, combat); event.setAppliedEffects(appliedEffects); - if (!game.replaceEvent(event)) { - int actualDamage = checkProtectionAbilities(event, attackerId, source, game); - if (actualDamage > 0) { - int countersToRemove = actualDamage; - if (countersToRemove > getCounters(game).getCount(CounterType.LOYALTY)) { - countersToRemove = getCounters(game).getCount(CounterType.LOYALTY); - } - removeCounters(CounterType.LOYALTY.getName(), countersToRemove, source, game); - DamagedEvent damagedEvent = new DamagedPlaneswalkerEvent(objectId, attackerId, controllerId, actualDamage, combat); - game.fireEvent(damagedEvent); - game.getState().addSimultaneousDamage(damagedEvent, game); - return actualDamage; - } + if (game.replaceEvent(event)) { + return 0; } - return 0; + int actualDamage = checkProtectionAbilities(event, attackerId, source, game); + if (actualDamage <= 0) { + return 0; + } + int countersToRemove = Math.min(actualDamage, getCounters(game).getCount(CounterType.LOYALTY)); + removeCounters(CounterType.LOYALTY.getName(), countersToRemove, source, game); + DamagedEvent damagedEvent = new DamagedPlaneswalkerEvent(objectId, attackerId, controllerId, actualDamage, combat); + damagedEvent.setExcess(actualDamage - countersToRemove); + game.fireEvent(damagedEvent); + game.getState().addSimultaneousDamage(damagedEvent, game); + return actualDamage; } protected int damageCreature(int damage, UUID attackerId, Ability source, Game game, boolean preventable, boolean combat, boolean markDamage, List appliedEffects) { GameEvent event = new DamageCreatureEvent(this.getId(), attackerId, this.getControllerId(), damage, preventable, combat); event.setAppliedEffects(appliedEffects); - if (!game.replaceEvent(event)) { - int actualDamage = checkProtectionAbilities(event, attackerId, source, game); - if (actualDamage > 0) { - MageObject attacker = game.getObject(attackerId); - if (attacker != null && (attacker.getAbilities().containsKey(InfectAbility.getInstance().getId()) - || attacker.getAbilities().containsKey(WitherAbility.getInstance().getId()))) { - if (markDamage) { - // mark damage only - markDamage(CounterType.M1M1.createInstance(actualDamage), attacker); - } else { - Ability damageSourceAbility = null; - if (attacker instanceof Permanent) { - damageSourceAbility = ((Permanent) attacker).getSpellAbility(); - } - // deal damage immediately - addCounters(CounterType.M1M1.createInstance(actualDamage), damageSourceAbility, game); - } - } else { - this.damage = CardUtil.addWithOverflowCheck(this.damage, actualDamage); - } - DamagedEvent damagedEvent = new DamagedCreatureEvent(this.getId(), attackerId, this.getControllerId(), actualDamage, combat); - game.fireEvent(damagedEvent); - game.getState().addSimultaneousDamage(damagedEvent, game); - return actualDamage; - } + if (game.replaceEvent(event)) { + return 0; } - return 0; + int actualDamage = checkProtectionAbilities(event, attackerId, source, game); + if (actualDamage <= 0) { + return 0; + } + MageObject attacker = game.getObject(attackerId); + int lethal = 0; + if (game.getState().getActivePowerInsteadOfToughnessForDamageLethalityFilters().stream().anyMatch(f -> f.match(this, game))) { + lethal = power.getValue(); + } else { + lethal = toughness.getValue(); + } + lethal = Math.max(lethal - this.damage, 0); + if (attacker.getAbilities().containsKey(DeathtouchAbility.getInstance().getId())) { + lethal = Math.min(1, lethal); + } + if (attacker != null && (attacker.getAbilities().containsKey(InfectAbility.getInstance().getId()) + || attacker.getAbilities().containsKey(WitherAbility.getInstance().getId()))) { + if (markDamage) { + // mark damage only + markDamage(CounterType.M1M1.createInstance(actualDamage), attacker); + } else { + Ability damageSourceAbility = null; + if (attacker instanceof Permanent) { + damageSourceAbility = ((Permanent) attacker).getSpellAbility(); + } + // deal damage immediately + addCounters(CounterType.M1M1.createInstance(actualDamage), damageSourceAbility, game); + } + } else { + this.damage = CardUtil.addWithOverflowCheck(this.damage, actualDamage); + } + DamagedEvent damagedEvent = new DamagedCreatureEvent(this.getId(), attackerId, this.getControllerId(), actualDamage, combat); + damagedEvent.setExcess(actualDamage - lethal); + game.fireEvent(damagedEvent); + game.getState().addSimultaneousDamage(damagedEvent, game); + return actualDamage; } private int checkProtectionAbilities(GameEvent event, UUID attackerId, Ability source, Game game) { @@ -1211,6 +1222,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { /** * Simple event without source + * * @param eventType * @param game */ @@ -1220,6 +1232,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { /** * Simple event without source + * * @param eventType * @param game * @return