fix harmonize implementation (fixes #13531)

This commit is contained in:
theelk801 2025-04-14 10:19:08 -04:00
parent 9261c1362e
commit 5876040be7
5 changed files with 37 additions and 109 deletions

View file

@ -549,6 +549,8 @@ public interface Ability extends Controllable, Serializable {
*/
Ability setCostAdjuster(CostAdjuster costAdjuster);
CostAdjuster getCostAdjuster();
/**
* Prepare {X} settings for announce
*/

View file

@ -1761,6 +1761,11 @@ public abstract class AbilityImpl implements Ability {
return this;
}
@Override
public CostAdjuster getCostAdjuster() {
return costAdjuster;
}
@Override
public void adjustX(Game game) {
if (costAdjuster != null) {

View file

@ -114,6 +114,7 @@ public abstract class CastFromGraveyardAbility extends SpellAbility {
spellAbilityCopy.addCost(this.getCosts().copy());
spellAbilityCopy.addCost(this.getManaCosts().copy());
spellAbilityCopy.setSpellAbilityCastMode(this.getSpellAbilityCastMode());
spellAbilityCopy.setCostAdjuster(this.getCostAdjuster());
spellAbilityToResolve = spellAbilityCopy;
ContinuousEffect effect = new CastFromGraveyardReplacementEffect();
effect.setTargetPointer(new FixedTarget(getSourceId(), game.getState().getZoneChangeCounter(getSourceId())));

View file

@ -4,15 +4,12 @@ import mage.MageInt;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.costs.Cost;
import mage.abilities.costs.VariableCostImpl;
import mage.abilities.costs.VariableCostType;
import mage.abilities.costs.CostAdjuster;
import mage.abilities.costs.common.TapTargetCost;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.common.cost.CostModificationEffectImpl;
import mage.cards.Card;
import mage.constants.*;
import mage.constants.Outcome;
import mage.constants.SpellAbilityCastMode;
import mage.filter.StaticFilters;
import mage.filter.common.FilterControlledPermanent;
import mage.filter.predicate.permanent.PermanentIdPredicate;
@ -23,9 +20,6 @@ import mage.target.TargetPermanent;
import mage.target.common.TargetControlledPermanent;
import mage.util.CardUtil;
import java.util.Objects;
import java.util.UUID;
/**
* @author TheElk801
*/
@ -36,8 +30,7 @@ public class HarmonizeAbility extends CastFromGraveyardAbility {
public HarmonizeAbility(Card card, String manaString) {
super(card, new ManaCostsImpl<>(manaString), SpellAbilityCastMode.HARMONIZE);
this.addCost(new HarmonizeCost());
this.addSubAbility(new SimpleStaticAbility(Zone.ALL, new HarmonizeCostReductionEffect()).setRuleVisible(false));
this.setCostAdjuster(HarmonizeAbilityAdjuster.instance);
}
private HarmonizeAbility(final HarmonizeAbility ability) {
@ -57,123 +50,45 @@ public class HarmonizeAbility extends CastFromGraveyardAbility {
}
}
class HarmonizeCostReductionEffect extends CostModificationEffectImpl {
HarmonizeCostReductionEffect() {
super(Duration.WhileOnStack, Outcome.Benefit, CostModificationType.REDUCE_COST);
}
private HarmonizeCostReductionEffect(final HarmonizeCostReductionEffect effect) {
super(effect);
}
enum HarmonizeAbilityAdjuster implements CostAdjuster {
instance;
@Override
public boolean apply(Game game, Ability source, Ability abilityToModify) {
SpellAbility spellAbility = (SpellAbility) abilityToModify;
int power;
public void reduceCost(Ability ability, Game game) {
if (game.inCheckPlayableState()) {
power = game
int amount = game
.getBattlefield()
.getActivePermanents(
StaticFilters.FILTER_CONTROLLED_UNTAPPED_CREATURE,
source.getControllerId(), source, game
).stream()
ability.getControllerId(), ability, game
)
.stream()
.map(MageObject::getPower)
.mapToInt(MageInt::getValue)
.max()
.orElse(0);
} else {
power = CardUtil
.castStream(spellAbility.getCosts().stream(), HarmonizeCost.class)
.map(HarmonizeCost::getChosenCreature)
.map(game::getPermanent)
.filter(Objects::nonNull)
.map(MageObject::getPower)
.mapToInt(MageInt::getValue)
.map(x -> Math.max(x, 0))
.sum();
CardUtil.reduceCost(ability, amount);
return;
}
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);
}
}
class HarmonizeCost extends VariableCostImpl {
private UUID chosenCreature = null;
HarmonizeCost() {
super(VariableCostType.ADDITIONAL, "", "");
}
private HarmonizeCost(final HarmonizeCost cost) {
super(cost);
this.chosenCreature = cost.chosenCreature;
}
@Override
public HarmonizeCost copy() {
return new HarmonizeCost(this);
}
@Override
public void clearPaid() {
super.clearPaid();
chosenCreature = null;
}
@Override
public int getMaxValue(Ability source, Game game) {
return game.getBattlefield().contains(StaticFilters.FILTER_CONTROLLED_UNTAPPED_CREATURE, source, game, 1) ? 1 : 0;
}
@Override
public int announceXValue(Ability source, Game game) {
Player player = game.getPlayer(source.getControllerId());
Player player = game.getPlayer(ability.getControllerId());
if (player == null || !game.getBattlefield().contains(
StaticFilters.FILTER_CONTROLLED_UNTAPPED_CREATURE, source, game, 1
StaticFilters.FILTER_CONTROLLED_UNTAPPED_CREATURE, ability, game, 1
) || !player.chooseUse(
Outcome.Benefit, "Tap an untapped creature you control for harmonize?", source, game
Outcome.Tap, "Tap a creature to reduce the cost of this spell?", ability, game
)) {
return 0;
return;
}
TargetPermanent target = new TargetPermanent(StaticFilters.FILTER_CONTROLLED_UNTAPPED_CREATURE);
target.withNotTarget(true);
target.withChooseHint("for harmonize");
player.choose(Outcome.PlayForFree, target, source, game);
target.withChooseHint("to pay for harmonize");
player.choose(Outcome.Tap, target, ability, game);
Permanent permanent = game.getPermanent(target.getFirstTarget());
if (permanent == null) {
return 0;
return;
}
chosenCreature = permanent.getId();
return 1;
}
private FilterControlledPermanent makeFilter() {
FilterControlledPermanent filter = new FilterControlledPermanent("tap the chosen creature");
filter.add(new PermanentIdPredicate(chosenCreature));
return filter;
}
@Override
public Cost getFixedCostsFromAnnouncedValue(int xValue) {
return new TapTargetCost(new TargetControlledPermanent(xValue, xValue, makeFilter(), true));
}
public UUID getChosenCreature() {
return chosenCreature;
CardUtil.reduceCost(ability, permanent.getPower().getValue());
FilterControlledPermanent filter = new FilterControlledPermanent("creature chosen to tap for harmonize");
filter.add(new PermanentIdPredicate(permanent.getId()));
ability.addCost(new TapTargetCost(new TargetControlledPermanent(filter)));
}
}

View file

@ -787,6 +787,11 @@ public class StackAbility extends StackObjectImpl implements Ability {
return this;
}
@Override
public CostAdjuster getCostAdjuster() {
return costAdjuster;
}
@Override
public void adjustX(Game game) {
if (costAdjuster != null) {