From d3e57b7fb9b31168105e2554b8226d74b78c3c38 Mon Sep 17 00:00:00 2001 From: Max Behling Date: Mon, 24 May 2021 02:42:11 -0500 Subject: [PATCH] Implemented Thrasta, Tempest's Roar (#7835) * Implemented ThrastaTempestsRoar --- .../src/mage/cards/t/ThrastaTempestsRoar.java | 87 +++++++++++++++++++ Mage.Sets/src/mage/sets/ModernHorizons2.java | 1 + .../TrampleOverPlaneswalkersAbility.java | 43 +++++++++ .../java/mage/game/combat/CombatGroup.java | 58 ++++++++++--- 4 files changed, 176 insertions(+), 13 deletions(-) create mode 100644 Mage.Sets/src/mage/cards/t/ThrastaTempestsRoar.java create mode 100644 Mage/src/main/java/mage/abilities/keyword/TrampleOverPlaneswalkersAbility.java diff --git a/Mage.Sets/src/mage/cards/t/ThrastaTempestsRoar.java b/Mage.Sets/src/mage/cards/t/ThrastaTempestsRoar.java new file mode 100644 index 0000000000..240f941352 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/ThrastaTempestsRoar.java @@ -0,0 +1,87 @@ +package mage.cards.t; + +import java.util.UUID; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AsEntersBattlefieldAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; +import mage.abilities.effects.common.cost.SpellCostReductionForEachSourceEffect; +import mage.abilities.hint.ValueHint; +import mage.abilities.keyword.HasteAbility; +import mage.abilities.keyword.HexproofAbility; +import mage.abilities.keyword.TrampleAbility; +import mage.abilities.keyword.TrampleOverPlaneswalkersAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.game.Game; +import mage.watchers.common.CastSpellLastTurnWatcher; + +/** + * @author Fubs + */ +public final class ThrastaTempestsRoar extends CardImpl { + + public ThrastaTempestsRoar(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{10}{G}{G}"); + addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.DINOSAUR); + this.power = new MageInt(7); + this.toughness = new MageInt(7); + + //This spell costs 3 less to cast for each other spell cast this turn + ThrastaDynamicValue spellCastCount = new ThrastaDynamicValue(); + //spellCastCount does not need -1 because cast count increases only after current spell cast/reduction. + this.addAbility(new SimpleStaticAbility(Zone.ALL, new SpellCostReductionForEachSourceEffect(3, spellCastCount)) + .addHint(new ValueHint("spell cast", spellCastCount)) + ); + + //Trample, Haste, and Trample over planeswalkers + this.addAbility(TrampleAbility.getInstance()); + this.addAbility(HasteAbility.getInstance()); + this.addAbility(TrampleOverPlaneswalkersAbility.getInstance()); + + //Thrasta has hexproof as long as it entered the battlefield this turn. + this.addAbility(new AsEntersBattlefieldAbility(new GainAbilitySourceEffect(HexproofAbility.getInstance(), Duration.EndOfTurn))); + } + + private ThrastaTempestsRoar(final mage.cards.t.ThrastaTempestsRoar card) { + super(card); + } + + @Override + public ThrastaTempestsRoar copy() { + return new mage.cards.t.ThrastaTempestsRoar(this); + } +} + +class ThrastaDynamicValue implements DynamicValue { + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + CastSpellLastTurnWatcher watcher = game.getState().getWatcher(CastSpellLastTurnWatcher.class); + if (watcher != null) { + return watcher.getAmountOfSpellsAllPlayersCastOnCurrentTurn(); + } + return 0; + } + + @Override + public ThrastaDynamicValue copy() { + return new ThrastaDynamicValue(); + } + + @Override + public String toString() { + return "1"; + } + + @Override + public String getMessage() { + return "spells cast this turn"; + } + +} diff --git a/Mage.Sets/src/mage/sets/ModernHorizons2.java b/Mage.Sets/src/mage/sets/ModernHorizons2.java index 79042d8b79..c7d6052719 100644 --- a/Mage.Sets/src/mage/sets/ModernHorizons2.java +++ b/Mage.Sets/src/mage/sets/ModernHorizons2.java @@ -46,5 +46,6 @@ public final class ModernHorizons2 extends ExpansionSet { cards.add(new SetCardInfo("Urza's Saga", 259, Rarity.RARE, mage.cards.u.UrzasSaga.class)); cards.add(new SetCardInfo("Verdant Catacombs", 260, Rarity.RARE, mage.cards.v.VerdantCatacombs.class)); cards.add(new SetCardInfo("Yusri, Fortune's Flame", 218, Rarity.RARE, mage.cards.y.YusriFortunesFlame.class)); + cards.add(new SetCardInfo("Thrasta, Tempest's Roar", 178, Rarity.MYTHIC, mage.cards.t.ThrastaTempestsRoar.class)); } } diff --git a/Mage/src/main/java/mage/abilities/keyword/TrampleOverPlaneswalkersAbility.java b/Mage/src/main/java/mage/abilities/keyword/TrampleOverPlaneswalkersAbility.java new file mode 100644 index 0000000000..bdeddea1ec --- /dev/null +++ b/Mage/src/main/java/mage/abilities/keyword/TrampleOverPlaneswalkersAbility.java @@ -0,0 +1,43 @@ +package mage.abilities.keyword; + +import mage.abilities.MageSingleton; +import mage.abilities.StaticAbility; +import mage.abilities.icon.abilities.TrampleAbilityIcon; +import mage.constants.Zone; + +import java.io.ObjectStreamException; + +/** + * @author Fubs + */ +public class TrampleOverPlaneswalkersAbility extends StaticAbility implements MageSingleton { + + private static final TrampleOverPlaneswalkersAbility instance; + + static { + instance = new TrampleOverPlaneswalkersAbility(); + } + + private Object readResolve() throws ObjectStreamException { + return instance; + } + + public static TrampleOverPlaneswalkersAbility getInstance() { + return instance; + } + + private TrampleOverPlaneswalkersAbility() { + super(Zone.BATTLEFIELD, null); + } + + @Override + public String getRule() { + return "Trample over planeswalkers (This creature can deal excess combat damage to the controller of the planeswalker it's attacking.)"; + } + + @Override + public TrampleOverPlaneswalkersAbility copy() { + return instance; + } + +} diff --git a/Mage/src/main/java/mage/game/combat/CombatGroup.java b/Mage/src/main/java/mage/game/combat/CombatGroup.java index 831e2544d8..2e9cb57088 100644 --- a/Mage/src/main/java/mage/game/combat/CombatGroup.java +++ b/Mage/src/main/java/mage/game/combat/CombatGroup.java @@ -102,6 +102,10 @@ public class CombatGroup implements Serializable, Copyable { return perm.getAbilities().containsKey(TrampleAbility.getInstance().getId()); } + private boolean hasTrampleOverPlaneswalkers(Permanent perm) { + return perm.getAbilities().containsKey(TrampleOverPlaneswalkersAbility.getInstance().getId()); + } + private boolean hasBanding(Permanent perm) { return perm.getAbilities().containsKey(BandingAbility.getInstance().getId()); } @@ -256,7 +260,7 @@ public class CombatGroup implements Serializable, Copyable { if (canDamage(attacker, first)) { //20091005 - 510.1c, 702.17c if (!blocked || hasTrample(attacker)) { - defenderDamage(attacker, getDamageValueFromPermanent(attacker, game), game); + defenderDamage(attacker, getDamageValueFromPermanent(attacker, game), game, false); } } } @@ -278,7 +282,7 @@ public class CombatGroup implements Serializable, Copyable { blocker.markDamage(damageAssigned, attacker.getId(), null, game, true, true); damage -= damageAssigned; if (damage > 0) { - defenderDamage(attacker, damage, game); + defenderDamage(attacker, damage, game, false); } } } else { @@ -343,7 +347,7 @@ public class CombatGroup implements Serializable, Copyable { } } if (damage > 0 && hasTrample(attacker) && excessDamageToDefender) { - defenderDamage(attacker, damage, game); + defenderDamage(attacker, damage, game, false); } else if (!blockerOrder.isEmpty()) { // Assign the damage left to first blocker assigned.put(blockerOrder.get(0), assigned.get(blockerOrder.get(0)) == null ? 0 : assigned.get(blockerOrder.get(0)) + damage); @@ -541,19 +545,47 @@ public class CombatGroup implements Serializable, Copyable { } } } + /** + * Do damage to attacked player or planeswalker + * @param attacker + * @param amount + * @param game + * @param damageToDefenderController excess damage to defender's controller (example: trample over planeswalker) + */ + private void defenderDamage(Permanent attacker, int amount, Game game, boolean damageToDefenderController) { + UUID affectedDefenderId = this.defenderId; + if (damageToDefenderController) { + affectedDefenderId = game.getControllerId(this.defenderId); + } - private void defenderDamage(Permanent attacker, int amount, Game game) { - if (this.defenderIsPlaneswalker) { - Permanent defender = game.getPermanent(defenderId); - if (defender != null) { - defender.markDamage(amount, attacker.getId(), null, game, true, true); - } - } else { - Player defender = game.getPlayer(defenderId); - if (defender != null) { - defender.damage(amount, attacker.getId(), null, game, true, true); + // on planeswalker + Permanent planeswalker = game.getPermanent(affectedDefenderId); + if (planeswalker != null) { + // apply excess damage from "trample over planeswaslkers" ability (example: Thrasta, Tempest's Roar) + if (hasTrampleOverPlaneswalkers(attacker)) { + int lethalDamage = planeswalker.getLethalDamage(attacker.getId(), game); + if (lethalDamage >= amount) { + // normal damage + planeswalker.markDamage(amount, attacker.getId(), null, game, true, true); + } else { + // damage with excess (additional damage to planeswalker's controller) + planeswalker.markDamage(lethalDamage, attacker.getId(), null, game, true, true); + amount -= lethalDamage; + if (amount > 0) { + defenderDamage(attacker, amount, game, true); + } + } + } else { + // normal damage + planeswalker.markDamage(amount, attacker.getId(), null, game, true, true); } } + + // on player + Player defender = game.getPlayer(affectedDefenderId); + if (defender != null) { + defender.damage(amount, attacker.getId(), null, game, true, true); + } } public boolean canBlock(Permanent blocker, Game game) {