From e0766ee44950e14b84b22fd29328a3f0e788ab72 Mon Sep 17 00:00:00 2001 From: tool4EvEr Date: Mon, 27 Dec 2021 23:07:12 +0100 Subject: [PATCH] Fix illegal distributions --- .../src/main/java/forge/ai/AiController.java | 4 +- .../game/ability/effects/LifeSetEffect.java | 57 +++++++++++++++++-- .../main/java/forge/game/player/Player.java | 8 +-- 3 files changed, 58 insertions(+), 11 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/AiController.java b/forge-ai/src/main/java/forge/ai/AiController.java index 746e217fe93..fa3744b2131 100644 --- a/forge-ai/src/main/java/forge/ai/AiController.java +++ b/forge-ai/src/main/java/forge/ai/AiController.java @@ -2146,10 +2146,10 @@ public class AiController { return library; } // smoothComputerManaCurve() - public int chooseNumber(SpellAbility sa, String title,List options, Player relatedPlayer) { + public int chooseNumber(SpellAbility sa, String title, List options, Player relatedPlayer) { switch(sa.getApi()) { - case SetLife: + case SetLife: // Reverse the Sands if (relatedPlayer.equals(sa.getHostCard().getController())) { return Collections.max(options); } else if (relatedPlayer.isOpponentOf(sa.getHostCard().getController())) { diff --git a/forge-game/src/main/java/forge/game/ability/effects/LifeSetEffect.java b/forge-game/src/main/java/forge/game/ability/effects/LifeSetEffect.java index c92d6cd6134..bb65f804a02 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/LifeSetEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/LifeSetEffect.java @@ -3,9 +3,12 @@ package forge.game.ability.effects; import java.util.ArrayList; import java.util.List; +import com.google.common.collect.Lists; + import forge.game.ability.AbilityUtils; import forge.game.ability.SpellAbilityEffect; import forge.game.player.Player; +import forge.game.player.PlayerCollection; import forge.game.spellability.SpellAbility; import forge.game.spellability.TargetRestrictions; import forge.util.Localizer; @@ -18,31 +21,75 @@ public class LifeSetEffect extends SpellAbilityEffect { @Override public void resolve(SpellAbility sa) { final boolean redistribute = sa.hasParam("Redistribute"); - final int lifeAmount = redistribute ? 20 : AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("LifeAmount"), sa); + final int lifeAmount = redistribute ? 0 : AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("LifeAmount"), sa); final TargetRestrictions tgt = sa.getTargetRestrictions(); final List lifetotals = new ArrayList<>(); - + final PlayerCollection players = getTargetPlayers(sa); + if (redistribute) { - for (final Player p : getTargetPlayers(sa)) { + for (final Player p : players) { if (tgt == null || p.canBeTargetedBy(sa)) { lifetotals.add(p.getLife()); } } } - for (final Player p : getTargetPlayers(sa)) { + for (final Player p : players.threadSafeIterable()) { if (tgt == null || p.canBeTargetedBy(sa)) { if (!redistribute) { p.setLife(lifeAmount, sa.getHostCard()); } else { - int life = sa.getActivatingPlayer().getController().chooseNumber(sa, Localizer.getInstance().getMessage("lblLifeTotal") + ": " + p, lifetotals, p); + List validChoices = getDistribution(players, true, lifetotals); + int life = sa.getActivatingPlayer().getController().chooseNumber(sa, Localizer.getInstance().getMessage("lblLifeTotal") + ": " + p, validChoices, p); p.setLife(life, sa.getHostCard()); lifetotals.remove((Integer) life); + players.remove(p); } } } } + private static List getDistribution(PlayerCollection players, boolean top, List remainingChoices) { + // distribution was successful + if (players.isEmpty()) { + // carry signal back + remainingChoices.add(1); + return remainingChoices; + } + List validChoices = Lists.newArrayList(remainingChoices); + for (Player p : players) { + for (Integer choice : remainingChoices) { + // 119.7/8 illegal choice + if ((p.getLife() < choice && !p.canGainLife()) || (p.getLife() > choice && !p.canLoseLife())) { + if (top) { + validChoices.remove(choice); + } + continue; + } + + // combination is valid, check next + PlayerCollection nextPlayers = new PlayerCollection(players); + nextPlayers.remove(p); + List nextChoices = new ArrayList<>(remainingChoices); + nextChoices.remove(choice); + nextChoices = getDistribution(nextPlayers, false, nextChoices); + if (nextChoices.isEmpty()) { + if (top) { + // top of recursion stack + validChoices.remove(choice); + } + } else if (!top) { + return nextChoices; + } + } + if (top) { + // checking first player is enough + return validChoices; + } + } + return new ArrayList(); + } + /* (non-Javadoc) * @see forge.card.abilityfactory.AbilityFactoryAlterLife.SpellEffect#getStackDescription(java.util.Map, forge.card.spellability.SpellAbility) */ diff --git a/forge-game/src/main/java/forge/game/player/Player.java b/forge-game/src/main/java/forge/game/player/Player.java index 7429fe661fb..06a95400878 100644 --- a/forge-game/src/main/java/forge/game/player/Player.java +++ b/forge-game/src/main/java/forge/game/player/Player.java @@ -483,15 +483,15 @@ public class Player extends GameEntity implements Comparable { return gainLife(lifeGain, source, null); } public final boolean gainLife(int lifeGain, final Card source, final SpellAbility sa) { + if (!canGainLife()) { + return false; + } + // Run any applicable replacement effects. final Map repParams = AbilityKey.mapFromAffected(this); repParams.put(AbilityKey.LifeGained, lifeGain); repParams.put(AbilityKey.Source, source); - if (!canGainLife()) { - return false; - } - switch (getGame().getReplacementHandler().run(ReplacementType.GainLife, repParams)) { case NotReplaced: break;