Merged changes from trunk to GuiRefactoring: 27198-27235

This commit is contained in:
elcnesh
2014-09-07 09:27:46 +00:00
parent c931379ce8
commit 5925c35486
33 changed files with 742 additions and 100 deletions

2
.gitattributes vendored
View File

@@ -15,6 +15,7 @@ forge-ai/.settings/org.eclipse.m2e.core.prefs -text
forge-ai/pom.xml -text
forge-ai/src/main/java/forge/ai/AiAttackController.java svneol=native#text/plain
forge-ai/src/main/java/forge/ai/AiBlockController.java svneol=native#text/plain
forge-ai/src/main/java/forge/ai/AiCardMemory.java -text
forge-ai/src/main/java/forge/ai/AiController.java svneol=native#text/plain
forge-ai/src/main/java/forge/ai/AiCostDecision.java -text
forge-ai/src/main/java/forge/ai/AiPlayDecision.java -text
@@ -15394,6 +15395,7 @@ forge-gui/res/editions/Duel[!!-~]Decks[!!-~]Jace[!!-~]vs.[!!-~]Vraska.txt -text
forge-gui/res/editions/Duel[!!-~]Decks[!!-~]Knights[!!-~]vs.[!!-~]Dragons.txt -text
forge-gui/res/editions/Duel[!!-~]Decks[!!-~]Phyrexia[!!-~]vs.[!!-~]the[!!-~]Coalition.txt -text
forge-gui/res/editions/Duel[!!-~]Decks[!!-~]Sorin[!!-~]vs.[!!-~]Tibalt.txt -text
forge-gui/res/editions/Duel[!!-~]Decks[!!-~]Speed[!!-~]vs.[!!-~]Cunning.txt -text
forge-gui/res/editions/Duel[!!-~]Decks[!!-~]Venser[!!-~]vs.[!!-~]Koth.txt -text
forge-gui/res/editions/Eighth[!!-~]Edition.txt -text
forge-gui/res/editions/Eventide.txt -text

View File

@@ -116,7 +116,7 @@ public class AiAttackController {
}
};
for (Card c : CardLists.filter(defender.getCardsIn(ZoneType.Battlefield), canAnimate)) {
if (c.isToken() && !c.isCopiedToken()) {
if (c.isToken() && c.getCopiedPermanent() == null) {
continue;
}
for (SpellAbility sa : c.getSpellAbilities()) {
@@ -607,6 +607,16 @@ public class AiAttackController {
return;
}
// Cards that are remembered to attack anyway (e.g. temporarily stolen creatures)
AiCardMemory aiMemory = ((PlayerControllerAi)ai.getController()).getAi().getCardMemory();
for (Card attacker : this.attackers) {
if (aiMemory.isRememberedCard(attacker, AiCardMemory.MemorySet.MANDATORY_ATTACKERS)) {
combat.addAttacker(attacker, defender);
attackersLeft.remove(attacker);
}
}
aiMemory.clearRememberedAttackers(); // avoid "leaking" remembered cards over to the next turn
// Exalted
if (combat.getAttackers().isEmpty()) {
boolean exalted = false;

View File

@@ -0,0 +1,250 @@
/*
* 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.ai;
import forge.game.card.Card;
import forge.game.player.Player;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/**
* <p>
* AiCardMemory class.
* </p>
*
* A simple class that allows the AI to "memorize" different cards on the battlefield (and possibly in other zones
* too, for instance as revealed from the opponent's hand) and assign them to different memory sets in order to help
* make somewhat more "educated" decisions to attack with certain cards or play certain spell abilities. Each
* AiController has its own memory that is created when the AI player is spawned. The card memory is accessible
* via AiController.getCardMemory.
*
* @author Forge
*/
public class AiCardMemory {
private HashSet<Card> memMandatoryAttackers = new HashSet<Card>();
private HashSet<Card> memHeldManaSources = new HashSet<Card>();
//private HashSet<Card> memRevealedCards = new HashSet<Card>();
/**
* Defines the memory set in which the card is remembered
* (which, in its turn, defines how the AI utilizes the information
* about remembered cards).
*/
public enum MemorySet {
MANDATORY_ATTACKERS,
HELD_MANA_SOURCES, // stub, not linked to AI code yet
//REVEALED_CARDS // stub, not linked to AI code yet
}
private Set<Card> getMemorySet(MemorySet set) {
switch (set) {
case MANDATORY_ATTACKERS:
return memMandatoryAttackers;
case HELD_MANA_SOURCES:
return memHeldManaSources;
//case REVEALED_CARDS:
// return memRevealedCards;
default:
return null;
}
}
/**
* Checks if the given card was remembered in the given memory set.
*
* @param c
* the card
* @param set the memory set that is to be checked
* @return true, if the card is remembered in the given memory set
*/
public boolean isRememberedCard(Card c, MemorySet set) {
if (c == null) {
return false;
}
Set<Card> memorySet = getMemorySet(set);
return memorySet == null ? false : memorySet.contains(c);
}
/**
* Checks if at least one card of the given name was remembered in the given memory set.
*
* @param cardName
* the card name
* @param set the memory set that is to be checked
* @return true, if at least one card with the given name is remembered in the given memory set
*/
public boolean isRememberedCardByName(String cardName, MemorySet set) {
Set<Card> memorySet = getMemorySet(set);
Iterator<Card> it = memorySet.iterator();
while (it.hasNext()) {
Card c = it.next();
if (c.getName().equals(cardName)) {
return true;
}
}
return false;
}
/**
* Checks if at least one card of the given name was remembered in the given memory set such
* that its owner is the given player.
*
* @param cardName
* the card name
* @param set the memory set that is to be checked
* @param owner the owner of the card
* @return true, if at least one card with the given name is remembered in the given memory set
*/
public boolean isRememberedCardByName(String cardName, MemorySet set, Player owner) {
Set<Card> memorySet = getMemorySet(set);
Iterator<Card> it = memorySet.iterator();
while (it.hasNext()) {
Card c = it.next();
if (c.getName().equals(cardName) && c.getOwner().equals(owner)) {
return true;
}
}
return false;
}
/**
* Remembers the given card in the given memory set.
* @param c
* the card
* @param set the memory set to remember the card in
* @return true, if the card is successfully stored in the given memory set
*/
public boolean rememberCard(Card c, MemorySet set) {
if (c == null)
return false;
getMemorySet(set).add(c);
return true;
}
/**
* Forgets the given card in the given memory set.
* @param c
* the card
* @param set the memory set to forget the card in
* @return true, if the card was previously remembered in the given memory set and was successfully forgotten
*/
public boolean forgetCard(Card c, MemorySet set) {
if (c == null) {
return false;
}
if (!isRememberedCard(c, set)) {
return false;
}
getMemorySet(set).remove(c);
return true;
}
/**
* Forgets a single card with the given name in the given memory set.
* @param cardName
* the card name
* @param set the memory set to forget the card in
* @return true, if at least one card with the given name was previously remembered in the given memory set and was successfully forgotten
*/
public boolean forgetAnyCardWithName(String cardName, MemorySet set) {
Set<Card> memorySet = getMemorySet(set);
Iterator<Card> it = memorySet.iterator();
while (it.hasNext()) {
Card c = it.next();
if (c.getName().equals(cardName)) {
return forgetCard(c, set);
}
}
return false;
}
/**
* Forgets a single card with the given name owned by the given player in the given memory set.
* @param cardName
* the card name
* @param set the memory set to forget the card in
* @param owner the owner of the card
* @return true, if at least one card with the given name was previously remembered in the given memory set and was successfully forgotten
*/
public boolean forgetAnyCardWithName(String cardName, MemorySet set, Player owner) {
Set<Card> memorySet = getMemorySet(set);
Iterator<Card> it = memorySet.iterator();
while (it.hasNext()) {
Card c = it.next();
if (c.getName().equals(cardName) && c.getOwner().equals(owner)) {
return forgetCard(c, set);
}
}
return false;
}
/**
* Determines if the memory set is empty.
* @param set the memory set to inspect.
* @return true, if the given memory set contains no remembered cards.
*/
public boolean isMemorySetEmpty(MemorySet set) {
return getMemorySet(set).isEmpty();
}
/**
* Clears the "remembered attackers" memory set stored in this card memory for the given player.
*/
public void clearRememberedAttackers() {
getMemorySet(MemorySet.MANDATORY_ATTACKERS).clear();
}
/**
* Clears the "remembered mana sources" memory set stored in this card memory for the given player.
*/
public void clearRememberedManaSources() {
getMemorySet(MemorySet.HELD_MANA_SOURCES).clear();
}
/**
* Clears the "remembered revealed cards" memory set stored in this card memory for the given player.
*/
//public void clearRememberedRevealedCards() {
// getMemorySet(MemorySet.REVEALED_CARDS).clear();
//}
/**
* Clears all memory sets stored in this card memory for the given player.
*/
public void clearAllRemembered() {
clearRememberedAttackers();
clearRememberedManaSources();
//clearRememberedRevealedCards();
}
}

View File

@@ -60,6 +60,7 @@ import forge.game.combat.Combat;
import forge.game.cost.Cost;
import forge.game.cost.CostDiscard;
import forge.game.cost.CostPart;
import forge.game.mana.ManaCostBeingPaid;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
@@ -94,6 +95,7 @@ public class AiController {
private final Player player;
private final Game game;
private final AiCardMemory memory;
public boolean bCheatShuffle;
public boolean canCheatShuffle() {
@@ -114,6 +116,10 @@ public class AiController {
return player;
}
public AiCardMemory getCardMemory() {
return memory;
}
/**
* <p>
* Constructor for ComputerAI_General.
@@ -122,6 +128,7 @@ public class AiController {
public AiController(final Player computerPlayer, final Game game0) {
player = computerPlayer;
game = game0;
memory = new AiCardMemory();
}
/**
@@ -628,6 +635,33 @@ public class AiController {
return bestSA;
} // playCounterSpell()
public SpellAbility predictSpellToCastInMain2(ApiType exceptSA) {
final List<Card> cards = getAvailableCards();
ArrayList<SpellAbility> all = getSpellAbilities(cards);
Collections.sort(all, saComparator); // put best spells first
for (final SpellAbility sa : getOriginalAndAltCostAbilities(all)) {
if (sa.getApi() == ApiType.Counter || sa.getApi() == exceptSA) {
continue;
}
sa.setActivatingPlayer(player);
if (sa.canPlay() && !ComputerUtil.castPermanentInMain1(player, sa) && sa.getHostCard() != null && !sa.getHostCard().getType().contains("Land") && ComputerUtilCost.canPayCost(sa, player)) {
return sa;
}
}
return null;
}
public void reserveManaSourcesForMain2(SpellAbility sa) {
ManaCostBeingPaid cost = ComputerUtilMana.calculateManaCost(sa, true, 0);
ArrayList<Card> manaSources = ComputerUtilMana.getManaSourcesToPayCost(cost, sa, player);
for (Card c : manaSources) {
((PlayerControllerAi)player.getController()).getAi().getCardMemory().rememberCard(c, AiCardMemory.MemorySet.HELD_MANA_SOURCES);
}
}
// This is for playing spells regularly (no Cascade/Ripple etc.)
private AiPlayDecision canPlayAndPayFor(final SpellAbility sa) {
if (!sa.canPlay()) {
@@ -1146,18 +1180,18 @@ public class AiController {
SpellAbility counter = chooseCounterSpell(getPlayableCounters(cards));
if( counter != null ) return counter;
SpellAbility counterETB = chooseSpellAbilyToPlay(this.getPossibleETBCounters(), false);
SpellAbility counterETB = chooseSpellAbilityToPlay(this.getPossibleETBCounters(), false);
if( counterETB != null )
return counterETB;
}
SpellAbility result = chooseSpellAbilyToPlay(getSpellAbilities(cards), true);
SpellAbility result = chooseSpellAbilityToPlay(getSpellAbilities(cards), true);
if( null == result)
return null;
return result;
}
private SpellAbility chooseSpellAbilyToPlay(final ArrayList<SpellAbility> all, boolean skipCounter) {
private SpellAbility chooseSpellAbilityToPlay(final ArrayList<SpellAbility> all, boolean skipCounter) {
if ( all == null || all.isEmpty() )
return null;

View File

@@ -21,6 +21,7 @@ import forge.game.mana.Mana;
import forge.game.mana.ManaCostAdjustment;
import forge.game.mana.ManaCostBeingPaid;
import forge.game.mana.ManaPool;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.replacement.ReplacementEffect;
import forge.game.spellability.AbilityManaPart;
@@ -230,6 +231,113 @@ public class ComputerUtilMana {
}
public static ArrayList<Card> getManaSourcesToPayCost(final ManaCostBeingPaid cost, final SpellAbility sa, final Player ai) {
ArrayList<Card> manaSources = new ArrayList<Card>();
adjustManaCostToAvoidNegEffects(cost, sa.getHostCard(), ai);
List<Mana> manaSpentToPay = new ArrayList<Mana>();
List<ManaCostShard> unpaidShards = cost.getUnpaidShards();
Collections.sort(unpaidShards); // most difficult shards must come first
for (ManaCostShard part : unpaidShards) {
if (part != ManaCostShard.X) {
if (cost.isPaid()) {
continue;
}
// get a mana of this type from floating, bail if none available
final Mana mana = getMana(ai, part, sa, cost.getSourceRestriction());
if (mana != null) {
if (ai.getManaPool().tryPayCostWithMana(sa, cost, mana)) {
manaSpentToPay.add(0, mana);
}
}
}
}
if (cost.isPaid()) {
// refund any mana taken from mana pool when test
refundMana(manaSpentToPay, ai, sa);
handleOfferingsAI(sa, true, cost.isPaid());
return manaSources;
}
// arrange all mana abilities by color produced.
final ArrayListMultimap<Integer, SpellAbility> manaAbilityMap = ComputerUtilMana.groupSourcesByManaColor(ai, true);
if (manaAbilityMap.isEmpty()) {
refundMana(manaSpentToPay, ai, sa);
handleOfferingsAI(sa, true, cost.isPaid());
return manaSources;
}
// select which abilities may be used for each shard
ArrayListMultimap<ManaCostShard, SpellAbility> sourcesForShards = ComputerUtilMana.groupAndOrderToPayShards(ai, manaAbilityMap, cost);
sortManaAbilities(sourcesForShards);
ManaCostShard toPay = null;
// Loop over mana needed
while (!cost.isPaid()) {
toPay = getNextShardToPay(cost);
Collection<SpellAbility> saList = sourcesForShards.get(toPay);
SpellAbility saPayment = null;
if (saList != null) {
for (final SpellAbility ma : saList) {
if (ma.getHostCard() == sa.getHostCard()) {
continue;
}
final String typeRes = cost.getSourceRestriction();
if (StringUtils.isNotBlank(typeRes) && !ma.getHostCard().isType(typeRes)) {
continue;
}
if (canPayShardWithSpellAbility(toPay, ai, ma, sa, true)) {
saPayment = ma;
manaSources.add(saPayment.getHostCard());
break;
}
}
} else {
break;
}
if (saPayment == null) {
if (!toPay.isPhyrexian() || !ai.canPayLife(2)) {
break; // cannot pay
}
cost.payPhyrexian();
continue;
}
setExpressColorChoice(sa, ai, cost, toPay, saPayment);
String manaProduced = toPay.isSnow() ? "S" : GameActionUtil.generatedMana(saPayment);
manaProduced = AbilityManaPart.applyManaReplacement(saPayment, manaProduced);
//System.out.println(manaProduced);
payMultipleMana(cost, manaProduced, ai);
// remove from available lists
Iterator<SpellAbility> itSa = sourcesForShards.values().iterator();
while (itSa.hasNext()) {
SpellAbility srcSa = itSa.next();
if (srcSa.getHostCard().equals(saPayment.getHostCard())) {
itSa.remove();
}
}
}
handleOfferingsAI(sa, true, cost.isPaid());
refundMana(manaSpentToPay, ai, sa);
return manaSources;
} // getManaSourcesToPayCost()
private static boolean payManaCost(final ManaCostBeingPaid cost, final SpellAbility sa, final Player ai, final boolean test, boolean checkPlayable) {
adjustManaCostToAvoidNegEffects(cost, sa.getHostCard(), ai);
List<Mana> manaSpentToPay = test ? new ArrayList<Mana>() : sa.getPayingMana();
@@ -255,9 +363,9 @@ public class ComputerUtilMana {
if (cost.isPaid()) {
// refund any mana taken from mana pool when test
if(test)
if (test) {
refundMana(manaSpentToPay, ai, sa);
}
handleOfferingsAI(sa, test, cost.isPaid());
return true;
}
@@ -307,7 +415,8 @@ public class ComputerUtilMana {
break;
}
}
} else {
}
else {
break;
}
@@ -381,9 +490,9 @@ public class ComputerUtilMana {
}
}
if (test)
if (test) {
refundMana(manaSpentToPay, ai, sa);
}
sa.getHostCard().setColorsPaid(cost.getColorsPaid());
// if (sa instanceof Spell_Permanent) // should probably add this
sa.getHostCard().setSunburstValue(cost.getSunburst());
@@ -485,8 +594,9 @@ public class ComputerUtilMana {
ManaCostShard toPay, SpellAbility saPayment) {
AbilityManaPart m = saPayment.getManaPart();
if (m.isComboMana())
if (m.isComboMana()) {
getComboManaChoice(ai, saPayment, sa, cost);
}
else if (saPayment.getApi() == ApiType.ManaReflected) {
System.out.println("Evaluate reflected mana of: " + saPayment.getHostCard());
Set<String> reflected = CardUtil.getReflectableManaColors(saPayment);
@@ -497,7 +607,8 @@ public class ComputerUtilMana {
return;
}
}
} else if (m.isAnyMana()) {
}
else if (m.isAnyMana()) {
byte colorChoice = 0;
if (toPay.isOr2Colorless())
colorChoice = toPay.getColorMask();
@@ -516,7 +627,13 @@ public class ComputerUtilMana {
private static boolean canPayShardWithSpellAbility(ManaCostShard toPay, Player ai, SpellAbility ma, SpellAbility sa, boolean checkCosts) {
final Card sourceCard = ma.getHostCard();
if (toPay.isSnow() && !sourceCard.isSnow()) { return false; }
if (isManaSourceReserved(ai, sourceCard, sa)) {
return false;
}
if (toPay.isSnow() && !sourceCard.isSnow()) {
return false;
}
AbilityManaPart m = ma.getManaPart();
if (!m.meetsManaRestrictions(sa)) {
@@ -530,7 +647,8 @@ public class ComputerUtilMana {
if (!CostPayment.canPayAdditionalCosts(ma.getPayCosts(), ma)) {
return false;
}
} else if (sourceCard.isTapped()) {
}
else if (sourceCard.isTapped()) {
return false;
}
}
@@ -541,8 +659,9 @@ public class ComputerUtilMana {
return true;
}
return false;
}
} else if (ma.getApi() == ApiType.ManaReflected) {
if (ma.getApi() == ApiType.ManaReflected) {
Set<String> reflected = CardUtil.getReflectableManaColors(ma);
for (byte c : MagicColor.WUBRG) {
@@ -556,10 +675,41 @@ public class ComputerUtilMana {
return true;
}
// isManaSourceReserved returns true if sourceCard is reserved as a mana source for payment
// for the future spell to be cast in Mana 2. However, if "sa" (the spell ability that is
// being considered for casting) is high priority, then mana source reservation will be
// ignored.
private static boolean isManaSourceReserved(Player ai, Card sourceCard, SpellAbility sa) {
if (sa == null) {
return false;
}
if (!(ai.getController() instanceof PlayerControllerAi)) {
return false;
}
// If the spell ability being considered is high priority, ignore mana source reservations.
// TODO: currently the only "low priority" spell abilities are +X/+X pumps.
if (!(sa.getApi() == ApiType.Pump && (sa.hasParam("NumAtk") || (sa.hasParam("NumDef"))))) {
return false;
}
PhaseType curPhase = ai.getGame().getPhaseHandler().getPhase();
if (curPhase == PhaseType.MAIN2 || curPhase == PhaseType.CLEANUP) {
((PlayerControllerAi)ai.getController()).getAi().getCardMemory().clearRememberedManaSources();
}
else {
if (((PlayerControllerAi)ai.getController()).getAi().getCardMemory().isRememberedCard(sourceCard, AiCardMemory.MemorySet.HELD_MANA_SOURCES)) {
// This mana source is held elsewhere for a Main Phase 2 spell.
return true;
}
}
return false;
}
private static ManaCostShard getNextShardToPay(ManaCostBeingPaid cost) {
// mind the priorities
// * Pay mono-colored first,
// * Pay mono-colored first,curPhase == PhaseType.CLEANUP
// * Pay 2/C with matching colors
// * pay hybrids
// * pay phyrexian, keep mana for colorless

View File

@@ -1085,6 +1085,11 @@ public class AttachAi extends SpellAbilityAi {
c = attachAIHighestEvaluationPreference(prefList);
}
// Consider exceptional cases which break the normal evaluation rules
if (!isUsefulAttachAction(c, sa)) {
return null;
}
return c;
}
@@ -1266,6 +1271,38 @@ public class AttachAi extends SpellAbilityAi {
return true;
}
/**
* Checks if it is useful to execute the attach action given the current context.
*
* @param card
* the card
* @param sa SpellAbility
* @return true, if the action is useful (beneficial) in the current minimal context (Card vs. Attach SpellAbility)
*/
private static boolean isUsefulAttachAction(Card c, SpellAbility sa) {
if (c == null) {
return false;
}
if (sa.getHostCard() == null) {
// FIXME: Not sure what should the resolution be if a SpellAbility has no host card. This should
// not happen normally. Possibly remove this block altogether? (if it's an impossible condition).
System.out.println("AttachAi: isUsefulAttachAction unexpectedly called with SpellAbility with no host card. Assuming it's a determined useful action.");
return true;
}
ArrayList<String> cardTypes = sa.getHostCard().getType();
if (cardTypes.contains("Equipment") && c.hasKeyword("CARDNAME can't attack or block.")) {
// useless to equip a creature that can't attack or block.
return false;
} else if (cardTypes.contains("Equipment") && c.isTapped() && c.hasKeyword("CARDNAME doesn't untap during your untap step.")) {
// useless to equip a creature that won't untap and is tapped.
return false;
}
return true;
}
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
return true;

View File

@@ -19,8 +19,10 @@ package forge.ai.ability;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import forge.ai.AiCardMemory;
import forge.ai.ComputerUtilCard;
import forge.ai.PlayerControllerAi;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
@@ -168,6 +170,10 @@ public class ControlGainAi extends SpellAbilityAi {
if (hasCreature) {
t = ComputerUtilCard.getBestCreatureAI(list);
if (lose != null && lose.contains("EOT")) {
// Remember to always attack with this creature since it'll bounce back to its owner at end of turn anyway
((PlayerControllerAi)ai.getController()).getAi().getCardMemory().rememberCard(t, AiCardMemory.MemorySet.MANDATORY_ATTACKERS);
}
} else if (hasArtifact) {
t = ComputerUtilCard.getBestArtifactAI(list);
} else if (hasLand) {

View File

@@ -3,13 +3,17 @@ package forge.ai.ability;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import forge.ai.AiCardMemory;
import forge.ai.AiController;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat;
import forge.ai.PlayerControllerAi;
import forge.ai.SpellAbilityAi;
import forge.card.MagicColor;
import forge.game.Game;
import forge.game.ability.ApiType;
import forge.game.card.Card;
import forge.game.card.CardFactory;
import forge.game.card.CardLists;
@@ -439,6 +443,16 @@ public abstract class PumpAiBase extends SpellAbilityAi {
return false;
}
// determine if some mana sources need to be held for the future spell to cast in Main 2 before determining whether to pump.
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
if (aic.getCardMemory().isMemorySetEmpty(AiCardMemory.MemorySet.HELD_MANA_SOURCES)) {
// only hold mana sources once
SpellAbility futureSpell = aic.predictSpellToCastInMain2(ApiType.Pump);
if (futureSpell != null && futureSpell.getHostCard() != null) {
aic.reserveManaSourcesForMain2(futureSpell);
}
}
// will the creature attack (only relevant for sorcery speed)?
if (phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)
&& phase.isPlayerTurn(ai)
@@ -448,7 +462,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
return true;
}
// buff attacker/blocker using using triggered pump
// buff attacker/blocker using triggered pump
if (sa.isTrigger() && phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
if (phase.isPlayerTurn(ai)) {
if (CombatUtil.canAttack(c)) {

View File

@@ -176,6 +176,7 @@ public class GameAction {
HashMap<String, Object> repParams = new HashMap<String, Object>();
repParams.put("Event", "Moved");
repParams.put("Affected", copied);
repParams.put("CardLKI", lastKnownInfo);
repParams.put("Origin", zoneFrom != null ? zoneFrom.getZoneType() : null);
repParams.put("Destination", zoneTo.getZoneType());

View File

@@ -34,7 +34,7 @@ public enum GameType {
mainDeck.add("Island", 12);
mainDeck.add("Swamp", 12);
mainDeck.add("Mountain", 12);
mainDeck.add("Swamp", 12);
mainDeck.add("Forest", 12);
deck.getOrCreate(DeckSection.Avatar).add(StaticData.instance().getVariantCards()
.getCard("Momir Vig, Simic Visionary Avatar"), 1);
return deck;

View File

@@ -152,7 +152,7 @@ public class CopyPermanentEffect extends SpellAbilityEffect {
for (int i = 0; i < multiplier; i++) {
final Card copy = CardFactory.copyCopiableCharacteristics(c, sa.getActivatingPlayer());
copy.setToken(true);
copy.setCopiedToken(true);
copy.setCopiedPermanent(c);
CardFactory.copyCopiableAbilities(c, copy);
// add keywords from sa
for (final String kw : keywords) {

View File

@@ -147,7 +147,7 @@ public class Card extends GameEntity implements Comparable<Card> {
private boolean tapped = false;
private boolean sickness = true; // summoning sickness
private boolean token = false;
private boolean copiedToken = false;
private Card copiedPermanent = null;
private boolean copiedSpell = false;
private ArrayList<Card> mustBlockCards = null;
@@ -2906,25 +2906,25 @@ public class Card extends GameEntity implements Comparable<Card> {
/**
* <p>
* Setter for the field <code>copiedToken</code>.
* Getter for the field <code>copiedPermanent</code>.
* </p>
*
* @param b
* a boolean.
* @return a Card.
*/
public final void setCopiedToken(final boolean b) {
this.copiedToken = b;
public final Card getCopiedPermanent() {
return this.copiedPermanent;
}
/**
* <p>
* isCopiedToken.
* Setter for the field <code>copiedPermanent</code>.
* </p>
*
* @return a boolean.
* @param c
* a Card.
*/
public final boolean isCopiedToken() {
return this.copiedToken;
public final void setCopiedPermanent(final Card c) {
this.copiedPermanent = c;
}
/**
@@ -6438,6 +6438,11 @@ public class Card extends GameEntity implements Comparable<Card> {
return false;
}
}
} else if (property.startsWith("hasKeyword")) {
// "withFlash" would find Flashback cards, add this to fix Mystical Teachings
if (!this.hasKeyword(property.substring(10))) {
return false;
}
} else if (property.startsWith("withFlashback")) {
boolean fb = false;
if (this.hasStartOfUnHiddenKeyword("Flashback")) {
@@ -8848,6 +8853,8 @@ public class Card extends GameEntity implements Comparable<Card> {
}
public boolean canBeShownTo(final Player viewer) {
if (viewer == null) { return false; }
Zone zone = this.getZone();
if (zone == null) { return true; } //cards outside any zone are visible to all
@@ -8922,7 +8929,7 @@ public class Card extends GameEntity implements Comparable<Card> {
}
public int getCMC(SplitCMCMode mode) {
if (isToken() && !isCopiedToken()) {
if (isToken() && getCopiedPermanent() == null) {
return 0;
}
@@ -9140,4 +9147,12 @@ public class Card extends GameEntity implements Comparable<Card> {
public Card getCardForUi() {
return this;
}
public String getOracleText() {
CardRules rules = cardRules;
if (copiedPermanent != null) { //return oracle text of copied permanent if applicable
rules = copiedPermanent.getRules();
}
return rules != null ? rules.getOracleText() : "";
}
} // end Card class

View File

@@ -73,7 +73,7 @@ public class CardFactory {
*/
public final static Card copyCard(final Card in, boolean assignNewId) {
Card out;
if (!(in.isToken() || in.isCopiedToken())) {
if (!(in.isToken() || in.getCopiedPermanent() != null)) {
out = assignNewId ? getCard(in.getPaperCard(), in.getOwner())
: getCard(in.getPaperCard(), in.getOwner(), in.getUniqueNumber());
} else { // token

View File

@@ -38,6 +38,7 @@ import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.card.CardPredicates.Presets;
import forge.game.cost.Cost;
import forge.game.cost.CostPayment;
import forge.game.event.GameEventCardStatsChanged;
import forge.game.phase.PhaseHandler;
import forge.game.player.Player;
@@ -137,7 +138,7 @@ public class CardFactoryUtil {
@Override
public boolean canPlay() {
return sourceCard.getController().equals(this.getActivatingPlayer()) && sourceCard.isFaceDown()
&& sourceCard.isInPlay();
&& sourceCard.isInPlay() && CostPayment.canPayAdditionalCosts(cost, this);
}
}; // morph_up

View File

@@ -172,7 +172,7 @@ public class Combat {
return ab;
}
CombatLki lki = lkiCache.get(c);
return lki == null ? null : lki.getFirstBand();
return lki == null || lki.isAttacker ? null : lki.getFirstBand();
}
public final List<AttackingBand> getAttackingBands() {

View File

@@ -72,6 +72,7 @@ public class ReplaceMoved extends ReplacementEffect {
@Override
public void setReplacingObjects(Map<String, Object> runParams, SpellAbility sa) {
sa.setReplacingObject("Card", runParams.get("Affected"));
sa.setReplacingObject("CardLKI", runParams.get("CardLKI"));
}
}

View File

@@ -61,10 +61,7 @@ public enum CDock implements ICDoc {
* End turn.
*/
public void endTurn() {
game.autoPassUntilEndOfTurn();
if (!CPrompt.SINGLETON_INSTANCE.passPriority()) {
game.autoPassCancel();
}
CPrompt.SINGLETON_INSTANCE.passPriorityUntilEndOfTurn();
}
public void revertLayout() {

View File

@@ -101,6 +101,10 @@ public enum CPrompt implements ICDoc {
return Singletons.getControl().getGameView().passPriority();
}
public boolean passPriorityUntilEndOfTurn() {
return Singletons.getControl().getGameView().passPriorityUntilEndOfTurn();
}
public void selectPlayer(final PlayerView player, final ITriggerEvent triggerEvent) {
Singletons.getControl().getGameView().selectPlayer(player, triggerEvent);
}

View File

@@ -1,7 +1,7 @@
Forge Beta: 08-29-2014 ver 1.5.25
Forge Beta: 09-##-2014 ver 1.5.26
14049 cards in total.
# cards in total.
-------------
@@ -9,65 +9,25 @@ Release Notes
-------------
- Improved Commander support -
There will no longer be a black rectangle for "Commander effect" in the command zone
The details previously available by hovering over that rectangle will now appear when hovering over the commander itself
The dialog for the commander replacement effect will now display the commander's name
There will no longer be a black rectangle for "Commander effect" in the command zone.
The details previously available by hovering over that rectangle will now appear when hovering over the commander itself.
The dialog for the commander replacement effect will now display the commander's name.
- Auto-targeting support -
When playing spells and abilities with the text "target opponent", if you only have one opponent, you will no longer be asked to choose the opponent to target.
When triggered abilities have only one valid target, that target will now be auto-selected
When triggered abilities have only one valid target, that target will now be auto-selected.
- New Commander 2014 and KTK cards -
- New Commander 2014 and Khans of Tarkir cards -
We have added a branch to our SVN for the new cards that are currently being scripted. These cards are not yet available in this build of forge. Please be patient and they will soon become available.
- Improved auto-yield support -
Pressing "End Turn" now properly skips your attack phase and doesn't get cancelled automatically if a spell or ability is put on the stack. You will still be given a chance to declare blockers if your opponent attacks, but after that the rest of your opponent's turn will now be skipped properly.
To alleviate pressing this accidentally, as long as you're auto-yielding this way, you'll be able to press Escape or the Cancel button to cancel the auto-yield and be given the chance to act again. Phases with stops and spells/abilities resolving will be given a slight delay to allow you to see what's going on and give you the chance to regain control by cancelling the yield.
Added support for auto-yielding abilities on the stack so you don't have to press OK each time
- Improved Gauntlet Support -
Add column to Load Gauntlet screen to display your deck for the gauntlet.
Fix so your deck is saved with a gauntlet when starting a quick gauntlet.
Instead of crashing, prompt user to select a deck if attempting to load a gauntlet that doesn't have one saved.
Make it so Last Activity column isn't cut off and is formatted properly.
Make it so double-clicking a gauntlet on the Load Gauntlet screen will launch it.
Support renaming gauntlets.
Support sorting gauntlets (and quests) case insensitive.
- Fixed loading overlay to show logo properly and have some transparency -
- Vastly Improved Boolean Expressions -
Boolean expressions no longer require the use of quotation marks around your search terms, making it a far more useful
feature. Boolean expressions been rewritten from the ground up to be faster and better at parsing your search terms.
With the new parsing also comes NOT operators (!). Place these before things to negate them. For example:
!r
will find all cards that don't have red in their mana costs (when searching by cost). These can be a bit finicky at
times since there's no order of operations yet. This will possibly be implemented in a future update. For now, be sure
to put all possibly ambiguous NOT operators within parenthesis:
!(r || g) instead of !r && !g
Unfortunately I did not have time to implement an order of operations for this release, but rest assured it's on its
way... eventually.
- Quest Draft Preferences -
You can now set how long you want quest draft tournaments to be available and how often you want them to be generated.
Existing draft tournaments will not be affected.
---------
New Cards
---------
Wishmonger
-------------------------------
@@ -79,13 +39,22 @@ Stitcher Geralf
Teferi, Temporal Archmage
--------------------
New KTK branch Cards
--------------------
--------------------------------
New Khans of Tarkir branch Cards
--------------------------------
Jeskai Elder
Thousand Winds
Zurgo Helmsmasher
Dragon-Style Twins
Herald of Anafenza
Rattleclaw Mystic
Temur Ascendancy
Ainok Bond-Kin
Mardu Skullhunter
Heir of the Wilds
Ivorytusk Fortress
Sagu Mauler
------------

View File

@@ -1170,6 +1170,47 @@ will find all humans or cats that are also warriors, clerics, or soldiers.
More improvements are on their way in the next version of Forge, including removing the need for quotation marks and adding NOT operators.
- Improved auto-yield support -
Pressing "End Turn" now properly skips your attack phase and doesn't get cancelled automatically if a spell or ability is put on the stack. You will still be given a chance to declare blockers if your opponent attacks, but after that the rest of your opponent's turn will now be skipped properly.
To alleviate pressing this accidentally, as long as you're auto-yielding this way, you'll be able to press Escape or the Cancel button to cancel the auto-yield and be given the chance to act again. Phases with stops and spells/abilities resolving will be given a slight delay to allow you to see what's going on and give you the chance to regain control by cancelling the yield.
Added support for auto-yielding abilities on the stack so you don't have to press OK each time.
- Improved Gauntlet Support -
Add column to Load Gauntlet screen to display your deck for the gauntlet.
Fix so your deck is saved with a gauntlet when starting a quick gauntlet.
Instead of crashing, prompt user to select a deck if attempting to load a gauntlet that doesn't have one saved.
Make it so Last Activity column isn't cut off and is formatted properly.
Make it so double-clicking a gauntlet on the Load Gauntlet screen will launch it.
Support renaming gauntlets.
Support sorting gauntlets (and quests) case insensitive.
- Fixed loading overlay to show logo properly and have some transparency -
- Vastly Improved Boolean Expressions -
Boolean expressions no longer require the use of quotation marks around your search terms, making it a far more useful
feature. Boolean expressions been rewritten from the ground up to be faster and better at parsing your search terms.
With the new parsing also comes NOT operators (!). Place these before things to negate them. For example:
!r
will find all cards that don't have red in their mana costs (when searching by cost). These can be a bit finicky at
times since there's no order of operations yet. This will possibly be implemented in a future update. For now, be sure
to put all possibly ambiguous NOT operators within parenthesis:
!(r || g) instead of !r && !g
Unfortunately I did not have time to implement an order of operations for this release, but rest assured it's on its
way... eventually.
- Quest Draft Preferences -
You can now set how long you want quest draft tournaments to be available and how often you want them to be generated.
Existing draft tournaments will not be affected.
Our Lawyers Made Us Do This:
----------------------------

View File

@@ -2,7 +2,7 @@ Name:Bola Warrior
ManaCost:1 R
Types:Creature Human Spellshaper Warrior
PT:1/1
A:AB$ Pump | Cost$ R T Discard<1/Card> | ValidTgts$ Creature | TgtPrompt$ Select target creature | KW$ CARDNAME can't block. | IsCurse$ True | SpellDescription$ Target creature can't block this turn.
A:AB$ Pump | Cost$ R T Discard<1/Card> | ValidTgts$ Creature | TgtPrompt$ Select target creature | KW$ HIDDEN CARDNAME can't block. | IsCurse$ True | SpellDescription$ Target creature can't block this turn.
SVar:RemAIDeck:True
SVar:Picture:http://www.wizards.com/global/images/magic/general/bola_warrior.jpg
Oracle:{R}, {T}, Discard a card: Target creature can't block this turn.

View File

@@ -4,6 +4,6 @@ Types:Creature Human Wizard
PT:2/2
A:AB$ Draw | Cost$ U T | NumCards$ 1 | SpellDescription$ Draw a card, then discard a card. | SubAbility$ DBDiscard
SVar:DBDiscard:DB$Discard | Defined$ You | NumCards$ 1 | Mode$ TgtChoose
A:AB$ Pump | Cost$ R T | ValidTgts$ Creature | TgtPrompt$ Select target creature | KW$ CARDNAME can't block. | IsCurse$ True | SpellDescription$ Target creature can't block this turn.
A:AB$ Pump | Cost$ R T | ValidTgts$ Creature | TgtPrompt$ Select target creature | KW$ HIDDEN CARDNAME can't block. | IsCurse$ True | SpellDescription$ Target creature can't block this turn.
SVar:Picture:http://www.wizards.com/global/images/magic/general/grixis_battlemage.jpg
Oracle:{U}, {T}: Draw a card, then discard a card.\n{R}, {T}: Target creature can't block this turn.

View File

@@ -2,7 +2,7 @@ Name:Jamuraan Lion
ManaCost:2 W
Types:Creature Cat
PT:3/1
A:AB$ Pump | Cost$ W T | ValidTgts$ Creature | TgtPrompt$ Select target creature | KW$ CARDNAME can't block. | IsCurse$ True | SpellDescription$ Target creature can't block this turn.
A:AB$ Pump | Cost$ W T | ValidTgts$ Creature | TgtPrompt$ Select target creature | KW$ HIDDEN CARDNAME can't block. | IsCurse$ True | SpellDescription$ Target creature can't block this turn.
SVar:RemAIDeck:True
SVar:Picture:http://www.wizards.com/global/images/magic/general/jamuraan_lion.jpg
Oracle:{W}, {T}: Target creature can't block this turn.

View File

@@ -2,7 +2,7 @@ Name:Krark-Clan Ogre
ManaCost:3 R R
Types:Creature Ogre
PT:3/3
A:AB$ Pump | Cost$ R Sac<1/Artifact> | ValidTgts$ Creature | TgtPrompt$ Select target creature | IsCurse$ True | KW$ CARDNAME can't block. | SpellDescription$ Target creature can't block this turn.
A:AB$ Pump | Cost$ R Sac<1/Artifact> | ValidTgts$ Creature | TgtPrompt$ Select target creature | IsCurse$ True | KW$ HIDDEN CARDNAME can't block. | SpellDescription$ Target creature can't block this turn.
SVar:RemAIDeck:True
SVar:Picture:http://www.wizards.com/global/images/magic/general/krark_clan_ogre.jpg
Oracle:{R}, Sacrifice an artifact: Target creature can't block this turn.

View File

@@ -2,7 +2,7 @@ Name:Mystical Teachings
ManaCost:3 U
Types:Instant
K:Flashback 5 B
A:SP$ ChangeZone | Cost$ 3 U | ChangeType$ Instant,Card.withFlash | ChangeNum$ 1 | Origin$ Library | Destination$ Hand | Shuffle$ True | SpellDescription$ Search your library for an instant card or a card with flash, reveal it, and put it into your hand. Then shuffle your library.
A:SP$ ChangeZone | Cost$ 3 U | ChangeType$ Instant,Card.hasKeywordFlash | ChangeNum$ 1 | Origin$ Library | Destination$ Hand | Shuffle$ True | SpellDescription$ Search your library for an instant card or a card with flash, reveal it, and put it into your hand. Then shuffle your library.
SVar:RemAIDeck:True
SVar:Picture:http://www.wizards.com/global/images/magic/general/mystical_teachings.jpg
Oracle:Search your library for an instant card or a card with flash, reveal it, and put it into your hand. Then shuffle your library.\nFlashback {5}{B} (You may cast this card from your graveyard for its flashback cost. Then exile it.)

View File

@@ -1,7 +1,7 @@
Name:Stun
ManaCost:1 R
Types:Instant
A:SP$ Pump | Cost$ 1 R | ValidTgts$ Creature | TgtPrompt$ Select target creature | KW$ CARDNAME can't block. | IsCurse$ True | SpellDescription$ Target creature can't block this turn. | SubAbility$ DBDraw
A:SP$ Pump | Cost$ 1 R | ValidTgts$ Creature | TgtPrompt$ Select target creature | KW$ HIDDEN CARDNAME can't block. | IsCurse$ True | SpellDescription$ Target creature can't block this turn. | SubAbility$ DBDraw
SVar:DBDraw:DB$Draw | NumCards$ 1 | SpellDescription$ Draw a card.
SVar:Picture:http://www.wizards.com/global/images/magic/general/stun.jpg
Oracle:Target creature can't block this turn.\nDraw a card.

View File

@@ -0,0 +1,88 @@
[metadata]
Code=DDN
Date=2014-09-05
Name=Duel Decks: Speed vs. Cunning
Type=Duel_Decks
[cards]
M Zurgo Helmsmasher
U Frenzied Goblin
C Infantry Veteran
C Leonin Snarecaster
C Dregscape Zombie
C Goblin Deathraiders
U Hellraiser Goblin
U Fleshbag Marauder
U Goblin Warchief
R Hell's Thunder
C Kathari Bomber
U Shambling Remains
U Mardu Heart-Piercer
U Beetleback Chief
R Krenko, Mob Boss
R Ogre Battledriver
U Flame-Kin Zealot
U Scourge Devil
U Oni of Wild Places
C Reckless Abandon
C Shock
C Bone Splinters
U Arc Trail
U Goblin Bombardment
C Krenko's Command
C Act of Treason
U Dauntless Onslaught
C Orcish Cannonade
C Fiery Fall
R Fury of the Horde
R Banefire
C Evolving Wilds
U Ghitu Encampment
U Nomad Outpost
L Mountain
L Mountain
L Mountain
L Mountain
L Plains
L Plains
L Plains
L Plains
L Swamp
L Swamp
L Swamp
M Arcanis the Omnipotent
U Faerie Impostor
C Coral Trickster
C Fathom Seer
U Jeskai Elder
U Willbender
C Sparkmage Apprentice
C Lone Missionary
C Master Decoy
C Echo Tracer
C Kor Hookmaster
U Stonecloaker
C Aquamorph Entity
C Hussar Patrol
R Lightning Angel
C Faerie Invaders
R Thousand Winds
R Sphinx of Uthuun
C Fleeting Distraction
C Stave Off
C Swift Justice
C Impulse
C Mana Leak
U Lightning Helix
R Hold the Line
U Inferno Trap
R Steam Augury
C Traumatic Visions
C Whiplash Trap
U Arrow Volley Trap
C Repeal
U Mystic Monastery
C Terramorphic Expanse
L Island
L Island
L Island

View File

@@ -64,7 +64,7 @@ public abstract class InputBase implements java.io.Serializable, Input {
}
protected boolean allowAwaitNextInput() {
return true;
return false;
}
private static final Timer awaitNextInputTimer = new Timer();

View File

@@ -28,6 +28,7 @@ import forge.game.player.Player;
import forge.game.zone.ZoneType;
import forge.player.PlayerControllerHuman;
import forge.util.ITriggerEvent;
import forge.util.ThreadUtil;
import forge.util.gui.SGuiDialog;
import forge.view.CardView;
@@ -90,14 +91,20 @@ public class InputBlock extends InputSyncronizedBase {
/** {@inheritDoc} */
@Override
public final void onOk() {
String blockErrors = CombatUtil.validateBlocks(combat, defender);
final String blockErrors = CombatUtil.validateBlocks(combat, defender);
if (blockErrors == null) {
// Done blocking
setCurrentAttacker(null);
stop();
}
else {
SGuiDialog.message(getGui(), blockErrors);
//must run in game thread to prevent problems for mobile game
ThreadUtil.invokeInGameThread(new Runnable() {
@Override
public void run() {
SGuiDialog.message(getGui(), blockErrors);
}
});
}
}

View File

@@ -91,7 +91,7 @@ public class InputPassPriority extends InputSyncronizedBase {
@Override
protected boolean allowAwaitNextInput() {
return !player.getController().mayAutoPass(); //don't allow awaiting next input if player chose to end the turn
return chosenSa == null && !player.getController().mayAutoPass(); //don't allow awaiting next input if player chose to end the turn or if a spell/ability is chosen
}
private void passPriority(final Runnable runnable) {

View File

@@ -58,8 +58,17 @@ public class InputProxy implements Observer {
}
public boolean passPriority() {
return passPriority(false);
}
public boolean passPriorityUntilEndOfTurn() {
return passPriority(true);
}
private boolean passPriority(final boolean passUntilEndOfTurn) {
final Input inp = getInput();
if (inp instanceof InputPassPriority) {
if (passUntilEndOfTurn) {
controller.autoPassUntilEndOfTurn();
}
inp.selectButtonOK();
return true;
}

View File

@@ -1421,6 +1421,11 @@ public class PlayerControllerLocal extends PlayerControllerHuman implements IGam
return getInputProxy().passPriority();
}
@Override
public boolean passPriorityUntilEndOfTurn() {
return getInputProxy().passPriorityUntilEndOfTurn();
}
@Override
public void selectPlayer(final PlayerView player, final ITriggerEvent triggerEvent) {
getInputProxy().selectPlayer(player, triggerEvent);

View File

@@ -55,6 +55,7 @@ public interface IGameView {
public abstract void selectButtonOk();
public abstract void selectButtonCancel();
public abstract boolean passPriority();
public abstract boolean passPriorityUntilEndOfTurn();
public abstract void selectPlayer(PlayerView player, ITriggerEvent triggerEvent);
public abstract void selectCard(CardView card, ITriggerEvent triggerEvent);
public abstract void selectAbility(SpellAbilityView sa);