This commit is contained in:
tool4EvEr
2021-12-24 09:52:22 +01:00
parent 4cf730ffa0
commit 0e0fdaa1ff
4 changed files with 209 additions and 200 deletions

View File

@@ -41,7 +41,6 @@ import forge.game.zone.ZoneType;
import forge.util.MyRandom;
import forge.util.TextUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import java.util.*;
@@ -67,16 +66,6 @@ public class ComputerUtilMana {
return payManaCost(cost, sa, ai, test, checkPlayable, effect);
}
private static void refundMana(List<Mana> manaSpent, Player ai, SpellAbility sa) {
if (sa.getHostCard() != null) {
sa.getHostCard().setCanCounter(true);
}
for (final Mana m : manaSpent) {
ai.getManaPool().addMana(m);
}
manaSpent.clear();
}
/**
* Return the number of colors used for payment for Converge
*/
@@ -598,7 +587,7 @@ public class ComputerUtilMana {
}
// get a mana of this type from floating, bail if none available
final Mana mana = getMana(ai, part, sa, cost.getSourceRestriction(), (byte) -1, cost.getXManaCostPaidByColor());
final Mana mana = CostPayment.getMana(ai, part, sa, cost.getSourceRestriction(), (byte) -1, cost.getXManaCostPaidByColor());
if (mana != null) {
if (ai.getManaPool().tryPayCostWithMana(sa, cost, mana, false)) {
manaSpentToPay.add(0, mana);
@@ -609,18 +598,16 @@ public class ComputerUtilMana {
if (cost.isPaid()) {
// refund any mana taken from mana pool when test
refundMana(manaSpentToPay, ai, sa);
handleOfferingsAI(sa, true, cost.isPaid());
ManaPool.refundMana(manaSpentToPay, ai, sa);
CostPayment.handleOfferings(sa, true, cost.isPaid());
return manaSources;
}
// arrange all mana abilities by color produced.
final ListMultimap<Integer, SpellAbility> manaAbilityMap = groupSourcesByManaColor(ai, true);
if (manaAbilityMap.isEmpty()) {
refundMana(manaSpentToPay, ai, sa);
handleOfferingsAI(sa, true, cost.isPaid());
ManaPool.refundMana(manaSpentToPay, ai, sa);
CostPayment.handleOfferings(sa, true, cost.isPaid());
return manaSources;
}
@@ -666,9 +653,8 @@ public class ComputerUtilMana {
Iterables.removeIf(sourcesForShards.values(), CardTraitPredicates.isHostCard(saPayment.getHostCard()));
}
handleOfferingsAI(sa, true, cost.isPaid());
refundMana(manaSpentToPay, ai, sa);
CostPayment.handleOfferings(sa, true, cost.isPaid());
ManaPool.refundMana(manaSpentToPay, ai, sa);
return manaSources;
} // getManaSourcesToPayCost()
@@ -681,7 +667,7 @@ public class ComputerUtilMana {
List<Mana> manaSpentToPay = test ? new ArrayList<>() : sa.getPayingMana();
List<SpellAbility> paymentList = Lists.newArrayList();
if (payManaCostFromPool(cost, sa, ai, test, manaSpentToPay)) {
if (ManaPool.payManaCostFromPool(cost, sa, ai, test, manaSpentToPay)) {
return true; // paid all from floating mana
}
@@ -870,7 +856,7 @@ public class ComputerUtilMana {
}
}
handleOfferingsAI(sa, test, cost.isPaid());
CostPayment.handleOfferings(sa, test, cost.isPaid());
// if (DEBUG_MANA_PAYMENT) {
// System.err.printf("%s > [%s] payment has %s (%s +%d) for (%s) %s:%n\t%s%n%n",
@@ -880,7 +866,7 @@ public class ComputerUtilMana {
// The cost is still unpaid, so refund the mana and report
if (!cost.isPaid()) {
refundMana(manaSpentToPay, ai, sa);
ManaPool.refundMana(manaSpentToPay, ai, sa);
if (test) {
resetPayment(paymentList);
return false;
@@ -891,7 +877,7 @@ public class ComputerUtilMana {
}
if (test) {
refundMana(manaSpentToPay, ai, sa);
ManaPool.refundMana(manaSpentToPay, ai, sa);
resetPayment(paymentList);
}
@@ -929,8 +915,8 @@ public class ComputerUtilMana {
final ListMultimap<Integer, SpellAbility> manaAbilityMap = groupSourcesByManaColor(ai, checkPlayable);
if (manaAbilityMap.isEmpty()) {
// no mana abilities, bailing out
refundMana(manaSpentToPay, ai, sa);
handleOfferingsAI(sa, test, cost.isPaid());
ManaPool.refundMana(manaSpentToPay, ai, sa);
CostPayment.handleOfferings(sa, test, cost.isPaid());
return null;
}
if (DEBUG_MANA_PAYMENT) {
@@ -968,157 +954,6 @@ public class ComputerUtilMana {
return sourcesForShards;
}
/**
* Checks if the given mana cost can be paid from floating mana.
* @param cost mana cost to pay for
* @param sa ability to pay for
* @param ai activating player
* @param test actual payment is made if this is false
* @param manaSpentToPay list of mana spent
* @return whether the floating mana is sufficient to pay the cost fully
*/
public static boolean payManaCostFromPool(final ManaCostBeingPaid cost, final SpellAbility sa, final Player ai,
final boolean test, List<Mana> manaSpentToPay) {
final boolean hasConverge = sa.getHostCard().hasConverge();
List<ManaCostShard> unpaidShards = cost.getUnpaidShards();
Collections.sort(unpaidShards); // most difficult shards must come first
for (ManaCostShard part : unpaidShards) {
if (part != ManaCostShard.X) {
if (cost.isPaid()) {
continue;
}
// get a mana of this type from floating, bail if none available
final Mana mana = getMana(ai, part, sa, cost.getSourceRestriction(), hasConverge ? cost.getColorsPaid() : -1, cost.getXManaCostPaidByColor());
if (mana != null) {
if (ai.getManaPool().tryPayCostWithMana(sa, cost, mana, test)) {
manaSpentToPay.add(0, mana);
}
}
}
}
// refund any mana taken from mana pool when test
if (test) {
refundMana(manaSpentToPay, ai, sa);
}
if (cost.isPaid()) {
handleOfferingsAI(sa, test, cost.isPaid());
return true;
}
return false;
}
/**
* <p>
* getManaFrom.
* </p>
*
* @param saBeingPaidFor
* a {@link forge.game.spellability.SpellAbility} object.
* @return a {@link forge.game.mana.Mana} object.
*/
private static Mana getMana(final Player ai, final ManaCostShard shard, final SpellAbility saBeingPaidFor,
String restriction, final byte colorsPaid, Map<String, Integer> xManaCostPaidByColor) {
final List<Pair<Mana, Integer>> weightedOptions = selectManaToPayFor(ai.getManaPool(), shard,
saBeingPaidFor, restriction, colorsPaid, xManaCostPaidByColor);
// Exclude border case
if (weightedOptions.isEmpty()) {
return null; // There is no matching mana in the pool
}
// select equal weight possibilities
List<Mana> manaChoices = new ArrayList<>();
int bestWeight = Integer.MIN_VALUE;
for (Pair<Mana, Integer> option : weightedOptions) {
int thisWeight = option.getRight();
Mana thisMana = option.getLeft();
if (thisWeight > bestWeight) {
manaChoices.clear();
bestWeight = thisWeight;
}
if (thisWeight == bestWeight) {
// add only distinct Mana-s
boolean haveDuplicate = false;
for (Mana m : manaChoices) {
if (m.equals(thisMana)) {
haveDuplicate = true;
break;
}
}
if (!haveDuplicate) {
manaChoices.add(thisMana);
}
}
}
// got an only one best option?
if (manaChoices.size() == 1) {
return manaChoices.get(0);
}
// if we are simulating mana payment for the human controller, use the first mana available (and avoid prompting the human player)
if (!(ai.getController() instanceof PlayerControllerAi)) {
return manaChoices.get(0);
}
// Let them choose then
return ai.getController().chooseManaFromPool(manaChoices);
}
private static List<Pair<Mana, Integer>> selectManaToPayFor(final ManaPool manapool, final ManaCostShard shard,
final SpellAbility saBeingPaidFor, String restriction, final byte colorsPaid, Map<String, Integer> xManaCostPaidByColor) {
final List<Pair<Mana, Integer>> weightedOptions = new ArrayList<>();
for (final Mana thisMana : manapool) {
if (shard == ManaCostShard.COLORED_X && !ManaCostBeingPaid.canColoredXShardBePaidByColor(MagicColor.toShortString(thisMana.getColor()), xManaCostPaidByColor)) {
continue;
}
if (!manapool.canPayForShardWithColor(shard, thisMana.getColor())) {
continue;
}
if (thisMana.getManaAbility() != null && !thisMana.getManaAbility().meetsSpellAndShardRestrictions(saBeingPaidFor, shard, thisMana.getColor())) {
continue;
}
boolean canPay = manapool.canPayForShardWithColor(shard, thisMana.getColor());
if (!canPay || (shard.isSnow() && !thisMana.isSnow())) {
continue;
}
if (StringUtils.isNotBlank(restriction) && !thisMana.getSourceCard().getType().hasStringType(restriction)) {
continue;
}
int weight = 0;
if (colorsPaid == -1) {
// prefer colorless mana to spend
weight += thisMana.isColorless() ? 5 : 0;
} else {
// get more colors for converge
weight += (thisMana.getColor() | colorsPaid) != colorsPaid ? 5 : 0;
}
// prefer restricted mana to spend
if (thisMana.isRestricted()) {
weight += 2;
}
// Spend non-snow mana first
if (!thisMana.isSnow()) {
weight += 1;
}
weightedOptions.add(Pair.of(thisMana, weight));
}
return weightedOptions;
}
private static void setExpressColorChoice(final SpellAbility sa, final Player ai, ManaCostBeingPaid cost,
ManaCostShard toPay, SpellAbility saPayment) {
@@ -1936,25 +1771,6 @@ public class ComputerUtilMana {
return res;
}
private static void handleOfferingsAI(final SpellAbility sa, boolean test, boolean costIsPaid) {
if (sa.isOffering() && sa.getSacrificedAsOffering() != null) {
final Card offering = sa.getSacrificedAsOffering();
offering.setUsedToPay(false);
if (costIsPaid && !test) {
sa.getHostCard().getGame().getAction().sacrifice(offering, sa, false, null, null);
}
sa.resetSacrificedAsOffering();
}
if (sa.isEmerge() && sa.getSacrificedAsEmerge() != null) {
final Card emerge = sa.getSacrificedAsEmerge();
emerge.setUsedToPay(false);
if (costIsPaid && !test) {
sa.getHostCard().getGame().getAction().sacrifice(emerge, sa, false, null, null);
}
sa.resetSacrificedAsEmerge();
}
}
/**
* Matches list of creatures to shards in mana cost for convoking.
* @param cost cost of convoked ability

View File

@@ -17,15 +17,25 @@
*/
package forge.game.cost;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import forge.card.MagicColor;
import forge.card.mana.ManaCostShard;
import forge.game.Game;
import forge.game.card.Card;
import forge.game.mana.Mana;
import forge.game.mana.ManaConversionMatrix;
import forge.game.mana.ManaCostBeingPaid;
import forge.game.mana.ManaPool;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
@@ -221,4 +231,132 @@ public class CostPayment extends ManaConversionMatrix {
}
return true;
}
/**
* <p>
* getManaFrom.
* </p>
*
* @param saBeingPaidFor
* a {@link forge.game.spellability.SpellAbility} object.
* @return a {@link forge.game.mana.Mana} object.
*/
public static Mana getMana(final Player ai, final ManaCostShard shard, final SpellAbility saBeingPaidFor,
String restriction, final byte colorsPaid, Map<String, Integer> xManaCostPaidByColor) {
final List<Pair<Mana, Integer>> weightedOptions = selectManaToPayFor(ai.getManaPool(), shard,
saBeingPaidFor, restriction, colorsPaid, xManaCostPaidByColor);
// Exclude border case
if (weightedOptions.isEmpty()) {
return null; // There is no matching mana in the pool
}
// select equal weight possibilities
List<Mana> manaChoices = new ArrayList<>();
int bestWeight = Integer.MIN_VALUE;
for (Pair<Mana, Integer> option : weightedOptions) {
int thisWeight = option.getRight();
Mana thisMana = option.getLeft();
if (thisWeight > bestWeight) {
manaChoices.clear();
bestWeight = thisWeight;
}
if (thisWeight == bestWeight) {
// add only distinct Mana-s
boolean haveDuplicate = false;
for (Mana m : manaChoices) {
if (m.equals(thisMana)) {
haveDuplicate = true;
break;
}
}
if (!haveDuplicate) {
manaChoices.add(thisMana);
}
}
}
// got an only one best option?
if (manaChoices.size() == 1) {
return manaChoices.get(0);
}
// if we are simulating mana payment for the human controller, use the first mana available (and avoid prompting the human player)
if (!ai.getController().isAI()) {
return manaChoices.get(0);
}
// Let them choose then
return ai.getController().chooseManaFromPool(manaChoices);
}
private static List<Pair<Mana, Integer>> selectManaToPayFor(final ManaPool manapool, final ManaCostShard shard,
final SpellAbility saBeingPaidFor, String restriction, final byte colorsPaid, Map<String, Integer> xManaCostPaidByColor) {
final List<Pair<Mana, Integer>> weightedOptions = new ArrayList<>();
for (final Mana thisMana : manapool) {
if (shard == ManaCostShard.COLORED_X && !ManaCostBeingPaid.canColoredXShardBePaidByColor(MagicColor.toShortString(thisMana.getColor()), xManaCostPaidByColor)) {
continue;
}
if (!manapool.canPayForShardWithColor(shard, thisMana.getColor())) {
continue;
}
if (thisMana.getManaAbility() != null && !thisMana.getManaAbility().meetsSpellAndShardRestrictions(saBeingPaidFor, shard, thisMana.getColor())) {
continue;
}
boolean canPay = manapool.canPayForShardWithColor(shard, thisMana.getColor());
if (!canPay || (shard.isSnow() && !thisMana.isSnow())) {
continue;
}
if (StringUtils.isNotBlank(restriction) && !thisMana.getSourceCard().getType().hasStringType(restriction)) {
continue;
}
int weight = 0;
if (colorsPaid == -1) {
// prefer colorless mana to spend
weight += thisMana.isColorless() ? 5 : 0;
} else {
// get more colors for converge
weight += (thisMana.getColor() | colorsPaid) != colorsPaid ? 5 : 0;
}
// prefer restricted mana to spend
if (thisMana.isRestricted()) {
weight += 2;
}
// Spend non-snow mana first
if (!thisMana.isSnow()) {
weight += 1;
}
weightedOptions.add(Pair.of(thisMana, weight));
}
return weightedOptions;
}
public static void handleOfferings(final SpellAbility sa, boolean test, boolean costIsPaid) {
if (sa.isOffering() && sa.getSacrificedAsOffering() != null) {
final Card offering = sa.getSacrificedAsOffering();
offering.setUsedToPay(false);
if (costIsPaid && !test) {
sa.getHostCard().getGame().getAction().sacrifice(offering, sa, false, null, null);
}
sa.resetSacrificedAsOffering();
}
if (sa.isEmerge() && sa.getSacrificedAsEmerge() != null) {
final Card emerge = sa.getSacrificedAsEmerge();
emerge.setUsedToPay(false);
if (costIsPaid && !test) {
sa.getHostCard().getGame().getAction().sacrifice(emerge, sa, false, null, null);
}
sa.resetSacrificedAsEmerge();
}
}
}

View File

@@ -18,6 +18,7 @@
package forge.game.mana;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -33,6 +34,7 @@ import forge.card.mana.ManaCostShard;
import forge.game.Game;
import forge.game.GlobalRuleChange;
import forge.game.ability.AbilityKey;
import forge.game.cost.CostPayment;
import forge.game.event.EventValueChangeType;
import forge.game.event.GameEventManaPool;
import forge.game.event.GameEventZone;
@@ -289,6 +291,16 @@ public class ManaPool extends ManaConversionMatrix implements Iterable<Mana> {
return true;
}
public static void refundMana(List<Mana> manaSpent, Player ai, SpellAbility sa) {
if (sa.getHostCard() != null) {
sa.getHostCard().setCanCounter(true);
}
for (final Mana m : manaSpent) {
ai.getManaPool().addMana(m);
}
manaSpent.clear();
}
public final void refundManaPaid(final SpellAbility sa) {
// Send all mana back to your mana pool, before accounting for it.
final List<Mana> manaPaid = sa.getPayingMana();
@@ -342,6 +354,47 @@ public class ManaPool extends ManaConversionMatrix implements Iterable<Mana> {
return shard.canBePaidWithManaOfColor((byte)0);
}
/**
* Checks if the given mana cost can be paid from floating mana.
* @param cost mana cost to pay for
* @param sa ability to pay for
* @param ai activating player
* @param test actual payment is made if this is false
* @param manaSpentToPay list of mana spent
* @return whether the floating mana is sufficient to pay the cost fully
*/
public static boolean payManaCostFromPool(final ManaCostBeingPaid cost, final SpellAbility sa, final Player ai,
final boolean test, List<Mana> manaSpentToPay) {
final boolean hasConverge = sa.getHostCard().hasConverge();
List<ManaCostShard> unpaidShards = cost.getUnpaidShards();
Collections.sort(unpaidShards); // most difficult shards must come first
for (ManaCostShard part : unpaidShards) {
if (part != ManaCostShard.X) {
if (cost.isPaid()) {
continue;
}
// get a mana of this type from floating, bail if none available
final Mana mana = CostPayment.getMana(ai, part, sa, cost.getSourceRestriction(), hasConverge ? cost.getColorsPaid() : -1, cost.getXManaCostPaidByColor());
if (mana != null) {
if (ai.getManaPool().tryPayCostWithMana(sa, cost, mana, test)) {
manaSpentToPay.add(0, mana);
}
}
}
}
if (cost.isPaid()) {
// refund any mana taken from mana pool when test
if (test) {
refundMana(manaSpentToPay, ai, sa);
}
CostPayment.handleOfferings(sa, test, cost.isPaid());
return true;
}
return false;
}
@Override
public Iterator<Mana> iterator() {
return floatingMana.values().iterator();

View File

@@ -1,14 +1,14 @@
package forge.gamemodes.match.input;
import java.util.ArrayList;
import java.util.List;
import forge.ai.ComputerUtilMana;
import forge.card.mana.ManaAtom;
import forge.card.mana.ManaCostShard;
import forge.game.cost.CostPayment;
import forge.game.mana.Mana;
import forge.game.mana.ManaConversionMatrix;
import forge.game.mana.ManaCostBeingPaid;
import forge.game.mana.ManaPool;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.localinstance.properties.ForgePreferences;
@@ -28,7 +28,9 @@ public class InputPayManaOfCostPayment extends InputPayMana {
// CR 118.3c forced cast must use pool mana
// TODO this introduces a small risk to lock up the GUI if the human "wastes" enough mana for abilities like Doubling Cube
if (spellAbility.getPayCosts().isMandatory()) {
mandatory = ComputerUtilMana.payManaCostFromPool(new ManaCostBeingPaid(cost), spellAbility, payer, true, new ArrayList<Mana>());
List<Mana> refund = new ArrayList<>();
mandatory = ManaPool.payManaCostFromPool(new ManaCostBeingPaid(cost), spellAbility, payer, true, refund);
ManaPool.refundMana(refund, payer, spellAbility);
}
// Set Mana cost being paid for SA to be able to reference it later