From 860230b2e58e0a5de3be68505f5df295facee581 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Tue, 25 Mar 2025 20:12:17 -0400 Subject: [PATCH] add initial ability --- .../main/java/mage/abilities/AbilityImpl.java | 1 + .../abilities/keyword/HarmonizeAbility.java | 216 +++++++++++++++++- .../mage/constants/SpellAbilityCastMode.java | 2 + 3 files changed, 215 insertions(+), 4 deletions(-) diff --git a/Mage/src/main/java/mage/abilities/AbilityImpl.java b/Mage/src/main/java/mage/abilities/AbilityImpl.java index 114e88343f5..d755ee5f64b 100644 --- a/Mage/src/main/java/mage/abilities/AbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/AbilityImpl.java @@ -483,6 +483,7 @@ public abstract class AbilityImpl implements Ability { // A player can't apply two alternative methods of casting or two alternative costs to a single spell. switch (((SpellAbility) this).getSpellAbilityCastMode()) { case FLASHBACK: + case HARMONIZE: case MADNESS: case TRANSFORMED: case DISTURB: diff --git a/Mage/src/main/java/mage/abilities/keyword/HarmonizeAbility.java b/Mage/src/main/java/mage/abilities/keyword/HarmonizeAbility.java index 92f6310401c..65824c135e1 100644 --- a/Mage/src/main/java/mage/abilities/keyword/HarmonizeAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/HarmonizeAbility.java @@ -1,19 +1,51 @@ package mage.abilities.keyword; +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; import mage.abilities.SpellAbility; +import mage.abilities.costs.Cost; +import mage.abilities.costs.Costs; +import mage.abilities.costs.common.TapTargetCost; import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.common.cost.CostModificationEffectImpl; import mage.cards.Card; -import mage.constants.Zone; +import mage.cards.ModalDoubleFacedCard; +import mage.cards.SplitCard; +import mage.constants.*; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeEvent; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetControlledPermanent; +import mage.target.targetpointer.FixedTarget; +import mage.util.CardUtil; + +import java.util.List; +import java.util.UUID; /** - * TODO: Implement this - * * @author TheElk801 */ public class HarmonizeAbility extends SpellAbility { + private String abilityName; + private SpellAbility spellAbilityToResolve; + public HarmonizeAbility(Card card, String manaString) { - super(new ManaCostsImpl<>(manaString), card.getName(), Zone.GRAVEYARD); + super(null, "", Zone.GRAVEYARD, SpellAbilityType.BASE_ALTERNATE, SpellAbilityCastMode.HARMONIZE); + this.setAdditionalCostsRuleVisible(false); + this.name = "Harmonize " + manaString; + this.addCost(new ManaCostsImpl<>(manaString)); + this.addCost(new TapTargetCost(new TargetControlledPermanent( + 0, 1, StaticFilters.FILTER_CONTROLLED_UNTAPPED_CREATURE, true + ))); + this.addEffect(new HarmonizeCostReductionEffect()); + this.timing = card.isSorcery() ? TimingRule.SORCERY : TimingRule.INSTANT; } private HarmonizeAbility(final HarmonizeAbility ability) { @@ -24,4 +56,180 @@ public class HarmonizeAbility extends SpellAbility { public HarmonizeAbility copy() { return new HarmonizeAbility(this); } + + @Override + public ActivationStatus canActivate(UUID playerId, Game game) { + // harmonize ability dynamicly added to all card's parts (split cards) + if (!super.canActivate(playerId, game).canActivate()) { + return ActivationStatus.getFalse(); + } + Card card = game.getCard(getSourceId()); + if (card == null) { + return ActivationStatus.getFalse(); + } + // Card must be in the graveyard zone + if (game.getState().getZone(card.getId()) != Zone.GRAVEYARD) { + return ActivationStatus.getFalse(); + } + // Cards with no Mana Costs cant't be harmonized (e.g. Ancestral Vision) + if (card.getManaCost().isEmpty()) { + return ActivationStatus.getFalse(); + } + // Harmonize can never cast a split card by Fuse, because Fuse only works from hand + if (card instanceof SplitCard) { + if (((SplitCard) card).getLeftHalfCard().getName().equals(abilityName)) { + return ((SplitCard) card).getLeftHalfCard().getSpellAbility().canActivate(playerId, game); + } else if (((SplitCard) card).getRightHalfCard().getName().equals(abilityName)) { + return ((SplitCard) card).getRightHalfCard().getSpellAbility().canActivate(playerId, game); + } + } else if (card instanceof ModalDoubleFacedCard) { + if (((ModalDoubleFacedCard) card).getLeftHalfCard().getName().equals(abilityName)) { + return ((ModalDoubleFacedCard) card).getLeftHalfCard().getSpellAbility().canActivate(playerId, game); + } else if (((ModalDoubleFacedCard) card).getRightHalfCard().getName().equals(abilityName)) { + return ((ModalDoubleFacedCard) card).getRightHalfCard().getSpellAbility().canActivate(playerId, game); + } + } + return card.getSpellAbility().canActivate(playerId, game); + } + + @Override + public SpellAbility getSpellAbilityToResolve(Game game) { + Card card = game.getCard(getSourceId()); + if (card == null) { + return spellAbilityToResolve; + } + if (spellAbilityToResolve != null) { + return spellAbilityToResolve; + } + SpellAbility spellAbilityCopy; + if (card instanceof SplitCard) { + if (((SplitCard) card).getLeftHalfCard().getName().equals(abilityName)) { + spellAbilityCopy = ((SplitCard) card).getLeftHalfCard().getSpellAbility().copy(); + } else if (((SplitCard) card).getRightHalfCard().getName().equals(abilityName)) { + spellAbilityCopy = ((SplitCard) card).getRightHalfCard().getSpellAbility().copy(); + } else { + spellAbilityCopy = null; + } + } else if (card instanceof ModalDoubleFacedCard) { + if (((ModalDoubleFacedCard) card).getLeftHalfCard().getName().equals(abilityName)) { + spellAbilityCopy = ((ModalDoubleFacedCard) card).getLeftHalfCard().getSpellAbility().copy(); + } else if (((ModalDoubleFacedCard) card).getRightHalfCard().getName().equals(abilityName)) { + spellAbilityCopy = ((ModalDoubleFacedCard) card).getRightHalfCard().getSpellAbility().copy(); + } else { + spellAbilityCopy = null; + } + } else { + spellAbilityCopy = card.getSpellAbility().copy(); + } + if (spellAbilityCopy == null) { + return null; + } + spellAbilityCopy.setId(this.getId()); + spellAbilityCopy.clearManaCosts(); + spellAbilityCopy.clearManaCostsToPay(); + spellAbilityCopy.addCost(this.getCosts().copy()); + spellAbilityCopy.addCost(this.getManaCosts().copy()); + spellAbilityCopy.setSpellAbilityCastMode(this.getSpellAbilityCastMode()); + spellAbilityToResolve = spellAbilityCopy; + ContinuousEffect effect = new HarmonizeReplacementEffect(); + effect.setTargetPointer(new FixedTarget(getSourceId(), game.getState().getZoneChangeCounter(getSourceId()))); + game.addEffect(effect, this); + return spellAbilityToResolve; + } + + @Override + public Costs getCosts() { + if (spellAbilityToResolve == null) { + return super.getCosts(); + } + return spellAbilityToResolve.getCosts(); + } +} + +class HarmonizeReplacementEffect extends ReplacementEffectImpl { + + public HarmonizeReplacementEffect() { + super(Duration.OneUse, Outcome.Exile); + } + + protected HarmonizeReplacementEffect(final HarmonizeReplacementEffect effect) { + super(effect); + } + + @Override + public HarmonizeReplacementEffect copy() { + return new HarmonizeReplacementEffect(this); + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + Player controller = game.getPlayer(source.getControllerId()); + Card card = game.getCard(event.getTargetId()); + if (controller == null || card == null) { + return false; + } + discard(); + return controller.moveCards( + card, Zone.EXILED, source, game, false, + false, false, event.getAppliedEffects() + ); + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ZONE_CHANGE; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + UUID cardId = CardUtil.getMainCardId(game, source.getSourceId()); // for split cards + return event.getTargetId().equals(cardId) + && ((ZoneChangeEvent) event).getFromZone().match(Zone.STACK) + && !((ZoneChangeEvent) event).getToZone().match(Zone.EXILED) + && ((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1 == game.getState().getZoneChangeCounter(cardId); + } +} + +class HarmonizeCostReductionEffect extends CostModificationEffectImpl { + + HarmonizeCostReductionEffect() { + super(Duration.WhileOnStack, Outcome.Benefit, CostModificationType.REDUCE_COST); + } + + private HarmonizeCostReductionEffect(final HarmonizeCostReductionEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source, Ability abilityToModify) { + if (!game.inCheckPlayableState()) { + return true; + } + SpellAbility spellAbility = (SpellAbility) abilityToModify; + List permanents = (List) getValue("tappedPermanents"); + if (permanents == null) { + return true; + } + int power = permanents + .stream() + .map(MageObject::getPower) + .mapToInt(MageInt::getValue) + .map(x -> Math.max(x, 0)) + .sum(); + if (power > 0) { + CardUtil.adjustCost(spellAbility, power); + } + return true; + } + + @Override + public boolean applies(Ability abilityToModify, Ability source, Game game) { + return abilityToModify instanceof SpellAbility + && abilityToModify.getSourceId().equals(source.getSourceId()); + } + + @Override + public HarmonizeCostReductionEffect copy() { + return new HarmonizeCostReductionEffect(this); + } } diff --git a/Mage/src/main/java/mage/constants/SpellAbilityCastMode.java b/Mage/src/main/java/mage/constants/SpellAbilityCastMode.java index a57e28325f6..6eedb18741f 100644 --- a/Mage/src/main/java/mage/constants/SpellAbilityCastMode.java +++ b/Mage/src/main/java/mage/constants/SpellAbilityCastMode.java @@ -15,6 +15,7 @@ public enum SpellAbilityCastMode { NORMAL("Normal"), MADNESS("Madness"), FLASHBACK("Flashback"), + HARMONIZE("Harmonize"), BESTOW("Bestow"), PROTOTYPE("Prototype"), MORPH("Morph", false, true), // and megamorph @@ -91,6 +92,7 @@ public enum SpellAbilityCastMode { case NORMAL: case MADNESS: case FLASHBACK: + case HARMONIZE: case DISTURB: case PLOT: case MORE_THAN_MEETS_THE_EYE: