Fix illegal distributions

This commit is contained in:
tool4EvEr
2021-12-27 23:07:12 +01:00
parent ef926235ef
commit e0766ee449
3 changed files with 58 additions and 11 deletions

View File

@@ -2149,7 +2149,7 @@ public class AiController {
public int chooseNumber(SpellAbility sa, String title, List<Integer> options, Player relatedPlayer) { public int chooseNumber(SpellAbility sa, String title, List<Integer> options, Player relatedPlayer) {
switch(sa.getApi()) switch(sa.getApi())
{ {
case SetLife: case SetLife: // Reverse the Sands
if (relatedPlayer.equals(sa.getHostCard().getController())) { if (relatedPlayer.equals(sa.getHostCard().getController())) {
return Collections.max(options); return Collections.max(options);
} else if (relatedPlayer.isOpponentOf(sa.getHostCard().getController())) { } else if (relatedPlayer.isOpponentOf(sa.getHostCard().getController())) {

View File

@@ -3,9 +3,12 @@ package forge.game.ability.effects;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import com.google.common.collect.Lists;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.player.PlayerCollection;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions; import forge.game.spellability.TargetRestrictions;
import forge.util.Localizer; import forge.util.Localizer;
@@ -18,31 +21,75 @@ public class LifeSetEffect extends SpellAbilityEffect {
@Override @Override
public void resolve(SpellAbility sa) { public void resolve(SpellAbility sa) {
final boolean redistribute = sa.hasParam("Redistribute"); 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 TargetRestrictions tgt = sa.getTargetRestrictions();
final List<Integer> lifetotals = new ArrayList<>(); final List<Integer> lifetotals = new ArrayList<>();
final PlayerCollection players = getTargetPlayers(sa);
if (redistribute) { if (redistribute) {
for (final Player p : getTargetPlayers(sa)) { for (final Player p : players) {
if (tgt == null || p.canBeTargetedBy(sa)) { if (tgt == null || p.canBeTargetedBy(sa)) {
lifetotals.add(p.getLife()); lifetotals.add(p.getLife());
} }
} }
} }
for (final Player p : getTargetPlayers(sa)) { for (final Player p : players.threadSafeIterable()) {
if (tgt == null || p.canBeTargetedBy(sa)) { if (tgt == null || p.canBeTargetedBy(sa)) {
if (!redistribute) { if (!redistribute) {
p.setLife(lifeAmount, sa.getHostCard()); p.setLife(lifeAmount, sa.getHostCard());
} else { } else {
int life = sa.getActivatingPlayer().getController().chooseNumber(sa, Localizer.getInstance().getMessage("lblLifeTotal") + ": " + p, lifetotals, p); List<Integer> validChoices = getDistribution(players, true, lifetotals);
int life = sa.getActivatingPlayer().getController().chooseNumber(sa, Localizer.getInstance().getMessage("lblLifeTotal") + ": " + p, validChoices, p);
p.setLife(life, sa.getHostCard()); p.setLife(life, sa.getHostCard());
lifetotals.remove((Integer) life); lifetotals.remove((Integer) life);
players.remove(p);
} }
} }
} }
} }
private static List<Integer> getDistribution(PlayerCollection players, boolean top, List<Integer> remainingChoices) {
// distribution was successful
if (players.isEmpty()) {
// carry signal back
remainingChoices.add(1);
return remainingChoices;
}
List<Integer> 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<Integer> 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<Integer>();
}
/* (non-Javadoc) /* (non-Javadoc)
* @see forge.card.abilityfactory.AbilityFactoryAlterLife.SpellEffect#getStackDescription(java.util.Map, forge.card.spellability.SpellAbility) * @see forge.card.abilityfactory.AbilityFactoryAlterLife.SpellEffect#getStackDescription(java.util.Map, forge.card.spellability.SpellAbility)
*/ */

View File

@@ -483,15 +483,15 @@ public class Player extends GameEntity implements Comparable<Player> {
return gainLife(lifeGain, source, null); return gainLife(lifeGain, source, null);
} }
public final boolean gainLife(int lifeGain, final Card source, final SpellAbility sa) { public final boolean gainLife(int lifeGain, final Card source, final SpellAbility sa) {
if (!canGainLife()) {
return false;
}
// Run any applicable replacement effects. // Run any applicable replacement effects.
final Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(this); final Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(this);
repParams.put(AbilityKey.LifeGained, lifeGain); repParams.put(AbilityKey.LifeGained, lifeGain);
repParams.put(AbilityKey.Source, source); repParams.put(AbilityKey.Source, source);
if (!canGainLife()) {
return false;
}
switch (getGame().getReplacementHandler().run(ReplacementType.GainLife, repParams)) { switch (getGame().getReplacementHandler().run(ReplacementType.GainLife, repParams)) {
case NotReplaced: case NotReplaced:
break; break;