- AF_Counter now uses actual Target object. AI seems to be casting away properly now.

- Converted all of the Counter SAs to use the new Target structure
This commit is contained in:
jendave
2011-08-06 22:35:06 +00:00
parent 72d5313e5c
commit fcc1e7da84
150 changed files with 670 additions and 621 deletions

View File

@@ -168,6 +168,27 @@ public class ComputerAI_General implements Computer {
return getPlayable(all);
}
private ArrayList<SpellAbility> getPossibleCounters(){
CardList all = new CardList();
all.addAll(AllZone.Computer_Hand.getCards());
all.addAll(AllZone.Computer_Battlefield.getCards());
all.addAll(CardFactoryUtil.getFlashbackCards(AllZone.ComputerPlayer).toArray());
CardList humanPlayable = new CardList();
humanPlayable.addAll(AllZone.Human_Battlefield.getCards());
humanPlayable = humanPlayable.filter(new CardListFilter()
{
public boolean addCard(Card c)
{
return (c.canAnyPlayerActivate());
}
});
all.addAll(humanPlayable.toArray());
return getPlayableCounters(all);
}
/**
* Returns the spellAbilities from the card list that the computer is able to play
*/
@@ -175,6 +196,7 @@ public class ComputerAI_General implements Computer {
ArrayList<SpellAbility> spellAbility = new ArrayList<SpellAbility>();
for(Card c:l)
for(SpellAbility sa:c.getSpellAbility())
// if SA is from AF_Counter don't add to getPlayable
//This try/catch should fix the "computer is thinking" bug
try {
sa.setActivatingPlayer(AllZone.ComputerPlayer);
@@ -187,6 +209,16 @@ public class ComputerAI_General implements Computer {
return spellAbility.toArray(new SpellAbility[spellAbility.size()]);
}
private ArrayList<SpellAbility> getPlayableCounters(CardList l) {
ArrayList<SpellAbility> spellAbility = new ArrayList<SpellAbility>();
for(Card c:l)
for(SpellAbility sa:c.getSpellAbility())
if (sa.getAbilityFactory() != null && sa.getAbilityFactory().getAPI().equals("Counter"))
spellAbility.add(sa);
return spellAbility;
}
public void begin_combat() {
stackResponse();
}
@@ -270,20 +302,27 @@ public class ComputerAI_General implements Computer {
return;
}
// top of stack is owned by human,
// top of stack is owned by human,
ArrayList<SpellAbility> possibleCounters = getPossibleCounters();
sas = getOtherPhases();
if (sas.length > 0){
if (possibleCounters.size() > 0 && ComputerUtil.playCounterSpell(possibleCounters)){
// Responding CounterSpell is on the Stack trying to Counter the Spell
// If playCounterSpell returns true, a Spell is hitting the Stack
return;
}
else if (sas.length > 0){
// Spell not Countered
// each AF should check the Stack/Phase on it's own
//ArrayList<Object> targets = topSA.getTarget().getTargets();
// does it target me or something I own?
// can i protect it? can I counter it?
// if i can't save it, can I activate an ability on that card in response? sacrifice etc?
// does it target his stuff? can I kill it in response?
//ArrayList<Object> targets = topSA.getTarget().getTargets();
// does it target me or something I own?
// can i protect it? can I counter it?
// if i can't save it, can I activate an ability on that card in response? sacrifice etc?
// does it target his stuff? can I kill it in response?
}
// if this hasn't been covered above, just PassPriority()
AllZone.Phase.passPriority();

View File

@@ -1,119 +0,0 @@
package forge;
import java.util.ArrayList;
import forge.card.cardFactory.CardFactoryUtil;
import forge.card.spellability.SpellAbility;
public class ComputerAI_counterSpells2 {
public static ArrayList<String> KeywordedCounterspells = new ArrayList<String>();
public static void counter_Spell(SpellAbility sa)
{
CardList counterSpells = getPlayableCounterSpells(sa);
boolean countered = false;
for (Card var:counterSpells)
{
if (countered)
break;
else if (CardUtil.getConvertedManaCost(var.getManaCost()) <= CardUtil.getConvertedManaCost(sa) ||
(var.getName().equals("Overwhelming Intellect") && CardUtil.getConvertedManaCost(sa) >= 3 ))
{
SpellAbility sp = var.getSpellAbility()[0];
//ComputerUtil.playNoStack(sp);
ComputerUtil.playStack(sp);
countered = true;
}
}
}
public static CardList getPlayableCounterSpells(final SpellAbility sa){
final String[] basic = {
"Mystic Snake"/*, "Force of Will", "Thwart" */
};
final String[] creature = {
"Overwhelming Intellect"
};
final String[] nonCreatureUnlessPay2 = {
"Spell Pierce"
};
final String[] unlessPay1 = {
"Force Spike", "Daze", "Runeboggle", "Spell Snip"
};
final String[] unlessPay3 = {
"Mana Leak"
};
final String[] unlessPay4 = {
"Convolute"
};
final int usableManaSources = CardFactoryUtil.getUsableManaSources(AllZone.HumanPlayer);
PlayerZone hand = AllZone.getZone(Constant.Zone.Hand, AllZone.ComputerPlayer);
CardList list = new CardList(hand.getCards());
list = list.filter(new CardListFilter()
{
public boolean addCard(Card c)
{
if(KeywordedCounterspells.contains(c.getName()))
{
System.out.println("Counterspell is keyworded, casting is a-go.");
return c.getSpells().get(0).canPlayAI();
}
if (usableManaSources == 0) {
if (checkArray(c, unlessPay1))
return true;
}
if (usableManaSources < 3) {
if (checkArray(c, unlessPay3))
return true;
}
if (usableManaSources < 4) {
if (checkArray(c, unlessPay4))
return true;
}
if (sa.getSourceCard().isCreature()) {
if (checkArray(c, creature))
return true;
}
else if (!sa.getSourceCard().isCreature() && usableManaSources < 2) {
if (checkArray(c, nonCreatureUnlessPay2))
return true;
}
if (checkArray(c, basic))
return true;
return false;
}
});
return list;
}
public static boolean checkArray(Card c, String[] type)
{
if (c.getSpellAbility().length == 0)
return false;
for(String s : type)
{
SpellAbility sa = c.getSpellAbility()[0];
if (s.equals(c.getName()) && ComputerUtil.canPayCost(sa) && !c.isUnCastable())
return true;
}
return false;
}
}

View File

@@ -6,6 +6,8 @@ import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import javax.swing.text.html.MinimalHTMLWriter;
import forge.card.abilityFactory.AbilityFactory;
import forge.card.cardFactory.CardFactoryUtil;
import forge.card.mana.ManaCost;
@@ -33,6 +35,12 @@ public class ComputerUtil
// MyRandom.shuffle(all);
for(SpellAbility sa : all){
// Don't add Counterspells to the "normal" playcard lookupss
AbilityFactory af = sa.getAbilityFactory();
if (af != null && af.getAPI().equals("Counter"))
continue;
sa.setActivatingPlayer(AllZone.ComputerPlayer);
if(canPayCost(sa) && sa.canPlay() && sa.canPlayAI())
{
@@ -65,6 +73,112 @@ public class ComputerUtil
return true;
}//playCards()
static public int counterSpellRestriction(SpellAbility sa){
// Move this to AF?
// Restriction Level is Based off a handful of factors
int restrict = 0;
Card source = sa.getSourceCard();
Target tgt = sa.getTarget();
AbilityFactory af = sa.getAbilityFactory();
HashMap<String, String> params = af.getMapParams();
// Play higher costing spells first?
Cost cost = sa.getPayCosts();
// Convert cost to CMC
String totalMana = source.getSVar("PayX"); // + cost.getCMC()
// Consider the costs here for relative "scoring"
if (cost.getDiscardType().equals("Hand")){
// Null Brooch aid
restrict -= (AllZoneUtil.getPlayerHand(AllZone.ComputerPlayer).size() * 20);
}
// Abilities before Spells (card advantage)
if (af.isAbility())
restrict += 40;
// TargetValidTargeting gets biggest bonus
if (tgt.getSAValidTargeting() != null){
restrict += 35;
}
// Unless Cost gets significant bonus + 10-Payment Amount
String unless = params.get("UnlessCost");
if (unless != null){
int amount = AbilityFactory.calculateAmount(source, unless, sa);
restrict += 20 - (2*amount);
}
// Then base on Targeting Restriction
String[] validTgts = tgt.getValidTgts();
if (validTgts.length != 1 || !validTgts[0].equals("Card"))
restrict += 10;
// And lastly give some bonus points to least restrictive TargetType (Spell,Ability,Triggered)
String tgtType = tgt.getTargetSpellAbilityType();
restrict -= (5*tgtType.split(",").length);
return restrict;
}
//if return true, go to next phase
static public boolean playCounterSpell(ArrayList<SpellAbility> possibleCounters)
{
SpellAbility bestSA = null;
int bestRestriction = Integer.MIN_VALUE;
for(SpellAbility sa : possibleCounters){
sa.setActivatingPlayer(AllZone.ComputerPlayer);
if(canPayCost(sa) && sa.canPlay() && sa.canPlayAI()){
if (bestSA == null){
bestSA = sa;
bestRestriction = counterSpellRestriction(sa);
}
else{
// Compare bestSA with this SA
int restrictionLevel = counterSpellRestriction(sa);
if (restrictionLevel > bestRestriction){
bestRestriction = restrictionLevel;
bestSA = sa;
}
}
}
}//while
if (bestSA == null)
return false;
// TODO
// "Look" at Targeted SA and "calculate" the threshold
// if (bestRestriction < targetedThreshold) return false;
AllZone.Stack.freezeStack();
Card source = bestSA.getSourceCard();
if(bestSA.isSpell() && !source.isCopiedSpell())
AllZone.GameAction.moveToStack(source);
Cost cost = bestSA.getPayCosts();
if (cost == null){
// Honestly Counterspells shouldn't use this branch
payManaCost(bestSA);
bestSA.chooseTargetAI();
bestSA.getBeforePayManaAI().execute();
AllZone.Stack.addAndUnfreeze(bestSA);
}
else{
Cost_Payment pay = new Cost_Payment(cost, bestSA);
pay.payComputerCosts();
}
return true;
}//playCounterSpell()
//this is used for AI's counterspells
final static public void playStack(SpellAbility sa)
{

View File

@@ -16,6 +16,7 @@ import forge.card.spellability.Ability_Triggered;
import forge.card.spellability.SpellAbility;
import forge.card.spellability.Spell_Permanent;
import forge.card.spellability.Target;
import forge.card.spellability.Target_Selection;
import forge.gui.GuiUtils;
import forge.gui.input.Input;
import forge.gui.input.Input_PayManaCost_Ability;
@@ -634,12 +635,8 @@ public class MagicStack extends MyObservable {
}
}
// attempt to counter human spell
// TODO this needs to move to the stack response section for the AI
if (sp.getSourceCard().getController().equals(AllZone.HumanPlayer) && CardFactoryUtil.isCounterable(sp.getSourceCard()))
ComputerAI_counterSpells2.counter_Spell(sp);
GameActionUtil.executePlayCardEffects(sp);
}
}
@@ -759,6 +756,10 @@ public class MagicStack extends MyObservable {
Card card = (Card)o;
fizzle &= !(CardFactoryUtil.isTargetStillValid(fizzSA, card));
}
if (o instanceof SpellAbility){
SpellAbility tgtSA = (SpellAbility)o;
fizzle &= !(Target_Selection.matchSpellAbility(fizzSA, tgtSA, tgt));
}
}
}
else if (fizzSA.getTargetCard() != null) {

View File

@@ -7,7 +7,6 @@ import forge.AllZone;
import forge.AllZoneUtil;
import forge.Card;
import forge.CardList;
import forge.ComputerAI_counterSpells2;
import forge.Constant;
import forge.Player;
import forge.card.cardFactory.CardFactoryUtil;
@@ -95,6 +94,9 @@ public class AbilityFactory {
return hasSpDesc;
}
private String API = "";
public String getAPI() { return API; }
//*******************************************************
public HashMap<String,String> getMapParams(String abString, Card hostCard) {
@@ -140,8 +142,7 @@ public class AbilityFactory {
mapParams = getMapParams(abString, hostCard);
// parse universal parameters
String API = "";
if (mapParams.containsKey("AB"))
{
isAb = true;
@@ -194,6 +195,15 @@ public class AbilityFactory {
if (mapParams.containsKey("TgtZone")) // if Targeting something not in play, this Key should be set
abTgt.setZone(mapParams.get("TgtZone"));
// Target Type mostly for Counter: Spell,Activated,Triggered,Ability (or any combination of)
// Ability = both activated and triggered abilities
if (mapParams.containsKey("TargetType"))
abTgt.setTargetSpellAbilityType(mapParams.get("TargetType"));
// TargetValidTargeting most for Counter: e.g. target spell that targets X.
if (mapParams.containsKey("TargetValidTargeting"))
abTgt.setSAValidTargeting(mapParams.get("TargetValidTargeting"));
}
hasSubAb = mapParams.containsKey("SubAbility");
@@ -261,7 +271,6 @@ public class AbilityFactory {
SA = AbilityFactory_Counters.createDrawbackProliferate(this);
}
if (API.equals("ChangeZone")){
if (isAb)
SA = AbilityFactory_ChangeZone.createAbilityChangeZone(this);
@@ -280,32 +289,6 @@ public class AbilityFactory {
SA = AbilityFactory_ChangeZone.createDrawbackChangeZoneAll(this);
}
// Fetch, Retrieve and Bounce should be converted ChangeZone
/*
if (API.equals("Fetch")){
if (isAb)
SA = AbilityFactory_Fetch.createAbilityFetch(this);
else if (isSp)
SA = AbilityFactory_Fetch.createSpellFetch(this);
}
if (API.equals("Retrieve")){
if (isAb)
SA = AbilityFactory_Fetch.createAbilityRetrieve(this);
else if (isSp)
SA = AbilityFactory_Fetch.createSpellRetrieve(this);
}
if (API.equals("Bounce")){
if (isAb)
SA = AbilityFactory_Bounce.createAbilityBounce(this);
else if (isSp)
SA = AbilityFactory_Bounce.createSpellBounce(this);
hostCard.setSVar("PlayMain1", "TRUE");
}
*/
// Convert above abilities to gain Drawback
if (API.equals("Pump"))
{
AbilityFactory_Pump afPump = new AbilityFactory_Pump(this);
@@ -546,7 +529,9 @@ public class AbilityFactory {
if(API.equals("Counter")){
AbilityFactory_CounterMagic c = new AbilityFactory_CounterMagic(this);
ComputerAI_counterSpells2.KeywordedCounterspells.add(hostC.getName());
if (isTargeted) // Since all "Counter" ABs Counter things on the Stack no need for it to be everywhere
abTgt.setZone("Stack");
if(isAb)
SA = c.getAbilityCounter(this);
@@ -969,12 +954,21 @@ public class AbilityFactory {
return players;
}
public static ArrayList<SpellAbility> getDefinedSpellAbilities(Card card, String def, SpellAbility sa){
ArrayList<SpellAbility> sas = new ArrayList<SpellAbility>();
// TODO: Define SpellAbilities
return sas;
}
public static ArrayList<Object> getDefinedObjects(Card card, String def, SpellAbility sa){
ArrayList<Object> objects = new ArrayList<Object>();
String defined = (def == null) ? "Self" : def;
objects.addAll(getDefinedPlayers(card, defined, sa));
objects.addAll(getDefinedCards(card, defined, sa));
objects.addAll(getDefinedSpellAbilities(card, defined, sa));
return objects;
}

View File

@@ -19,12 +19,10 @@ import forge.card.spellability.Ability_Activated;
import forge.card.spellability.Ability_Sub;
import forge.card.spellability.Spell;
import forge.card.spellability.SpellAbility;
import forge.card.spellability.Target;
import forge.card.spellability.Target_Selection;
import forge.gui.GuiUtils;
import forge.gui.input.Input;
//Type - Spell or Ability or SpellOrAbility
//CounterValid - a "valid" expression for types to counter
//SpellTarget - a "valid" expression for targets of the spell to counter
//Destination - send countered spell to: (only applies to Spells; ignored for Abilities)
// -Graveyard (Default)
// -Exile
@@ -37,34 +35,22 @@ import forge.gui.input.Input;
//ExtraActions - implemented exactly as spCounter used them (can probably be updated to SubAbility/Drawback), then this param is eliminated
//Examples:
//A:SP$Counter | Cost$ 1 G | Type$ Ability | SpellDescription$ Counter target activated ability.
//A:AB$Counter | Cost$ G G | Type$ Spell | Destination$ Exile | CounterValid$ Color.Black | SpellDescription$ xxxxx
//A:SP$Counter | Cost$ 1 G | TargetType$ Activated | SpellDescription$ Counter target activated ability.
//A:AB$Counter | Cost$ G G | TargetType$ Spell | Destination$ Exile | ValidTgts$ Color.Black | SpellDescription$ xxxxx
public class AbilityFactory_CounterMagic {
private AbilityFactory af = null;
private HashMap<String,String> params = null;
private String targetType = null;
private String destination = null;
private String[] splitTargetingRestrictions = null;
private String[] splitSpellTargetRestrictions = null;
private String[] splitExtraActions;
private String unlessCost = null;
private final SpellAbility[] tgt = new SpellAbility[1];
public AbilityFactory_CounterMagic(AbilityFactory newAF) {
af = newAF;
params = af.getMapParams();
targetType = params.containsKey("Type") ? params.get("Type") : "Spell";
destination = params.containsKey("Destination") ? params.get("Destination") : "Graveyard";
if(params.containsKey("CounterValid")) {
splitTargetingRestrictions = params.get("CounterValid").split(",");
}
else splitTargetingRestrictions = new String[] {"Card"};
if(params.containsKey("SpellTarget")) {
splitSpellTargetRestrictions = params.get("SpellTarget").split(",");
}
if(params.containsKey("ExtraActions")) {
splitExtraActions = params.get("ExtraActions").split(" ");
}
@@ -73,7 +59,6 @@ public class AbilityFactory_CounterMagic {
if(params.containsKey("UnlessCost"))
unlessCost = params.get("UnlessCost").trim();
tgt[0] = null;
}
public SpellAbility getAbilityCounter(final AbilityFactory AF) {
@@ -86,13 +71,6 @@ public class AbilityFactory_CounterMagic {
return counterStackDescription(af, this);
}
@Override
public boolean canPlay() {
// super takes care of AdditionalCosts
//important to keep super.canPlay() first due to targeting hack in counterCanPlay
return super.canPlay() && counterCanPlay(af, this);
}
@Override
public boolean canPlayAI() {
return counterCanPlayAI(af, this);
@@ -121,13 +99,6 @@ public class AbilityFactory_CounterMagic {
return counterStackDescription(af, this);
}
@Override
public boolean canPlay() {
// super takes care of AdditionalCosts
//important to keep super.canPlay() first due to targeting hack in counterCanPlay
return super.canPlay() && counterCanPlay(af, this);
}
@Override
public boolean canPlayAI() {
return counterCanPlayAI(af, this);
@@ -141,63 +112,121 @@ public class AbilityFactory_CounterMagic {
};
return spCounter;
}
// Add Counter Drawback
private void counterResolve(final AbilityFactory af, final SpellAbility sa) {
private boolean counterCanPlayAI(final AbilityFactory af, final SpellAbility sa){
boolean toReturn = true;
if(AllZone.Stack.size() < 1) {
return false;
}
SpellAbility topSA = AllZone.Stack.peek();
if (!CardFactoryUtil.isCounterable(topSA.getSourceCard()))
return false;
Target tgt = sa.getTarget();
if (Target_Selection.matchSpellAbility(sa, topSA, tgt))
tgt.addTarget(topSA);
else
return false;
Card source = sa.getSourceCard();
//copied from spCounter
if(matchSpellAbility(sa.getSourceCard(), tgt[0], splitTargetingRestrictions, splitSpellTargetRestrictions, targetType)
&& AllZone.Stack.contains(tgt[0])
&& !tgt[0].getSourceCard().keywordsContain("CARDNAME can't be countered.")) {
final SpellAbility tgtSA = tgt[0];
Card tgtSACard = tgtSA.getSourceCard();
System.out.println("Send countered spell to " + destination);
if(unlessCost != null) {
String unlessCostFinal = unlessCost;
if(unlessCost.equals("X"))
{
unlessCostFinal = Integer.toString(CardFactoryUtil.xCount(af.getHostCard(), af.getHostCard().getSVar("X")));
}
Ability ability = new Ability(af.getHostCard(), unlessCostFinal) {
@Override
public void resolve() {
;
}
};
final Command unpaidCommand = new Command() {
private static final long serialVersionUID = 8094833091127334678L;
public void execute() {
removeFromStack(tgtSA,sa);
if(params.containsKey("PowerSink")) doPowerSink(AllZone.HumanPlayer);
}
};
if(AllZone.Stack.peek().getActivatingPlayer().isHuman()) {
GameActionUtil.payManaDuringAbilityResolve(af.getHostCard() + "\r\n", ability.getManaCost(),
Command.Blank, unpaidCommand);
} else {
if(ComputerUtil.canPayCost(ability)) ComputerUtil.playNoStack(ability);
else {
removeFromStack(tgtSA,sa);
if(params.containsKey("PowerSink")) doPowerSink(AllZone.ComputerPlayer);
}
}
doExtraActions(tgtSA,sa);
if (unlessCost != null){
// Is this Usable Mana Sources? Or Total Available Mana?
int usableManaSources = CardFactoryUtil.getUsableManaSources(AllZone.HumanPlayer);
int toPay = 0;
boolean setPayX = false;
if (unlessCost.equals("X") && source.getSVar(unlessCost).equals("Count$xPaid")){
setPayX = true;
toPay = ComputerUtil.determineLeftoverMana(sa);
}
else
{
removeFromStack(tgtSA,sa);
doExtraActions(tgtSA,sa);
}
toPay = AbilityFactory.calculateAmount(source, unlessCost, sa);
if(tgtSA.isAbility() && params.containsKey("DestroyPermanent")) {
AllZone.GameAction.destroy(tgtSACard);
}
if (toPay == 0 || toPay <= usableManaSources)
return false;
if (setPayX)
source.setSVar("PayX", Integer.toString(toPay));
}
// TODO: Improve AI
// Will return true if this spell can counter (or will likely counter) and false if it can't
// But really it should be more picky about how it counters things
Ability_Sub subAb = sa.getSubAbility();
if (subAb != null)
toReturn &= subAb.chkAI_Drawback();
return toReturn;
}
private void counterResolve(final AbilityFactory af, final SpellAbility sa) {
// TODO: Before this resolves we should see if any of our targets are still on the stack
Card source = sa.getSourceCard();
Target tgt = sa.getTarget();
ArrayList<SpellAbility> sas = tgt.getTargetSAs();
if (sas == null){
sas = AbilityFactory.getDefinedSpellAbilities(sa.getSourceCard(), params.get("Defined"), sa);
}
for(final SpellAbility tgtSA : sas){
Card tgtSACard = tgtSA.getSourceCard();
if (AllZone.Stack.contains(tgtSA) && !tgtSACard.keywordsContain("CARDNAME can't be countered.")){
// TODO: Unless Cost should be generalized for all AFS
if(unlessCost != null) {
String unlessCostFinal = unlessCost;
if(unlessCost.equals("X"))
unlessCostFinal = Integer.toString(CardFactoryUtil.xCount(af.getHostCard(), af.getHostCard().getSVar("X")));
// Above xCount should probably be changed to a AF.calculateAmount
Ability ability = new Ability(af.getHostCard(), unlessCostFinal) {
@Override
public void resolve() {
;
}
};
final Command unpaidCommand = new Command() {
private static final long serialVersionUID = 8094833091127334678L;
public void execute() {
removeFromStack(tgtSA, sa);
if(params.containsKey("PowerSink")) doPowerSink(AllZone.HumanPlayer);
}
};
if(tgtSA.getActivatingPlayer().isHuman()) {
GameActionUtil.payManaDuringAbilityResolve(af.getHostCard() + "\r\n", ability.getManaCost(),
Command.Blank, unpaidCommand);
} else {
if(ComputerUtil.canPayCost(ability))
ComputerUtil.playNoStack(ability);
else {
removeFromStack(tgtSA,sa);
if(params.containsKey("PowerSink")) doPowerSink(AllZone.ComputerPlayer);
}
}
}
else
removeFromStack(tgtSA,sa);
// Destroy Permanent may be able to be turned into a SubAbility
if(tgtSA.isAbility() && params.containsKey("DestroyPermanent")) {
AllZone.GameAction.destroy(tgtSACard);
}
}
// Do Extra Actions whether or not the spell was actually countered
doExtraActions(tgtSA,sa);
}
if (af.hasSubAbility()){
@@ -206,14 +235,11 @@ public class AbilityFactory_CounterMagic {
abSub.resolve();
}
else{
//I think UntapUpTo is the main thing holding this back
String DrawBack = params.get("SubAbility");
CardFactoryUtil.doDrawBack(DrawBack, 0, source.getController(), source.getController().getOpponent(), source.getController(), source, null, sa);
}
}
//reset tgts
tgt[0] = null;
}
private void doPowerSink(Player p) {
@@ -240,14 +266,25 @@ public class AbilityFactory_CounterMagic {
else
sb.append(" ");
sb.append("countering ");
sb.append(tgt[0].getSourceCard().getName());
if(tgt[0].isAbility()) sb.append("'s ability.");
else sb.append(".");
sb.append("countering");
if(tgt[0].isAbility() && params.containsKey("DestroyPermanent")) {
sb.append(" Destroy "+tgt[0].getSourceCard()).append(".");
ArrayList<SpellAbility> sas = sa.getTarget().getTargetSAs();
if (sas == null)
sas = AbilityFactory.getDefinedSpellAbilities(sa.getSourceCard(), params.get("Defined"), sa);
boolean isAbility = false;
for(final SpellAbility tgtSA : sas){
sb.append(" ");
sb.append(tgtSA.getSourceCard().getName());
isAbility = tgtSA.isAbility();
if(isAbility) sb.append("'s ability");
}
if(isAbility && params.containsKey("DestroyPermanent")) {
sb.append(" and Destroy it");
}
sb.append(".");
Ability_Sub abSub = sa.getSubAbility();
if (abSub != null){
@@ -256,57 +293,6 @@ public class AbilityFactory_CounterMagic {
return sb.toString();
}//end counterStackDescription
private boolean counterCanPlay(final AbilityFactory af, final SpellAbility sa) {
ArrayList<SpellAbility> choosables = new ArrayList<SpellAbility>();
for(int i = 0; i < AllZone.Stack.size(); i++) {
choosables.add(AllZone.Stack.peek(i));
}
for(int i = 0; i < choosables.size(); i++) {
if(!matchSpellAbility(sa.getSourceCard(), choosables.get(i),
splitTargetingRestrictions, splitSpellTargetRestrictions, targetType)) {
choosables.remove(i);
}
}
if(tgt[0] == null && choosables.size() > 0 ) AllZone.InputControl.setInput(getInput(sa));
return choosables.size() > 0;
}
private boolean counterCanPlayAI(final AbilityFactory af, final SpellAbility sa){
boolean toReturn = false;
if(AllZone.Stack.size() < 1) {
return false;
}
SpellAbility topSA = AllZone.Stack.peek();
if (!CardFactoryUtil.isCounterable(topSA.getSourceCard()))
return false;
if(matchSpellAbility(sa.getSourceCard(), topSA, splitTargetingRestrictions, splitSpellTargetRestrictions, targetType)) {
tgt[0] = topSA;
toReturn = true;
}
else
return false;
Card source = sa.getSourceCard();
if (unlessCost != null && unlessCost.equals("X") && source.getSVar(unlessCost).equals("Count$xPaid")){
int xPay = ComputerUtil.determineLeftoverMana(sa);
if (xPay == 0) // todo: compare xPay to human's leftover mana
return false;
source.setSVar("PayX", Integer.toString(xPay));
}
Ability_Sub subAb = sa.getSubAbility();
if (subAb != null)
toReturn &= subAb.chkAI_Drawback();
return toReturn;
}
private void removeFromStack(SpellAbility tgtSA,SpellAbility srcSA)
{
@@ -336,11 +322,14 @@ public class AbilityFactory_CounterMagic {
}
else {
throw new IllegalArgumentException("AbilityFactory_CounterMagic: Invalid Destination argument for card " + srcSA.getSourceCard().getName());
}
}
if (!tgtSA.isAbility())
System.out.println("Send countered spell to " + destination);
}
private void doExtraActions(SpellAbility tgtSA,SpellAbility srcSA)
{
// TODO: Convert Extra Actions
for(int ea = 0; ea < splitExtraActions.length; ea++) {
boolean isOptional = false;
@@ -493,125 +482,4 @@ public class AbilityFactory_CounterMagic {
}
}
}
private static boolean matchSpellAbility(Card srcCard, SpellAbility sa, String[] splitRestrictions, String[] splitTargetRestrictions, String targetType) {
boolean result = true;
if(targetType.equals("Spell")) {
if(sa.isAbility()) {
System.out.println(srcCard.getName() + " can only counter spells, not abilities.");
return false;
}
}
else if(targetType.equals("Ability")) {
if(sa.isSpell()) {
System.out.println(srcCard.getName() + " can only counter abilities, not spells.");
return false;
}
}
else if(targetType.equals("SpellOrAbility")) {
//Do nothing. This block is only for clarity and enforcing parameters.
}
else {
throw new IllegalArgumentException("Invalid target type for card " + srcCard.getName());
}
if(splitTargetRestrictions != null)
{
result = false;
if(sa.getTarget() != null)
{
for(Object o : sa.getTarget().getTargets())
{
if(matchesValid(o,splitTargetRestrictions,srcCard))
{
result = true;
break;
}
}
}
else
{
return false;
}
}
if(!matchesValid(sa.getSourceCard(),splitRestrictions,srcCard))
{
return false;
}
return result;
}//matchSpellAbility
private static boolean matchesValid(Object o,String[] valids,Card srcCard)
{
if(o instanceof Card)
{
Card c = (Card)o;
return c.isValidCard(valids, srcCard.getController(), srcCard);
}
if(o instanceof Player)
{
for(String v : valids)
{
if(v.equalsIgnoreCase("Player"))
{
return true;
}
if(v.equalsIgnoreCase("Opponent"))
{
if(o.equals(srcCard.getController().getOpponent()))
{
return true;
}
}
if(v.equalsIgnoreCase("You"))
{
return o.equals(srcCard.getController());
}
}
}
return false;
}
private Input getInput(final SpellAbility sa) {
Input runtime = new Input() {
private static final long serialVersionUID = 5360660530175041997L;
@Override
public void showMessage() {
ArrayList<SpellAbility> choosables = new ArrayList<SpellAbility>();
for(int i = 0; i < AllZone.Stack.size(); i++) {
choosables.add(AllZone.Stack.peek(i));
}
for(int i = 0; i < choosables.size(); i++) {
if(!matchSpellAbility(sa.getSourceCard(), choosables.get(i), splitTargetingRestrictions, splitSpellTargetRestrictions, targetType) || choosables.get(i).getSourceCard().equals(sa.getSourceCard())) {
choosables.remove(i);
}
}
HashMap<String,SpellAbility> map = new HashMap<String,SpellAbility>();
for(SpellAbility sa : choosables) {
map.put(sa.getStackDescription(),sa);
}
String[] choices = new String[map.keySet().size()];
choices = map.keySet().toArray(choices);
String madeChoice = GuiUtils.getChoice("Select target spell.",choices);
tgt[0] = map.get(madeChoice);
System.out.println(tgt[0]);
stop();
}//showMessage()
};//Input
return runtime;
}
}

View File

@@ -22,7 +22,6 @@ import forge.CardListFilter;
import forge.CardListUtil;
import forge.CardUtil;
import forge.Command;
import forge.ComputerAI_counterSpells2;
import forge.ComputerUtil;
import forge.Constant;
import forge.Counters;
@@ -2055,13 +2054,10 @@ public class CardFactory implements NewConstants {
if (getSourceCard().getAttachedCards().length > 0)
{
// Isochron Scepter might be broken?
Card c = copyCard(getSourceCard().getAttachedCards()[0]);
if (ComputerAI_counterSpells2.KeywordedCounterspells.contains(c.getName()))
{
SpellAbility sa = c.getSpellAbility()[0];
return sa.canPlay();
}
else return true;
SpellAbility sa = c.getSpellAbility()[0];
return sa.canPlay();
}
else
return false;

View File

@@ -13,15 +13,8 @@ public class Target {
private boolean bMandatory = false;
private Card srcCard;
public boolean getMandatory()
{
return bMandatory;
}
public void setMandatory(boolean m)
{
bMandatory = m;
}
public boolean getMandatory() { return bMandatory; }
public void setMandatory(boolean m) { bMandatory = m; }
private boolean tgtValid = false;
private String ValidTgts[];
@@ -43,21 +36,30 @@ public class Target {
public void setZone(String tZone) { tgtZone = tZone; }
public String getZone() { return tgtZone; }
// Used for Counters. Currently, Spell,Activated,Triggered can be Comma-separated
private String targetSpellAbilityType = null;
public void setTargetSpellAbilityType(String tgtSAType) { targetSpellAbilityType = tgtSAType; }
public String getTargetSpellAbilityType() { return targetSpellAbilityType; }
// Used for Counters. The target SA of this SA must be targeting a Valid X
private String saValidTargeting = null;
public void setSAValidTargeting(String saValidTgting) { saValidTargeting = saValidTgting; }
public String getSAValidTargeting() { return saValidTargeting; }
// Card or Player are legal targets.
private ArrayList<Card> targetCards = new ArrayList<Card>();
private ArrayList<Player> targetPlayers = new ArrayList<Player>();
private ArrayList<SpellAbility> targetSAs = new ArrayList<SpellAbility>();
public void addTarget(Object o){
if (o instanceof Player){
Player p = (Player)o;
if (!targetPlayers.contains(p))
targetPlayers.add(p);
}
if (o instanceof Card){
Card c = (Card)o;
if (!targetCards.contains(c))
targetCards.add(c);
}
if (o instanceof Player)
addTarget((Player)o);
else if (o instanceof Card)
addTarget((Card)o);
else if (o instanceof SpellAbility)
addTarget((SpellAbility)o);
}
public boolean addTarget(Card c){
@@ -77,6 +79,15 @@ public class Target {
}
return false;
}
public boolean addTarget(SpellAbility sa){
if (!targetSAs.contains(sa)){
targetSAs.add(sa);
numTargeted++;
return true;
}
return false;
}
public ArrayList<Card> getTargetCards(){
return targetCards;
@@ -86,10 +97,15 @@ public class Target {
return targetPlayers;
}
public ArrayList<SpellAbility> getTargetSAs(){
return targetSAs;
}
public ArrayList<Object> getTargets(){
ArrayList<Object> tgts = new ArrayList<Object>();
tgts.addAll(targetPlayers);
tgts.addAll(targetCards);
tgts.addAll(targetSAs);
return tgts;
}
@@ -101,6 +117,7 @@ public class Target {
numTargeted = 0;
targetCards.clear();
targetPlayers.clear();
targetSAs.clear();
}
public Target(Card src,String parse){

View File

@@ -1,6 +1,7 @@
package forge.card.spellability;
import java.util.ArrayList;
import java.util.HashMap;
import forge.AllZone;
import forge.AllZoneUtil;
@@ -76,14 +77,10 @@ public class Target_Selection {
}
//targets still needed
if(target.getMandatory())
{
AllZone.InputControl.setInput(input_targetValid(ability, target.getValidTgts(), target.getVTSelection(), this, req, target.hasCandidates()));
}
else
{
AllZone.InputControl.setInput(input_targetValid(ability, target.getValidTgts(), target.getVTSelection(), this, req, false));
}
boolean mandatoryTarget = target.getMandatory() ? target.hasCandidates() : false;
AllZone.InputControl.setInput(input_targetValid(ability, target.getValidTgts(), target.getVTSelection(), this, req, mandatoryTarget));
return false;
}
@@ -100,6 +97,12 @@ public class Target_Selection {
Target tgt = select.getTgt();
String zone = tgt.getZone();
if (zone.equals(Constant.Zone.Stack)){
// If Zone is Stack, the choices are handled slightly differently
stopSetNext(input_cardFromStack(sa, message, select, req, mandatory));
return;
}
CardList choices = AllZoneUtil.getCardsInZone(zone).getValidCards(Tgts, sa.getActivatingPlayer(), sa.getSourceCard());
// Remove cards already targeted
@@ -120,10 +123,10 @@ public class Target_Selection {
canTargetOpponent = true;
}
stopSetNext(input_targetSpecific(sa, choices, message, true, canTargetPlayer, canTargetOpponent, select, req,mandatory));
stopSetNext(input_targetSpecific(sa, choices, message, true, canTargetPlayer, canTargetOpponent, select, req, mandatory));
}
else{
stopSetNext(input_cardFromList(sa, choices, message, true, select, req,mandatory));
stopSetNext(input_cardFromList(sa, choices, message, true, select, req, mandatory));
}
}
};
@@ -245,5 +248,140 @@ public class Target_Selection {
};//Input
return target;
}
}
public static Input input_cardFromStack(final SpellAbility sa, final String message,
final Target_Selection select, final SpellAbility_Requirements req, final boolean mandatory){
Input targetOnStack = new Input() {
private static final long serialVersionUID = 5360660530175041997L;
@Override
public void showMessage() {
// Find what's targetable, then allow human to choose
ArrayList<SpellAbility> choosables = getTargetableOnStack(sa, select.getTgt());
HashMap<String,SpellAbility> map = new HashMap<String,SpellAbility>();
for(SpellAbility sa : choosables) {
map.put(sa.getStackDescription(),sa);
}
String[] choices = new String[map.keySet().size()];
choices = map.keySet().toArray(choices);
if (choices.length == 0){
select.setCancel(true);
}
else{
String madeChoice = GuiUtils.getChoiceOptional(message, choices);
if (madeChoice != null){
Target tgt = select.getTgt();
tgt.addTarget(map.get(madeChoice));
}
else
select.setCancel(true);
}
stop();
req.finishedTargeting();
}//showMessage()
};
return targetOnStack;
}
// TODO: The following three functions are Utility functions for TargetOnStack, probably should be moved
// The following should be select.getTargetableOnStack()
public static ArrayList<SpellAbility> getTargetableOnStack(SpellAbility sa, Target tgt){
ArrayList<SpellAbility> choosables = new ArrayList<SpellAbility>();
for(int i = 0; i < AllZone.Stack.size(); i++) {
choosables.add(AllZone.Stack.peek(i));
}
for(int i = 0; i < choosables.size(); i++) {
if (!matchSpellAbility(sa, choosables.get(i), tgt)){
choosables.remove(i);
}
}
return choosables;
}
public static boolean matchSpellAbility(SpellAbility sa, SpellAbility match, Target tgt){
String saType = tgt.getTargetSpellAbilityType();
if (match.isSpell()){
if (!saType.contains("Spell"))
return false;
}
else if (match.isTrigger()){
if (!saType.contains("Triggered"))
return false;
}
else if (match.isAbility()){
if (!saType.contains("Activated"))
return false;
}
String splitTargetRestrictions = tgt.getSAValidTargeting();
if(splitTargetRestrictions != null){
// TODO: What about spells with SubAbilities with Targets?
Target matchTgt = match.getTarget();
if (matchTgt == null)
return false;
boolean result = false;
for(Object o : matchTgt.getTargets()){
if(matchesValid(o, splitTargetRestrictions.split(","), sa)){
result = true;
break;
}
}
if (!result)
return false;
}
if(!matchesValid(match, tgt.getValidTgts(), sa)){
return false;
}
return true;
}
private static boolean matchesValid(Object o, String[] valids, SpellAbility sa)
{
Card srcCard = sa.getSourceCard();
Player activatingPlayer = sa.getActivatingPlayer();
if(o instanceof Card){
Card c = (Card)o;
return c.isValidCard(valids, activatingPlayer, srcCard);
}
if(o instanceof Player){
for(String v : valids){
if(v.equalsIgnoreCase("Player"))
return true;
if(v.equalsIgnoreCase("Opponent")){
if(o.equals(activatingPlayer.getOpponent())){
return true;
}
}
if(v.equalsIgnoreCase("You"))
return o.equals(activatingPlayer);
}
}
if (o instanceof SpellAbility){
Card c = ((SpellAbility)o).getSourceCard();
return c.isValidCard(valids, activatingPlayer, srcCard);
}
return false;
}
}