Merge branch 'gameCacheRework' into 'master'

Human Play: try to remove usage of game.getCard

See merge request core-developers/forge!2804
This commit is contained in:
Michael Kamensky
2020-05-24 11:02:52 +00:00
53 changed files with 596 additions and 753 deletions

View File

@@ -769,7 +769,7 @@ public class AiController {
return AiPlayDecision.CantPlayAi;
}
}
else if (sa.getPayCosts() != null){
else {
Cost payCosts = sa.getPayCosts();
ManaCost mana = payCosts.getTotalMana();
if (mana != null) {
@@ -858,7 +858,7 @@ public class AiController {
int neededMana = 0;
boolean dangerousRecurringCost = false;
Cost costWithBuyback = sa.getPayCosts() != null ? sa.getPayCosts().copy() : Cost.Zero;
Cost costWithBuyback = sa.getPayCosts().copy();
for (OptionalCostValue opt : GameActionUtil.getOptionalCostValues(sa)) {
if (opt.getType() == OptionalCost.Buyback) {
costWithBuyback.add(opt.getCost());
@@ -907,8 +907,8 @@ public class AiController {
public int compare(final SpellAbility a, final SpellAbility b) {
// sort from highest cost to lowest
// we want the highest costs first
int a1 = a.getPayCosts() == null ? 0 : a.getPayCosts().getTotalMana().getCMC();
int b1 = b.getPayCosts() == null ? 0 : b.getPayCosts().getTotalMana().getCMC();
int a1 = a.getPayCosts().getTotalMana().getCMC();
int b1 = b.getPayCosts().getTotalMana().getCMC();
// deprioritize SAs explicitly marked as preferred to be activated last compared to all other SAs
if (a.hasParam("AIActivateLast") && !b.hasParam("AIActivateLast")) {
@@ -927,12 +927,12 @@ public class AiController {
// deprioritize pump spells with pure energy cost (can be activated last,
// since energy is generally scarce, plus can benefit e.g. Electrostatic Pummeler)
int a2 = 0, b2 = 0;
if (a.getApi() == ApiType.Pump && a.getPayCosts() != null && a.getPayCosts().getCostEnergy() != null) {
if (a.getApi() == ApiType.Pump && a.getPayCosts().getCostEnergy() != null) {
if (a.getPayCosts().hasOnlySpecificCostType(CostPayEnergy.class)) {
a2 = a.getPayCosts().getCostEnergy().convertAmount();
}
}
if (b.getApi() == ApiType.Pump && b.getPayCosts() != null && b.getPayCosts().getCostEnergy() != null) {
if (b.getApi() == ApiType.Pump && b.getPayCosts().getCostEnergy() != null) {
if (b.getPayCosts().hasOnlySpecificCostType(CostPayEnergy.class)) {
b2 = b.getPayCosts().getCostEnergy().convertAmount();
}
@@ -956,8 +956,7 @@ public class AiController {
return 1;
}
if (a.getHostCard().equals(b.getHostCard()) && a.getApi() == b.getApi()
&& a.getPayCosts() != null && b.getPayCosts() != null) {
if (a.getHostCard().equals(b.getHostCard()) && a.getApi() == b.getApi()) {
// Cheaper Spectacle costs should be preferred
// FIXME: Any better way to identify that these are the same ability, one with Spectacle and one not?
// (looks like it's not a full-fledged alternative cost as such, and is not processed with other alt costs)
@@ -1479,7 +1478,7 @@ public class AiController {
}
for (SpellAbility sa : card.getSpellAbilities()) {
if (sa.getPayCosts() != null && sa.isAbility()
if (sa.isAbility()
&& sa.getPayCosts().getCostMana() != null
&& sa.getPayCosts().getCostMana().getMana().getCMC() > 0
&& (!sa.getPayCosts().hasTapCost() || !isTapLand)

View File

@@ -494,7 +494,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
@Override
public boolean apply(Card card) {
for (final SpellAbility sa : card.getSpellAbilities()) {
if (sa.isManaAbility() && sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()) {
if (sa.isManaAbility() && sa.getPayCosts().hasTapCost()) {
return true;
}
}

View File

@@ -113,9 +113,7 @@ public class ComputerUtilAbility {
List<SpellAbility> priorityAltSa = Lists.newArrayList();
List<SpellAbility> otherAltSa = Lists.newArrayList();
for (SpellAbility altSa : saAltCosts) {
if (altSa.getPayCosts() == null || sa.getPayCosts() == null) {
otherAltSa.add(altSa);
} else if (sa.getPayCosts().isOnlyManaCost()
if (sa.getPayCosts().isOnlyManaCost()
&& altSa.getPayCosts().isOnlyManaCost() && sa.getPayCosts().getTotalMana().compareTo(altSa.getPayCosts().getTotalMana()) == 1) {
// the alternative cost is strictly cheaper, so why not? (e.g. Omniscience etc.)
priorityAltSa.add(altSa);

View File

@@ -1474,7 +1474,7 @@ public class ComputerUtilCard {
if (totalPowerUnblocked >= opp.getLife()) {
return true;
} else if (totalPowerUnblocked > dmg && sa.getHostCard() != null && sa.getHostCard().isInPlay()) {
if (sa.getPayCosts() != null && sa.getPayCosts().hasNoManaCost()) {
if (sa.getPayCosts().hasNoManaCost()) {
return true; // always activate abilities which cost no mana and which can increase unblocked damage
}
}
@@ -1785,10 +1785,6 @@ public class ComputerUtilCard {
for (Card c : otb) {
for (SpellAbility sa : c.getSpellAbilities()) {
if (sa.getPayCosts() == null) {
continue;
}
CostPayEnergy energyCost = sa.getPayCosts().getCostEnergy();
if (energyCost != null) {
int amount = energyCost.convertAmount();

View File

@@ -1028,7 +1028,7 @@ public class ComputerUtilCombat {
return power;
}
for (SpellAbility ability : blocker.getAllSpellAbilities()) {
if (!(ability instanceof AbilityActivated) || ability.getPayCosts() == null) {
if (!(ability instanceof AbilityActivated)) {
continue;
}
if (ability.hasParam("ActivationPhases") || ability.hasParam("SorcerySpeed") || ability.hasParam("ActivationZone")) {
@@ -1203,7 +1203,7 @@ public class ComputerUtilCombat {
return toughness;
}
for (SpellAbility ability : blocker.getAllSpellAbilities()) {
if (!(ability instanceof AbilityActivated) || ability.getPayCosts() == null) {
if (!(ability instanceof AbilityActivated)) {
continue;
}
@@ -1426,7 +1426,7 @@ public class ComputerUtilCombat {
return power;
}
for (SpellAbility ability : attacker.getAllSpellAbilities()) {
if (!(ability instanceof AbilityActivated) || ability.getPayCosts() == null) {
if (!(ability instanceof AbilityActivated)) {
continue;
}
if (ability.hasParam("ActivationPhases") || ability.hasParam("SorcerySpeed") || ability.hasParam("ActivationZone")) {
@@ -1662,7 +1662,7 @@ public class ComputerUtilCombat {
return toughness;
}
for (SpellAbility ability : attacker.getAllSpellAbilities()) {
if (!(ability instanceof AbilityActivated) || ability.getPayCosts() == null) {
if (!(ability instanceof AbilityActivated)) {
continue;
}
@@ -2517,7 +2517,7 @@ public class ComputerUtilCombat {
final Player controller = combatant.getController();
for (Card c : controller.getCardsIn(ZoneType.Battlefield)) {
for (SpellAbility ability : c.getAllSpellAbilities()) {
if (!(ability instanceof AbilityActivated) || ability.getPayCosts() == null) {
if (!(ability instanceof AbilityActivated)) {
continue;
}
if (ability.getApi() != ApiType.Pump) {

View File

@@ -529,7 +529,7 @@ public class ComputerUtilCost {
public boolean apply(Card card) {
boolean hasManaSa = false;
for (final SpellAbility sa : card.getSpellAbilities()) {
if (sa.isManaAbility() && sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()) {
if (sa.isManaAbility() && sa.getPayCosts().hasTapCost()) {
hasManaSa = true;
break;
}

View File

@@ -507,16 +507,10 @@ public class ComputerUtilMana {
}
}
else {
if (saPayment.getPayCosts() != null) {
final CostPayment pay = new CostPayment(saPayment.getPayCosts(), saPayment);
if (!pay.payComputerCosts(new AiCostDecision(ai, saPayment))) {
saList.remove(saPayment);
continue;
}
}
else {
System.err.println("Ability " + saPayment + " from " + saPayment.getHostCard() + " had NULL as payCost");
saPayment.getHostCard().tap();
final CostPayment pay = new CostPayment(saPayment.getPayCosts(), saPayment);
if (!pay.payComputerCosts(new AiCostDecision(ai, saPayment))) {
saList.remove(saPayment);
continue;
}
ai.getGame().getStack().addAndUnfreeze(saPayment);
@@ -837,10 +831,9 @@ public class ComputerUtilMana {
if (checkCosts) {
// Check if AI can still play this mana ability
ma.setActivatingPlayer(ai);
if (ma.getPayCosts() != null) { // if the AI can't pay the additional costs skip the mana ability
if (!CostPayment.canPayAdditionalCosts(ma.getPayCosts(), ma)) {
return false;
}
// if the AI can't pay the additional costs skip the mana ability
if (!CostPayment.canPayAdditionalCosts(ma.getPayCosts(), ma)) {
return false;
}
else if (sourceCard.isTapped()) {
return false;
@@ -1144,7 +1137,7 @@ public class ComputerUtilMana {
ManaCostBeingPaid cost = new ManaCostBeingPaid(mana, restriction);
// Tack xMana Payments into mana here if X is a set value
if (sa.getPayCosts() != null && (cost.getXcounter() > 0 || extraMana > 0)) {
if (cost.getXcounter() > 0 || extraMana > 0) {
int manaToAdd = 0;
if (test && extraMana > 0) {
final int multiplicator = Math.max(cost.getXcounter(), 1);
@@ -1218,7 +1211,7 @@ public class ComputerUtilMana {
for (SpellAbility ma : src.getManaAbilities()) {
ma.setActivatingPlayer(p);
if (!checkPlayable || ma.canPlay()) {
int costsToActivate = ma.getPayCosts() != null && ma.getPayCosts().getCostMana() != null ? ma.getPayCosts().getCostMana().convertAmount() : 0;
int costsToActivate = ma.getPayCosts().getCostMana() != null ? ma.getPayCosts().getCostMana().convertAmount() : 0;
int producedMana = ma.getParamOrDefault("Produced", "").split(" ").length;
int producedAmount = AbilityUtils.calculateAmount(src, ma.getParamOrDefault("Amount", "1"), ma);
@@ -1594,7 +1587,7 @@ public class ComputerUtilMana {
}
public static int determineMaxAffordableX(Player ai, SpellAbility sa) {
if (sa.getPayCosts() == null || sa.getPayCosts().getCostMana() == null) {
if (sa.getPayCosts().getCostMana() == null) {
return -1;
}

View File

@@ -242,7 +242,7 @@ public class CreatureEvaluator implements Function<Card, Integer> {
&& "+X".equals(sa.getParam("NumDef"))
&& !sa.usesTargeting()
&& (!sa.hasParam("Defined") || "Self".equals(sa.getParam("Defined")))) {
if (sa.getPayCosts() != null && sa.getPayCosts().hasOnlySpecificCostType(CostPayEnergy.class)) {
if (sa.getPayCosts().hasOnlySpecificCostType(CostPayEnergy.class)) {
// Electrostatic Pummeler, can be expanded for similar cards
int initPower = getEffectivePower(sa.getHostCard());
int pumpedPower = initPower;

View File

@@ -1216,7 +1216,7 @@ public class PlayerControllerAi extends PlayerController {
public List<OptionalCostValue> chooseOptionalCosts(SpellAbility chosen,
List<OptionalCostValue> optionalCostValues) {
List<OptionalCostValue> chosenOptCosts = Lists.newArrayList();
Cost costSoFar = chosen.getPayCosts() != null ? chosen.getPayCosts().copy() : Cost.Zero;
Cost costSoFar = chosen.getPayCosts().copy();
for (OptionalCostValue opt : optionalCostValues) {
// Choose the optional cost if it can be paid (to be improved later, check for playability and other conditions perhaps)
@@ -1255,7 +1255,7 @@ public class PlayerControllerAi extends PlayerController {
// TODO: improve the logic depending on the keyword and the playability of the cost-modified SA (enough targets present etc.)
int chosenAmount = 0;
Cost costSoFar = sa.getPayCosts() != null ? sa.getPayCosts().copy() : Cost.Zero;
Cost costSoFar = sa.getPayCosts().copy();
for (int i = 0; i < max; i++) {
costSoFar.add(cost);

View File

@@ -267,7 +267,7 @@ public abstract class SpellAbilityAi {
// TODO probably also consider if winter orb or similar are out
if (sa.getPayCosts() == null || sa instanceof AbilitySub) {
if (sa instanceof AbilitySub) {
return true; // This is only true for Drawbacks and triggers
}

View File

@@ -247,7 +247,6 @@ public class AnimateAi extends SpellAbilityAi {
final Player ai = sa.getActivatingPlayer();
final PhaseHandler ph = ai.getGame().getPhaseHandler();
final boolean alwaysActivatePWAbility = sa.hasParam("Planeswalker")
&& sa.getPayCosts() != null
&& sa.getPayCosts().hasSpecificCostType(CostPutCounter.class)
&& sa.getTargetRestrictions() != null
&& sa.getTargetRestrictions().getMinTargets(sa.getHostCard(), sa) == 0;

View File

@@ -398,7 +398,7 @@ public class AttachAi extends SpellAbilityAi {
if (!c.isCreature() && !c.getType().hasSubtype("Vehicle") && !c.isTapped()) {
// try to identify if this thing can actually tap
for (SpellAbility ab : c.getAllSpellAbilities()) {
if (ab.getPayCosts() != null && ab.getPayCosts().hasTapCost()) {
if (ab.getPayCosts().hasTapCost()) {
return true;
}
}
@@ -560,7 +560,7 @@ public class AttachAi extends SpellAbilityAi {
@Override
public boolean apply(final Card c) {
for (final SpellAbility sa : c.getSpellAbilities()) {
if (sa.isAbility() && sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()) {
if (sa.isAbility() && sa.getPayCosts().hasTapCost()) {
return false;
}
}

View File

@@ -1065,8 +1065,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
if (destination.equals(ZoneType.Exile) || origin.contains(ZoneType.Battlefield)) {
// don't rush bouncing stuff when not going to attack
if (!immediately && sa.getPayCosts() != null
&& game.getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)
if (!immediately && game.getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)
&& game.getPhaseHandler().isPlayerTurn(ai)
&& ai.getCreaturesInPlay().isEmpty()) {
return false;
@@ -1103,8 +1102,8 @@ public class ChangeZoneAi extends SpellAbilityAi {
}
}
boolean doWithoutTarget = sa.hasParam("Planeswalker") && sa.getTargetRestrictions() != null
&& sa.getTargetRestrictions().getMinTargets(source, sa) == 0 && sa.getPayCosts() != null
boolean doWithoutTarget = sa.hasParam("Planeswalker") && sa.usesTargeting()
&& sa.getTargetRestrictions().getMinTargets(source, sa) == 0
&& sa.getPayCosts().hasSpecificCostType(CostPutCounter.class);
if (list.isEmpty() && !doWithoutTarget) {
@@ -1790,6 +1789,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
}
public boolean doReturnCommanderLogic(SpellAbility sa, Player aiPlayer) {
@SuppressWarnings("unchecked")
Map<AbilityKey, Object> originalParams = (Map<AbilityKey, Object>)sa.getReplacingObject(AbilityKey.OriginalParams);
SpellAbility causeSa = (SpellAbility)originalParams.get(AbilityKey.Cause);
SpellAbility causeSub = null;

View File

@@ -233,7 +233,7 @@ public class ChooseCardAi extends SpellAbilityAi {
return false;
}
for (SpellAbility sa : c.getAllSpellAbilities()) {
if (sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()) {
if (sa.getPayCosts().hasTapCost()) {
return false;
}
}

View File

@@ -29,8 +29,8 @@ public class CopySpellAbilityAi extends SpellAbilityAi {
final SpellAbility top = game.getStack().peekAbility();
if (top != null
&& top.getPayCosts() != null && top.getPayCosts().getCostMana() != null
&& sa.getPayCosts() != null && sa.getPayCosts().getCostMana() != null
&& top.getPayCosts().getCostMana() != null
&& sa.getPayCosts().getCostMana() != null
&& top.getPayCosts().getCostMana().getMana().getCMC() >= sa.getPayCosts().getCostMana().getMana().getCMC() + diff) {
// The copied spell has a significantly higher CMC than the copy spell, consider copying
chance = 100;

View File

@@ -454,7 +454,6 @@ public class CountersPutAi extends SpellAbilityAi {
// but try to do it in Main 2 then so that the AI has a chance to play creatures first.
if (list.isEmpty()
&& sa.hasParam("Planeswalker")
&& sa.getPayCosts() != null
&& sa.getPayCosts().hasOnlySpecificCostType(CostPutCounter.class)
&& sa.isTargetNumberValid()
&& sa.getTargets().getNumTargeted() == 0
@@ -706,7 +705,7 @@ public class CountersPutAi extends SpellAbilityAi {
SpellAbility testSa = sa;
int countX = 0;
int nonXGlyphs = 0;
while (testSa != null && testSa.getPayCosts() != null && countX == 0) {
while (testSa != null && countX == 0) {
countX = testSa.getPayCosts().getTotalMana().countX();
nonXGlyphs = testSa.getPayCosts().getTotalMana().getGlyphCount() - countX;
testSa = testSa.getSubAbility();

View File

@@ -120,7 +120,7 @@ public class CountersPutAllAi extends SpellAbilityAi {
//Check for cards that could profit from the ability
PhaseHandler phase = ai.getGame().getPhaseHandler();
if (type.equals("P1P1") && sa.isAbility() && source.isCreature()
&& sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()
&& sa.getPayCosts().hasTapCost()
&& sa instanceof AbilitySub
&& (!phase.getNextTurn().equals(ai)
|| phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS))) {

View File

@@ -53,7 +53,6 @@ public class DamageAllAi extends SpellAbilityAi {
x = source.getCounters(CounterType.LOYALTY);
}
if (x == -1) {
Player bestOpp = determineOppToKill(ai, sa, source, dmg);
if (determineOppToKill(ai, sa, source, dmg) != null) {
// we already know we can kill a player, so go for it
return true;
@@ -138,7 +137,7 @@ public class DamageAllAi extends SpellAbilityAi {
}
int minGain = 200; // The minimum gain in destroyed creatures
if (sa.getPayCosts() != null && sa.getPayCosts().isReusuableResource()) {
if (sa.getPayCosts().isReusuableResource()) {
if (computerList.isEmpty()) {
minGain = 10; // nothing to lose
// no creatures to lose and player can be damaged

View File

@@ -285,7 +285,7 @@ public class DamageDealAi extends DamageAiBase {
}
}
if ("XCountersDamage".equals(logic) && sa.getPayCosts() != null) {
if ("XCountersDamage".equals(logic)) {
// Check to ensure that we have enough counters to remove per the defined PayX
for (CostPart part : sa.getPayCosts().getCostParts()) {
if (part instanceof CostRemoveCounter) {
@@ -449,7 +449,7 @@ public class DamageDealAi extends DamageAiBase {
int pwScore = curLoyalty * 10;
for (SpellAbility sa : pw.getSpellAbilities()) {
if (sa.hasParam("Ultimate") && sa.getPayCosts() != null) {
if (sa.hasParam("Ultimate")) {
Integer loyaltyCost = 0;
CostRemoveCounter remLoyalty = sa.getPayCosts().getCostPartByType(CostRemoveCounter.class);
if (remLoyalty != null) {
@@ -794,8 +794,7 @@ public class DamageDealAi extends DamageAiBase {
if (((phase.is(PhaseType.END_OF_TURN) && phase.getNextTurn().equals(ai))
|| (SpellAbilityAi.isSorcerySpeed(sa) && phase.is(PhaseType.MAIN2))
|| ("PingAfterAttack".equals(logic) && phase.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) && phase.isPlayerTurn(ai))
|| sa.getPayCosts() == null || immediately
|| this.shouldTgtP(ai, sa, dmg, noPrevention)) &&
|| immediately || shouldTgtP(ai, sa, dmg, noPrevention)) &&
(!avoidTargetP(ai, sa))) {
tcs.add(enemy);
if (divided) {
@@ -1126,8 +1125,7 @@ public class DamageDealAi extends DamageAiBase {
continue;
}
// currently works only with cards that don't have additional costs (only mana is supported)
if (ab.getPayCosts() != null
&& (ab.getPayCosts().hasNoManaCost() || ab.getPayCosts().hasOnlySpecificCostType(CostPartMana.class))) {
if (ab.getPayCosts().hasNoManaCost() || ab.getPayCosts().hasOnlySpecificCostType(CostPartMana.class)) {
String dmgDef = "0";
if (ab.getApi() == ApiType.DealDamage) {
dmgDef = ab.getParamOrDefault("NumDmg", "0");
@@ -1151,7 +1149,7 @@ public class DamageDealAi extends DamageAiBase {
}
// FIXME: should it also check restrictions for targeting players?
ManaCost costSa = sa.getPayCosts() != null ? sa.getPayCosts().getTotalMana() : ManaCost.NO_COST;
ManaCost costSa = sa.getPayCosts().getTotalMana();
ManaCost costAb = ab.getPayCosts().getTotalMana(); // checked for null above
ManaCost total = ManaCost.combine(costSa, costAb);
SpellAbility combinedAb = ab.copyWithDefinedCost(new Cost(total, false));

View File

@@ -262,22 +262,20 @@ public class DrawAi extends SpellAbilityAi {
// Draw up to max hand size but leave at least 3 in library
numCards = Math.min(computerMaxHandSize - computerHandSize, computerLibrarySize - 3);
if (sa.getPayCosts() != null) {
if (sa.getPayCosts().hasSpecificCostType(CostPayLife.class)) {
// [Necrologia, Pay X Life : Draw X Cards]
// Don't draw more than what's "safe" and don't risk a near death experience
// Maybe would be better to check for "serious danger" and take more risk?
while ((ComputerUtil.aiLifeInDanger(ai, false, numCards) && (numCards > 0))) {
numCards--;
}
} else if (sa.getPayCosts().hasSpecificCostType(CostSacrifice.class)) {
// [e.g. Krav, the Unredeemed and other cases which say "Sacrifice X creatures: draw X cards]
// TODO: Add special logic to limit/otherwise modify the ChosenX value here
if (sa.getPayCosts().hasSpecificCostType(CostPayLife.class)) {
// [Necrologia, Pay X Life : Draw X Cards]
// Don't draw more than what's "safe" and don't risk a near death experience
// Maybe would be better to check for "serious danger" and take more risk?
while ((ComputerUtil.aiLifeInDanger(ai, false, numCards) && (numCards > 0))) {
numCards--;
}
} else if (sa.getPayCosts().hasSpecificCostType(CostSacrifice.class)) {
// [e.g. Krav, the Unredeemed and other cases which say "Sacrifice X creatures: draw X cards]
// TODO: Add special logic to limit/otherwise modify the ChosenX value here
// Skip this ability if nothing is to be chosen for sacrifice
if (numCards <= 0) {
return false;
}
// Skip this ability if nothing is to be chosen for sacrifice
if (numCards <= 0) {
return false;
}
}

View File

@@ -113,7 +113,7 @@ public class EffectAi extends SpellAbilityAi {
} else if (logic.equals("SpellCopy")) {
// fetch Instant or Sorcery and AI has reason to play this turn
// does not try to get itself
final ManaCost costSa = sa.getPayCosts() != null ? sa.getPayCosts().getTotalMana() : ManaCost.NO_COST;
final ManaCost costSa = sa.getPayCosts().getTotalMana();
final int count = CardLists.count(ai.getCardsIn(ZoneType.Hand), new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
@@ -135,7 +135,7 @@ public class EffectAi extends SpellAbilityAi {
AiPlayDecision decision = ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(ab);
// see if we can pay both for this spell and for the Effect spell we're considering
if (decision == AiPlayDecision.WillPlay || decision == AiPlayDecision.WaitForMain2) {
ManaCost costAb = ab.getPayCosts() != null ? ab.getPayCosts().getTotalMana() : ManaCost.NO_COST;
ManaCost costAb = ab.getPayCosts().getTotalMana();
ManaCost total = ManaCost.combine(costSa, costAb);
SpellAbility combinedAb = ab.copyWithDefinedCost(new Cost(total, false));
// can we pay both costs?

View File

@@ -88,7 +88,6 @@ public class LifeGainAi extends SpellAbilityAi {
if (lifeCritical
&& sa.isAbility()
&& sa.getHostCard() != null && sa.getHostCard().isCreature()
&& sa.getPayCosts() != null
&& (sa.getPayCosts().hasSpecificCostType(CostRemoveCounter.class) || sa.getPayCosts().hasSpecificCostType(CostSacrifice.class))) {
if (!game.getStack().isEmpty()) {
SpellAbility saTop = game.getStack().peekAbility();

View File

@@ -81,7 +81,7 @@ public class ManaEffectAi extends SpellAbilityAi {
return true; // handled elsewhere, does not meet the standard requirements
}
return sa.getPayCosts() != null && sa.getPayCosts().hasNoManaCost() && sa.getPayCosts().isReusuableResource()
return sa.getPayCosts().hasNoManaCost() && sa.getPayCosts().isReusuableResource()
&& sa.getSubAbility() == null && ComputerUtil.playImmediately(ai, sa);
// return super.checkApiLogic(ai, sa);
}
@@ -119,7 +119,7 @@ public class ManaEffectAi extends SpellAbilityAi {
int numCounters = 0;
int manaSurplus = 0;
if ("XChoice".equals(host.getSVar("X"))
&& sa.getPayCosts() != null && sa.getPayCosts().hasSpecificCostType(CostRemoveCounter.class)) {
&& sa.getPayCosts().hasSpecificCostType(CostRemoveCounter.class)) {
CounterType ctrType = CounterType.KI; // Petalmane Baku
for (CostPart part : sa.getPayCosts().getCostParts()) {
if (part instanceof CostRemoveCounter) {

View File

@@ -84,8 +84,8 @@ public class PlayAi extends SpellAbilityAi {
return ComputerUtil.targetPlayableSpellCard(ai, cards, sa, sa.hasParam("WithoutManaCost"));
} else if (logic.startsWith("NeedsChosenCard")) {
int minCMC = 0;
if (sa.getPayCosts() != null && sa.getPayCosts().getCostMana() != null) {
minCMC = sa.getPayCosts().getCostMana().getMana().getCMC();
if (sa.getPayCosts().getCostMana() != null) {
minCMC = sa.getPayCosts().getTotalMana().getCMC();
}
validOpts = CardLists.filter(validOpts, CardPredicates.greaterCMC(minCMC));
return chooseSingleCard(ai, sa, validOpts, sa.hasParam("Optional"), null) != null;
@@ -156,9 +156,7 @@ public class PlayAi extends SpellAbilityAi {
if (sa.hasParam("WithoutManaCost")) {
// Try to avoid casting instants and sorceries with X in their cost, since X will be assumed to be 0.
if (!(spell instanceof SpellPermanent)) {
if (spell.getPayCosts() != null
&& spell.getPayCosts().getCostMana() != null
&& spell.getPayCosts().getCostMana().getMana().countX() > 0) {
if (spell.getPayCosts().getTotalMana().countX() > 0) {
continue;
}
}

View File

@@ -202,7 +202,7 @@ public class ProtectAi extends SpellAbilityAi {
if (game.getStack().isEmpty()) {
// If the cost is tapping, don't activate before declare
// attack/block
if ((sa.getPayCosts() != null) && sa.getPayCosts().hasTapCost()) {
if (sa.getPayCosts().hasTapCost()) {
if (game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)
&& game.getPhaseHandler().isPlayerTurn(ai)) {
list.remove(sa.getHostCard());

View File

@@ -515,7 +515,7 @@ public class PumpAi extends PumpAiBase {
if (game.getStack().isEmpty()) {
// If the cost is tapping, don't activate before declare
// attack/block
if (sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()) {
if (sa.getPayCosts().hasTapCost()) {
if (game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)
&& game.getPhaseHandler().isPlayerTurn(ai)) {
list.remove(sa.getHostCard());

View File

@@ -94,7 +94,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
List<Card> attackers = CardLists.filter(ai.getCreaturesInPlay(), new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (c.equals(sa.getHostCard()) && sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()
if (c.equals(sa.getHostCard()) && sa.getPayCosts().hasTapCost()
&& (combat == null || !combat.isAttacking(c))) {
return false;
}
@@ -112,7 +112,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
List<Card> attackers = CardLists.filter(ai.getCreaturesInPlay(), new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (c.equals(sa.getHostCard()) && sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()
if (c.equals(sa.getHostCard()) && sa.getPayCosts().hasTapCost()
&& (combat == null || !combat.isAttacking(c))) {
return false;
}

View File

@@ -26,7 +26,7 @@ public class RearrangeTopOfLibraryAi extends SpellAbilityAi {
final PhaseHandler ph = aiPlayer.getGame().getPhaseHandler();
final Card source = sa.getHostCard();
if (source.isPermanent() && sa.getRestrictions().isInstantSpeed() && sa.getPayCosts() != null
if (source.isPermanent() && sa.getRestrictions().isInstantSpeed()
&& (sa.getPayCosts().hasTapCost() || sa.getPayCosts().hasManaCost())) {
// If it has an associated cost, try to only do this before own turn
if (!(ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn() == aiPlayer)) {

View File

@@ -46,12 +46,10 @@ public class ScryAi extends SpellAbilityAi {
// and right before the beginning of AI's turn, if possible, to avoid mana locking the AI and also to
// try to scry right before drawing a card. Also, avoid tapping creatures in the AI's turn, if possible,
// even if there's no mana cost.
if (sa.getPayCosts() != null) {
if (sa.getPayCosts().hasTapCost()
&& (sa.getPayCosts().hasManaCost() || (sa.getHostCard() != null && sa.getHostCard().isCreature()))
&& !SpellAbilityAi.isSorcerySpeed(sa)) {
return ph.getNextTurn() == ai && ph.is(PhaseType.END_OF_TURN);
}
if (sa.getPayCosts().hasTapCost()
&& (sa.getPayCosts().hasManaCost() || (sa.getHostCard() != null && sa.getHostCard().isCreature()))
&& !SpellAbilityAi.isSorcerySpeed(sa)) {
return ph.getNextTurn() == ai && ph.is(PhaseType.END_OF_TURN);
}
// AI logic to scry in Main 1 if there is no better option, otherwise scry at opponent's EOT
@@ -76,8 +74,7 @@ public class ScryAi extends SpellAbilityAi {
boolean hasSomethingElse = false;
for (Card c : CardLists.filter(ai.getCardsIn(ZoneType.Hand), Predicates.not(CardPredicates.Presets.LANDS))) {
for (SpellAbility ab : c.getAllSpellAbilities()) {
if (ab.getPayCosts() != null
&& ab.getPayCosts().hasManaCost()
if (ab.getPayCosts().hasManaCost()
&& ComputerUtilMana.hasEnoughManaSourcesToCast(ab, ai)) {
// TODO: currently looks for non-Scry cards, can most certainly be made smarter.
if (ab.getApi() != ApiType.Scry) {

View File

@@ -47,12 +47,10 @@ public class SurveilAi extends SpellAbilityAi {
// and right before the beginning of AI's turn, if possible, to avoid mana locking the AI and also to
// try to scry right before drawing a card. Also, avoid tapping creatures in the AI's turn, if possible,
// even if there's no mana cost.
if (sa.getPayCosts() != null) {
if (sa.getPayCosts().hasTapCost()
&& (sa.getPayCosts().hasManaCost() || (sa.getHostCard() != null && sa.getHostCard().isCreature()))
&& !SpellAbilityAi.isSorcerySpeed(sa)) {
return ph.getNextTurn() == ai && ph.is(PhaseType.END_OF_TURN);
}
if (sa.getPayCosts().hasTapCost()
&& (sa.getPayCosts().hasManaCost() || (sa.getHostCard() != null && sa.getHostCard().isCreature()))
&& !SpellAbilityAi.isSorcerySpeed(sa)) {
return ph.getNextTurn() == ai && ph.is(PhaseType.END_OF_TURN);
}
// in the player's turn Surveil should only be done in Main1 or in Upkeep if able

View File

@@ -126,7 +126,7 @@ public abstract class TapAiBase extends SpellAbilityAi {
}
for (final SpellAbility sa : c.getSpellAbilities()) {
if (sa.isAbility() && sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()) {
if (sa.isAbility() && sa.getPayCosts().hasTapCost()) {
return true;
}
}
@@ -147,7 +147,7 @@ public abstract class TapAiBase extends SpellAbilityAi {
}
for (final SpellAbility sa : c.getSpellAbilities()) {
if (sa.isAbility() && sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()) {
if (sa.isAbility() && sa.getPayCosts().hasTapCost()) {
return true;
}
}

View File

@@ -153,12 +153,11 @@ public class UntapAi extends SpellAbilityAi {
// Try to avoid potential infinite recursion,
// e.g. Kiora's Follower untapping another Kiora's Follower and repeating infinitely
if (sa.getPayCosts() != null && sa.getPayCosts().hasOnlySpecificCostType(CostTap.class)) {
if (sa.getPayCosts().hasOnlySpecificCostType(CostTap.class)) {
CardCollection toRemove = new CardCollection();
for (Card c : untapList) {
for (SpellAbility ab : c.getAllSpellAbilities()) {
if (ab.getApi() == ApiType.Untap
&& ab.getPayCosts() != null
&& ab.getPayCosts().hasOnlySpecificCostType(CostTap.class)
&& ab.canTarget(source)) {
toRemove.add(c);

View File

@@ -81,7 +81,7 @@ public class ForgeScript {
return !cardState.getTypeWithChanges().hasSubtype(subType);
} else if (property.equals("hasActivatedAbilityWithTapCost")) {
for (final SpellAbility sa : cardState.getSpellAbilities()) {
if (sa.isAbility() && (sa.getPayCosts() != null) && sa.getPayCosts().hasTapCost()) {
if (sa.isAbility() && sa.getPayCosts().hasTapCost()) {
return true;
}
}

View File

@@ -177,19 +177,6 @@ public class Game {
playerCache.put(Integer.valueOf(id), player);
}
private final GameEntityCache<Card, CardView> cardCache = new GameEntityCache<>();
public Card getCard(CardView cardView) {
return cardCache.get(cardView);
}
public void addCard(int id, Card card) {
cardCache.put(Integer.valueOf(id), card);
}
public CardCollection getCardList(Iterable<CardView> cardViews) {
CardCollection list = new CardCollection();
cardCache.addToList(cardViews, list);
return list;
}
// methods that deal with saving, retrieving and clearing LKI information about cards on zone change
private final HashMap<Integer, Card> changeZoneLKIInfo = new HashMap<>();
public final void addChangeZoneLKIInfo(Card c) {
@@ -537,6 +524,33 @@ public class Game {
return visit.getFound(notFound);
}
private static class CardIdVisitor extends Visitor<Card> {
Card found = null;
int id;
private CardIdVisitor(final int id) {
this.id = id;
}
@Override
public boolean visit(Card object) {
if (this.id == object.getId()) {
found = object;
}
return found == null;
}
public Card getFound() {
return found;
}
}
public Card findById(int id) {
CardIdVisitor visit = new CardIdVisitor(id);
this.forEachCardInGame(visit);
return visit.getFound();
}
// Allows visiting cards in game without allocating a temporary list.
public void forEachCardInGame(Visitor<Card> visitor) {
for (final Player player : getPlayers()) {
@@ -874,7 +888,6 @@ public class Game {
}
public void clearCaches() {
cardCache.clear();
lastStateBattlefield.clear();
lastStateGraveyard.clear();

View File

@@ -13,6 +13,11 @@ public class GameEntityCache<Entity extends IIdentifiable, View extends Trackabl
public void put(Integer id, Entity entity) {
entityCache.put(id, entity);
}
public void putAll(Iterable<Entity> entities) {
for (Entity e : entities) {
put(e.getId(), e);
}
}
public void remove(Integer id) {
entityCache.remove(id);

View File

@@ -24,6 +24,12 @@ public abstract class GameEntityView extends TrackableObject {
return collection;
}
public static <T extends GameEntity, V extends GameEntityView> GameEntityViewMap<T, V> getMap(Iterable<T> spabs) {
GameEntityViewMap<T, V> gameViewCache = new GameEntityViewMap<T, V>();
gameViewCache.putAll(spabs);
return gameViewCache;
}
protected GameEntityView(final int id0, final Tracker tracker) {
super(id0, tracker);
}

View File

@@ -0,0 +1,52 @@
package forge.game;
import java.util.List;
import java.util.Map;
import com.google.common.collect.ForwardingMap;
import com.google.common.collect.Maps;
import forge.trackable.TrackableCollection;
public class GameEntityViewMap<Entity extends GameEntity, View extends GameEntityView> extends ForwardingMap<View, Entity> {
private Map<View, Entity> dataMap = Maps.newLinkedHashMap();
@Override
protected Map<View, Entity> delegate() {
return dataMap;
}
@SuppressWarnings("unchecked")
public void put(Entity e) {
this.put((View) e.getView(), e);
}
public void putAll(Iterable<Entity> entities) {
for (Entity e : entities) {
put(e);
}
}
public void remove(Entity e) {
this.remove(e.getView());
}
public void removeAll(Iterable<Entity> entities) {
for (Entity e : entities) {
remove(e);
}
}
public void addToList(Iterable<View> views, List<Entity> list) {
for (View view : views) {
Entity entity = get(view);
if (entity != null) {
list.add(entity);
}
}
}
public TrackableCollection<View> getTrackableKeys() {
return new TrackableCollection<View>(this.keySet());
}
}

View File

@@ -1344,7 +1344,7 @@ public class AbilityUtils {
Player pl = sa.getActivatingPlayer();
final Game game = pl.getGame();
if (sa.isTrigger() && sa.getParent() == null && sa.getPayCosts() != null) {
if (sa.isTrigger() && sa.getParent() == null) {
// when trigger cost are paid before the effect does resolve, need to clean the trigger
game.getTriggerHandler().resetActiveTriggers();
}

View File

@@ -295,7 +295,7 @@ public class Card extends GameEntity implements Comparable<Card> {
* @param id0 the unique id of the new card.
*/
public Card(final int id0, final Game game0) {
this(id0, null, true, game0);
this(id0, null, game0);
}
/**
@@ -307,15 +307,9 @@ public class Card extends GameEntity implements Comparable<Card> {
* @see IPaperCard
*/
public Card(final int id0, final IPaperCard paperCard0, final Game game0) {
this(id0, paperCard0, true, game0);
}
public Card(final int id0, final IPaperCard paperCard0, final boolean allowCache, final Game game0) {
super(id0);
game = game0;
if (id0 >= 0 && allowCache && game != null) {
game.addCard(id0, this);
}
paperCard = paperCard0;
view = new CardView(id0, game == null ? null : game.getTracker());
currentState = new CardState(view.getCurrentState(), this);
@@ -2083,9 +2077,7 @@ public class Card extends GameEntity implements Comparable<Card> {
if (sa.isAdventure() && state.getView().getState().equals(CardStateName.Original)) {
StringBuilder sbSA = new StringBuilder();
sbSA.append("Adventure — ").append(getState(CardStateName.Adventure).getName());
if (sa.getPayCosts() != null) {
sbSA.append(" ").append(sa.getPayCosts().toSimpleString());
}
sbSA.append(" ").append(sa.getPayCosts().toSimpleString());
sbSA.append(": ");
sbSA.append(sAbility);
sAbility = sbSA.toString();

View File

@@ -118,39 +118,6 @@ public class CardFactory {
}
/**
* <p>
* copyCardWithChangedStats
* </p>
*
* This method copies the card together with certain temporarily changed stats of the card
* (namely, changed color, changed types, changed keywords).
*
* copyCardWithChangedStats must NOT be used for ordinary card copy operations because
* according to MTG rules the changed text (including keywords, types) is not copied over
* to cards cloned by another card. However, this method is useful, for example, for certain
* triggers that demand the latest information about the changes to the card which is lost
* when the card changes its zone after GameAction::changeZone is called.
*
* @param in
* a {@link forge.game.card.Card} object.
* @param assignNewId
* a boolean
* @return a {@link forge.game.card.Card} object.
*/
public static final Card copyCardWithChangedStats(final Card in, boolean assignNewId) {
Card out = copyCard(in, assignNewId);
// Copy changed color, type, keyword arrays (useful for some triggers that require
// information about the latest state of the card as it left the battlefield)
out.setChangedCardColors(in.getChangedCardColors());
out.setChangedCardKeywords(in.getChangedCardKeywords());
out.setChangedCardTypes(in.getChangedCardTypesMap());
out.setChangedCardNames(in.getChangedCardNames());
return out;
}
/**
* <p>
* copySpellHost.
@@ -471,23 +438,6 @@ public class CardFactory {
CardFactoryUtil.addAbilityFactoryAbilities(c, face.getAbilities());
}
/**
* Create a copy of a card, including its copiable characteristics (but not
* abilities).
* @param from
* @param newOwner
* @return
*/
public static Card copyCopiableCharacteristics(final Card from, final Player newOwner) {
int id = newOwner == null ? 0 : newOwner.getGame().nextCardId();
final Card c = new Card(id, from.getPaperCard(), from.getGame());
c.setOwner(newOwner);
c.setSetCode(from.getSetCode());
copyCopiableCharacteristics(from, c);
return c;
}
/**
* Copy the copiable characteristics of one card to another, taking the
* states of both cards into account.

View File

@@ -206,7 +206,7 @@ public final class CardUtil {
.build()
);
final Card newCopy = new Card(in.getId(), in.getPaperCard(), false, in.getGame());
final Card newCopy = new Card(in.getId(), in.getPaperCard(), in.getGame());
newCopy.setSetCode(in.getSetCode());
newCopy.setOwner(in.getOwner());
newCopy.setController(in.getController(), 0);

View File

@@ -3067,7 +3067,7 @@ public class Player extends GameEntity implements Comparable<Player> {
public void createMonarchEffect() {
final PlayerZone com = getZone(ZoneType.Command);
if (monarchEffect == null) {
monarchEffect = new Card(game.nextCardId(), null, false, game);
monarchEffect = new Card(game.nextCardId(), null, game);
monarchEffect.setOwner(this);
monarchEffect.setImageKey("t:monarch");
monarchEffect.setName("The Monarch");
@@ -3163,7 +3163,7 @@ public class Player extends GameEntity implements Comparable<Player> {
final PlayerZone com = getZone(ZoneType.Command);
if(bless) {
blessingEffect = new Card(game.nextCardId(), null, false, game);
blessingEffect = new Card(game.nextCardId(), null, game);
blessingEffect.setOwner(this);
blessingEffect.setImageKey("t:blessing");
blessingEffect.setName("City's Blessing");
@@ -3257,7 +3257,7 @@ public class Player extends GameEntity implements Comparable<Player> {
final PlayerZone com = getZone(ZoneType.Command);
keywordEffect = new Card(game.nextCardId(), null, false, game);
keywordEffect = new Card(game.nextCardId(), null, game);
keywordEffect.setImmutable(true);
keywordEffect.setOwner(this);
keywordEffect.setName("Keyword Effects");

View File

@@ -34,7 +34,7 @@ public class ReplaceProduceMana extends ReplacementEffect {
//Check for tapping
if (!hasParam("NoTapCheck")) {
final SpellAbility manaAbility = (SpellAbility) runParams.get(AbilityKey.AbilityMana);
if (manaAbility == null || manaAbility.getRootAbility().getPayCosts() == null || !manaAbility.getRootAbility().getPayCosts().hasTapCost()) {
if (manaAbility == null || !manaAbility.getRootAbility().getPayCosts().hasTapCost()) {
return false;
}
}

View File

@@ -145,10 +145,8 @@ public abstract class Spell extends SpellAbility implements java.io.Serializable
return false;
}
if (this.getPayCosts() != null) {
if (!CostPayment.canPayAdditionalCosts(this.getPayCosts(), this)) {
return false;
}
if (!CostPayment.canPayAdditionalCosts(this.getPayCosts(), this)) {
return false;
}
return checkOtherRestrictions(card);

View File

@@ -442,16 +442,10 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
}
public boolean costHasX() {
if (getPayCosts() == null) {
return false;
}
return getPayCosts().hasXInAnyCostPart();
}
public boolean costHasManaX() {
if (getPayCosts() == null) {
return false;
}
if (getPayCosts().hasNoManaCost()) {
return false;
}
@@ -890,9 +884,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
clone.triggeringObjects = AbilityKey.newMap(this.triggeringObjects);
if (getPayCosts() != null) {
clone.setPayCosts(getPayCosts().copy());
}
clone.setPayCosts(getPayCosts().copy());
if (manaPart != null) {
clone.manaPart = new AbilityManaPart(host, mapParams);
}
@@ -1422,16 +1414,15 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
int maxTargets = getTargetRestrictions().getMaxTargets(hostCard, this);
int numTargets = getTargets().getNumTargeted();
if (maxTargets == 0 && this.getPayCosts() != null
&& this.getPayCosts().hasSpecificCostType(CostRemoveCounter.class)
&& this.hasSVar(this.getParam("TargetMax"))
&& this.getSVar(this.getParam("TargetMax")).startsWith("Count$CardCounters")
&& this.getHostCard() != null && this.getHostCard().hasSVar("CostCountersRemoved")) {
if (maxTargets == 0 && getPayCosts().hasSpecificCostType(CostRemoveCounter.class)
&& hasSVar(getParam("TargetMax"))
&& getSVar(getParam("TargetMax")).startsWith("Count$CardCounters")
&& getHostCard() != null && getHostCard().hasSVar("CostCountersRemoved")) {
// TODO: Current AI implementation removes the counters during payment before the
// ability is added to stack, resulting in maxTargets=0 at this point. We are
// assuming here that the AI logic specified a legal number, and that number ended
// up being in CostCountersRemoved that is created on the card during payment.
maxTargets = Integer.parseInt(this.getHostCard().getSVar("CostCountersRemoved"));
maxTargets = Integer.parseInt(getHostCard().getSVar("CostCountersRemoved"));
}
return minTargets <= numTargets && maxTargets >= numTargets;
@@ -1775,9 +1766,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
targetRestrictions.applyTargetTextChanges(this);
}
if (getPayCosts() != null) {
getPayCosts().applyTextChangeEffects(this);
}
getPayCosts().applyTextChangeEffects(this);
stackDescription = AbilityUtils.applyDescriptionTextChangeEffects(originalStackDescription, this);
description = AbilityUtils.applyDescriptionTextChangeEffects(originalDescription, this);

View File

@@ -60,7 +60,7 @@ public class TriggerTapsForMana extends Trigger {
//Check for tapping
if (!hasParam("NoTapCheck")) {
final SpellAbility manaAbility = (SpellAbility) runParams.get(AbilityKey.AbilityMana);
if (manaAbility == null || manaAbility.getRootAbility().getPayCosts() == null || !manaAbility.getRootAbility().getPayCosts().hasTapCost()) {
if (manaAbility == null || !manaAbility.getRootAbility().getPayCosts().hasTapCost()) {
return false;
}
}

View File

@@ -1,161 +0,0 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.match.input;
import forge.card.mana.ManaAtom;
import forge.card.mana.ManaCost;
import forge.card.mana.ManaCostShard;
import forge.game.Game;
import forge.game.card.Card;
import forge.game.mana.ManaCostBeingPaid;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.model.FModel;
import forge.player.PlayerControllerHuman;
import forge.properties.ForgePreferences;
import forge.util.ITriggerEvent;
import forge.util.Localizer;
//pays the cost of a card played from the player's hand
//the card is removed from the players hand if the cost is paid
//CANNOT be used for ABILITIES
public class InputPayManaSimple extends InputPayMana {
// anything that uses this should be converted to Ability_Cost
/** Constant <code>serialVersionUID=3467312982164195091L</code>. */
private static final long serialVersionUID = 3467312982164195091L;
private final Card originalCard;
private final ManaCost originalManaCost;
public InputPayManaSimple(final PlayerControllerHuman controller, final Game game, final SpellAbility sa, final ManaCostBeingPaid manaCostToPay) {
super(controller, sa, sa.getActivatingPlayer());
this.originalManaCost = manaCostToPay.toManaCost();
this.originalCard = sa.getHostCard();
if (sa.getHostCard().isCopiedSpell() && sa.isSpell()) {
this.manaCost = new ManaCostBeingPaid(ManaCost.ZERO);
game.getStack().add(this.saPaidFor);
}
else {
this.manaCost = manaCostToPay;
}
}
@Override
protected void onManaAbilityPaid() {
if (this.manaCost.isPaid()) {
this.originalCard.setSunburstValue(this.manaCost.getSunburst());
}
}
/** {@inheritDoc} */
@Override
protected final void onPlayerSelected(final Player selected, final ITriggerEvent triggerEvent) {
if (player == selected) {
if (player.canPayLife(this.phyLifeToLose + 2)) {
if (manaCost.payPhyrexian()) {
this.phyLifeToLose += 2;
} else {
if (player.hasKeyword("PayLifeInsteadOf:B") && manaCost.hasAnyKind(ManaAtom.BLACK)) {
manaCost.decreaseShard(ManaCostShard.BLACK, 1);
this.phyLifeToLose += 2;
}
}
}
this.showMessage();
}
}
/**
* <p>
* done.
* </p>
*/
@Override
protected void done() {
this.originalCard.setSunburstValue(this.manaCost.getSunburst());
if (this.phyLifeToLose > 0) {
player.payLife(this.phyLifeToLose, this.originalCard);
}
if (!this.saPaidFor.getHostCard().isCopiedSpell()) {
if (this.saPaidFor.isSpell()) {
this.saPaidFor.setHostCard(game.getAction().moveToStack(this.originalCard, null));
}
}
}
/** {@inheritDoc} */
@Override
protected final void onCancel() {
player.getManaPool().refundManaPaid(this.saPaidFor);
// Update UI
this.stop();
}
/** {@inheritDoc} */
@Override
public final void showMessage() {
if (isFinished()) { return; }
updateButtons();
if (this.manaCost.isPaid() && !new ManaCostBeingPaid(this.originalManaCost).isPaid()) {
this.done();
this.stop();
}
else {
updateMessage();
}
}
/* (non-Javadoc)
* @see forge.control.input.InputPayManaBase#updateMessage()
*/
@Override
protected String getMessage() {
final StringBuilder msg = new StringBuilder();
final Localizer localizer = Localizer.getInstance();
if (FModel.getPreferences().getPrefBoolean(ForgePreferences.FPref.UI_DETAILED_SPELLDESC_IN_PROMPT)) {
msg.append(saPaidFor.getStackDescription().replace("(Targeting ERROR)", "")).append("\n\n");
}
msg.append(localizer.getMessage("lblPayManaCost")).append(" ").append(this.manaCost.toString(false, player.getManaPool()));
if (this.phyLifeToLose > 0) {
msg.append(" ").append(String.format(localizer.getMessage("lblLifePaidForPhyrexianMana"), this.phyLifeToLose));
}
boolean isLifeInsteadBlack = player.hasKeyword("PayLifeInsteadOf:B") && manaCost.hasAnyKind(ManaAtom.BLACK);
if (manaCost.containsPhyrexianMana() || isLifeInsteadBlack) {
StringBuilder sb = new StringBuilder();
if (manaCost.containsPhyrexianMana() && !isLifeInsteadBlack) {
sb.append(localizer.getMessage("lblClickOnYourLifeTotalToPayLifeForPhyrexianMana"));
} else if (!manaCost.containsPhyrexianMana() && isLifeInsteadBlack) {
sb.append(localizer.getMessage("lblClickOnYourLifeTotalToPayLifeForBlackMana"));
} else if (manaCost.containsPhyrexianMana() && isLifeInsteadBlack) {
sb.append(localizer.getMessage("lblClickOnYourLifeTotalToPayLifeForPhyrexianOrBlackMana"));
}
msg.append("\n(").append(sb).append(")");
}
// has its own variant of checkIfPaid
return msg.toString();
}
}

View File

@@ -110,7 +110,7 @@ public class InputProxy implements Observer {
}
private Card getCard(final CardView cardView) {
return controller.getGame().getCard(cardView);
return controller.getCard(cardView);
}
public final String getActivateAction(final CardView cardView) {

View File

@@ -14,6 +14,8 @@ import com.google.common.collect.Lists;
import forge.card.CardType;
import forge.game.Game;
import forge.game.GameEntity;
import forge.game.GameEntityView;
import forge.game.GameEntityViewMap;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardCollection;
@@ -35,6 +37,7 @@ import forge.match.input.InputSelectManyBase;
import forge.util.Aggregates;
import forge.util.TextUtil;
import forge.util.collect.FCollectionView;
import forge.util.gui.SGuiChoose;
import forge.util.ITriggerEvent;
import forge.util.Localizer;
import forge.util.CardTranslation;
@@ -310,11 +313,12 @@ public class HumanCostDecision extends CostDecisionMakerBase {
if (nNeeded == 0) {
return PaymentDecision.number(0);
}
final Game game = controller.getGame();
final Player p = game.getPlayer(controller.getGui().oneOrNone(Localizer.getInstance().getMessage("lblExileFromWhoseZone", cost.getFrom().getTranslatedName()), PlayerView.getCollection(payableZone)));
if (p == null) {
GameEntityViewMap<Player, PlayerView> gameCachePlayer = GameEntityView.getMap(payableZone);
final PlayerView pv = controller.getGui().oneOrNone(Localizer.getInstance().getMessage("lblExileFromWhoseZone", cost.getFrom().getTranslatedName()), gameCachePlayer.getTrackableKeys());
if (pv == null || !gameCachePlayer.containsKey(pv)) {
return null;
}
final Player p = gameCachePlayer.get(pv);
final CardCollection typeList = CardLists.filter(list, CardPredicates.isOwner(p));
final int count = typeList.size();
@@ -322,8 +326,13 @@ public class HumanCostDecision extends CostDecisionMakerBase {
return null;
}
final CardCollection toExile = game.getCardList(controller.getGui().many(Localizer.getInstance().getMessage("lblExileFromZone", cost.getFrom().getTranslatedName()), Localizer.getInstance().getMessage("lblToBeExiled"), nNeeded, CardView.getCollection(typeList), null));
return PaymentDecision.card(toExile);
GameEntityViewMap<Card, CardView> gameCacheExile = GameEntityView.getMap(typeList);
List<CardView> views = controller.getGui().many(
Localizer.getInstance().getMessage("lblExileFromZone", cost.getFrom().getTranslatedName()),
Localizer.getInstance().getMessage("lblToBeExiled"), nNeeded, gameCacheExile.getTrackableKeys(), null);
List<Card> result = Lists.newArrayList();
gameCacheExile.addToList(views, result);
return PaymentDecision.card(result);
}
@Override
@@ -395,20 +404,17 @@ public class HumanCostDecision extends CostDecisionMakerBase {
return PaymentDecision.card(list);
}
private Card getCard(final CardView cardView) {
return controller.getGame().getCard(cardView);
}
private PaymentDecision exileFromMiscZone(final CostExile cost, final SpellAbility sa, final int nNeeded, final CardCollection typeList) {
if (typeList.size() < nNeeded) { return null; }
GameEntityViewMap<Card, CardView> gameCacheCard = GameEntityView.getMap(typeList);
final CardCollection exiled = new CardCollection();
for (int i = 0; i < nNeeded; i++) {
final Card c = getCard(controller.getGui().oneOrNone(Localizer.getInstance().getMessage("lblExileProgressFromZone", String.valueOf(i + 1), String.valueOf(nNeeded), cost.getFrom().getTranslatedName()), CardView.getCollection(typeList)));
if (c == null) { return null; }
final CardView cv = controller.getGui().oneOrNone(Localizer.getInstance().getMessage("lblExileProgressFromZone", String.valueOf(i + 1), String.valueOf(nNeeded), cost.getFrom().getTranslatedName()), gameCacheCard.getTrackableKeys());
if (cv == null || !gameCacheCard.containsKey(cv)) { return null; }
typeList.remove(c);
exiled.add(c);
exiled.add(gameCacheCard.remove(cv));
}
return PaymentDecision.card(exiled);
}
@@ -438,12 +444,17 @@ public class HumanCostDecision extends CostDecisionMakerBase {
if (ability.isOptionalTrigger()) {
min = 0;
}
final CardCollection choice = controller.getGame().getCardList(controller.getGui().many(Localizer.getInstance().getMessage("lblChooseAnExiledCardPutIntoGraveyard"), Localizer.getInstance().getMessage("lblToGraveyard"), min, c, CardView.getCollection(list), CardView.get(source)));
GameEntityViewMap<Card, CardView> gameCacheExile = GameEntityView.getMap(list);
List<CardView> views = controller.getGui().many(
Localizer.getInstance().getMessage("lblChooseAnExiledCardPutIntoGraveyard"),
Localizer.getInstance().getMessage("lblToGraveyard"), min, c, CardView.getCollection(list), CardView.get(source));
if (choice == null || choice.size() < c) {
if (views == null || views.size() < c) {
return null;
}
return PaymentDecision.card(choice);
List<Card> result = Lists.newArrayList();
gameCacheExile.addToList(views, result);
return PaymentDecision.card(result);
}
@Override
@@ -555,11 +566,12 @@ public class HumanCostDecision extends CostDecisionMakerBase {
return PaymentDecision.players(oppsThatCanGainLife);
}
final Player chosenToGain = controller.getGame().getPlayer(controller.getGui().oneOrNone(Localizer.getInstance().getMessage("lblCardChooseAnOpponentToGainNLife", CardTranslation.getTranslatedName(source.getName()), String.valueOf(c)), PlayerView.getCollection(oppsThatCanGainLife)));
if (chosenToGain == null) {
GameEntityViewMap<Player, PlayerView> gameCachePlayer = GameEntityView.getMap(oppsThatCanGainLife);
final PlayerView pv = controller.getGui().oneOrNone(Localizer.getInstance().getMessage("lblCardChooseAnOpponentToGainNLife", CardTranslation.getTranslatedName(source.getName()), String.valueOf(c)), gameCachePlayer.getTrackableKeys());
if (pv == null || !gameCachePlayer.containsKey(pv)) {
return null;
}
return PaymentDecision.players(Lists.newArrayList(chosenToGain));
return PaymentDecision.players(Lists.newArrayList(gameCachePlayer.get(pv)));
}
@Override
@@ -694,13 +706,13 @@ public class HumanCostDecision extends CostDecisionMakerBase {
}
final CardCollection chosen = new CardCollection();
GameEntityViewMap<Card, CardView> gameCacheCard = GameEntityView.getMap(typeList);
for (int i = 0; i < nNeeded; i++) {
final Card c = getCard(controller.getGui().oneOrNone(Localizer.getInstance().getMessage("lblFromZonePutToLibrary", fromZone.getTranslatedName()), CardView.getCollection(typeList)));
if (c == null) {
final CardView cv = controller.getGui().oneOrNone(Localizer.getInstance().getMessage("lblFromZonePutToLibrary", fromZone.getTranslatedName()), gameCacheCard.getTrackableKeys());
if (cv == null || !gameCacheCard.containsKey(cv)) {
return null;
}
typeList.remove(c);
chosen.add(c);
chosen.add(gameCacheCard.remove(cv));
}
return PaymentDecision.card(chosen);
}
@@ -710,10 +722,12 @@ public class HumanCostDecision extends CostDecisionMakerBase {
return PaymentDecision.number(0);
}
final Player p = controller.getGame().getPlayer(controller.getGui().oneOrNone(TextUtil.concatNoSpace(Localizer.getInstance().getMessage("lblPutCardsFromWhoseZone"), fromZone.getTranslatedName()), PlayerView.getCollection(payableZone)));
if (p == null) {
GameEntityViewMap<Player, PlayerView> gameCachePlayer = GameEntityView.getMap(payableZone);
PlayerView pv = SGuiChoose.oneOrNone(TextUtil.concatNoSpace(Localizer.getInstance().getMessage("lblPutCardsFromWhoseZone"), fromZone.getTranslatedName()), gameCachePlayer.getTrackableKeys());
if (pv == null || !gameCachePlayer.containsKey(pv)) {
return null;
}
Player p = gameCachePlayer.get(pv);
final CardCollection typeList = CardLists.filter(list, CardPredicates.isOwner(p));
if (typeList.size() < nNeeded) {
@@ -721,13 +735,13 @@ public class HumanCostDecision extends CostDecisionMakerBase {
}
final CardCollection chosen = new CardCollection();
GameEntityViewMap<Card, CardView> gameCacheCard = GameEntityView.getMap(typeList);
for (int i = 0; i < nNeeded; i++) {
final Card c = getCard(controller.getGui().oneOrNone(Localizer.getInstance().getMessage("lblPutZoneCardsToLibrary", fromZone.getTranslatedName()), CardView.getCollection(typeList)));
if (c == null) {
final CardView cv = controller.getGui().oneOrNone(Localizer.getInstance().getMessage("lblPutZoneCardsToLibrary", fromZone.getTranslatedName()), gameCacheCard.getTrackableKeys());
if (cv == null || !gameCacheCard.containsKey(cv)) {
return null;
}
typeList.remove(c);
chosen.add(c);
chosen.add(gameCacheCard.remove(cv));
}
return PaymentDecision.card(chosen);
}
@@ -1068,15 +1082,14 @@ public class HumanCostDecision extends CostDecisionMakerBase {
}
// Rift Elemental only - always removes 1 counter, so there will be no code for N counters.
final List<CardView> suspended = Lists.newArrayList();
for (final Card crd : validCards) {
if (crd.getCounters(cost.counter) > 0) {
suspended.add(CardView.get(crd));
}
GameEntityViewMap<Card, CardView> gameCacheSuspended = GameEntityView.getMap(CardLists.filter(validCards, CardPredicates.hasCounter(cost.counter)));
final CardView cv = controller.getGui().oneOrNone(Localizer.getInstance().getMessage("lblRemoveCountersFromAInZoneCard", cost.zone.getTranslatedName()), gameCacheSuspended.getTrackableKeys());
if (cv == null || !gameCacheSuspended.containsKey(cv)) {
return null;
}
final Card card = getCard(controller.getGui().oneOrNone(Localizer.getInstance().getMessage("lblRemoveCountersFromAInZoneCard", cost.zone.getTranslatedName()), suspended));
return null == card ? null : PaymentDecision.card(card, c);
return PaymentDecision.card(gameCacheSuspended.get(cv), c);
}
@Override

View File

@@ -6,6 +6,8 @@ import forge.FThreads;
import forge.card.mana.ManaCost;
import forge.game.Game;
import forge.game.GameActionUtil;
import forge.game.GameEntityView;
import forge.game.GameEntityViewMap;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.ability.effects.CharmEffect;
@@ -23,7 +25,6 @@ import forge.game.trigger.TriggerType;
import forge.game.zone.ZoneType;
import forge.match.input.InputPayMana;
import forge.match.input.InputPayManaOfCostPayment;
import forge.match.input.InputPayManaSimple;
import forge.match.input.InputSelectCardsFromList;
import forge.util.TextUtil;
import forge.util.collect.FCollectionView;
@@ -94,34 +95,9 @@ public class HumanPlay {
source.animateBestow();
}
// Need to check PayCosts, and Ability + All SubAbilities for Target
boolean newAbility = sa.getPayCosts() != null;
SpellAbility ability = sa;
while ((ability != null) && !newAbility) {
final TargetRestrictions tgt = ability.getTargetRestrictions();
newAbility |= tgt != null;
ability = ability.getSubAbility();
}
// System.out.println("Playing:" + sa.getDescription() + " of " + sa.getHostCard() + " new = " + newAbility);
if (newAbility) {
final HumanPlaySpellAbility req = new HumanPlaySpellAbility(controller, sa);
if (!req.playAbility(true, false, false)) {
if (flippedToCast && !castFaceDown) {
source.turnFaceDown(true);
}
return false;
}
} else if (payManaCostIfNeeded(controller, p, sa)) {
if (sa.isSpell() && !source.isCopiedSpell()) {
sa.setHostCard(p.getGame().getAction().moveToStack(source, sa));
}
p.getGame().getStack().add(sa);
} else {
// Failed to pay costs, revert to original state
final HumanPlaySpellAbility req = new HumanPlaySpellAbility(controller, sa);
if (!req.playAbility(true, false, false)) {
if (flippedToCast && !castFaceDown) {
source.turnFaceDown(true);
}
@@ -159,26 +135,6 @@ public class HumanPlay {
//final List<SpellAbility> abilities = GameActionUtil.getOptionalCosts(original);
}
private static boolean payManaCostIfNeeded(final PlayerControllerHuman controller, final Player p, final SpellAbility sa) {
final ManaCostBeingPaid manaCost;
if (sa.getHostCard().isCopiedSpell() && sa.isSpell()) {
manaCost = new ManaCostBeingPaid(ManaCost.ZERO);
}
else {
manaCost = new ManaCostBeingPaid(sa.getPayCosts().getTotalMana());
CostAdjustment.adjust(manaCost, sa, null, false);
}
boolean isPaid = manaCost.isPaid();
if (!isPaid) {
InputPayManaSimple inputPay = new InputPayManaSimple(controller, p.getGame(), sa, manaCost);
inputPay.showAndWait();
isPaid = inputPay.isPaid();
}
return isPaid;
}
/**
* <p>
* playSpellAbilityForFree.
@@ -193,26 +149,15 @@ public class HumanPlay {
source.setSplitStateToPlayAbility(sa);
if (sa.getPayCosts() != null) {
if (!sa.isCopied()) {
if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) {
CharmEffect.makeChoices(sa);
}
sa = AbilityUtils.addSpliceEffects(sa);
if (!sa.isCopied()) {
if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) {
CharmEffect.makeChoices(sa);
}
sa = AbilityUtils.addSpliceEffects(sa);
}
final HumanPlaySpellAbility req = new HumanPlaySpellAbility(controller, sa);
req.playAbility(mayChooseNewTargets, true, false);
}
else {
if (sa.isSpell()) {
final Card c = sa.getHostCard();
if (!c.isCopiedSpell()) {
sa.setHostCard(game.getAction().moveToStack(c, sa));
}
}
game.getStack().add(sa);
}
final HumanPlaySpellAbility req = new HumanPlaySpellAbility(controller, sa);
req.playAbility(mayChooseNewTargets, true, false);
}
/**
@@ -230,14 +175,8 @@ public class HumanPlay {
public final static void playSpellAbilityNoStack(final PlayerControllerHuman controller, final Player player, final SpellAbility sa, boolean useOldTargets) {
sa.setActivatingPlayer(player);
if (sa.getPayCosts() != null) {
final HumanPlaySpellAbility req = new HumanPlaySpellAbility(controller, sa);
req.playAbility(!useOldTargets, false, true);
}
else if (payManaCostIfNeeded(controller, player, sa)) {
AbilityUtils.resolve(sa);
}
final HumanPlaySpellAbility req = new HumanPlaySpellAbility(controller, sa);
req.playAbility(!useOldTargets, false, true);
}
// ------------------------------------------------------------------------
@@ -506,14 +445,13 @@ public class HumanPlay {
} else {
// replace this with input
CardCollection newList = new CardCollection();
GameEntityViewMap<Card, CardView> gameCacheList = GameEntityView.getMap(list);
for (int i = 0; i < nNeeded; i++) {
final Card c = p.getGame().getCard(SGuiChoose.oneOrNone(Localizer.getInstance().getMessage("lblExileFromZone", from.getTranslatedName()), CardView.getCollection(list)));
if (c == null) {
final CardView cv = SGuiChoose.oneOrNone(Localizer.getInstance().getMessage("lblExileFromZone", from.getTranslatedName()), gameCacheList.getTrackableKeys());
if (cv == null || !gameCacheList.containsKey(cv)) {
return false;
}
list.remove(c);
newList.add(c);
newList.add(gameCacheList.remove(cv));
}
costExile.payAsDecided(p, PaymentDecision.card(newList), sourceAbility);
}
@@ -543,27 +481,28 @@ public class HumanPlay {
payableZone.add(player);
}
}
Player chosen = controller.getGame().getPlayer(SGuiChoose.oneOrNone(Localizer.getInstance().getMessage("lblPutCardFromWhoseZone", from.getTranslatedName()), PlayerView.getCollection(payableZone)));
if (chosen == null) {
GameEntityViewMap<Player, PlayerView> gameCachePlayer = GameEntityView.getMap(payableZone);
PlayerView pv = SGuiChoose.oneOrNone(Localizer.getInstance().getMessage("lblPutCardFromWhoseZone", from.getTranslatedName()), gameCachePlayer.getTrackableKeys());
if (pv == null || !gameCachePlayer.containsKey(pv)) {
return false;
}
Player chosen = gameCachePlayer.get(pv);
List<Card> typeList = CardLists.filter(list, CardPredicates.isOwner(chosen));
GameEntityViewMap<Card, CardView> gameCacheTypeList = GameEntityView.getMap(typeList);
for (int i = 0; i < amount; i++) {
if (typeList.isEmpty()) {
if (gameCacheTypeList.isEmpty()) {
return false;
}
final Card c = p.getGame().getCard(SGuiChoose.oneOrNone(Localizer.getInstance().getMessage("lblPutCardToLibrary"), CardView.getCollection(typeList)));
if (c != null) {
typeList.remove(c);
p.getGame().getAction().moveToLibrary(c, Integer.parseInt(((CostPutCardToLib) part).getLibPos()), null);
}
else {
final CardView cv = SGuiChoose.oneOrNone(Localizer.getInstance().getMessage("lblPutCardToLibrary"), gameCacheTypeList.getTrackableKeys());
if (cv == null || !gameCacheTypeList.containsKey(cv)) {
return false;
}
final Card c = gameCacheTypeList.get(cv);
gameCacheTypeList.remove(c);
p.getGame().getAction().moveToLibrary(c, Integer.parseInt(((CostPutCardToLib) part).getLibPos()), null);
}
}
else { // Tainted Specter, Gurzigost, etc.

View File

@@ -114,7 +114,7 @@ public class HumanPlaySpellAbility {
ability = GameActionUtil.addExtraKeywordCost(ability);
Cost abCost = ability.getPayCosts() == null ? new Cost("0", ability.isAbility()) : ability.getPayCosts();
Cost abCost = ability.getPayCosts();
CostPayment payment = new CostPayment(abCost, ability);
// TODO Apply this to the SAStackInstance instead of the Player

View File

@@ -96,6 +96,7 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
private final Localizer localizer = Localizer.getInstance();
protected Map<SpellAbilityView, SpellAbility> spellViewCache = null;
protected GameEntityViewMap<Card, CardView> gameCache = new GameEntityViewMap<>();
public PlayerControllerHuman(final Game game0, final Player p, final LobbyPlayer lp) {
super(game0, p, lp);
@@ -151,7 +152,7 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
if (t instanceof Card) {
tempShowCard((Card) t);
} else if (t instanceof CardView) {
tempShowCard(game.getCard((CardView) t));
tempShowCard(getCard((CardView) t));
}
}
}
@@ -287,14 +288,16 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
if (defender != null && assignDamageAsIfNotBlocked(attacker)) {
map.put(null, damageDealt);
} else {
final List<CardView> vBlockers = CardView.getCollection(blockers);
if ((attacker.hasKeyword(Keyword.TRAMPLE) && defender != null) || (blockers.size() > 1)) {
GameEntityViewMap<Card, CardView> gameCacheBlockers = GameEntityView.getMap(blockers);
final CardView vAttacker = CardView.get(attacker);
final GameEntityView vDefender = GameEntityView.get(defender);
final Map<CardView, Integer> result = getGui().assignCombatDamage(vAttacker, vBlockers, damageDealt,
final Map<CardView, Integer> result = getGui().assignCombatDamage(vAttacker, gameCacheBlockers.getTrackableKeys(), damageDealt,
vDefender, overrideOrder);
for (final Entry<CardView, Integer> e : result.entrySet()) {
map.put(game.getCard(e.getKey()), e.getValue());
if (gameCacheBlockers.containsKey(e.getKey())) {
map.put(gameCacheBlockers.get(e.getKey()), e.getValue());
}
}
} else {
map.put(blockers.get(0), damageDealt);
@@ -377,9 +380,9 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
final boolean useUiPointAtCard =
(FModel.getPreferences().getPrefBoolean(FPref.UI_SELECT_FROM_CARD_DISPLAYS) && (!GuiBase.getInterface().isLibgdxPort())) ?
(cz.is(ZoneType.Battlefield) || cz.is(ZoneType.Hand) || cz.is(ZoneType.Library) ||
cz.is(ZoneType.Graveyard) || cz.is(ZoneType.Exile) || cz.is(ZoneType.Flashback) || cz.is(ZoneType.Command)) :
(cz.is(ZoneType.Hand) && cz.getPlayer() == player || cz.is(ZoneType.Battlefield));
(cz.is(ZoneType.Battlefield) || cz.is(ZoneType.Hand) || cz.is(ZoneType.Library) ||
cz.is(ZoneType.Graveyard) || cz.is(ZoneType.Exile) || cz.is(ZoneType.Flashback) || cz.is(ZoneType.Command)) :
(cz.is(ZoneType.Hand) && cz.getPlayer() == player || cz.is(ZoneType.Battlefield));
if (!useUiPointAtCard) {
return false;
}
@@ -412,23 +415,15 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
}
tempShowCards(sourceList);
final CardCollection choices = getGame().getCardList(getGui().many(title, localizer.getMessage("lblChosenCards"), min, max,
CardView.getCollection(sourceList), CardView.get(sa.getHostCard())));
GameEntityViewMap<Card, CardView> gameCachechoose = GameEntityView.getMap(sourceList);
List<CardView> views = getGui().many(title, localizer.getMessage("lblChosenCards"), min, max,
gameCachechoose.getTrackableKeys(), CardView.get(sa.getHostCard()));
endTempShowCards();
final CardCollection choices = new CardCollection();
gameCachechoose.addToList(views, choices);
return choices;
}
// pfps there should be a better way
private GameEntity convertToEntity(final GameEntityView view) {
if (view instanceof CardView) {
return game.getCard((CardView) view);
} else if (view instanceof PlayerView) {
return game.getPlayer((PlayerView) view);
} else return null;
}
@SuppressWarnings("unchecked")
@Override
public <T extends GameEntity> T chooseSingleEntityForEffect(final FCollectionView<T> optionList,
final DelayedReveal delayedReveal, final SpellAbility sa, final String title, final boolean isOptional,
@@ -454,6 +449,9 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
if (delayedReveal != null) {
tempShow(delayedReveal.getCards());
}
GameEntityViewMap<T, GameEntityView> gameCacheChoose = GameEntityView.getMap(optionList);
if (useSelectCardsInput(optionList)) {
final InputSelectEntitiesFromList<T> input = new InputSelectEntitiesFromList<>(this, isOptional ? 0 : 1, 1,
optionList, sa);
@@ -465,21 +463,25 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
}
final GameEntityView result = getGui().chooseSingleEntityForEffect(title,
GameEntityView.getEntityCollection(optionList), delayedReveal, isOptional);
gameCacheChoose.getTrackableKeys(), delayedReveal, isOptional);
endTempShowCards();
return (T) convertToEntity(result);
if (result != null || !gameCacheChoose.containsKey(result)) {
return null;
}
return gameCacheChoose.get(result);
}
@SuppressWarnings("unchecked")
@Override
public <T extends GameEntity> List<T> chooseEntitiesForEffect(final FCollectionView<T> optionList, final int min, final int max,
final DelayedReveal delayedReveal, final SpellAbility sa, final String title, final Player targetedPlayer) {
public <T extends GameEntity> List<T> chooseEntitiesForEffect(final FCollectionView<T> optionList, final int min,
final int max, final DelayedReveal delayedReveal, final SpellAbility sa, final String title,
final Player targetedPlayer) {
// useful details for debugging problems with the mass select logic
Sentry.getContext().addExtra("Card", sa.getCardView().toString());
Sentry.getContext().addExtra("SpellAbility", sa.toString());
// Human is supposed to read the message and understand from it what to // choose
// Human is supposed to read the message and understand from it what to //
// choose
if (optionList.isEmpty()) {
if (delayedReveal != null) {
reveal(delayedReveal.getCards(), delayedReveal.getZone(), delayedReveal.getOwner(),
@@ -494,29 +496,24 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
tempShow(optionList);
if (useSelectCardsInput(optionList)) {
final InputSelectEntitiesFromList<T> input = new InputSelectEntitiesFromList<>(this, min, max,
optionList, sa);
final InputSelectEntitiesFromList<T> input = new InputSelectEntitiesFromList<>(this, min, max, optionList,
sa);
input.setCancelAllowed(min == 0);
input.setMessage(MessageUtil.formatMessage(title, player, targetedPlayer));
input.showAndWait();
endTempShowCards();
return (List<T>) input.getSelected();
}
final List<GameEntityView> chosen = getGui().chooseEntitiesForEffect(title,
GameEntityView.getEntityCollection(optionList), min, max, delayedReveal);
endTempShowCards();
List<T> results = new ArrayList<>(); //pfps I'm not sure that the chosens should be modified this way
if (chosen instanceof List && chosen.size() > 0) {
for (GameEntityView entry: chosen) {
if (entry instanceof CardView) {
results.add((T)game.getCard((CardView) entry));
}
if (entry instanceof PlayerView) {
results.add((T)game.getPlayer((PlayerView) entry));
}
}
}
GameEntityViewMap<T, GameEntityView> gameCacheEntity = GameEntityView.getMap(optionList);
final List<GameEntityView> views = getGui().chooseEntitiesForEffect(title, gameCacheEntity.getTrackableKeys(), min, max, delayedReveal);
endTempShowCards();
List<T> results = Lists.newArrayList();
if (views != null) {
gameCacheEntity.addToList(views, results);
}
return results;
}
@@ -686,43 +683,50 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
@Override
public CardCollection orderBlockers(final Card attacker, final CardCollection blockers) {
GameEntityViewMap<Card, CardView> gameCacheBlockers = GameEntityView.getMap(blockers);
final CardView vAttacker = CardView.get(attacker);
getGui().setPanelSelection(vAttacker);
return game.getCardList(getGui().order(localizer.getMessage("lblChooseDamageOrderFor", CardTranslation.getTranslatedName(vAttacker.getName())), localizer.getMessage("lblDamagedFirst"),
CardView.getCollection(blockers), vAttacker));
List<CardView> chosen = getGui().order(localizer.getMessage("lblChooseDamageOrderFor", CardTranslation.getTranslatedName(vAttacker.getName())), localizer.getMessage("lblDamagedFirst"),
gameCacheBlockers.getTrackableKeys(), vAttacker);
CardCollection chosenCards = new CardCollection();
gameCacheBlockers.addToList(chosen, chosenCards);
return chosenCards;
}
@Override
public List<Card> exertAttackers(List<Card> attackers) {
HashMap<CardView, Card> mapCVtoC = new HashMap<>();
for (Card card : attackers) {
mapCVtoC.put(card.getView(), card);
}
List<CardView> chosen;
List<CardView> choices = new ArrayList<>(mapCVtoC.keySet());
chosen = getGui().order(localizer.getMessage("lblExertAttackersConfirm"), localizer.getMessage("lblExerted"), 0, choices.size(), choices, null, null, false);
List<Card> chosenCards = new ArrayList<>();
for (CardView cardView : chosen) {
chosenCards.add(mapCVtoC.get(cardView));
}
GameEntityViewMap<Card, CardView> gameCacheExert = GameEntityView.getMap(attackers);
List<CardView> chosen = getGui().order(localizer.getMessage("lblExertAttackersConfirm"), localizer.getMessage("lblExerted"),
0, gameCacheExert.size(), gameCacheExert.getTrackableKeys(), null, null, false);
List<Card> chosenCards = new CardCollection();
gameCacheExert.addToList(chosen, chosenCards);
return chosenCards;
}
@Override
public CardCollection orderBlocker(final Card attacker, final Card blocker, final CardCollection oldBlockers) {
GameEntityViewMap<Card, CardView> gameCacheBlockers = GameEntityView.getMap(oldBlockers);
final CardView vAttacker = CardView.get(attacker);
getGui().setPanelSelection(vAttacker);
return game.getCardList(getGui().insertInList(
List<CardView> chosen = getGui().insertInList(
localizer.getMessage("lblChooseBlockerAfterWhichToPlaceAttackert", CardTranslation.getTranslatedName(vAttacker.getName())),
CardView.get(blocker), CardView.getCollection(oldBlockers)));
CardView.get(blocker), CardView.getCollection(oldBlockers));
CardCollection chosenCards = new CardCollection();
gameCacheBlockers.addToList(chosen, chosenCards);
return chosenCards;
}
@Override
public CardCollection orderAttackers(final Card blocker, final CardCollection attackers) {
GameEntityViewMap<Card, CardView> gameCacheAttackers = GameEntityView.getMap(attackers);
final CardView vBlocker = CardView.get(blocker);
getGui().setPanelSelection(vBlocker);
return game.getCardList(getGui().order(localizer.getMessage("lblChooseDamageOrderFor", CardTranslation.getTranslatedName(vBlocker.getName())), localizer.getMessage("lblDamagedFirst"),
CardView.getCollection(attackers), vBlocker));
List<CardView> chosen = getGui().order(localizer.getMessage("lblChooseDamageOrderFor", CardTranslation.getTranslatedName(vBlocker.getName())), localizer.getMessage("lblDamagedFirst"),
CardView.getCollection(attackers), vBlocker);
CardCollection chosenCards = new CardCollection();
gameCacheAttackers.addToList(chosen, chosenCards);
return chosenCards;
}
@Override
@@ -739,7 +743,7 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
}
final String fm = MessageUtil.formatMessage(message, getLocalPlayerView(), owner);
if (!cards.isEmpty()) {
tempShowCards(game.getCardList(cards));
tempShowCards(getCardList(cards));
getGui().reveal(fm, cards);
endTempShowCards();
} else {
@@ -749,23 +753,27 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
}
public List<Card> manipulateCardList(final String title, final Iterable<Card> cards, final Iterable<Card> manipulable, final boolean toTop, final boolean toBottom, final boolean toAnywhere) {
Iterable<CardView> result = getGui().manipulateCardList(title, CardView.getCollection(cards), CardView.getCollection(manipulable), toTop, toBottom, toAnywhere);
return game.getCardList(result);
GameEntityViewMap<Card, CardView> gameCacheManipulate = GameEntityView.getMap(cards);
gameCacheManipulate.putAll(manipulable);
List<CardView> views = getGui().manipulateCardList(title, CardView.getCollection(cards), CardView.getCollection(manipulable), toTop, toBottom, toAnywhere);
List<Card> result = new CardCollection();
gameCacheManipulate.addToList(views, result);
return result;
}
public ImmutablePair<CardCollection, CardCollection> arrangeForMove(final String title, final FCollectionView<Card> cards, final List<Card> manipulable, final boolean topOK, final boolean bottomOK) {
List<Card> result = manipulateCardList(localizer.getMessage("lblMoveCardstoToporBbottomofLibrary"), cards, manipulable, topOK, bottomOK, false);
List<Card> result = manipulateCardList(localizer.getMessage("lblMoveCardstoToporBbottomofLibrary"), cards, manipulable, topOK, bottomOK, false);
CardCollection toBottom = new CardCollection();
CardCollection toTop = new CardCollection();
for (int i = 0; i<cards.size() && manipulable.contains(result.get(i)) ; i++ ) {
toTop.add(result.get(i));
}
if (toTop.size() < cards.size()) { // the top isn't everything
for (int i = result.size()-1; i>=0 && manipulable.contains(result.get(i)); i-- ) {
toBottom.add(result.get(i));
for (int i = 0; i<cards.size() && manipulable.contains(result.get(i)) ; i++ ) {
toTop.add(result.get(i));
}
}
return ImmutablePair.of(toTop,toBottom);
if (toTop.size() < cards.size()) { // the top isn't everything
for (int i = result.size()-1; i>=0 && manipulable.contains(result.get(i)); i-- ) {
toBottom.add(result.get(i));
}
}
return ImmutablePair.of(toTop,toBottom);
}
@Override
@@ -789,16 +797,24 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
toBottom = topN;
}
} else {
toBottom = game.getCardList(getGui().many(localizer.getMessage("lblSelectCardsToBeOutOnTheBottomOfYourLibrary"),
localizer.getMessage("lblCardsToPutOnTheBottom"), -1, CardView.getCollection(topN), null));
GameEntityViewMap<Card, CardView> cardCacheScry = GameEntityView.getMap(topN);
toBottom = new CardCollection();
List<CardView> views = getGui().many(localizer.getMessage("lblSelectCardsToBeOutOnTheBottomOfYourLibrary"),
localizer.getMessage("lblCardsToPutOnTheBottom"), -1, cardCacheScry.getTrackableKeys(), null);
cardCacheScry.addToList(views, toBottom);
topN.removeAll(toBottom);
if (topN.isEmpty()) {
toTop = null;
} else if (topN.size() == 1) {
toTop = topN;
} else {
toTop = game.getCardList(getGui().order(localizer.getMessage("lblArrangeCardsToBePutOnTopOfYourLibrary"),
localizer.getMessage("lblTopOfLibrary"), CardView.getCollection(topN), null));
GameEntityViewMap<Card, CardView> cardCacheOrder = GameEntityView.getMap(topN);
toTop = new CardCollection();
views = getGui().order(localizer.getMessage("lblArrangeCardsToBePutOnTopOfYourLibrary"),
localizer.getMessage("lblTopOfLibrary"), cardCacheOrder.getTrackableKeys(), null);
cardCacheOrder.addToList(views, toTop);
}
}
}
@@ -827,16 +843,22 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
toGrave = topN;
}
} else {
toGrave = game.getCardList(getGui().many(localizer.getMessage("lblSelectCardsToBePutIntoTheGraveyard"),
localizer.getMessage("lblCardsToPutInTheGraveyard"), -1, CardView.getCollection(topN), null));
GameEntityViewMap<Card, CardView> gameCacheSurveil = GameEntityView.getMap(topN);
toGrave = new CardCollection();
List<CardView> views = getGui().many(localizer.getMessage("lblSelectCardsToBePutIntoTheGraveyard"),
localizer.getMessage("lblCardsToPutInTheGraveyard"), -1, gameCacheSurveil.getTrackableKeys(), null);
gameCacheSurveil.addToList(views, toGrave);
topN.removeAll(toGrave);
if (topN.isEmpty()) {
toTop = null;
} else if (topN.size() == 1) {
toTop = topN;
} else {
toTop = game.getCardList(getGui().order(localizer.getMessage("lblArrangeCardsToBePutOnTopOfYourLibrary"),
localizer.getMessage("lblTopOfLibrary"), CardView.getCollection(topN), null));
GameEntityViewMap<Card, CardView> cardCacheOrder = GameEntityView.getMap(topN);
toTop = new CardCollection();
views = getGui().order(localizer.getMessage("lblArrangeCardsToBePutOnTopOfYourLibrary"),
localizer.getMessage("lblTopOfLibrary"), cardCacheOrder.getTrackableKeys(), null);
cardCacheOrder.addToList(views, toTop);
}
}
endTempShowCards();
@@ -882,32 +904,28 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
}
}
List<CardView> choices;
tempShowCards(cards);
GameEntityViewMap<Card, CardView> gameCacheMove = GameEntityView.getMap(cards);
List<CardView> choices = gameCacheMove.getTrackableKeys();
switch (destinationZone) {
case Library:
choices = getGui().order(localizer.getMessage("lblChooseOrderCardsPutIntoLibrary"), localizer.getMessage("lblClosestToTop"),
CardView.getCollection(cards), null);
choices = getGui().order(localizer.getMessage("lblChooseOrderCardsPutIntoLibrary"), localizer.getMessage("lblClosestToTop"), choices, null);
break;
case Battlefield:
choices = getGui().order(localizer.getMessage("lblChooseOrderCardsPutOntoBattlefield"), localizer.getMessage("lblPutFirst"),
CardView.getCollection(cards), null);
choices = getGui().order(localizer.getMessage("lblChooseOrderCardsPutOntoBattlefield"), localizer.getMessage("lblPutFirst"), choices, null);
break;
case Graveyard:
choices = getGui().order(localizer.getMessage("lblChooseOrderCardsPutIntoGraveyard"), localizer.getMessage("lblClosestToBottom"),
CardView.getCollection(cards), null);
choices = getGui().order(localizer.getMessage("lblChooseOrderCardsPutIntoGraveyard"), localizer.getMessage("lblClosestToBottom"), choices, null);
break;
case PlanarDeck:
choices = getGui().order(localizer.getMessage("lblChooseOrderCardsPutIntoPlanarDeck"), localizer.getMessage("lblClosestToTop"),
CardView.getCollection(cards), null);
choices = getGui().order(localizer.getMessage("lblChooseOrderCardsPutIntoPlanarDeck"), localizer.getMessage("lblClosestToTop"), choices, null);
break;
case SchemeDeck:
choices = getGui().order(localizer.getMessage("lblChooseOrderCardsPutIntoSchemeDeck"), localizer.getMessage("lblClosestToTop"),
CardView.getCollection(cards), null);
choices = getGui().order(localizer.getMessage("lblChooseOrderCardsPutIntoSchemeDeck"), localizer.getMessage("lblClosestToTop"), choices, null);
break;
case Stack:
choices = getGui().order(localizer.getMessage("lblChooseOrderCopiesCast"), localizer.getMessage("lblPutFirst"), CardView.getCollection(cards),
null);
choices = getGui().order(localizer.getMessage("lblChooseOrderCopiesCast"), localizer.getMessage("lblPutFirst"), choices, null);
break;
default:
System.out.println("ZoneType " + destinationZone + " - Not Ordered");
@@ -915,7 +933,9 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
return cards;
}
endTempShowCards();
return game.getCardList(choices);
CardCollection result = new CardCollection();
gameCacheMove.addToList(choices, result);
return result;
}
@Override
@@ -923,10 +943,12 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
final CardCollection valid, final int min, final int max) {
if (p != player) {
tempShowCards(valid);
final CardCollection choices = game
.getCardList(getGui().many(String.format(localizer.getMessage("lblChooseMinCardToDiscard"), min),
localizer.getMessage("lblDiscarded"), min, min, CardView.getCollection(valid), null));
GameEntityViewMap<Card, CardView> gameCacheDiscard = GameEntityView.getMap(valid);
List<CardView> views = getGui().many(String.format(localizer.getMessage("lblChooseMinCardToDiscard"), min),
localizer.getMessage("lblDiscarded"), min, min, gameCacheDiscard.getTrackableKeys(), null);
endTempShowCards();
final CardCollection choices = new CardCollection();
gameCacheDiscard.addToList(views, choices);
return choices;
}
@@ -949,18 +971,19 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
cntChoice.add(Integer.valueOf(i));
}
final int chosenAmount = getGui().one(localizer.getMessage("lblDelveHowManyCards"), cntChoice.build()).intValue();
for (int i = 0; i < chosenAmount; i++) {
final CardView nowChosen = getGui().oneOrNone(localizer.getMessage("lblExileWhichCard", String.valueOf(i + 1), String.valueOf(chosenAmount)), CardView.getCollection(grave));
if (nowChosen == null) {
GameEntityViewMap<Card, CardView> gameCacheGrave = GameEntityView.getMap(grave);
for (int i = 0; i < chosenAmount; i++) {
String title = localizer.getMessage("lblExileWhichCard", String.valueOf(i + 1), String.valueOf(chosenAmount));
final CardView nowChosen = getGui().oneOrNone(title, gameCacheGrave.getTrackableKeys());
if (nowChosen == null || !gameCacheGrave.containsKey(nowChosen)) {
// User canceled,abort delving.
toExile.clear();
break;
}
final Card card = game.getCard(nowChosen);
grave.remove(card);
toExile.add(card);
toExile.add(gameCacheGrave.remove(nowChosen));
}
return toExile;
}
@@ -1355,6 +1378,8 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
@Override
public List<SpellAbility> chooseSaToActivateFromOpeningHand(final List<SpellAbility> usableFromOpeningHand) {
final CardCollection srcCards = new CardCollection();
for (final SpellAbility sa : usableFromOpeningHand) {
srcCards.add(sa.getHostCard());
@@ -1363,10 +1388,15 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
if (srcCards.isEmpty()) {
return result;
}
GameEntityViewMap<Card, CardView> gameCacheOpenHand = GameEntityView.getMap(srcCards);
final List<CardView> chosen = getGui().many(localizer.getMessage("lblChooseCardsActivateOpeningHandandOrder"),
localizer.getMessage("lblActivateFirst"), -1, CardView.getCollection(srcCards), null);
for (final CardView view : chosen) {
final Card c = game.getCard(view);
if (!gameCacheOpenHand.containsKey(view)) {
continue;
}
final Card c = gameCacheOpenHand.get(view);
for (final SpellAbility sa : usableFromOpeningHand) {
if (sa.getHostCard() == c) {
result.add(sa);
@@ -2160,12 +2190,14 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
public void modifyCountersOnPermanent(boolean subtract) {
final String titleMsg = subtract ? localizer.getMessage("lblRemoveCountersFromWhichCard") : localizer.getMessage("lblAddCountersToWhichCard");
final CardCollectionView cards = game.getCardsIn(ZoneType.Battlefield);
final Card card = game
.getCard(getGui().oneOrNone(titleMsg, CardView.getCollection(cards)));
if (card == null) {
GameEntityViewMap<Card, CardView> gameCacheCounters = GameEntityView.getMap(game.getCardsIn(ZoneType.Battlefield));
final CardView cv = getGui().oneOrNone(titleMsg, gameCacheCounters.getTrackableKeys());
if (cv == null || !gameCacheCounters.containsKey(cv)) {
return;
}
final Card card = gameCacheCounters.get(cv);
final ImmutableList<CounterType> counters = subtract ? ImmutableList.copyOf(card.getCounters().keySet())
: CounterType.values;
@@ -2247,11 +2279,14 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
*/
@Override
public void setPlayerLife() {
final Player player = game.getPlayer(
getGui().oneOrNone(localizer.getMessage("lblSetLifeforWhichPlayer"), PlayerView.getCollection(game.getPlayers())));
if (player == null) {
GameEntityViewMap<Player, PlayerView> gameCachePlayer = GameEntityView.getMap(game.getPlayers());
final PlayerView pv = getGui().oneOrNone(localizer.getMessage("lblSetLifeforWhichPlayer"), gameCachePlayer.getTrackableKeys());
if (pv == null || !gameCachePlayer.containsKey(pv)) {
return;
}
final Player player = gameCachePlayer.get(pv);
final Integer life = getGui().getInteger(localizer.getMessage("lblSetLifetoWhat"), 0);
if (life == null) {
@@ -2375,12 +2410,21 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
}
}
final Player p = repeatLast ? lastAddedPlayer
: game.getPlayer(getGui().oneOrNone(message,
PlayerView.getCollection(game.getPlayers())));
if (p == null) {
return;
Player pOld = lastAddedPlayer;
if (repeatLast) {
if (pOld == null) {
return;
}
} else {
GameEntityViewMap<Player, PlayerView> gameCachePlayer = GameEntityView.getMap(game.getPlayers());
PlayerView pv = getGui().oneOrNone(message, gameCachePlayer.getTrackableKeys());
if (pv == null || !gameCachePlayer.containsKey(pv)) {
return;
}
pOld = gameCachePlayer.get(pv);
}
final Player p = pOld;
final CardDb carddb = FModel.getMagicDb().getCommonCards();
final List<ICardFace> faces = Lists.newArrayList(carddb.getAllFaces());
@@ -2483,30 +2527,33 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
*/
@Override
public void exileCardsFromHand() {
final Player p = game.getPlayer(getGui().oneOrNone(localizer.getMessage("lblExileCardsFromPlayerHandConfirm"),
PlayerView.getCollection(game.getPlayers())));
if (p == null) {
GameEntityViewMap<Player, PlayerView> gameCachePlayer = GameEntityView.getMap(game.getPlayers());
final PlayerView pv = getGui().oneOrNone(localizer.getMessage("lblExileCardsFromPlayerHandConfirm"),
gameCachePlayer.getTrackableKeys());
if (pv == null || !gameCachePlayer.containsKey(pv)) {
return;
}
Player p = gameCachePlayer.get(pv);
final CardCollection selection;
GameEntityViewMap<Card, CardView> gameCacheExile = GameEntityView.getMap(p.getCardsIn(ZoneType.Hand));
CardCollectionView cardsInHand = p.getCardsIn(ZoneType.Hand);
selection = game.getCardList(getGui().many(localizer.getMessage("lblChooseCardsExile"), localizer.getMessage("lblDiscarded"), 0, -1,
CardView.getCollection(cardsInHand), null));
List<CardView> views = getGui().many(localizer.getMessage("lblChooseCardsExile"), localizer.getMessage("lblDiscarded"), 0, -1,
gameCacheExile.getTrackableKeys(), null);
if (selection != null && selection.size() > 0) {
for (Card c : selection) {
if (c == null) {
continue;
}
if (game.getAction().moveTo(ZoneType.Exile, c, null) != null) {
StringBuilder sb = new StringBuilder();
sb.append(p).append(" exiles ").append(c).append(" due to Dev Cheats.");
game.getGameLog().add(GameLogEntryType.DISCARD, sb.toString());
} else {
game.getGameLog().add(GameLogEntryType.INFORMATION, "DISCARD CHEAT ERROR");
}
final CardCollection selection = new CardCollection();
gameCacheExile.addToList(views, selection);
for (Card c : selection) {
if (c == null) {
continue;
}
if (game.getAction().moveTo(ZoneType.Exile, c, null) != null) {
StringBuilder sb = new StringBuilder();
sb.append(p).append(" exiles ").append(c).append(" due to Dev Cheats.");
game.getGameLog().add(GameLogEntryType.DISCARD, sb.toString());
} else {
game.getGameLog().add(GameLogEntryType.INFORMATION, "DISCARD CHEAT ERROR");
}
}
}
@@ -2518,30 +2565,33 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
*/
@Override
public void exileCardsFromBattlefield() {
final Player p = game.getPlayer(getGui().oneOrNone(localizer.getMessage("lblExileCardsFromPlayerBattlefieldConfirm"),
PlayerView.getCollection(game.getPlayers())));
if (p == null) {
GameEntityViewMap<Player, PlayerView> gameCachePlayer = GameEntityView.getMap(game.getPlayers());
final PlayerView pv = getGui().oneOrNone(localizer.getMessage("lblExileCardsFromPlayerBattlefieldConfirm"),
gameCachePlayer.getTrackableKeys());
if (pv == null || !gameCachePlayer.containsKey(pv)) {
return;
}
Player p = gameCachePlayer.get(pv);
final CardCollection selection;
GameEntityViewMap<Card, CardView> gameCacheExile = GameEntityView.getMap(p.getCardsIn(ZoneType.Battlefield));
CardCollectionView cardsInPlay = p.getCardsIn(ZoneType.Battlefield);
selection = game.getCardList(getGui().many(localizer.getMessage("lblChooseCardsExile"), localizer.getMessage("lblDiscarded"), 0, -1,
CardView.getCollection(cardsInPlay), null));
List<CardView> views = getGui().many(localizer.getMessage("lblChooseCardsExile"), localizer.getMessage("lblDiscarded"), 0, -1,
gameCacheExile.getTrackableKeys(), null);
if (selection != null && selection.size() > 0) {
for (Card c : selection) {
if (c == null) {
continue;
}
if (game.getAction().moveTo(ZoneType.Exile, c, null) != null) {
StringBuilder sb = new StringBuilder();
sb.append(p).append(" exiles ").append(c).append(" due to Dev Cheats.");
game.getGameLog().add(GameLogEntryType.ZONE_CHANGE, sb.toString());
} else {
game.getGameLog().add(GameLogEntryType.INFORMATION, "EXILE FROM PLAY CHEAT ERROR");
}
final CardCollection selection = new CardCollection();
gameCacheExile.addToList(views, selection);
for (Card c : selection) {
if (c == null) {
continue;
}
if (game.getAction().moveTo(ZoneType.Exile, c, null) != null) {
StringBuilder sb = new StringBuilder();
sb.append(p).append(" exiles ").append(c).append(" due to Dev Cheats.");
game.getGameLog().add(GameLogEntryType.ZONE_CHANGE, sb.toString());
} else {
game.getGameLog().add(GameLogEntryType.INFORMATION, "EXILE FROM PLAY CHEAT ERROR");
}
}
}
@@ -2553,33 +2603,36 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
*/
@Override
public void removeCardsFromGame() {
final Player p = game.getPlayer(getGui().oneOrNone(localizer.getMessage("lblRemoveCardBelongingWitchPlayer"),
PlayerView.getCollection(game.getPlayers())));
if (p == null) {
GameEntityViewMap<Player, PlayerView> gameCachePlayer = GameEntityView.getMap(game.getPlayers());
final PlayerView pv = getGui().oneOrNone(localizer.getMessage("lblRemoveCardBelongingWitchPlayer"),
gameCachePlayer.getTrackableKeys());
if (pv == null || !gameCachePlayer.containsKey(pv)) {
return;
}
Player p = gameCachePlayer.get(pv);
final String zone = getGui().one(localizer.getMessage("lblRemoveCardFromWhichZone"),
Arrays.asList("Hand", "Battlefield", "Library", "Graveyard", "Exile"));
final CardCollection selection;
CardCollectionView cards = p.getCardsIn(ZoneType.smartValueOf(zone));
selection = game.getCardList(getGui().many(localizer.getMessage("lblChooseCardsRemoveFromGame"), localizer.getMessage("lblRemoved"), 0, -1,
CardView.getCollection(cards), null));
GameEntityViewMap<Card, CardView> gameCacheExile = GameEntityView.getMap(cards);
List<CardView> views = getGui().many(localizer.getMessage("lblChooseCardsRemoveFromGame"), localizer.getMessage("lblRemoved"), 0, -1,
gameCacheExile.getTrackableKeys(), null);
if (selection != null && selection.size() > 0) {
for (Card c : selection) {
if (c == null) {
continue;
}
c.getZone().remove(c);
c.ceaseToExist();
final CardCollection selection = new CardCollection();
gameCacheExile.addToList(views, selection);
StringBuilder sb = new StringBuilder();
sb.append(p).append(" removes ").append(c).append(" from game due to Dev Cheats.");
game.getGameLog().add(GameLogEntryType.ZONE_CHANGE, sb.toString());
for (Card c : selection) {
if (c == null) {
continue;
}
c.getZone().remove(c);
c.ceaseToExist();
StringBuilder sb = new StringBuilder();
sb.append(p).append(" removes ").append(c).append(" from game due to Dev Cheats.");
game.getGameLog().add(GameLogEntryType.ZONE_CHANGE, sb.toString());
}
}
@@ -2590,11 +2643,13 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
*/
@Override
public void riggedPlanarRoll() {
final Player player = game.getPlayer(
getGui().oneOrNone(localizer.getMessage("lblWhichPlayerShouldRoll"), PlayerView.getCollection(game.getPlayers())));
if (player == null) {
GameEntityViewMap<Player, PlayerView> gameCachePlayer = GameEntityView.getMap(game.getPlayers());
final PlayerView pv = getGui().oneOrNone(localizer.getMessage("lblWhichPlayerShouldRoll"), gameCachePlayer.getTrackableKeys());
if (pv == null || !gameCachePlayer.containsKey(pv)) {
return;
}
final Player player = gameCachePlayer.get(pv);
final PlanarDice res = getGui().oneOrNone(localizer.getMessage("lblChooseResult"), PlanarDice.values);
if (res == null) {
@@ -2868,7 +2923,7 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
@Override
public void reorderHand(final CardView card, final int index) {
final PlayerZone hand = player.getZone(ZoneType.Hand);
hand.reorder(game.getCard(card), index);
hand.reorder(getCard(card), index);
player.updateZoneForView(hand);
}
@@ -2880,24 +2935,19 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
@Override
public List<Card> chooseCardsForSplice(SpellAbility sa, List<Card> cards) {
HashMap<CardView, Card> mapCVtoC = new HashMap<>();
for (Card card : cards) {
mapCVtoC.put(card.getView(), card);
}
List<CardView> choices = new ArrayList<>(mapCVtoC.keySet());
List<CardView> chosen;
chosen = getGui().many(
GameEntityViewMap<Card, CardView> gameCacheSplice = GameEntityView.getMap(cards);
List<CardView> chosen = getGui().many(
localizer.getMessage("lblChooseCardstoSpliceonto"),
localizer.getMessage("lblChosenCards"),
0,
choices.size(),
choices,
gameCacheSplice.size(),
gameCacheSplice.getTrackableKeys(),
sa.getHostCard().getView()
);
List<Card> chosenCards = new ArrayList<>();
for (CardView cardView : chosen) {
chosenCards.add(mapCVtoC.get(cardView));
}
List<Card> chosenCards = new CardCollection();
gameCacheSplice.addToList(chosen, chosenCards);
return chosenCards;
}
@@ -2942,5 +2992,25 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
return result;
}
public Card getCard(final CardView cardView) {
if (gameCache.containsKey(cardView)) {
return gameCache.get(cardView);
}
final Card c = getGame().findById(cardView.getId());
gameCache.put(cardView, c);
return c;
}
public CardCollection getCardList(Iterable<CardView> cardViews) {
CardCollection result = new CardCollection();
for(CardView cardView : cardViews){
final Card c = this.getCard(cardView);
if (c != null) {
result.add(c);
}
}
return result;
}
}

View File

@@ -21,6 +21,8 @@ import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import forge.game.Game;
import forge.game.GameEntity;
import forge.game.GameEntityView;
import forge.game.GameEntityViewMap;
import forge.game.GameObject;
import forge.game.card.Card;
import forge.game.card.CardUtil;
@@ -175,6 +177,8 @@ public class TargetSelection {
// Send in a list of valid cards, and popup a choice box to target
final Game game = ability.getActivatingPlayer().getGame();
GameEntityViewMap<Card, CardView> gameCacheChooseCard = GameEntityView.getMap(choices);
final List<CardView> crdsBattle = Lists.newArrayList();
final List<CardView> crdsExile = Lists.newArrayList();
final List<CardView> crdsGrave = Lists.newArrayList();
@@ -245,7 +249,10 @@ public class TargetSelection {
}
if (chosen instanceof CardView) {
ability.getTargets().add(game.getCard((CardView) chosen));
if (!gameCacheChooseCard.containsKey(chosen)) {
return false;
}
ability.getTargets().add(gameCacheChooseCard.get((CardView) chosen));
}
return true;
}