fix incorrect mana bills for human (eg: ai plays Mana Leak, with Goblin Electromancer in play)

This commit is contained in:
Maxmtg
2014-02-23 14:12:53 +00:00
parent d72e5d0f5d
commit 4ff887959d
11 changed files with 197 additions and 213 deletions

2
.gitattributes vendored
View File

@@ -491,6 +491,7 @@ forge-game/src/main/java/forge/game/event/GameEventZone.java -text
forge-game/src/main/java/forge/game/event/IGameEventVisitor.java -text
forge-game/src/main/java/forge/game/event/package-info.java -text
forge-game/src/main/java/forge/game/mana/Mana.java svneol=native#text/plain
forge-game/src/main/java/forge/game/mana/ManaCostAdjustment.java -text
forge-game/src/main/java/forge/game/mana/ManaCostBeingPaid.java svneol=native#text/plain
forge-game/src/main/java/forge/game/mana/ManaPool.java svneol=native#text/plain
forge-game/src/main/java/forge/game/mana/package-info.java svneol=native#text/plain
@@ -554,7 +555,6 @@ forge-game/src/main/java/forge/game/staticability/StaticAbilityCantAttackBlock.j
forge-game/src/main/java/forge/game/staticability/StaticAbilityCantBeCast.java svneol=native#text/plain
forge-game/src/main/java/forge/game/staticability/StaticAbilityCantTarget.java -text
forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java svneol=native#text/plain
forge-game/src/main/java/forge/game/staticability/StaticAbilityCostChange.java -text
forge-game/src/main/java/forge/game/staticability/StaticAbilityETBTapped.java -text
forge-game/src/main/java/forge/game/staticability/StaticAbilityMayLookAt.java -text
forge-game/src/main/java/forge/game/staticability/StaticAbilityPreventDamage.java svneol=native#text/plain

View File

@@ -19,6 +19,7 @@ import forge.game.cost.Cost;
import forge.game.cost.CostPartMana;
import forge.game.cost.CostPayment;
import forge.game.mana.Mana;
import forge.game.mana.ManaCostAdjustment;
import forge.game.mana.ManaCostBeingPaid;
import forge.game.mana.ManaPool;
import forge.game.player.Player;
@@ -633,7 +634,7 @@ public class ComputerUtilMana {
restriction = payCosts.getCostMana().getRestiction();
}
ManaCostBeingPaid cost = new ManaCostBeingPaid(mana, restriction);
cost.applySpellCostChange(sa, test);
ManaCostAdjustment.adjust(cost, sa, test);
final Card card = sa.getHostCard();
// Tack xMana Payments into mana here if X is a set value

View File

@@ -717,9 +717,9 @@ public class PlayerControllerAi extends PlayerController {
}
@Override
public boolean payManaCost(ManaCost toPay, CostPartMana costPartMana, SpellAbility sa, String prompt /* ai needs hints as well */ ) {
public boolean payManaCost(ManaCost toPay, CostPartMana costPartMana, SpellAbility sa, String prompt /* ai needs hints as well */, boolean isActivatedSa ) {
// TODO Auto-generated method stub
ManaCostBeingPaid cost = ComputerUtilMana.calculateManaCost(sa, false, 0);
ManaCostBeingPaid cost = isActivatedSa ? ComputerUtilMana.calculateManaCost(sa, false, 0) : new ManaCostBeingPaid(toPay);
return ComputerUtilMana.payManaCost(cost, sa, player);
}

View File

@@ -128,7 +128,7 @@ public class CostPartMana extends CostPart {
sa.clearManaPaid();
// decision not used here, the whole payment is interactive!
return payer.getController().payManaCost(this, sa, null);
return payer.getController().payManaCost(this, sa, null, true);
}
}

View File

@@ -1,40 +1,181 @@
/*
* 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.game.staticability;
package forge.game.mana;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import com.google.common.collect.Lists;
import forge.card.mana.ManaCostShard;
import forge.game.Game;
import forge.game.card.Card;
import forge.game.card.CardFactoryUtil;
import forge.game.mana.ManaCostBeingPaid;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.player.Player;
import forge.game.spellability.AbilityActivated;
import forge.game.spellability.Spell;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.staticability.StaticAbility;
import forge.game.zone.ZoneType;
import java.util.List;
import java.util.Map;
public class ManaCostAdjustment {
public static final void adjust(ManaCostBeingPaid cost, final SpellAbility sa, boolean test) {
final Game game = sa.getActivatingPlayer().getGame();
// Beached
final Card originalCard = sa.getHostCard();
if (sa.isXCost() && !originalCard.isCopiedSpell()) {
originalCard.setXManaCostPaid(0);
}
if (sa.isTrigger()) {
return;
}
if (sa.isSpell()) {
if (sa.isDelve()) {
final Player pc = originalCard.getController();
final List<Card> mutableGrave = new ArrayList<Card>(pc.getCardsIn(ZoneType.Graveyard));
final List<Card> toExile = pc.getController().chooseCardsToDelve(cost.getColorlessManaAmount(), mutableGrave);
for (final Card c : toExile) {
cost.decreaseColorlessMana(1);
if (!test) {
pc.getGame().getAction().exile(c);
}
}
}
else if (sa.getHostCard().hasKeyword("Convoke")) {
adjustCostByConvoke(cost, sa);
}
} // isSpell
List<Card> cardsOnBattlefield = Lists.newArrayList(game.getCardsIn(ZoneType.Battlefield));
cardsOnBattlefield.addAll(game.getCardsIn(ZoneType.Stack));
cardsOnBattlefield.addAll(game.getCardsIn(ZoneType.Command));
if (!cardsOnBattlefield.contains(originalCard)) {
cardsOnBattlefield.add(originalCard);
}
final ArrayList<StaticAbility> raiseAbilities = new ArrayList<StaticAbility>();
final ArrayList<StaticAbility> reduceAbilities = new ArrayList<StaticAbility>();
final ArrayList<StaticAbility> setAbilities = new ArrayList<StaticAbility>();
// Sort abilities to apply them in proper order
for (Card c : cardsOnBattlefield) {
final ArrayList<StaticAbility> staticAbilities = c.getStaticAbilities();
for (final StaticAbility stAb : staticAbilities) {
if (stAb.getMapParams().get("Mode").equals("RaiseCost")) {
raiseAbilities.add(stAb);
}
else if (stAb.getMapParams().get("Mode").equals("ReduceCost")) {
reduceAbilities.add(stAb);
}
else if (stAb.getMapParams().get("Mode").equals("SetCost")) {
setAbilities.add(stAb);
}
}
}
// Raise cost
for (final StaticAbility stAb : raiseAbilities) {
applyAbility(stAb, "RaiseCost", sa, cost);
}
// Reduce cost
for (final StaticAbility stAb : reduceAbilities) {
applyAbility(stAb, "ReduceCost", sa, cost);
}
if (sa.isSpell() && sa.isOffering()) { // cost reduction from offerings
adjustCostByOffering(cost, sa);
}
// Set cost (only used by Trinisphere) is applied last
for (final StaticAbility stAb : setAbilities) {
applyAbility(stAb, "SetCost", sa, cost);
}
} // GetSpellCostChange
/**
* The Class StaticAbility_CantBeCast.
* Apply ability.
*
* @param mode
* the mode
* @param sa
* the SpellAbility
* @param originalCost
* the originalCost
* @return the modified ManaCost
*/
public class StaticAbilityCostChange {
private static final void applyAbility(StaticAbility stAb, final String mode, final SpellAbility sa, final ManaCostBeingPaid originalCost) {
// don't apply the ability if it hasn't got the right mode
if (!stAb.getMapParams().get("Mode").equals(mode)) {
return;
}
if (stAb.isSuppressed() || !stAb.checkConditions()) {
return;
}
if (mode.equals("RaiseCost")) {
applyRaiseCostAbility(stAb, sa, originalCost);
}
if (mode.equals("ReduceCost")) {
applyReduceCostAbility(stAb, sa, originalCost);
}
if (mode.equals("SetCost")) { //Set cost is only used by Trinisphere
applyRaiseCostAbility(stAb, sa, originalCost);
}
}
private static void adjustCostByConvoke(ManaCostBeingPaid cost, final SpellAbility sa) {
List<Card> untappedCreats = CardLists.filter(sa.getActivatingPlayer().getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES);
untappedCreats = CardLists.filter(untappedCreats, CardPredicates.Presets.UNTAPPED);
Map<Card, ManaCostShard> convokedCards = sa.getActivatingPlayer().getController().chooseCardsForConvoke(sa, cost.toManaCost(), untappedCreats);
// Convoked creats are tapped here with triggers suppressed,
// Then again when payment is done(In InputPayManaCost.done()) with suppression cleared.
// This is to make sure that triggers go off at the right time
// AND that you can't use mana tapabilities of convoked creatures to pay the convoked cost.
for (final Entry<Card, ManaCostShard> conv : convokedCards.entrySet()) {
sa.addTappedForConvoke(conv.getKey());
cost.decreaseShard(conv.getValue(), 1);
conv.getKey().setTapped(true);
}
}
private static void adjustCostByOffering(final ManaCostBeingPaid cost, final SpellAbility sa) {
String offeringType = "";
for (String kw : sa.getHostCard().getKeyword()) {
if (kw.endsWith(" offering")) {
offeringType = kw.split(" ")[0];
break;
}
}
Card toSac = null;
List<Card> canOffer = CardLists.filter(sa.getActivatingPlayer().getCardsIn(ZoneType.Battlefield),
CardPredicates.isType(offeringType));
final List<Card> toSacList = sa.getHostCard().getController().getController().choosePermanentsToSacrifice(sa, 0, 1, canOffer,
offeringType);
if (!toSacList.isEmpty()) {
toSac = toSacList.get(0);
}
else {
return;
}
cost.subtractManaCost(toSac.getManaCost());
sa.setSacrificedAsOffering(toSac);
toSac.setUsedToPay(true); //stop it from interfering with mana input
}
/**
* Applies applyRaiseCostAbility ability.
@@ -46,7 +187,7 @@ public class StaticAbilityCostChange {
* @param originalCost
* a ManaCost
*/
public static void applyRaiseCostAbility(final StaticAbility staticAbility, final SpellAbility sa, final ManaCostBeingPaid manaCost) {
private static void applyRaiseCostAbility(final StaticAbility staticAbility, final SpellAbility sa, final ManaCostBeingPaid manaCost) {
final Map<String, String> params = staticAbility.getMapParams();
final Card hostCard = staticAbility.getHostCard();
final Player activator = sa.getActivatingPlayer();
@@ -193,7 +334,7 @@ public class StaticAbilityCostChange {
* @param originalCost
* a ManaCost
*/
public static void applyReduceCostAbility(final StaticAbility staticAbility, final SpellAbility sa, final ManaCostBeingPaid manaCost) {
private static void applyReduceCostAbility(final StaticAbility staticAbility, final SpellAbility sa, final ManaCostBeingPaid manaCost) {
//Can't reduce zero cost
if (manaCost.toString().equals("{0}")) {
return;
@@ -293,4 +434,5 @@ public class StaticAbilityCostChange {
}
}
}
}

View File

@@ -19,20 +19,12 @@ package forge.game.mana;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.card.ColorSet;
import forge.card.MagicColor;
import forge.card.mana.IParserManaCost;
import forge.card.mana.ManaCost;
import forge.card.mana.ManaCostShard;
import forge.game.Game;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.staticability.StaticAbility;
import forge.game.zone.ZoneType;
import forge.util.maps.EnumMapToAmount;
import forge.util.maps.MapToAmount;
@@ -488,128 +480,6 @@ public class ManaCostBeingPaid {
unpaidShards.remove(ManaCostShard.COLORLESS);
}
public final void applySpellCostChange(final SpellAbility sa, boolean test) {
final Game game = sa.getActivatingPlayer().getGame();
// Beached
final Card originalCard = sa.getHostCard();
final SpellAbility spell = sa;
if (sa.isXCost() && !originalCard.isCopiedSpell()) {
originalCard.setXManaCostPaid(0);
}
if (sa.isTrigger()) {
return;
}
if (spell.isSpell()) {
if (spell.isDelve()) {
final Player pc = originalCard.getController();
final List<Card> mutableGrave = new ArrayList<Card>(pc.getCardsIn(ZoneType.Graveyard));
final List<Card> toExile = pc.getController().chooseCardsToDelve(this.getColorlessManaAmount(), mutableGrave);
for (final Card c : toExile) {
decreaseColorlessMana(1);
if (!test) {
pc.getGame().getAction().exile(c);
}
}
}
else if (spell.getHostCard().hasKeyword("Convoke")) {
adjustCostByConvoke(sa);
}
} // isSpell
List<Card> cardsOnBattlefield = Lists.newArrayList(game.getCardsIn(ZoneType.Battlefield));
cardsOnBattlefield.addAll(game.getCardsIn(ZoneType.Stack));
cardsOnBattlefield.addAll(game.getCardsIn(ZoneType.Command));
if (!cardsOnBattlefield.contains(originalCard)) {
cardsOnBattlefield.add(originalCard);
}
final ArrayList<StaticAbility> raiseAbilities = new ArrayList<StaticAbility>();
final ArrayList<StaticAbility> reduceAbilities = new ArrayList<StaticAbility>();
final ArrayList<StaticAbility> setAbilities = new ArrayList<StaticAbility>();
// Sort abilities to apply them in proper order
for (Card c : cardsOnBattlefield) {
final ArrayList<StaticAbility> staticAbilities = c.getStaticAbilities();
for (final StaticAbility stAb : staticAbilities) {
if (stAb.getMapParams().get("Mode").equals("RaiseCost")) {
raiseAbilities.add(stAb);
}
else if (stAb.getMapParams().get("Mode").equals("ReduceCost")) {
reduceAbilities.add(stAb);
}
else if (stAb.getMapParams().get("Mode").equals("SetCost")) {
setAbilities.add(stAb);
}
}
}
// Raise cost
for (final StaticAbility stAb : raiseAbilities) {
stAb.applyAbility("RaiseCost", spell, this);
}
// Reduce cost
for (final StaticAbility stAb : reduceAbilities) {
stAb.applyAbility("ReduceCost", spell, this);
}
if (spell.isSpell() && spell.isOffering()) { // cost reduction from offerings
adjustCostByOffering(sa, spell);
}
// Set cost (only used by Trinisphere) is applied last
for (final StaticAbility stAb : setAbilities) {
stAb.applyAbility("SetCost", spell, this);
}
} // GetSpellCostChange
private void adjustCostByConvoke(final SpellAbility sa) {
List<Card> untappedCreats = CardLists.filter(sa.getActivatingPlayer().getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES);
untappedCreats = CardLists.filter(untappedCreats, CardPredicates.Presets.UNTAPPED);
Map<Card, ManaCostShard> convokedCards = sa.getActivatingPlayer().getController().chooseCardsForConvoke(sa, this.toManaCost(), untappedCreats);
// Convoked creats are tapped here with triggers suppressed,
// Then again when payment is done(In InputPayManaCost.done()) with suppression cleared.
// This is to make sure that triggers go off at the right time
// AND that you can't use mana tapabilities of convoked creatures to pay the convoked cost.
for (final Entry<Card, ManaCostShard> conv : convokedCards.entrySet()) {
sa.addTappedForConvoke(conv.getKey());
this.decreaseShard(conv.getValue(), 1);
conv.getKey().setTapped(true);
}
}
private void adjustCostByOffering(final SpellAbility sa, final SpellAbility spell) {
String offeringType = "";
for (String kw : sa.getHostCard().getKeyword()) {
if (kw.endsWith(" offering")) {
offeringType = kw.split(" ")[0];
break;
}
}
Card toSac = null;
List<Card> canOffer = CardLists.filter(spell.getActivatingPlayer().getCardsIn(ZoneType.Battlefield),
CardPredicates.isType(offeringType));
final List<Card> toSacList = sa.getHostCard().getController().getController().choosePermanentsToSacrifice(spell, 0, 1, canOffer,
offeringType);
if (!toSacList.isEmpty()) {
toSac = toSacList.get(0);
}
else {
return;
}
subtractManaCost(toSac.getManaCost());
sa.setSacrificedAsOffering(toSac);
toSac.setUsedToPay(true); //stop it from interfering with mana input
}
public String getSourceRestriction() {
return sourceRestriction;
}

View File

@@ -222,10 +222,10 @@ public abstract class PlayerController {
public List<Card> cheatShuffle(List<Card> list) { return list; }
public Collection<? extends PaperCard> complainCardsCantPlayWell(Deck myDeck) { return null; }
public final boolean payManaCost(CostPartMana costPartMana, SpellAbility sa, String prompt) {
return payManaCost(costPartMana.getManaCostFor(sa), costPartMana, sa, prompt);
public final boolean payManaCost(CostPartMana costPartMana, SpellAbility sa, String prompt, boolean isActivatedAbility) {
return payManaCost(costPartMana.getManaCostFor(sa), costPartMana, sa, prompt, isActivatedAbility);
}
public abstract boolean payManaCost(ManaCost toPay, CostPartMana costPartMana, SpellAbility sa, String prompt);
public abstract boolean payManaCost(ManaCost toPay, CostPartMana costPartMana, SpellAbility sa, String prompt, boolean isActivatedAbility);
public abstract Map<Card, ManaCostShard> chooseCardsForConvoke(SpellAbility sa, ManaCost manaCost, List<Card> untappedCreats);

View File

@@ -23,7 +23,6 @@ import forge.game.GameEntity;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.cost.Cost;
import forge.game.mana.ManaCostBeingPaid;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
@@ -324,38 +323,6 @@ public class StaticAbility extends CardTraitBase {
return false;
}
/**
* Apply ability.
*
* @param mode
* the mode
* @param sa
* the SpellAbility
* @param originalCost
* the originalCost
* @return the modified ManaCost
*/
public final void applyAbility(final String mode, final SpellAbility sa, final ManaCostBeingPaid originalCost) {
// don't apply the ability if it hasn't got the right mode
if (!this.mapParams.get("Mode").equals(mode)) {
return;
}
if (this.isSuppressed() || !this.checkConditions()) {
return;
}
if (mode.equals("RaiseCost")) {
StaticAbilityCostChange.applyRaiseCostAbility(this, sa, originalCost);
}
if (mode.equals("ReduceCost")) {
StaticAbilityCostChange.applyReduceCostAbility(this, sa, originalCost);
}
if (mode.equals("SetCost")) { //Set cost is only used by Trinisphere
StaticAbilityCostChange.applyRaiseCostAbility(this, sa, originalCost);
}
}
/**
* Apply ability.

View File

@@ -1,6 +1,7 @@
package forge.gui.player;
import com.google.common.base.Predicate;
import forge.FThreads;
import forge.card.MagicColor;
import forge.card.mana.ManaCost;
@@ -15,6 +16,7 @@ import forge.game.ability.effects.FlipCoinEffect;
import forge.game.card.*;
import forge.game.card.CardPredicates.Presets;
import forge.game.cost.*;
import forge.game.mana.ManaCostAdjustment;
import forge.game.mana.ManaCostBeingPaid;
import forge.game.player.Player;
import forge.game.spellability.Ability;
@@ -24,6 +26,7 @@ import forge.game.zone.ZoneType;
import forge.gui.GuiChoose;
import forge.gui.input.*;
import forge.util.Lang;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
@@ -124,7 +127,7 @@ public class HumanPlay {
}
else {
manaCost = new ManaCostBeingPaid(sa.getPayCosts().getTotalMana());
manaCost.applySpellCostChange(sa, false);
ManaCostAdjustment.adjust(manaCost, sa, false);
}
boolean isPaid = manaCost.isPaid();
@@ -627,8 +630,9 @@ public class HumanPlay {
prompt = source + "\n" + promptCurrent;
}
if( sourceAbility != null )
sourceAbility.clearManaPaid();
boolean paid = p.getController().payManaCost(cost.getCostMana(), sourceAbility, prompt);
boolean paid = p.getController().payManaCost(cost.getCostMana(), sourceAbility, prompt, false);
if (!paid) {
p.getManaPool().refundManaPaid(sourceAbility);
}
@@ -679,7 +683,7 @@ public class HumanPlay {
return done;
}
public static boolean payManaCost(final ManaCost realCost, final CostPartMana mc, final SpellAbility ability, final Player activator, String prompt) {
public static boolean payManaCost(final ManaCost realCost, final CostPartMana mc, final SpellAbility ability, final Player activator, String prompt, boolean isActivatedSa) {
final Card source = ability.getHostCard();
ManaCostBeingPaid toPay = new ManaCostBeingPaid(realCost, mc.getRestiction());
@@ -699,8 +703,8 @@ public class HumanPlay {
toPay.addManaCost(mkCost);
}
toPay.applySpellCostChange(ability, false);
if( isActivatedSa )
ManaCostAdjustment.adjust(toPay, ability, false);
InputPayMana inpPayment;
if (ability.isOffering() && ability.getSacrificedAsOffering() == null) {

View File

@@ -957,8 +957,7 @@ public class PlayerControllerHuman extends PlayerController {
@Override
public boolean payCostToPreventEffect(Cost cost, SpellAbility sa, boolean alreadyPaid, List<Player> allPayers) {
// if it's paid by the AI already the human can pay, but it won't change anything
final Card source = sa.getHostCard();
return HumanPlay.payCostDuringAbilityResolve(player, source, cost, sa, null);
return HumanPlay.payCostDuringAbilityResolve(player, sa.getHostCard(), cost, sa, null);
}
@Override
@@ -1062,8 +1061,8 @@ public class PlayerControllerHuman extends PlayerController {
}
@Override
public boolean payManaCost(ManaCost toPay, CostPartMana costPartMana, SpellAbility sa, String prompt /* ai needs hints as well */ ) {
return HumanPlay.payManaCost(toPay, costPartMana, sa, player, prompt);
public boolean payManaCost(ManaCost toPay, CostPartMana costPartMana, SpellAbility sa, String prompt, boolean isActivatedSa) {
return HumanPlay.payManaCost(toPay, costPartMana, sa, player, prompt, isActivatedSa);
}
@Override

View File

@@ -566,9 +566,10 @@ public class PlayerControllerForTests extends PlayerController {
}
@Override
public boolean payManaCost(ManaCost toPay, CostPartMana costPartMana, SpellAbility sa, String prompt /* ai needs hints as well */ ) {
public boolean payManaCost(ManaCost toPay, CostPartMana costPartMana, SpellAbility sa, String prompt /* ai needs hints as well */, boolean isActivatedSa ) {
// TODO Auto-generated method stub
return ComputerUtilMana.payManaCost(new ManaCostBeingPaid(toPay), sa, player);
ManaCostBeingPaid cost = new ManaCostBeingPaid(toPay);
return ComputerUtilMana.payManaCost(cost, sa, player);
}
@Override