mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-15 18:28:00 +00:00
Merge remote-tracking branch 'core-developers/master'
This commit is contained in:
@@ -751,7 +751,7 @@ public class AiController {
|
||||
if (xPay <= 0) {
|
||||
return AiPlayDecision.CantAffordX;
|
||||
}
|
||||
card.setSVar("PayX", Integer.toString(xPay));
|
||||
sa.setSVar("PayX", Integer.toString(xPay));
|
||||
} else if (mana.isZero()) {
|
||||
// if mana is zero, but card mana cost does have X, then something is wrong
|
||||
ManaCost cardCost = card.getManaCost();
|
||||
@@ -1061,8 +1061,8 @@ public class AiController {
|
||||
} else if ("VolrathsShapeshifter".equals(sa.getParam("AILogic"))) {
|
||||
return SpecialCardAi.VolrathsShapeshifter.targetBestCreature(player, sa);
|
||||
} else if ("DiscardCMCX".equals(sa.getParam("AILogic"))) {
|
||||
final int CMC = Integer.parseInt(sourceCard.getSVar("PayX"));
|
||||
CardCollection discards = CardLists.filter(player.getCardsIn(ZoneType.Hand), CardPredicates.hasCMC(CMC));
|
||||
final int cmc = Integer.parseInt(sa.getSVar("PayX"));
|
||||
CardCollection discards = CardLists.filter(player.getCardsIn(ZoneType.Hand), CardPredicates.hasCMC(cmc));
|
||||
if (discards.isEmpty()) {
|
||||
return null;
|
||||
} else {
|
||||
@@ -1802,6 +1802,9 @@ public class AiController {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
CardCollection result = new CardCollection();
|
||||
if (sa.hasParam("AIMaxAmount")) {
|
||||
max = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("AIMaxAmount"), sa);
|
||||
}
|
||||
switch(sa.getApi()) {
|
||||
case TwoPiles:
|
||||
// TODO: improve AI
|
||||
|
||||
@@ -4,12 +4,15 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.card.CardType;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameEntityCounterTable;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
@@ -20,6 +23,7 @@ import forge.game.card.CardPredicates.Presets;
|
||||
import forge.game.card.CounterEnumType;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.cost.*;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.SpellAbilityStackInstance;
|
||||
@@ -632,101 +636,98 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
return PaymentDecision.card(aic.getCardsToDiscard(c, type.split(";"), ability));
|
||||
}
|
||||
|
||||
protected int removeCounter(GameEntityCounterTable table, List<Card> prefs, CounterEnumType cType, int stillToRemove) {
|
||||
int removed = 0;
|
||||
if (!prefs.isEmpty() && stillToRemove > 0) {
|
||||
Collections.sort(prefs, CardPredicates.compareByCounterType(cType));
|
||||
|
||||
for (Card prefCard : prefs) {
|
||||
// already enough removed
|
||||
if (stillToRemove <= removed) {
|
||||
break;
|
||||
}
|
||||
int thisRemove = Math.min(prefCard.getCounters(cType), stillToRemove);
|
||||
if (thisRemove > 0) {
|
||||
removed += thisRemove;
|
||||
table.put(prefCard, CounterType.get(cType), thisRemove);
|
||||
}
|
||||
}
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaymentDecision visit(CostRemoveAnyCounter cost) {
|
||||
final String amount = cost.getAmount();
|
||||
final int c = AbilityUtils.calculateAmount(source, amount, ability);
|
||||
final String type = cost.getType();
|
||||
final Card originalHost = ObjectUtils.defaultIfNull(ability.getOriginalHost(), source);
|
||||
|
||||
CardCollectionView typeList = CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), type.split(";"), player, source, ability);
|
||||
if (c <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
CardCollectionView typeList = CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), cost.getType().split(";"), player, source, ability);
|
||||
// only cards with counters are of interest
|
||||
typeList = CardLists.filter(typeList, CardPredicates.hasCounters());
|
||||
|
||||
// no target
|
||||
if (typeList.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO fill up a GameEntityCounterTable
|
||||
// cost now has counter type or null
|
||||
// the amount might be different from 1, could be X
|
||||
// currently if amount is bigger than one,
|
||||
// it tries to remove all counters from one source and type at once
|
||||
|
||||
|
||||
int toRemove = 0;
|
||||
final GameEntityCounterTable table = new GameEntityCounterTable();
|
||||
|
||||
// currently the only one using remove any counter using a type uses p1p1
|
||||
|
||||
// the first things are benefit from removing counters
|
||||
|
||||
// try to remove +1/+1 counter from undying creature
|
||||
List<Card> prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterEnumType.P1P1, c),
|
||||
CardPredicates.hasKeyword("Undying"));
|
||||
|
||||
if (!prefs.isEmpty()) {
|
||||
Collections.sort(prefs, CardPredicates.compareByCounterType(CounterEnumType.P1P1));
|
||||
PaymentDecision result = PaymentDecision.card(prefs);
|
||||
result.ct = CounterType.get(CounterEnumType.P1P1);
|
||||
return result;
|
||||
}
|
||||
|
||||
// try to remove -1/-1 counter from persist creature
|
||||
prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterEnumType.M1M1, c),
|
||||
CardPredicates.hasKeyword("Persist"));
|
||||
if (c > toRemove && (cost.counter == null || cost.counter.is(CounterEnumType.M1M1))) {
|
||||
List<Card> prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterEnumType.M1M1), CardPredicates.hasKeyword(Keyword.PERSIST));
|
||||
|
||||
if (!prefs.isEmpty()) {
|
||||
Collections.sort(prefs, CardPredicates.compareByCounterType(CounterEnumType.M1M1));
|
||||
PaymentDecision result = PaymentDecision.card(prefs);
|
||||
result.ct = CounterType.get(CounterEnumType.M1M1);
|
||||
return result;
|
||||
toRemove += removeCounter(table, prefs, CounterEnumType.M1M1, c - toRemove);
|
||||
}
|
||||
|
||||
// try to remove Time counter from Chronozoa, it will generate more
|
||||
prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterEnumType.TIME, c),
|
||||
CardPredicates.nameEquals("Chronozoa"));
|
||||
// try to remove +1/+1 counter from undying creature
|
||||
if (c > toRemove && (cost.counter == null || cost.counter.is(CounterEnumType.P1P1))) {
|
||||
List<Card> prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterEnumType.P1P1), CardPredicates.hasKeyword(Keyword.UNDYING));
|
||||
|
||||
if (!prefs.isEmpty()) {
|
||||
Collections.sort(prefs, CardPredicates.compareByCounterType(CounterEnumType.TIME));
|
||||
PaymentDecision result = PaymentDecision.card(prefs);
|
||||
result.ct = CounterType.get(CounterEnumType.TIME);
|
||||
return result;
|
||||
toRemove += removeCounter(table, prefs, CounterEnumType.P1P1, c - toRemove);
|
||||
}
|
||||
|
||||
// try to remove Quest counter on something with enough counters for the
|
||||
// effect to continue
|
||||
prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterEnumType.QUEST, c));
|
||||
if (c > toRemove && cost.counter == null && originalHost.hasSVar("AIRemoveCounterCostPriority") && !"ANY".equalsIgnoreCase(originalHost.getSVar("AIRemoveCounterCostPriority"))) {
|
||||
String[] counters = TextUtil.split(originalHost.getSVar("AIRemoveCounterCostPriority"), ',');
|
||||
|
||||
if (!prefs.isEmpty()) {
|
||||
prefs = CardLists.filter(prefs, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card crd) {
|
||||
// a Card without MaxQuestEffect doesn't need any Quest
|
||||
// counters
|
||||
int e = 0;
|
||||
if (crd.hasSVar("MaxQuestEffect")) {
|
||||
e = Integer.parseInt(crd.getSVar("MaxQuestEffect"));
|
||||
}
|
||||
return crd.getCounters(CounterEnumType.QUEST) >= e + c;
|
||||
}
|
||||
});
|
||||
Collections.sort(prefs, Collections.reverseOrder(CardPredicates.compareByCounterType(CounterEnumType.QUEST)));
|
||||
PaymentDecision result = PaymentDecision.card(prefs);
|
||||
result.ct = CounterType.get(CounterEnumType.QUEST);
|
||||
return result;
|
||||
}
|
||||
for (final String ctr : counters) {
|
||||
CounterType ctype = CounterType.getType(ctr);
|
||||
// ctype == null means any type
|
||||
// any type is just used to return null for this
|
||||
|
||||
// filter for only cards with enough counters
|
||||
typeList = CardLists.filter(typeList, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card crd) {
|
||||
for (Integer i : crd.getCounters().values()) {
|
||||
if (i >= c) {
|
||||
return true;
|
||||
for (Card card : CardLists.filter(typeList, CardPredicates.hasCounter(ctype))) {
|
||||
int thisRemove = Math.min(card.getCounters(ctype), c - toRemove);
|
||||
if (thisRemove > 0) {
|
||||
toRemove += thisRemove;
|
||||
table.put(card, ctype, thisRemove);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// nothing with enough counters of any type
|
||||
if (typeList.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// filter for negative counters
|
||||
if (c > toRemove && cost.counter == null) {
|
||||
List<Card> negatives = CardLists.filter(typeList, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card crd) {
|
||||
for (Map.Entry<CounterType, Integer> e : crd.getCounters().entrySet()) {
|
||||
if (e.getValue() >= c && ComputerUtil.isNegativeCounter(e.getKey(), crd)) {
|
||||
for (CounterType cType : table.filterToRemove(crd).keySet()) {
|
||||
if (ComputerUtil.isNegativeCounter(cType, crd)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -735,25 +736,29 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
});
|
||||
|
||||
if (!negatives.isEmpty()) {
|
||||
final Card card = ComputerUtilCard.getBestAI(negatives);
|
||||
PaymentDecision result = PaymentDecision.card(card);
|
||||
|
||||
for (Map.Entry<CounterType, Integer> e : card.getCounters().entrySet()) {
|
||||
if (e.getValue() >= c && ComputerUtil.isNegativeCounter(e.getKey(), card)) {
|
||||
result.ct = e.getKey();
|
||||
break;
|
||||
// TODO sort negatives to remove from best Cards first?
|
||||
for (final Card crd : negatives) {
|
||||
for (Map.Entry<CounterType, Integer> e : table.filterToRemove(crd).entrySet()) {
|
||||
if (ComputerUtil.isNegativeCounter(e.getKey(), crd)) {
|
||||
int over = Math.min(e.getValue(), c - toRemove);
|
||||
if (over > 0) {
|
||||
toRemove += over;
|
||||
table.put(crd, e.getKey(), over);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// filter for useless counters
|
||||
// they have no effect on the card, if they are there or removed
|
||||
if (c > toRemove && cost.counter == null) {
|
||||
List<Card> useless = CardLists.filter(typeList, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card crd) {
|
||||
for (Map.Entry<CounterType, Integer> e : crd.getCounters().entrySet()) {
|
||||
if (e.getValue() >= c && ComputerUtil.isUselessCounter(e.getKey())) {
|
||||
for (CounterType ctype : table.filterToRemove(crd).keySet()) {
|
||||
if (ComputerUtil.isUselessCounter(ctype, crd)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -762,62 +767,98 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
});
|
||||
|
||||
if (!useless.isEmpty()) {
|
||||
final Card card = useless.get(0);
|
||||
PaymentDecision result = PaymentDecision.card(card);
|
||||
|
||||
for (Map.Entry<CounterType, Integer> e : card.getCounters().entrySet()) {
|
||||
if (e.getValue() >= c && ComputerUtil.isUselessCounter(e.getKey())) {
|
||||
result.ct = e.getKey();
|
||||
break;
|
||||
for (final Card crd : useless) {
|
||||
for (Map.Entry<CounterType, Integer> e : table.filterToRemove(crd).entrySet()) {
|
||||
if (ComputerUtil.isUselessCounter(e.getKey(), crd)) {
|
||||
int over = Math.min(e.getValue(), c - toRemove);
|
||||
if (over > 0) {
|
||||
toRemove += over;
|
||||
table.put(crd, e.getKey(), over);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// try a way to pay unless cost
|
||||
if ("Chisei, Heart of Oceans".equals(ComputerUtilAbility.getAbilitySourceName(ability))) {
|
||||
final Card card = ComputerUtilCard.getWorstAI(typeList);
|
||||
PaymentDecision result = PaymentDecision.card(card);
|
||||
for (Map.Entry<CounterType, Integer> e : card.getCounters().entrySet()) {
|
||||
if (e.getValue() >= c) {
|
||||
result.ct = e.getKey();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
// try to remove Time counter from Chronozoa, it will generate more token
|
||||
if (c > toRemove && (cost.counter == null || cost.counter.is(CounterEnumType.TIME))) {
|
||||
List<Card> prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterEnumType.TIME), CardPredicates.nameEquals("Chronozoa"));
|
||||
|
||||
toRemove += removeCounter(table, prefs, CounterEnumType.TIME, c - toRemove);
|
||||
}
|
||||
|
||||
// check if the card defines its own priorities for counter removal as cost
|
||||
if (source.hasSVar("AIRemoveCounterCostPriority")) {
|
||||
String[] counters = TextUtil.split(source.getSVar("AIRemoveCounterCostPriority"), ',');
|
||||
|
||||
for (final String ctr : counters) {
|
||||
List<Card> withCtr = CardLists.filter(typeList, new Predicate<Card>() {
|
||||
// try to remove Quest counter on something with enough counters for the
|
||||
// effect to continue
|
||||
if (c > toRemove && (cost.counter == null || cost.counter.is(CounterEnumType.QUEST))) {
|
||||
List<Card> prefs = CardLists.filter(typeList, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card crd) {
|
||||
for (Map.Entry<CounterType, Integer> e : crd.getCounters().entrySet()) {
|
||||
if (e.getValue() >= c && (ctr.equals("ANY") || e.getKey().equals(CounterType.getType(ctr)))) {
|
||||
return true;
|
||||
// a Card without MaxQuestEffect doesn't need any Quest
|
||||
// counters
|
||||
int e = 0;
|
||||
if (crd.hasSVar("MaxQuestEffect")) {
|
||||
e = Integer.parseInt(crd.getSVar("MaxQuestEffect"));
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return crd.getCounters(CounterEnumType.QUEST) > e;
|
||||
}
|
||||
});
|
||||
if (!withCtr.isEmpty()) {
|
||||
final Card card = withCtr.get(0);
|
||||
PaymentDecision result = PaymentDecision.card(card);
|
||||
Collections.sort(prefs, Collections.reverseOrder(CardPredicates.compareByCounterType(CounterEnumType.QUEST)));
|
||||
|
||||
for (Map.Entry<CounterType, Integer> e : card.getCounters().entrySet()) {
|
||||
if (e.getValue() >= c && (ctr.equals("ANY") || e.getKey().equals(CounterType.getType(ctr)))) {
|
||||
result.ct = e.getKey();
|
||||
break;
|
||||
for (final Card crd : prefs) {
|
||||
int e = 0;
|
||||
if (crd.hasSVar("MaxQuestEffect")) {
|
||||
e = Integer.parseInt(crd.getSVar("MaxQuestEffect"));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
int over = Math.min(crd.getCounters(CounterEnumType.QUEST) - e, c - toRemove);
|
||||
if (over > 0) {
|
||||
toRemove += over;
|
||||
table.put(crd, CounterType.get(CounterEnumType.QUEST), over);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
// remove Lore counters from Sagas to keep them longer
|
||||
if (c > toRemove && (cost.counter == null || cost.counter.is(CounterEnumType.LORE))) {
|
||||
List<Card> prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterEnumType.LORE), CardPredicates.isType("Saga"));
|
||||
// TODO add Svars and other stuff to keep the Sagas on specific levels
|
||||
// also add a way for the AI to respond to the last Chapter ability to keep the Saga on the field if wanted
|
||||
toRemove += removeCounter(table, prefs, CounterEnumType.LORE, c - toRemove);
|
||||
}
|
||||
|
||||
|
||||
// TODO add logic to remove positive counters?
|
||||
if (c > toRemove && cost.counter != null) {
|
||||
// TODO add logic for Ooze Flux, should probably try to make a token as big as possible
|
||||
// without killing own non undying creatures in the process
|
||||
// the amount of X should probably be tweaked for this
|
||||
List<Card> withCtr = CardLists.filter(typeList, CardPredicates.hasCounter(cost.counter));
|
||||
for (Card card : withCtr) {
|
||||
int thisRemove = Math.min(card.getCounters(cost.counter), c - toRemove);
|
||||
if (thisRemove > 0) {
|
||||
toRemove += thisRemove;
|
||||
table.put(card, cost.counter, thisRemove);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Used to not return null
|
||||
// Special part for CostPriority Any
|
||||
if (c > toRemove && cost.counter == null && originalHost.hasSVar("AIRemoveCounterCostPriority") && "ANY".equalsIgnoreCase(originalHost.getSVar("AIRemoveCounterCostPriority"))) {
|
||||
for (Card card : typeList) {
|
||||
// TODO try not to remove to much positive counters from the same card
|
||||
for (Map.Entry<CounterType, Integer> e : table.filterToRemove(card).entrySet()) {
|
||||
int thisRemove = Math.min(e.getValue(), c - toRemove);
|
||||
if (thisRemove > 0) {
|
||||
toRemove += thisRemove;
|
||||
table.put(card, e.getKey(), thisRemove);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if table is empty, than no counter was removed
|
||||
return table.isEmpty() ? null : PaymentDecision.counters(table);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -843,7 +884,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
}
|
||||
}
|
||||
} else if (sVar.equals("Count$xPaid")) {
|
||||
c = AbilityUtils.calculateAmount(source, "PayX", null);
|
||||
c = AbilityUtils.calculateAmount(source, "PayX", ability);
|
||||
} else {
|
||||
c = AbilityUtils.calculateAmount(source, amount, ability);
|
||||
}
|
||||
|
||||
@@ -161,8 +161,6 @@ public class ComputerUtil {
|
||||
|
||||
// Play higher costing spells first?
|
||||
final Cost cost = sa.getPayCosts();
|
||||
// Convert cost to CMC
|
||||
// String totalMana = source.getSVar("PayX"); // + cost.getCMC()
|
||||
|
||||
// Consider the costs here for relative "scoring"
|
||||
if (hasDiscardHandCost(cost)) {
|
||||
@@ -2809,7 +2807,17 @@ public class ComputerUtil {
|
||||
}
|
||||
|
||||
// this countertypes has no effect
|
||||
public static boolean isUselessCounter(CounterType type) {
|
||||
public static boolean isUselessCounter(CounterType type, Card c) {
|
||||
|
||||
// Quest counter on a card without MaxQuestEffect are useless
|
||||
if (type.is(CounterEnumType.QUEST)) {
|
||||
int e = 0;
|
||||
if ( c.hasSVar("MaxQuestEffect")) {
|
||||
e = Integer.parseInt(c.getSVar("MaxQuestEffect"));
|
||||
}
|
||||
return c.getCounters(type) > e;
|
||||
}
|
||||
|
||||
return type.is(CounterEnumType.AWAKENING) || type.is(CounterEnumType.MANIFESTATION) || type.is(CounterEnumType.PETRIFICATION)
|
||||
|| type.is(CounterEnumType.TRAINING);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
import forge.util.TextUtil;
|
||||
import forge.util.collect.FCollectionView;
|
||||
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.List;
|
||||
@@ -53,9 +55,6 @@ public class ComputerUtilCost {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean checkRemoveCounterCost(final Cost cost, final Card source) {
|
||||
return checkRemoveCounterCost(cost, source, null);
|
||||
}
|
||||
/**
|
||||
* Check remove counter cost.
|
||||
*
|
||||
@@ -69,6 +68,7 @@ public class ComputerUtilCost {
|
||||
if (cost == null) {
|
||||
return true;
|
||||
}
|
||||
final AiCostDecision decision = new AiCostDecision(sa.getActivatingPlayer(), sa);
|
||||
for (final CostPart part : cost.getCostParts()) {
|
||||
if (part instanceof CostRemoveCounter) {
|
||||
final CostRemoveCounter remCounter = (CostRemoveCounter) part;
|
||||
@@ -88,17 +88,17 @@ public class ComputerUtilCost {
|
||||
|
||||
// Remove X counters - set ChosenX to max possible value here, the SAs should correct that
|
||||
// value later as the AI decides what to do (in checkApiLogic / checkAiLogic)
|
||||
if (sa != null && sa.hasSVar(remCounter.getAmount())) {
|
||||
if (sa.hasSVar(remCounter.getAmount())) {
|
||||
final String sVar = sa.getSVar(remCounter.getAmount());
|
||||
if (sVar.equals("XChoice") && !sa.hasSVar("ChosenX")) {
|
||||
sa.setSVar("ChosenX", String.valueOf(source.getCounters(type)));
|
||||
} else if (sVar.equals("Count$xPaid") && sa.hasSVar("PayX")) {
|
||||
sa.setSVar("PayX", Integer.toString(Math.min(Integer.valueOf(sa.getSVar("PayX")), source.getCounters(type))));
|
||||
}
|
||||
}
|
||||
|
||||
// check the sa what the PaymentDecision is.
|
||||
// ignore Loyality abilities with Zero as Cost
|
||||
if (sa != null && !type.is(CounterEnumType.LOYALTY)) {
|
||||
final AiCostDecision decision = new AiCostDecision(sa.getActivatingPlayer(), sa);
|
||||
if (!type.is(CounterEnumType.LOYALTY)) {
|
||||
PaymentDecision pay = decision.visit(remCounter);
|
||||
if (pay == null || pay.c <= 0) {
|
||||
return false;
|
||||
@@ -111,14 +111,10 @@ public class ComputerUtilCost {
|
||||
return false;
|
||||
}
|
||||
} else if (part instanceof CostRemoveAnyCounter) {
|
||||
if (sa != null) {
|
||||
final CostRemoveAnyCounter remCounter = (CostRemoveAnyCounter) part;
|
||||
|
||||
PaymentDecision decision = new AiCostDecision(sa.getActivatingPlayer(), sa).visit(remCounter);
|
||||
return decision != null;
|
||||
}
|
||||
|
||||
return false;
|
||||
PaymentDecision pay = decision.visit(remCounter);
|
||||
return pay != null;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -677,4 +673,26 @@ public class ComputerUtilCost {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static int getMaxXValue(SpellAbility sa, Player ai) {
|
||||
final Cost abCost = sa.getPayCosts();
|
||||
if (abCost == null || !abCost.hasXInAnyCostPart()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
Integer val = null;
|
||||
|
||||
if (sa.costHasManaX()) {
|
||||
val = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
}
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
// if announce is used as min targets, check what the max possible number would be
|
||||
if ("X".equals(sa.getTargetRestrictions().getMinTargets())) {
|
||||
val = ObjectUtils.min(val, CardUtil.getValidCardsToTarget(sa.getTargetRestrictions(), sa).size());
|
||||
}
|
||||
}
|
||||
|
||||
return ObjectUtils.defaultIfNull(ObjectUtils.min(val, abCost.getMaxForNonManaX(sa, ai)), 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.*;
|
||||
import forge.ai.ability.AnimateAi;
|
||||
import forge.card.CardStateName;
|
||||
import forge.card.ColorSet;
|
||||
import forge.card.MagicColor;
|
||||
import forge.card.mana.ManaAtom;
|
||||
@@ -119,10 +118,6 @@ public class ComputerUtilMana {
|
||||
return score;
|
||||
}
|
||||
|
||||
private static void sortManaAbilities(final Multimap<ManaCostShard, SpellAbility> manaAbilityMap) {
|
||||
sortManaAbilities(manaAbilityMap, null);
|
||||
}
|
||||
|
||||
private static void sortManaAbilities(final Multimap<ManaCostShard, SpellAbility> manaAbilityMap, final SpellAbility sa) {
|
||||
final Map<Card, Integer> manaCardMap = Maps.newHashMap();
|
||||
final List<Card> orderedCards = Lists.newArrayList();
|
||||
@@ -1194,11 +1189,11 @@ public class ComputerUtilMana {
|
||||
} else {
|
||||
// For Count$xPaid set PayX in the AFs then use that here
|
||||
// Else calculate it as appropriate.
|
||||
final String xSvar = card.getSVar("X").startsWith("Count$xPaid") ? "PayX" : "X";
|
||||
if (!sa.getSVar(xSvar).isEmpty() || card.hasSVar(xSvar) || card.getState(CardStateName.Original).hasSVar(xSvar)) {
|
||||
if (xSvar.equals("PayX") && (card.hasSVar(xSvar) || card.getState(CardStateName.Original).hasSVar(xSvar))) {
|
||||
final String xSvar = sa.getSVar("X").startsWith("Count$xPaid") ? "PayX" : "X";
|
||||
if (sa.hasSVar(xSvar)) {
|
||||
if (xSvar.equals("PayX")) {
|
||||
// X SVar may end up being an empty string when copying a spell with no cost (e.g. Jhoira Avatar)
|
||||
String xValue = card.hasSVar(xSvar) ? card.getSVar(xSvar) : card.getState(CardStateName.Original).getSVar(xSvar);
|
||||
String xValue = sa.getSVar(xSvar);
|
||||
manaToAdd = xValue.isEmpty() ? 0 : Integer.parseInt(xValue) * cost.getXcounter(); // X
|
||||
} else {
|
||||
manaToAdd = AbilityUtils.calculateAmount(card, xSvar, sa) * cost.getXcounter();
|
||||
@@ -1206,9 +1201,7 @@ public class ComputerUtilMana {
|
||||
}
|
||||
}
|
||||
|
||||
String manaXColor = sa.getParam("XColor");
|
||||
ManaCostShard shardToGrow = ManaCostShard.parseNonGeneric(manaXColor == null ? "1" : manaXColor);
|
||||
cost.increaseShard(shardToGrow, manaToAdd);
|
||||
cost.increaseShard(ManaCostShard.parseNonGeneric(sa.getParamOrDefault("XColor", "1")), manaToAdd);
|
||||
|
||||
if (!test) {
|
||||
sa.setXManaCostPaid(manaToAdd / cost.getXcounter());
|
||||
|
||||
@@ -957,9 +957,7 @@ public class PlayerControllerAi extends PlayerController {
|
||||
final Ability emptyAbility = new AbilityStatic(source, cost, sa.getTargetRestrictions()) { @Override public void resolve() { } };
|
||||
emptyAbility.setActivatingPlayer(player);
|
||||
emptyAbility.setTriggeringObjects(sa.getTriggeringObjects());
|
||||
for (String sVar : sa.getSVars()) {
|
||||
emptyAbility.setSVar(sVar, sa.getSVar(sVar));
|
||||
}
|
||||
emptyAbility.setSVars(sa.getSVars());
|
||||
if (ComputerUtilCost.willPayUnlessCost(sa, player, cost, alreadyPaid, allPayers) && ComputerUtilCost.canPayCost(emptyAbility, player)) {
|
||||
ComputerUtil.playNoStack(player, emptyAbility, getGame()); // AI needs something to resolve to pay that cost
|
||||
return true;
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package forge.ai;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
import forge.ai.ability.TokenAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
@@ -12,8 +16,6 @@ import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Aggregates;
|
||||
|
||||
/*
|
||||
@@ -31,17 +33,34 @@ public class SpecialAiLogic {
|
||||
Card source = sa.getHostCard();
|
||||
Game game = source.getGame();
|
||||
PhaseHandler ph = game.getPhaseHandler();
|
||||
TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
boolean isDestroy = ApiType.Destroy.equals(sa.getApi());
|
||||
SpellAbility tokenSA = sa.findSubAbilityByType(ApiType.Token);
|
||||
if (tokenSA == null) {
|
||||
// Used wrong AI logic?
|
||||
return false;
|
||||
}
|
||||
|
||||
CardCollection listOpp = CardLists.getValidCards(ai.getOpponents().getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, source, sa);
|
||||
listOpp = CardLists.getTargetableCards(listOpp, sa);
|
||||
List<Card> targetable = CardUtil.getValidCardsToTarget(sa.getTargetRestrictions(), sa);
|
||||
|
||||
Card choice = ComputerUtilCard.getMostExpensivePermanentAI(listOpp);
|
||||
CardCollection listOpp = CardLists.filterControlledBy(targetable, ai.getOpponents());
|
||||
if (isDestroy) {
|
||||
listOpp = CardLists.getNotKeyword(listOpp, Keyword.INDESTRUCTIBLE);
|
||||
// TODO add handling for cards like targeting dies
|
||||
}
|
||||
|
||||
final Card token = choice != null ? TokenAi.spawnToken(choice.getController(), sa.getSubAbility()) : null;
|
||||
if (token == null || !token.isCreature() || token.getNetToughness() < 1) {
|
||||
return true; // becomes Terminate
|
||||
} else if (choice != null && choice.isPlaneswalker()) {
|
||||
Card choice = null;
|
||||
if (!listOpp.isEmpty()) {
|
||||
choice = ComputerUtilCard.getMostExpensivePermanentAI(listOpp);
|
||||
// can choice even be null?
|
||||
|
||||
if (choice != null) {
|
||||
final Card token = TokenAi.spawnToken(choice.getController(), tokenSA);
|
||||
if (!token.isCreature() || token.getNetToughness() < 1) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(choice);
|
||||
return true;
|
||||
}
|
||||
if (choice.isPlaneswalker()) {
|
||||
if (choice.getCurrentLoyalty() * 35 > ComputerUtilCard.evaluateCreature(token)) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(choice);
|
||||
@@ -49,42 +68,48 @@ public class SpecialAiLogic {
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
boolean hasOppTarget = true;
|
||||
if (choice != null
|
||||
&& ((!choice.isCreature() || choice.isTapped()) && ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS) && ph.getPlayerTurn() == ai) // prevent surprise combatant
|
||||
}
|
||||
if ((!choice.isCreature() || choice.isTapped()) && ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS) && ph.isPlayerTurn(ai) // prevent surprise combatant
|
||||
|| ComputerUtilCard.evaluateCreature(choice) < 1.5 * ComputerUtilCard.evaluateCreature(token)) {
|
||||
|
||||
hasOppTarget = false;
|
||||
choice = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// See if we have anything we can upgrade
|
||||
if (!hasOppTarget) {
|
||||
CardCollection listOwn = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, source, sa);
|
||||
listOwn = CardLists.getTargetableCards(listOwn, sa);
|
||||
if (choice == null) {
|
||||
CardCollection listOwn = CardLists.filterControlledBy(targetable, ai);
|
||||
final Card token = TokenAi.spawnToken(ai, tokenSA);
|
||||
|
||||
Card bestOwnCardToUpgrade = ComputerUtilCard.getWorstCreatureAI(CardLists.filter(listOwn, new Predicate<Card>() {
|
||||
Card bestOwnCardToUpgrade = null;
|
||||
if (isDestroy) {
|
||||
// just choose any Indestructible
|
||||
// TODO maybe filter something that doesn't like to be targeted, or does something benefit by targeting
|
||||
bestOwnCardToUpgrade = Iterables.getFirst(CardLists.getKeyword(listOwn, Keyword.INDESTRUCTIBLE), null);
|
||||
}
|
||||
if (bestOwnCardToUpgrade == null) {
|
||||
bestOwnCardToUpgrade = ComputerUtilCard.getWorstCreatureAI(CardLists.filter(listOwn, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(Card card) {
|
||||
return card.isCreature() && (ComputerUtilCard.isUselessCreature(ai, card)
|
||||
|| ComputerUtilCard.evaluateCreature(token) > 2 * ComputerUtilCard.evaluateCreature(card));
|
||||
}
|
||||
}));
|
||||
}
|
||||
if (bestOwnCardToUpgrade != null) {
|
||||
if (ComputerUtilCard.isUselessCreature(ai, bestOwnCardToUpgrade) || (ph.getPhase().isAfter(PhaseType.COMBAT_END) || ph.getPlayerTurn() != ai)) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(bestOwnCardToUpgrade);
|
||||
return true;
|
||||
if (ComputerUtilCard.isUselessCreature(ai, bestOwnCardToUpgrade) || (ph.getPhase().isAfter(PhaseType.COMBAT_END) || !ph.isPlayerTurn(ai))) {
|
||||
choice = bestOwnCardToUpgrade;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
}
|
||||
|
||||
if (choice != null) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(choice);
|
||||
return true;
|
||||
}
|
||||
|
||||
return hasOppTarget;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// A logic for cards that say "Sacrifice a creature: CARDNAME gets +X/+X until EOT"
|
||||
|
||||
@@ -875,7 +875,7 @@ public class SpecialCardAi {
|
||||
tokenSize = 11;
|
||||
}
|
||||
|
||||
source.setSVar("PayX", Integer.toString(tokenSize));
|
||||
sa.setSVar("PayX", Integer.toString(tokenSize));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -78,16 +78,14 @@ public abstract class SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
|
||||
if (sa.hasParam("AITgtBeforeCostEval")) {
|
||||
// Cost payment requires a valid target to be specified, e.g. Quillmane Baku, so run the API logic first
|
||||
// to set the target, then decide on paying costs (slower, so only use for cards where it matters)
|
||||
return checkApiLogic(ai, sa) && (cost == null || willPayCosts(ai, sa, cost, source));
|
||||
if (!checkApiLogic(ai, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// needs to be after API logic because needs to check possible X Cost?
|
||||
if (cost != null && !willPayCosts(ai, sa, cost, source)) {
|
||||
return false;
|
||||
}
|
||||
return checkApiLogic(ai, sa);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean checkConditions(final Player ai, final SpellAbility sa, SpellAbilityCondition con) {
|
||||
|
||||
@@ -14,7 +14,6 @@ import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.staticability.StaticAbility;
|
||||
import forge.game.staticability.StaticAbilityContinuous;
|
||||
import forge.game.staticability.StaticAbilityLayer;
|
||||
@@ -38,15 +37,13 @@ import forge.game.ability.effects.AnimateEffectBase;
|
||||
public class AnimateAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
final Game game = ai.getGame();
|
||||
final PhaseHandler ph = game.getPhaseHandler();
|
||||
if ("Attacking".equals(aiLogic)) { // Launch the Fleet
|
||||
if (ph.getPlayerTurn().isOpponentOf(ai) || ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
||||
return false;
|
||||
}
|
||||
List<Card> list = CardLists.getValidCards(ai.getCreaturesInPlay(), tgt.getValidTgts(), ai, source, sa);
|
||||
List<Card> list = CardLists.getTargetableCards(ai.getCreaturesInPlay(), sa);
|
||||
for (Card c : list) {
|
||||
if (ComputerUtilCard.doesCreatureAttackAI(ai, c)) {
|
||||
sa.getTargets().add(c);
|
||||
@@ -224,10 +221,7 @@ public class AnimateAi extends SpellAbilityAi {
|
||||
} else if (sa.usesTargeting() && mandatory) {
|
||||
// fallback if animate is mandatory
|
||||
sa.resetTargets();
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
CardCollectionView list = aiPlayer.getGame().getCardsIn(tgt.getZone());
|
||||
list = CardLists.getValidCards(list, tgt.getValidTgts(), aiPlayer, source, sa);
|
||||
List<Card> list = CardUtil.getValidCardsToTarget(sa.getTargetRestrictions(), sa);
|
||||
if (list.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
@@ -253,12 +247,8 @@ public class AnimateAi extends SpellAbilityAi {
|
||||
|
||||
// something is used for animate into creature
|
||||
if (types.isCreature()) {
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
CardCollectionView list = ai.getGame().getCardsIn(tgt.getZone());
|
||||
list = CardLists.getValidCards(list, tgt.getValidTgts(), ai, source, sa);
|
||||
// need to targetable
|
||||
list = CardLists.getTargetableCards(list, sa);
|
||||
final Game game = ai.getGame();
|
||||
CardCollectionView list = CardLists.getTargetableCards(game.getCardsIn(ZoneType.Battlefield), sa);
|
||||
|
||||
// Filter AI-specific targets if provided
|
||||
list = ComputerUtil.filterAITgts(sa, ai, (CardCollection)list, false);
|
||||
|
||||
@@ -93,7 +93,7 @@ public class AttachAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (abCost.getTotalMana().countX() > 0 && source.getSVar("X").equals("Count$xPaid")) {
|
||||
if (abCost.getTotalMana().countX() > 0 && sa.getSVar("X").equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value. (Endless Scream and Venarian
|
||||
// Gold)
|
||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
@@ -102,7 +102,7 @@ public class AttachAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
|
||||
source.setSVar("PayX", Integer.toString(xPay));
|
||||
sa.setSVar("PayX", Integer.toString(xPay));
|
||||
}
|
||||
|
||||
if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Chained to the Rocks")) {
|
||||
|
||||
@@ -339,10 +339,10 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
|
||||
String type = sa.getParam("ChangeType");
|
||||
if (type != null) {
|
||||
if (type.contains("X") && source.getSVar("X").equals("Count$xPaid")) {
|
||||
if (type.contains("X") && sa.getSVar("X").equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(xPay));
|
||||
sa.setSVar("PayX", Integer.toString(xPay));
|
||||
type = type.replace("X", Integer.toString(xPay));
|
||||
}
|
||||
}
|
||||
@@ -384,11 +384,11 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
|
||||
String num = sa.getParam("ChangeNum");
|
||||
if (num != null) {
|
||||
if (num.contains("X") && source.getSVar("X").equals("Count$xPaid")) {
|
||||
if (num.contains("X") && sa.getSVar("X").equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
xPay = Math.min(xPay, list.size());
|
||||
source.setSVar("PayX", Integer.toString(xPay));
|
||||
sa.setSVar("PayX", Integer.toString(xPay));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -474,8 +474,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
// Fetching should occur fairly often as it helps cast more spells, and
|
||||
// have access to more mana
|
||||
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
if (sa.hasParam("AILogic")) {
|
||||
if (sa.getParam("AILogic").equals("Never")) {
|
||||
/*
|
||||
@@ -496,10 +494,10 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
|
||||
// this works for hidden because the mana is paid first.
|
||||
final String type = sa.getParam("ChangeType");
|
||||
if (type != null && type.contains("X") && source.getSVar("X").equals("Count$xPaid")) {
|
||||
if (type != null && type.contains("X") && sa.getSVar("X").equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(xPay));
|
||||
sa.setSVar("PayX", Integer.toString(xPay));
|
||||
}
|
||||
|
||||
Iterable<Player> pDefined;
|
||||
@@ -1353,29 +1351,13 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
final Card source = sa.getHostCard();
|
||||
final ZoneType origin = ZoneType.listValueOf(sa.getParam("Origin")).get(0);
|
||||
final ZoneType destination = ZoneType.smartValueOf(sa.getParam("Destination"));
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
|
||||
CardCollection list = CardLists.getValidCards(ai.getGame().getCardsIn(origin), tgt.getValidTgts(), ai, source, sa);
|
||||
|
||||
// Narrow down the list:
|
||||
if (origin.equals(ZoneType.Battlefield)) {
|
||||
// filter out untargetables
|
||||
CardCollection list = CardLists.getValidCards(ai.getGame().getCardsIn(tgt.getZone()), tgt.getValidTgts(), ai, source, sa);
|
||||
list = CardLists.getTargetableCards(list, sa);
|
||||
|
||||
// if Destination is hand, either bounce opponents dangerous stuff
|
||||
// or save my about to die stuff
|
||||
|
||||
// if Destination is exile, filter out my cards
|
||||
}
|
||||
else if (origin.equals(ZoneType.Graveyard)) {
|
||||
// Retrieve from Graveyard to:
|
||||
}
|
||||
|
||||
for (final Card c : sa.getTargets().getTargetCards()) {
|
||||
list.remove(c);
|
||||
}
|
||||
list.removeAll(sa.getTargets().getTargetCards());
|
||||
|
||||
if (list.isEmpty()) {
|
||||
return false;
|
||||
@@ -1387,12 +1369,13 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
Card choice = null;
|
||||
|
||||
if (!list.isEmpty()) {
|
||||
if (ComputerUtilCard.getMostExpensivePermanentAI(list, sa, false).isCreature()
|
||||
&& (destination.equals(ZoneType.Battlefield) || origin.equals(ZoneType.Battlefield))) {
|
||||
Card mostExpensivePermanent = ComputerUtilCard.getMostExpensivePermanentAI(list, sa, false);
|
||||
if (mostExpensivePermanent.isCreature()
|
||||
&& (destination.equals(ZoneType.Battlefield) || tgt.getZone().contains(ZoneType.Battlefield))) {
|
||||
// if a creature is most expensive take the best
|
||||
choice = ComputerUtilCard.getBestCreatureToBounceAI(list);
|
||||
} else if (destination.equals(ZoneType.Battlefield) || origin.equals(ZoneType.Battlefield)) {
|
||||
choice = ComputerUtilCard.getMostExpensivePermanentAI(list, sa, false);
|
||||
} else if (destination.equals(ZoneType.Battlefield) || tgt.getZone().contains(ZoneType.Battlefield)) {
|
||||
choice = mostExpensivePermanent;
|
||||
} else if (destination.equals(ZoneType.Hand) || destination.equals(ZoneType.Library)) {
|
||||
List<Card> nonLands = CardLists.getNotType(list, "Land");
|
||||
// Prefer to pull a creature, generally more useful for AI.
|
||||
@@ -1845,7 +1828,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
|
||||
int toPay = 0;
|
||||
boolean setPayX = false;
|
||||
if (unlessCost.equals("X") && source.getSVar(unlessCost).equals("Count$xPaid")) {
|
||||
if (unlessCost.equals("X") && sa.getSVar(unlessCost).equals("Count$xPaid")) {
|
||||
setPayX = true;
|
||||
toPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
} else {
|
||||
@@ -1861,7 +1844,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
if (setPayX) {
|
||||
source.setSVar("PayX", Integer.toString(toPay));
|
||||
sa.setSVar("PayX", Integer.toString(toPay));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,6 +130,12 @@ public class ChooseCardAi extends SpellAbilityAi {
|
||||
|
||||
return (ComputerUtilCard.evaluateCreatureList(aiCreatures) + minGain) < ComputerUtilCard
|
||||
.evaluateCreatureList(oppCreatures);
|
||||
} else if (aiLogic.equals("OwnCard")) {
|
||||
CardCollectionView ownChoices = CardLists.filter(choices, CardPredicates.isController(ai));
|
||||
if (ownChoices.isEmpty()) {
|
||||
ownChoices = CardLists.filter(choices, CardPredicates.isControlledByAnyOf(ai.getAllies()));
|
||||
}
|
||||
return !ownChoices.isEmpty();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -156,6 +162,12 @@ public class ChooseCardAi extends SpellAbilityAi {
|
||||
choice = ComputerUtilCard.getBestAI(options);
|
||||
} else if ("WorstCard".equals(logic)) {
|
||||
choice = ComputerUtilCard.getWorstAI(options);
|
||||
} else if ("OwnCard".equals(logic)) {
|
||||
CardCollectionView ownChoices = CardLists.filter(options, CardPredicates.isController(ai));
|
||||
if (ownChoices.isEmpty()) {
|
||||
ownChoices = CardLists.filter(options, CardPredicates.isControlledByAnyOf(ai.getAllies()));
|
||||
}
|
||||
choice = ComputerUtilCard.getBestAI(ownChoices);
|
||||
} else if (logic.equals("BestBlocker")) {
|
||||
if (!CardLists.filter(options, Presets.UNTAPPED).isEmpty()) {
|
||||
options = CardLists.filter(options, Presets.UNTAPPED);
|
||||
|
||||
@@ -44,7 +44,7 @@ public class ChooseColorAi extends SpellAbilityAi {
|
||||
}
|
||||
// Set PayX here to maximum value.
|
||||
int x = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(x));
|
||||
sa.setSVar("PayX", Integer.toString(x));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ public class ChooseSourceAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) {
|
||||
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source, sa)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,7 +280,7 @@ public class ControlGainAi extends SpellAbilityAi {
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, final Player ai) {
|
||||
final Game game = ai.getGame();
|
||||
if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) {
|
||||
if (!sa.usesTargeting()) {
|
||||
if (sa.hasParam("AllValid")) {
|
||||
CardCollectionView tgtCards = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents());
|
||||
tgtCards = AbilityUtils.filterListByType(tgtCards, sa.getParam("AllValid"), sa);
|
||||
|
||||
@@ -103,7 +103,7 @@ public class CounterAi extends SpellAbilityAi {
|
||||
|
||||
int toPay = 0;
|
||||
boolean setPayX = false;
|
||||
if (unlessCost.equals("X") && source.getSVar(unlessCost).equals("Count$xPaid")) {
|
||||
if (unlessCost.equals("X") && sa.getSVar(unlessCost).equals("Count$xPaid")) {
|
||||
setPayX = true;
|
||||
toPay = Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), usableManaSources + 1);
|
||||
} else {
|
||||
@@ -123,7 +123,7 @@ public class CounterAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
if (setPayX) {
|
||||
source.setSVar("PayX", Integer.toString(toPay));
|
||||
sa.setSVar("PayX", Integer.toString(toPay));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -267,7 +267,7 @@ public class CounterAi extends SpellAbilityAi {
|
||||
|
||||
int toPay = 0;
|
||||
boolean setPayX = false;
|
||||
if (unlessCost.equals("X") && source.getSVar(unlessCost).equals("Count$xPaid")) {
|
||||
if (unlessCost.equals("X") && sa.getSVar(unlessCost).equals("Count$xPaid")) {
|
||||
setPayX = true;
|
||||
toPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
} else {
|
||||
@@ -289,7 +289,7 @@ public class CounterAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
if (setPayX) {
|
||||
source.setSVar("PayX", Integer.toString(toPay));
|
||||
sa.setSVar("PayX", Integer.toString(toPay));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -338,7 +338,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
if (amountStr.equals("X")) {
|
||||
if (source.getSVar(amountStr).equals("Count$xPaid")) {
|
||||
if (sa.getSVar(amountStr).equals("Count$xPaid")) {
|
||||
// By default, set PayX here to maximum value (used for most SAs of this type).
|
||||
amount = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
|
||||
@@ -359,7 +359,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
|
||||
source.setSVar("PayX", Integer.toString(amount));
|
||||
sa.setSVar("PayX", Integer.toString(amount));
|
||||
} else if ("ExiledCreatureFromGraveCMC".equals(logic)) {
|
||||
// e.g. Necropolis
|
||||
amount = Aggregates.max(CardLists.filter(ai.getCardsIn(ZoneType.Graveyard), CardPredicates.Presets.CREATURES), CardPredicates.Accessors.fnGetCmc);
|
||||
@@ -698,8 +698,8 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
list = new CardCollection(AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa));
|
||||
|
||||
if (amountStr.equals("X")
|
||||
&& !source.hasSVar("PayX") /* SubAbility on something that already had set PayX, e.g. Endless One ETB counters */
|
||||
&& ((sa.hasParam(amountStr) && sa.getSVar(amountStr).equals("Count$xPaid")) || source.getSVar(amountStr).equals("Count$xPaid") )) {
|
||||
&& !sa.hasSVar("PayX") /* SubAbility on something that already had set PayX, e.g. Endless One ETB counters */
|
||||
&& ((sa.hasParam(amountStr) && sa.getSVar(amountStr).equals("Count$xPaid")))) {
|
||||
|
||||
// detect if there's more than one X in the cost (Hangarback Walker, Walking Ballista, etc.)
|
||||
SpellAbility testSa = sa;
|
||||
@@ -725,7 +725,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
// Account for the multiple X in cost
|
||||
if (countX > 1) { payX /= countX; }
|
||||
|
||||
source.setSVar("PayX", Integer.toString(payX));
|
||||
sa.setSVar("PayX", Integer.toString(payX));
|
||||
}
|
||||
|
||||
if (!mandatory) {
|
||||
@@ -1031,7 +1031,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
}
|
||||
} else {
|
||||
for (CounterType type : options) {
|
||||
if (!ComputerUtil.isNegativeCounter(type, c) && !ComputerUtil.isUselessCounter(type)) {
|
||||
if (!ComputerUtil.isNegativeCounter(type, c) && !ComputerUtil.isUselessCounter(type, c)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,10 +82,10 @@ public class CountersPutAllAi extends SpellAbilityAi {
|
||||
// TODO improve X value to don't overpay when extra mana won't do
|
||||
// anything more useful
|
||||
final int amount;
|
||||
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
||||
if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
amount = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(amount));
|
||||
sa.setSVar("PayX", Integer.toString(amount));
|
||||
} else {
|
||||
amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
||||
}
|
||||
|
||||
@@ -157,7 +157,7 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
|
||||
if (!ComputerUtil.isNegativeCounter(aType, best)) {
|
||||
sa.getTargets().add(best);
|
||||
return true;
|
||||
} else if (!ComputerUtil.isUselessCounter(aType)) {
|
||||
} else if (!ComputerUtil.isUselessCounter(aType, best)) {
|
||||
// whould remove positive counter
|
||||
if (best.getCounters(aType) <= amount) {
|
||||
sa.getTargets().add(best);
|
||||
|
||||
@@ -143,7 +143,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
||||
// variable amount for Hex Parasite
|
||||
int amount;
|
||||
boolean xPay = false;
|
||||
if (amountStr.equals("X") && source.getSVar("X").equals("Count$xPaid")) {
|
||||
if (amountStr.equals("X") && sa.getSVar("X").equals("Count$xPaid")) {
|
||||
final int manaLeft = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
|
||||
if (manaLeft == 0) {
|
||||
@@ -167,7 +167,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
||||
if (amount >= ice) {
|
||||
sa.getTargets().add(depth);
|
||||
if (xPay) {
|
||||
source.setSVar("PayX", Integer.toString(ice));
|
||||
sa.setSVar("PayX", Integer.toString(ice));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -186,7 +186,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
||||
Card best = ComputerUtilCard.getBestPlaneswalkerAI(planeswalkerList);
|
||||
sa.getTargets().add(best);
|
||||
if (xPay) {
|
||||
source.setSVar("PayX", Integer.toString(best.getCurrentLoyalty()));
|
||||
sa.setSVar("PayX", Integer.toString(best.getCurrentLoyalty()));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -297,7 +297,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
||||
int amount;
|
||||
boolean xPay = false;
|
||||
// Timecrafting has X R
|
||||
if (amountStr.equals("X") && source.getSVar("X").equals("Count$xPaid")) {
|
||||
if (amountStr.equals("X") && sa.getSVar("X").equals("Count$xPaid")) {
|
||||
final int manaLeft = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
|
||||
if (manaLeft == 0) {
|
||||
@@ -317,7 +317,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
||||
int timeCount = best.getCounters(CounterEnumType.TIME);
|
||||
sa.getTargets().add(best);
|
||||
if (xPay) {
|
||||
source.setSVar("PayX", Integer.toString(timeCount));
|
||||
sa.setSVar("PayX", Integer.toString(timeCount));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ public class DamageAllAi extends SpellAbilityAi {
|
||||
|
||||
if (best_x > 0) {
|
||||
if (sa.getSVar(damage).equals("Count$xPaid")) {
|
||||
source.setSVar("PayX", Integer.toString(best_x));
|
||||
sa.setSVar("PayX", Integer.toString(best_x));
|
||||
}
|
||||
if (damage.equals("ChosenX")) {
|
||||
source.setSVar("ChosenX", "Number$" + best_x);
|
||||
@@ -203,7 +203,7 @@ public class DamageAllAi extends SpellAbilityAi {
|
||||
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
dmg = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(dmg));
|
||||
sa.setSVar("PayX", Integer.toString(dmg));
|
||||
}
|
||||
|
||||
if (sa.hasParam("ValidPlayers")) {
|
||||
@@ -285,7 +285,7 @@ public class DamageAllAi extends SpellAbilityAi {
|
||||
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
dmg = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(dmg));
|
||||
sa.setSVar("PayX", Integer.toString(dmg));
|
||||
}
|
||||
|
||||
if (sa.hasParam("ValidPlayers")) {
|
||||
|
||||
@@ -12,7 +12,6 @@ import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.card.*;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.cost.CostPart;
|
||||
import forge.game.cost.CostPartMana;
|
||||
import forge.game.cost.CostRemoveCounter;
|
||||
import forge.game.keyword.Keyword;
|
||||
@@ -75,8 +74,8 @@ public class DamageDealAi extends DamageAiBase {
|
||||
}
|
||||
|
||||
// Set PayX here to maximum value.
|
||||
dmg = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(dmg));
|
||||
dmg = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||
sa.setSVar("PayX", Integer.toString(dmg));
|
||||
} else if (sa.getSVar(damage).equals("Count$CardsInYourHand") && source.isInZone(ZoneType.Hand)) {
|
||||
dmg--; // the card will be spent casting the spell, so actual damage is 1 less
|
||||
}
|
||||
@@ -96,7 +95,7 @@ public class DamageDealAi extends DamageAiBase {
|
||||
|
||||
if (damage.equals("X")) {
|
||||
if (sa.getSVar(damage).equals("Count$xPaid") || sourceName.equals("Crater's Claws")) {
|
||||
dmg = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
dmg = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||
|
||||
// Try not to waste spells like Blaze or Fireball on early targets, try to do more damage with them if possible
|
||||
if (ai.getController().isAI()) {
|
||||
@@ -113,7 +112,7 @@ public class DamageDealAi extends DamageAiBase {
|
||||
}
|
||||
|
||||
// Set PayX here to maximum value. It will be adjusted later depending on the target.
|
||||
source.setSVar("PayX", Integer.toString(dmg));
|
||||
sa.setSVar("PayX", Integer.toString(dmg));
|
||||
} else if (sa.getSVar(damage).contains("InYourHand") && source.isInZone(ZoneType.Hand)) {
|
||||
dmg = CardFactoryUtil.xCount(source, sa.getSVar(damage)) - 1; // the card will be spent casting the spell, so actual damage is 1 less
|
||||
} else if (sa.getSVar(damage).equals("TargetedPlayer$CardsInHand")) {
|
||||
@@ -225,7 +224,7 @@ public class DamageDealAi extends DamageAiBase {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) {
|
||||
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -266,7 +265,7 @@ public class DamageDealAi extends DamageAiBase {
|
||||
}
|
||||
}
|
||||
|
||||
if ((damage.equals("X") && source.getSVar(damage).equals("Count$xPaid")) ||
|
||||
if ((damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) ||
|
||||
sourceName.equals("Crater's Claws")){
|
||||
// If I can kill my target by paying less mana, do it
|
||||
if (sa.usesTargeting() && !sa.getTargets().isTargetingAnyPlayer() && !sa.hasParam("DividedAsYouChoose")) {
|
||||
@@ -281,25 +280,13 @@ public class DamageDealAi extends DamageAiBase {
|
||||
if (sourceName.equals("Crater's Claws") && ai.hasFerocious()) {
|
||||
actualPay = actualPay > 2 ? actualPay - 2 : 0;
|
||||
}
|
||||
source.setSVar("PayX", Integer.toString(actualPay));
|
||||
}
|
||||
}
|
||||
|
||||
if ("XCountersDamage".equals(logic)) {
|
||||
// Check to ensure that we have enough counters to remove per the defined PayX
|
||||
for (CostPart part : sa.getPayCosts().getCostParts()) {
|
||||
if (part instanceof CostRemoveCounter) {
|
||||
if (source.getCounters(((CostRemoveCounter) part).counter) < Integer.valueOf(source.getSVar("PayX"))) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
sa.setSVar("PayX", Integer.toString(actualPay));
|
||||
}
|
||||
}
|
||||
|
||||
if ("DiscardCMCX".equals(sa.getParam("AILogic"))) {
|
||||
final int CMC = Integer.parseInt(source.getSVar("PayX"));
|
||||
return !CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.hasCMC(CMC)).isEmpty();
|
||||
final int cmc = Integer.parseInt(sa.getSVar("PayX"));
|
||||
return !CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.hasCMC(cmc)).isEmpty();
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -990,7 +977,7 @@ public class DamageDealAi extends DamageAiBase {
|
||||
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
dmg = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(dmg));
|
||||
sa.setSVar("PayX", Integer.toString(dmg));
|
||||
}
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
@@ -1002,7 +989,7 @@ public class DamageDealAi extends DamageAiBase {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (damage.equals("X") && source.getSVar(damage).equals("Count$xPaid") && !sa.hasParam("DividedAsYouChoose")) {
|
||||
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid") && !sa.hasParam("DividedAsYouChoose")) {
|
||||
// If I can kill my target by paying less mana, do it
|
||||
int actualPay = 0;
|
||||
final boolean noPrevention = sa.hasParam("NoPrevention");
|
||||
@@ -1018,7 +1005,7 @@ public class DamageDealAi extends DamageAiBase {
|
||||
}
|
||||
}
|
||||
|
||||
source.setSVar("PayX", Integer.toString(actualPay));
|
||||
sa.setSVar("PayX", Integer.toString(actualPay));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1078,7 +1065,7 @@ public class DamageDealAi extends DamageAiBase {
|
||||
saTgt.resetTargets();
|
||||
saTgt.getTargets().add(tgtCreature != null && dmg < opponent.getLife() ? tgtCreature : opponent);
|
||||
|
||||
source.setSVar("PayX", Integer.toString(dmg));
|
||||
sa.setSVar("PayX", Integer.toString(dmg));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ public class DamagePreventAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkRemoveCounterCost(cost, hostCard)) {
|
||||
if (!ComputerUtilCost.checkRemoveCounterCost(cost, hostCard, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ public class DamagePreventAllAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkRemoveCounterCost(cost, hostCard)) {
|
||||
if (!ComputerUtilCost.checkRemoveCounterCost(cost, hostCard, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ public class DebuffAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkRemoveCounterCost(cost, source)) {
|
||||
if (!ComputerUtilCost.checkRemoveCounterCost(cost, source, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ public class DebuffAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
|
||||
if (!sa.usesTargeting() || !sa.getTargetRestrictions().doesTarget()) {
|
||||
if (!sa.usesTargeting()) {
|
||||
List<Card> cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ public class DebuffAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) {
|
||||
if (!sa.usesTargeting()) {
|
||||
// TODO - copied from AF_Pump.pumpDrawbackAI() - what should be
|
||||
// here?
|
||||
} else {
|
||||
|
||||
@@ -13,7 +13,6 @@ import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
public class DestroyAi extends SpellAbilityAi {
|
||||
@@ -23,89 +22,19 @@ public class DestroyAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(final Player ai, SpellAbility sa) {
|
||||
// AI needs to be expanded, since this function can be pretty complex
|
||||
// based on what the expected targets could be
|
||||
final Cost abCost = sa.getPayCosts();
|
||||
final TargetRestrictions abTgt = sa.getTargetRestrictions();
|
||||
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
|
||||
final Card source = sa.getHostCard();
|
||||
final boolean noRegen = sa.hasParam("NoRegen");
|
||||
final String logic = sa.getParam("AILogic");
|
||||
boolean hasXCost = false;
|
||||
|
||||
CardCollection list;
|
||||
|
||||
if (abCost != null) {
|
||||
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
hasXCost = abCost.getCostMana() != null && abCost.getCostMana().getAmountOfX() > 0;
|
||||
}
|
||||
|
||||
if ("AtOpponentsCombatOrAfter".equals(sa.getParam("AILogic"))) {
|
||||
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
||||
if (ph.getPlayerTurn() == ai || ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
||||
return false;
|
||||
}
|
||||
} else if ("AtEOT".equals(sa.getParam("AILogic"))) {
|
||||
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
||||
if (!ph.is(PhaseType.END_OF_TURN)) {
|
||||
return false;
|
||||
}
|
||||
} else if ("AtEOTIfNotAttacking".equals(sa.getParam("AILogic"))) {
|
||||
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
||||
if (!ph.is(PhaseType.END_OF_TURN) || !ai.getCreaturesAttackedThisTurn().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ability that's intended to destroy own useless token to trigger Grave Pacts
|
||||
// should be fired at end of turn or when under attack after blocking to make opponent sac something
|
||||
boolean havepact = false;
|
||||
|
||||
// TODO replace it with look for a dies -> sacrifice trigger check
|
||||
havepact |= ai.isCardInPlay("Grave Pact");
|
||||
havepact |= ai.isCardInPlay("Butcher of Malakir");
|
||||
havepact |= ai.isCardInPlay("Dictate of Erebos");
|
||||
if ("Pactivator".equals(logic) && havepact) {
|
||||
if ((!ai.getGame().getPhaseHandler().isPlayerTurn(ai))
|
||||
&& ((ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN)) || (ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)))
|
||||
&& (ai.getOpponents().getCreaturesInPlay().size() > 0)) {
|
||||
ai.getController().chooseTargetsFor(sa);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Targeting
|
||||
if (abTgt != null) {
|
||||
if (sa.usesTargeting()) {
|
||||
sa.resetTargets();
|
||||
if (sa.hasParam("TargetingPlayer")) {
|
||||
Player targetingPlayer = AbilityUtils.getDefinedPlayers(source, sa.getParam("TargetingPlayer"), sa).get(0);
|
||||
sa.setTargetingPlayer(targetingPlayer);
|
||||
return targetingPlayer.getController().chooseTargetsFor(sa);
|
||||
}
|
||||
if ("MadSarkhanDragon".equals(logic)) {
|
||||
if ("MadSarkhanDragon".equals(aiLogic)) {
|
||||
return SpecialCardAi.SarkhanTheMad.considerMakeDragon(ai, sa);
|
||||
} else if (logic != null && logic.startsWith("MinLoyalty.")) {
|
||||
int minLoyalty = Integer.parseInt(logic.substring(logic.indexOf(".") + 1));
|
||||
} else if (aiLogic.startsWith("MinLoyalty.")) {
|
||||
int minLoyalty = Integer.parseInt(aiLogic.substring(aiLogic.indexOf(".") + 1));
|
||||
if (source.getCounters(CounterEnumType.LOYALTY) < minLoyalty) {
|
||||
return false;
|
||||
}
|
||||
} else if ("Polymorph".equals(logic)) {
|
||||
list = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa);
|
||||
} else if ("Polymorph".equals(aiLogic)) {
|
||||
CardCollection list = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa);
|
||||
if (list.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
@@ -124,7 +53,108 @@ public class DestroyAi extends SpellAbilityAi {
|
||||
}
|
||||
sa.getTargets().add(worst);
|
||||
return true;
|
||||
} else if ("Pongify".equals(aiLogic)) {
|
||||
return SpecialAiLogic.doPongifyLogic(ai, sa);
|
||||
}
|
||||
}
|
||||
return super.checkAiLogic(ai, sa, aiLogic);
|
||||
}
|
||||
|
||||
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph,
|
||||
final String logic) {
|
||||
if ("AtOpponentsCombatOrAfter".equals(logic)) {
|
||||
if (ph.isPlayerTurn(ai) || ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
||||
return false;
|
||||
}
|
||||
} else if ("AtEOT".equals(logic)) {
|
||||
if (!ph.is(PhaseType.END_OF_TURN)) {
|
||||
return false;
|
||||
}
|
||||
} else if ("AtEOTIfNotAttacking".equals(logic)) {
|
||||
if (!ph.is(PhaseType.END_OF_TURN) || !ai.getCreaturesAttackedThisTurn().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
} else if ("Pactivator".equals(logic)) {
|
||||
// Ability that's intended to destroy own useless token to trigger Grave Pacts
|
||||
// should be fired at end of turn or when under attack after blocking to make opponent sac something
|
||||
boolean havepact = false;
|
||||
|
||||
// TODO replace it with look for a dies -> sacrifice trigger check
|
||||
havepact |= ai.isCardInPlay("Grave Pact");
|
||||
havepact |= ai.isCardInPlay("Butcher of Malakir");
|
||||
havepact |= ai.isCardInPlay("Dictate of Erebos");
|
||||
if (havepact) {
|
||||
if ((!ph.isPlayerTurn(ai))
|
||||
&& ((ph.is(PhaseType.END_OF_TURN)) || (ph.is(PhaseType.COMBAT_DECLARE_BLOCKERS)))
|
||||
&& (ai.getOpponents().getCreaturesInPlay().size() > 0)) {
|
||||
CardCollection list = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa);
|
||||
Card worst = ComputerUtilCard.getWorstAI(list);
|
||||
if (worst != null) {
|
||||
sa.getTargets().add(worst);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||
final Card source = sa.getHostCard();
|
||||
final boolean noRegen = sa.hasParam("NoRegen");
|
||||
final String logic = sa.getParam("AILogic");
|
||||
|
||||
CardCollection list;
|
||||
|
||||
|
||||
|
||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Targeting
|
||||
if (sa.usesTargeting()) {
|
||||
// Assume there where already enough targets chosen by AI Logic Above
|
||||
if (!sa.canAddMoreTarget() && sa.isTargetNumberValid()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// reset targets before AI Logic part
|
||||
sa.resetTargets();
|
||||
int maxTargets;
|
||||
|
||||
if (sa.costHasManaX()) {
|
||||
// TODO: currently the AI will maximize mana spent on X, trying to maximize damage. This may need improvement.
|
||||
maxTargets = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||
// need to set XPaid to get the right number for
|
||||
sa.setXManaCostPaid(maxTargets);
|
||||
// need to check for maxTargets
|
||||
maxTargets = Math.min(maxTargets, sa.getMaxTargets());
|
||||
} else {
|
||||
maxTargets = sa.getMaxTargets();
|
||||
}
|
||||
if (sa.hasParam("AIMaxTgtsCount")) {
|
||||
// Cards that have confusing costs for the AI (e.g. Eliminate the Competition) can have forced max target constraints specified
|
||||
// TODO: is there a better way to predict things like "sac X" costs without needing a special AI variable?
|
||||
maxTargets = Math.min(CardFactoryUtil.xCount(sa.getHostCard(), "Count$" + sa.getParam("AIMaxTgtsCount")), maxTargets);
|
||||
}
|
||||
|
||||
if (maxTargets == 0) {
|
||||
// can't afford X or otherwise target anything
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sa.hasParam("TargetingPlayer")) {
|
||||
Player targetingPlayer = AbilityUtils.getDefinedPlayers(source, sa.getParam("TargetingPlayer"), sa).get(0);
|
||||
sa.setTargetingPlayer(targetingPlayer);
|
||||
return targetingPlayer.getController().chooseTargetsFor(sa);
|
||||
}
|
||||
|
||||
// AI doesn't destroy own cards if it isn't defined in AI logic
|
||||
list = CardLists.getTargetableCards(ai.getOpponents().getCardsIn(ZoneType.Battlefield), sa);
|
||||
if ("FatalPush".equals(logic)) {
|
||||
final int cmcMax = ai.hasRevolt() ? 4 : 2;
|
||||
@@ -184,33 +214,12 @@ public class DestroyAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
|
||||
int maxTargets = abTgt.getMaxTargets(sa.getHostCard(), sa);
|
||||
|
||||
if (hasXCost) {
|
||||
// TODO: currently the AI will maximize mana spent on X, trying to maximize damage. This may need improvement.
|
||||
maxTargets = Math.min(ComputerUtilMana.determineMaxAffordableX(ai, sa), abTgt.getMaxTargets(sa.getHostCard(), sa));
|
||||
// X can't be more than the lands we have in our hand for "discard X lands"!
|
||||
if ("ScorchedEarth".equals(logic)) {
|
||||
int lands = CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.Presets.LANDS).size();
|
||||
maxTargets = Math.min(maxTargets, lands);
|
||||
}
|
||||
}
|
||||
if (sa.hasParam("AIMaxTgtsCount")) {
|
||||
// Cards that have confusing costs for the AI (e.g. Eliminate the Competition) can have forced max target constraints specified
|
||||
// TODO: is there a better way to predict things like "sac X" costs without needing a special AI variable?
|
||||
maxTargets = Math.min(CardFactoryUtil.xCount(sa.getHostCard(), "Count$" + sa.getParam("AIMaxTgtsCount")), maxTargets);
|
||||
}
|
||||
|
||||
if (maxTargets == 0) {
|
||||
// can't afford X or otherwise target anything
|
||||
return false;
|
||||
}
|
||||
|
||||
// target loop
|
||||
// TODO use can add more Targets
|
||||
while (sa.getTargets().size() < maxTargets) {
|
||||
if (list.isEmpty()) {
|
||||
if ((sa.getTargets().size() < abTgt.getMinTargets(sa.getHostCard(), sa))
|
||||
|| (sa.getTargets().size() == 0)) {
|
||||
if (!sa.isMinTargetChosen() || sa.isZeroTargets()) {
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
} else {
|
||||
@@ -222,10 +231,6 @@ public class DestroyAi extends SpellAbilityAi {
|
||||
Card choice = null;
|
||||
// If the targets are only of one type, take the best
|
||||
if (CardLists.getNotType(list, "Creature").isEmpty()) {
|
||||
if ("Pongify".equals(logic)) {
|
||||
return SpecialAiLogic.doPongifyLogic(ai, sa);
|
||||
}
|
||||
|
||||
choice = ComputerUtilCard.getBestCreatureAI(list);
|
||||
if ("OppDestroyYours".equals(logic)) {
|
||||
Card aiBest = ComputerUtilCard.getBestCreatureAI(ai.getCreaturesInPlay());
|
||||
@@ -246,15 +251,14 @@ public class DestroyAi extends SpellAbilityAi {
|
||||
choice = ComputerUtilCard.getMostExpensivePermanentAI(list, sa, true);
|
||||
}
|
||||
//option to hold removal instead only applies for single targeted removal
|
||||
if (!sa.isTrigger() && abTgt.getMaxTargets(sa.getHostCard(), sa) == 1) {
|
||||
if (!sa.isTrigger() && sa.getMaxTargets() == 1) {
|
||||
if (choice == null || !ComputerUtilCard.useRemovalNow(sa, choice, 0, ZoneType.Graveyard)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (choice == null) { // can't find anything left
|
||||
if ((sa.getTargets().size() < abTgt.getMinTargets(sa.getHostCard(), sa))
|
||||
|| (sa.getTargets().size() == 0)) {
|
||||
if (!sa.isMinTargetChosen() || sa.isZeroTargets()) {
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
} else {
|
||||
@@ -298,22 +302,19 @@ public class DestroyAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
final boolean noRegen = sa.hasParam("NoRegen");
|
||||
if (tgt != null) {
|
||||
if (sa.usesTargeting()) {
|
||||
sa.resetTargets();
|
||||
|
||||
CardCollection list = CardLists.getTargetableCards(ai.getGame().getCardsIn(ZoneType.Battlefield), sa);
|
||||
list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, sa);
|
||||
|
||||
if (list.isEmpty() || list.size() < sa.getMinTargets()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try to avoid targeting creatures that are dead on board
|
||||
list = ComputerUtil.filterCreaturesThatWillDieThisTurn(ai, list, sa);
|
||||
|
||||
if (list.isEmpty() || list.size() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
CardCollection preferred = CardLists.getNotKeyword(list, Keyword.INDESTRUCTIBLE);
|
||||
preferred = CardLists.filterControlledBy(preferred, ai.getOpponents());
|
||||
if (CardLists.getNotType(preferred, "Creature").isEmpty()) {
|
||||
@@ -344,10 +345,9 @@ public class DestroyAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
|
||||
while (sa.getTargets().size() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
|
||||
while (sa.canAddMoreTarget()) {
|
||||
if (preferred.isEmpty()) {
|
||||
if (sa.getTargets().size() == 0
|
||||
|| sa.getTargets().size() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||
if (!sa.isMinTargetChosen()) {
|
||||
if (!mandatory) {
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
@@ -371,7 +371,7 @@ public class DestroyAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
|
||||
while (sa.getTargets().size() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||
while (sa.canAddMoreTarget()) {
|
||||
if (list.isEmpty()) {
|
||||
break;
|
||||
} else {
|
||||
@@ -392,7 +392,7 @@ public class DestroyAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
|
||||
return sa.getTargets().size() >= tgt.getMinTargets(sa.getHostCard(), sa);
|
||||
return sa.isTargetNumberValid();
|
||||
} else {
|
||||
return mandatory;
|
||||
}
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
import com.google.common.base.Predicates;
|
||||
import forge.ai.*;
|
||||
import forge.card.MagicColor;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.game.combat.Combat;
|
||||
|
||||
public class DestroyAllAi extends SpellAbilityAi {
|
||||
|
||||
@@ -78,10 +80,10 @@ public class DestroyAllAi extends SpellAbilityAi {
|
||||
valid = sa.getParam("ValidCards");
|
||||
}
|
||||
|
||||
if (valid.contains("X") && source.getSVar("X").equals("Count$xPaid")) {
|
||||
if (valid.contains("X") && sa.getSVar("X").equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(xPay));
|
||||
sa.setSVar("PayX", Integer.toString(xPay));
|
||||
valid = valid.replace("X", Integer.toString(xPay));
|
||||
}
|
||||
|
||||
@@ -106,6 +108,14 @@ public class DestroyAllAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
|
||||
// Special handling for Raiding Party
|
||||
if (logic.equals("RaidingParty")) {
|
||||
int numAiCanSave = Math.min(CardLists.filter(ai.getCreaturesInPlay(), Predicates.and(CardPredicates.isColor(MagicColor.WHITE), CardPredicates.Presets.UNTAPPED)).size() * 2, ailist.size());
|
||||
int numOppsCanSave = Math.min(CardLists.filter(ai.getOpponents().getCreaturesInPlay(), Predicates.and(CardPredicates.isColor(MagicColor.WHITE), CardPredicates.Presets.UNTAPPED)).size() * 2, opplist.size());
|
||||
|
||||
return numOppsCanSave < opplist.size() && (ailist.size() - numAiCanSave < opplist.size() - numOppsCanSave);
|
||||
}
|
||||
|
||||
// If effect is destroying creatures and AI is about to lose, activate effect anyway no matter what!
|
||||
if ((!CardLists.getType(opplist, "Creature").isEmpty()) && (ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS))
|
||||
&& (ai.getGame().getCombat() != null && ComputerUtilCombat.lifeInSeriousDanger(ai, ai.getGame().getCombat()))) {
|
||||
|
||||
@@ -75,9 +75,9 @@ public class DigAi extends SpellAbilityAi {
|
||||
|
||||
final String num = sa.getParam("DigNum");
|
||||
final boolean payXLogic = sa.hasParam("AILogic") && sa.getParam("AILogic").startsWith("PayX");
|
||||
if (num != null && (num.equals("X") && host.getSVar(num).equals("Count$xPaid")) || payXLogic) {
|
||||
if (num != null && (num.equals("X") && sa.getSVar(num).equals("Count$xPaid")) || payXLogic) {
|
||||
// By default, set PayX here to maximum value.
|
||||
if (!(sa instanceof AbilitySub) || host.getSVar("PayX").equals("")) {
|
||||
if (!(sa instanceof AbilitySub) || sa.getSVar("PayX").equals("")) {
|
||||
int manaToSave = 0;
|
||||
|
||||
// Special logic that asks the AI to conserve a certain amount of mana when paying X
|
||||
@@ -89,7 +89,7 @@ public class DigAi extends SpellAbilityAi {
|
||||
if (numCards <= 0) {
|
||||
return false;
|
||||
}
|
||||
host.setSVar("PayX", Integer.toString(numCards));
|
||||
sa.setSVar("PayX", Integer.toString(numCards));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -76,14 +76,14 @@ public class DigUntilAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
final String num = sa.getParam("Amount");
|
||||
if ((num != null) && num.equals("X") && source.getSVar(num).equals("Count$xPaid")) {
|
||||
if ((num != null) && num.equals("X") && sa.getSVar(num).equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
if (!(sa instanceof AbilitySub) || source.getSVar("PayX").equals("")) {
|
||||
if (!(sa instanceof AbilitySub) || sa.getSVar("PayX").equals("")) {
|
||||
int numCards = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
if (numCards <= 0) {
|
||||
return false;
|
||||
}
|
||||
source.setSVar("PayX", Integer.toString(numCards));
|
||||
sa.setSVar("PayX", Integer.toString(numCards));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ public class DiscardAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) {
|
||||
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -85,14 +85,14 @@ public class DiscardAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
if (sa.hasParam("NumCards")) {
|
||||
if (sa.getParam("NumCards").equals("X") && source.getSVar("X").equals("Count$xPaid")) {
|
||||
if (sa.getParam("NumCards").equals("X") && sa.getSVar("X").equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
final int cardsToDiscard = Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), ai.getWeakestOpponent()
|
||||
.getCardsIn(ZoneType.Hand).size());
|
||||
if (cardsToDiscard < 1) {
|
||||
return false;
|
||||
}
|
||||
source.setSVar("PayX", Integer.toString(cardsToDiscard));
|
||||
sa.setSVar("PayX", Integer.toString(cardsToDiscard));
|
||||
} else {
|
||||
if (AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumCards"), sa) < 1) {
|
||||
return false;
|
||||
|
||||
@@ -238,17 +238,17 @@ public class DrawAi extends SpellAbilityAi {
|
||||
boolean xPaid = false;
|
||||
final String num = sa.getParam("NumCards");
|
||||
if (num != null && num.equals("X")) {
|
||||
if (source.getSVar(num).equals("Count$xPaid")) {
|
||||
if (sa.getSVar(num).equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
if (drawback && !source.getSVar("PayX").equals("")) {
|
||||
numCards = Integer.parseInt(source.getSVar("PayX"));
|
||||
if (drawback && !sa.getSVar("PayX").equals("")) {
|
||||
numCards = Integer.parseInt(sa.getSVar("PayX"));
|
||||
} else {
|
||||
numCards = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
// try not to overdraw
|
||||
int safeDraw = Math.min(computerMaxHandSize - computerHandSize, computerLibrarySize - 3);
|
||||
if (sa.getHostCard().isInstant() || sa.getHostCard().isSorcery()) { safeDraw++; } // card will be spent
|
||||
numCards = Math.min(numCards, safeDraw);
|
||||
source.setSVar("PayX", Integer.toString(numCards));
|
||||
sa.setSVar("PayX", Integer.toString(numCards));
|
||||
assumeSafeX = true;
|
||||
}
|
||||
xPaid = true;
|
||||
@@ -340,7 +340,7 @@ public class DrawAi extends SpellAbilityAi {
|
||||
// for drawing and losing life
|
||||
if (numCards >= oppA.getLife()) {
|
||||
if (xPaid) {
|
||||
source.setSVar("PayX", Integer.toString(oppA.getLife()));
|
||||
sa.setSVar("PayX", Integer.toString(oppA.getLife()));
|
||||
}
|
||||
sa.getTargets().add(oppA);
|
||||
return true;
|
||||
@@ -400,7 +400,7 @@ public class DrawAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
if (xPaid) {
|
||||
source.setSVar("PayX", Integer.toString(numCards));
|
||||
sa.setSVar("PayX", Integer.toString(numCards));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -411,7 +411,7 @@ public class DrawAi extends SpellAbilityAi {
|
||||
if (sa.getHostCard().isInZone(ZoneType.Hand)) {
|
||||
numCards++; // the card will be spent
|
||||
}
|
||||
source.setSVar("PayX", Integer.toString(numCards));
|
||||
sa.setSVar("PayX", Integer.toString(numCards));
|
||||
} else {
|
||||
// Don't draw too many cards and then risk discarding
|
||||
// cards at EOT
|
||||
|
||||
@@ -49,7 +49,7 @@ public class LifeGainAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkRemoveCounterCost(cost, source)) {
|
||||
if (!ComputerUtilCost.checkRemoveCounterCost(cost, source, sa)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
@@ -125,10 +125,10 @@ public class LifeGainAi extends SpellAbilityAi {
|
||||
final String amountStr = sa.getParam("LifeAmount");
|
||||
int lifeAmount = 0;
|
||||
boolean activateForCost = ComputerUtil.activateForCost(sa, ai);
|
||||
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
||||
if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(xPay));
|
||||
sa.setSVar("PayX", Integer.toString(xPay));
|
||||
lifeAmount = xPay;
|
||||
} else {
|
||||
lifeAmount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
||||
@@ -213,12 +213,11 @@ public class LifeGainAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
|
||||
final Card source = sa.getHostCard();
|
||||
final String amountStr = sa.getParam("LifeAmount");
|
||||
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
||||
if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(xPay));
|
||||
sa.setSVar("PayX", Integer.toString(xPay));
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -34,14 +34,14 @@ public class LifeLoseAi extends SpellAbilityAi {
|
||||
final Card source = sa.getHostCard();
|
||||
final String amountStr = sa.getParam("LifeAmount");
|
||||
int amount = 0;
|
||||
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
||||
if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) {
|
||||
// something already set PayX
|
||||
if (source.hasSVar("PayX")) {
|
||||
amount = Integer.parseInt(source.getSVar("PayX"));
|
||||
if (sa.hasSVar("PayX")) {
|
||||
amount = Integer.parseInt(sa.getSVar("PayX"));
|
||||
} else {
|
||||
// Set PayX here to maximum value.
|
||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(xPay));
|
||||
sa.setSVar("PayX", Integer.toString(xPay));
|
||||
amount = xPay;
|
||||
}
|
||||
} else {
|
||||
@@ -72,10 +72,9 @@ public class LifeLoseAi extends SpellAbilityAi {
|
||||
final String amountStr = sa.getParam("LifeAmount");
|
||||
int amount = 0;
|
||||
|
||||
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
||||
if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
amount = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
// source.setSVar("PayX", Integer.toString(amount));
|
||||
} else {
|
||||
amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
||||
}
|
||||
@@ -101,10 +100,10 @@ public class LifeLoseAi extends SpellAbilityAi {
|
||||
final String amountStr = sa.getParam("LifeAmount");
|
||||
int amount = 0;
|
||||
|
||||
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
||||
if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
amount = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(amount));
|
||||
sa.setSVar("PayX", Integer.toString(amount));
|
||||
} else {
|
||||
amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
||||
}
|
||||
@@ -172,10 +171,10 @@ public class LifeLoseAi extends SpellAbilityAi {
|
||||
final Card source = sa.getHostCard();
|
||||
final String amountStr = sa.getParam("LifeAmount");
|
||||
int amount = 0;
|
||||
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
||||
if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(xPay));
|
||||
sa.setSVar("PayX", Integer.toString(xPay));
|
||||
amount = xPay;
|
||||
} else {
|
||||
amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
||||
|
||||
@@ -16,8 +16,6 @@ public class LifeSetAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
// Ability_Cost abCost = sa.getPayCosts();
|
||||
final Card source = sa.getHostCard();
|
||||
final int myLife = ai.getLife();
|
||||
final Player opponent = ai.getWeakestOpponent();
|
||||
final int hlife = opponent.getLife();
|
||||
@@ -42,10 +40,10 @@ public class LifeSetAi extends SpellAbilityAi {
|
||||
// would be paid
|
||||
int amount;
|
||||
// we shouldn't have to worry too much about PayX for SetLife
|
||||
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
||||
if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(xPay));
|
||||
sa.setSVar("PayX", Integer.toString(xPay));
|
||||
amount = xPay;
|
||||
} else {
|
||||
amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
||||
@@ -114,10 +112,10 @@ public class LifeSetAi extends SpellAbilityAi {
|
||||
final String amountStr = sa.getParam("LifeAmount");
|
||||
|
||||
int amount;
|
||||
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
||||
if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(xPay));
|
||||
sa.setSVar("PayX", Integer.toString(xPay));
|
||||
amount = xPay;
|
||||
} else {
|
||||
amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
||||
|
||||
@@ -80,7 +80,7 @@ public class ManifestAi extends SpellAbilityAi {
|
||||
// Handle either Manifest X cards, or Manifest 1 card and give it X P1P1s
|
||||
// Set PayX here to maximum value.
|
||||
int x = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(x));
|
||||
sa.setSVar("PayX", Integer.toString(x));
|
||||
if (x <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -75,7 +75,6 @@ public class MillAi extends SpellAbilityAi {
|
||||
* - check for Laboratory Maniac effect (needs to check for actual
|
||||
* effect due to possibility of "lose abilities" effect)
|
||||
*/
|
||||
final Card source = sa.getHostCard();
|
||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||
return false; // prevents mill 0 infinite loop?
|
||||
}
|
||||
@@ -90,10 +89,10 @@ public class MillAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
if ((sa.getParam("NumCards").equals("X") || sa.getParam("NumCards").equals("Z"))
|
||||
&& source.getSVar("X").startsWith("Count$xPaid")) {
|
||||
&& sa.getSVar("X").startsWith("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
final int cardsToDiscard = getNumToDiscard(ai, sa);
|
||||
source.setSVar("PayX", Integer.toString(cardsToDiscard));
|
||||
sa.setSVar("PayX", Integer.toString(cardsToDiscard));
|
||||
return cardsToDiscard > 0;
|
||||
}
|
||||
return true;
|
||||
@@ -182,11 +181,10 @@ public class MillAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Card source = sa.getHostCard();
|
||||
if (sa.getParam("NumCards").equals("X") && source.getSVar("X").equals("Count$xPaid")) {
|
||||
if (sa.getParam("NumCards").equals("X") && sa.getSVar("X").equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
final int cardsToDiscard = getNumToDiscard(aiPlayer, sa);
|
||||
source.setSVar("PayX", Integer.toString(cardsToDiscard));
|
||||
sa.setSVar("PayX", Integer.toString(cardsToDiscard));
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -164,7 +164,7 @@ public class ProtectAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||
if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) {
|
||||
if (!sa.usesTargeting()) {
|
||||
final List<Card> cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||
if (cards.size() == 0) {
|
||||
return false;
|
||||
@@ -359,12 +359,7 @@ public class ProtectAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
final Card host = sa.getHostCard();
|
||||
if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) {
|
||||
if (host.isCreature()) {
|
||||
// TODO
|
||||
}
|
||||
} else {
|
||||
if (sa.usesTargeting()) {
|
||||
return protectTgtAI(ai, sa, false);
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ public class ProtectAllAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkRemoveCounterCost(cost, hostCard)) {
|
||||
if (!ComputerUtilCost.checkRemoveCounterCost(cost, hostCard, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,8 +10,6 @@ import forge.game.ability.ApiType;
|
||||
import forge.game.card.*;
|
||||
import forge.game.card.CardPredicates.Presets;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.cost.CostPart;
|
||||
import forge.game.cost.CostRemoveCounter;
|
||||
import forge.game.cost.CostTapType;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
@@ -288,19 +286,19 @@ public class PumpAi extends PumpAiBase {
|
||||
}
|
||||
}
|
||||
|
||||
if (source.getSVar("X").equals("Count$xPaid")) {
|
||||
source.setSVar("PayX", "");
|
||||
if (sa.getSVar("X").equals("Count$xPaid")) {
|
||||
sa.setSVar("PayX", "");
|
||||
}
|
||||
|
||||
int defense;
|
||||
if (numDefense.contains("X") && source.getSVar("X").equals("Count$xPaid")) {
|
||||
if (numDefense.contains("X") && sa.getSVar("X").equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
int xPay = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||
if (sourceName.equals("Necropolis Fiend")) {
|
||||
xPay = Math.min(xPay, sa.getActivatingPlayer().getCardsIn(ZoneType.Graveyard).size());
|
||||
sa.setSVar("X", Integer.toString(xPay));
|
||||
}
|
||||
source.setSVar("PayX", Integer.toString(xPay));
|
||||
sa.setSVar("PayX", Integer.toString(xPay));
|
||||
defense = xPay;
|
||||
if (numDefense.equals("-X")) {
|
||||
defense = -xPay;
|
||||
@@ -313,13 +311,13 @@ public class PumpAi extends PumpAiBase {
|
||||
}
|
||||
|
||||
int attack;
|
||||
if (numAttack.contains("X") && source.getSVar("X").equals("Count$xPaid")) {
|
||||
if (numAttack.contains("X") && sa.getSVar("X").equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
final String toPay = source.getSVar("PayX");
|
||||
final String toPay = sa.getSVar("PayX");
|
||||
|
||||
if (toPay.equals("")) {
|
||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(xPay));
|
||||
final int xPay = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||
sa.setSVar("PayX", Integer.toString(xPay));
|
||||
attack = xPay;
|
||||
} else {
|
||||
attack = Integer.parseInt(toPay);
|
||||
@@ -353,7 +351,7 @@ public class PumpAi extends PumpAiBase {
|
||||
}
|
||||
|
||||
//Untargeted
|
||||
if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) {
|
||||
if (!sa.usesTargeting()) {
|
||||
final List<Card> cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||
|
||||
if (cards.isEmpty()) {
|
||||
@@ -398,19 +396,10 @@ public class PumpAi extends PumpAiBase {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ("DebuffForXCounters".equals(sa.getParam("AILogic")) && sa.getTargetCard() != null) {
|
||||
// e.g. Skullmane Baku
|
||||
CounterType ctrType = CounterType.get(CounterEnumType.KI);
|
||||
for (CostPart part : sa.getPayCosts().getCostParts()) {
|
||||
if (part instanceof CostRemoveCounter) {
|
||||
ctrType = ((CostRemoveCounter)part).counter;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!sa.getSVar("PayX").isEmpty() && sa.getTargetCard() != null) {
|
||||
// Do not pay more counters than necessary to kill the targeted creature
|
||||
int chosenX = Math.min(source.getCounters(ctrType), sa.getTargetCard().getNetToughness());
|
||||
sa.setSVar("ChosenX", String.valueOf(chosenX));
|
||||
int chosenX = Math.min(sa.getSVarInt("PayX"), sa.getTargetCard().getNetToughness());
|
||||
sa.setSVar("PayX", String.valueOf(chosenX));
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -573,12 +562,12 @@ public class PumpAi extends PumpAiBase {
|
||||
}
|
||||
}
|
||||
|
||||
while (sa.getTargets().size() < tgt.getMaxTargets(source, sa)) {
|
||||
while (sa.canAddMoreTarget()) {
|
||||
Card t = null;
|
||||
// boolean goodt = false;
|
||||
|
||||
if (list.isEmpty()) {
|
||||
if (sa.getTargets().size() < tgt.getMinTargets(source, sa) || sa.getTargets().size() == 0) {
|
||||
if (!sa.isMinTargetChosen() || sa.isZeroTargets()) {
|
||||
if (mandatory || ComputerUtil.activateForCost(sa, ai)) {
|
||||
return pumpMandatoryTarget(ai, sa);
|
||||
}
|
||||
@@ -678,28 +667,27 @@ public class PumpAi extends PumpAiBase {
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final Card source = sa.getHostCard();
|
||||
final String numDefense = sa.hasParam("NumDef") ? sa.getParam("NumDef") : "";
|
||||
final String numAttack = sa.hasParam("NumAtt") ? sa.getParam("NumAtt") : "";
|
||||
|
||||
int defense;
|
||||
if (numDefense.contains("X") && source.getSVar("X").equals("Count$xPaid")) {
|
||||
if (numDefense.contains("X") && sa.getSVar("X").equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(xPay));
|
||||
final int xPay = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||
sa.setSVar("PayX", Integer.toString(xPay));
|
||||
defense = xPay;
|
||||
} else {
|
||||
defense = AbilityUtils.calculateAmount(sa.getHostCard(), numDefense, sa);
|
||||
}
|
||||
|
||||
int attack;
|
||||
if (numAttack.contains("X") && source.getSVar("X").equals("Count$xPaid")) {
|
||||
if (numAttack.contains("X") && sa.getSVar("X").equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
final String toPay = source.getSVar("PayX");
|
||||
final String toPay = sa.getSVar("PayX");
|
||||
|
||||
if (toPay.equals("")) {
|
||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(xPay));
|
||||
final int xPay = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||
sa.setSVar("PayX", Integer.toString(xPay));
|
||||
attack = xPay;
|
||||
} else {
|
||||
attack = Integer.parseInt(toPay);
|
||||
@@ -708,7 +696,7 @@ public class PumpAi extends PumpAiBase {
|
||||
attack = AbilityUtils.calculateAmount(sa.getHostCard(), numAttack, sa);
|
||||
}
|
||||
|
||||
if (sa.getTargetRestrictions() == null) {
|
||||
if (!sa.usesTargeting()) {
|
||||
if (mandatory) {
|
||||
return true;
|
||||
}
|
||||
@@ -749,28 +737,28 @@ public class PumpAi extends PumpAiBase {
|
||||
return false;
|
||||
}
|
||||
|
||||
int defense;
|
||||
if (numDefense.contains("X") && source.getSVar("X").equals("Count$xPaid")) {
|
||||
defense = Integer.parseInt(source.getSVar("PayX"));
|
||||
} else {
|
||||
defense = AbilityUtils.calculateAmount(sa.getHostCard(), numDefense, sa);
|
||||
}
|
||||
|
||||
int attack;
|
||||
if (numAttack.contains("X") && source.getSVar("X").equals("Count$xPaid")) {
|
||||
if (source.getSVar("PayX").equals("")) {
|
||||
if (numAttack.contains("X") && sa.getSVar("X").equals("Count$xPaid")) {
|
||||
if (sa.getSVar("PayX").equals("")) {
|
||||
// X is not set yet
|
||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa.getRootAbility(), ai);
|
||||
source.setSVar("PayX", Integer.toString(xPay));
|
||||
final int xPay = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||
sa.setSVar("PayX", Integer.toString(xPay));
|
||||
attack = xPay;
|
||||
} else {
|
||||
attack = Integer.parseInt(source.getSVar("PayX"));
|
||||
attack = Integer.parseInt(sa.getSVar("PayX"));
|
||||
}
|
||||
} else {
|
||||
attack = AbilityUtils.calculateAmount(sa.getHostCard(), numAttack, sa);
|
||||
}
|
||||
|
||||
if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) {
|
||||
int defense;
|
||||
if (numDefense.contains("X") && sa.getSVar("X").equals("Count$xPaid")) {
|
||||
defense = Integer.parseInt(sa.getSVar("PayX"));
|
||||
} else {
|
||||
defense = AbilityUtils.calculateAmount(sa.getHostCard(), numDefense, sa);
|
||||
}
|
||||
|
||||
if (!sa.usesTargeting()) {
|
||||
if (source.isCreature()) {
|
||||
if (!source.hasKeyword(Keyword.INDESTRUCTIBLE) && source.getNetToughness() + defense <= source.getDamage()) {
|
||||
return false;
|
||||
|
||||
@@ -2,22 +2,18 @@ package forge.ai.ability;
|
||||
|
||||
|
||||
import forge.ai.*;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
|
||||
public class RepeatAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
final Card source = sa.getHostCard();
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Player opp = ai.getWeakestOpponent();
|
||||
|
||||
if (tgt != null) {
|
||||
if (sa.usesTargeting()) {
|
||||
if (!opp.canBeTargetedBy(sa)) {
|
||||
return false;
|
||||
}
|
||||
@@ -31,7 +27,7 @@ public class RepeatAi extends SpellAbilityAi {
|
||||
}
|
||||
// Set PayX here to maximum value.
|
||||
final int max = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(max));
|
||||
sa.setSVar("PayX", Integer.toString(max));
|
||||
return max > 0;
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -103,10 +103,10 @@ public class SacrificeAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (num.equals("X") && source.getSVar(num).equals("Count$xPaid")) {
|
||||
if (num.equals("X") && sa.getSVar(num).equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
final int xPay = Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), amount);
|
||||
source.setSVar("PayX", Integer.toString(xPay));
|
||||
sa.setSVar("PayX", Integer.toString(xPay));
|
||||
}
|
||||
|
||||
final int half = (amount / 2) + (amount % 2); // Half of amount
|
||||
@@ -135,7 +135,7 @@ public class SacrificeAi extends SpellAbilityAi {
|
||||
final String num = sa.hasParam("Amount") ? sa.getParam("Amount") : "1";
|
||||
int amount = AbilityUtils.calculateAmount(source, num, sa);
|
||||
|
||||
if (num.equals("X") && source.getSVar(num).equals("Count$xPaid")) {
|
||||
if (num.equals("X") && sa.getSVar(num).equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
amount = Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), amount);
|
||||
}
|
||||
|
||||
@@ -28,10 +28,10 @@ public class SacrificeAllAi extends SpellAbilityAi {
|
||||
valid = sa.getParam("ValidCards");
|
||||
}
|
||||
|
||||
if (valid.contains("X") && source.getSVar("X").equals("Count$xPaid")) {
|
||||
if (valid.contains("X") && sa.getSVar("X").equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(xPay));
|
||||
sa.setSVar("PayX", Integer.toString(xPay));
|
||||
valid = TextUtil.fastReplace(valid, "X", Integer.toString(xPay));
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ public class StoreSVarAi extends SpellAbilityAi {
|
||||
// Set PayX here to half the remaining mana to allow for Main 2 and other combat shenanigans.
|
||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai) / 2;
|
||||
if (xPay == 0) { return false; }
|
||||
source.setSVar("PayX", Integer.toString(xPay));
|
||||
sa.setSVar("PayX", Integer.toString(xPay));
|
||||
}
|
||||
|
||||
final String logic = sa.getParam("AILogic");
|
||||
|
||||
@@ -3,18 +3,11 @@ package forge.ai.ability;
|
||||
import forge.ai.*;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CounterEnumType;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.cost.CostPart;
|
||||
import forge.game.cost.CostRemoveCounter;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class TapAi extends TapAiBase {
|
||||
@Override
|
||||
@@ -48,7 +41,6 @@ public class TapAi extends TapAiBase {
|
||||
return false;
|
||||
}
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
final Cost abCost = sa.getPayCosts();
|
||||
if (abCost != null) {
|
||||
@@ -57,32 +49,27 @@ public class TapAi extends TapAiBase {
|
||||
}
|
||||
}
|
||||
|
||||
if (tgt == null) {
|
||||
final List<Card> defined = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
|
||||
|
||||
if (!sa.usesTargeting()) {
|
||||
boolean bFlag = false;
|
||||
for (final Card c : defined) {
|
||||
for (final Card c : AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa)) {
|
||||
bFlag |= c.isUntapped();
|
||||
}
|
||||
|
||||
return bFlag;
|
||||
} else {
|
||||
if ("TapForXCounters".equals(sa.getParam("AILogic"))) {
|
||||
// e.g. Waxmane Baku
|
||||
CounterType ctrType = CounterType.get(CounterEnumType.KI);
|
||||
for (CostPart part : sa.getPayCosts().getCostParts()) {
|
||||
if (part instanceof CostRemoveCounter) {
|
||||
ctrType = ((CostRemoveCounter)part).counter;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// X controls the minimum targets
|
||||
if ("X".equals(sa.getTargetRestrictions().getMinTargets()) && sa.getSVar("X").equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
int xPay = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||
sa.setSVar("PayX", Integer.toString(xPay));
|
||||
|
||||
int numTargetable = Math.min(sa.getHostCard().getCounters(ctrType), ai.getOpponents().getCreaturesInPlay().size());
|
||||
sa.setSVar("ChosenX", String.valueOf(numTargetable));
|
||||
// TODO need to set XManaCostPaid for targets, maybe doesn't need PayX anymore?
|
||||
sa.setXManaCostPaid(xPay);
|
||||
// TODO since change of PayX. the shouldCastLessThanMax logic might be faulty
|
||||
}
|
||||
|
||||
sa.resetTargets();
|
||||
return tapPrefTargeting(ai, source, tgt, sa, false);
|
||||
return tapPrefTargeting(ai, source, sa, false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
import java.util.List;
|
||||
@@ -41,21 +40,18 @@ public abstract class TapAiBase extends SpellAbilityAi {
|
||||
*/
|
||||
private boolean tapTargetList(final Player ai, final SpellAbility sa, final CardCollection tapList, final boolean mandatory) {
|
||||
final Card source = sa.getHostCard();
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
|
||||
for (final Card c : sa.getTargets().getTargetCards()) {
|
||||
tapList.remove(c);
|
||||
}
|
||||
tapList.removeAll(sa.getTargets().getTargetCards());
|
||||
|
||||
if (tapList.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
while (sa.getTargets().size() < tgt.getMaxTargets(source, sa)) {
|
||||
while (sa.canAddMoreTarget()) {
|
||||
Card choice = null;
|
||||
|
||||
if (tapList.size() == 0) {
|
||||
if (sa.getTargets().size() < tgt.getMinTargets(source, sa) || sa.getTargets().size() == 0) {
|
||||
if (tapList.isEmpty()) {
|
||||
if (!sa.isMinTargetChosen() || sa.isZeroTargets()) {
|
||||
if (!mandatory) {
|
||||
sa.resetTargets();
|
||||
}
|
||||
@@ -76,7 +72,7 @@ public abstract class TapAiBase extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
if (choice == null) { // can't find anything left
|
||||
if (sa.getTargets().size() < tgt.getMinTargets(sa.getHostCard(), sa) || sa.getTargets().size() == 0) {
|
||||
if (!sa.isMinTargetChosen() || sa.isZeroTargets()) {
|
||||
if (!mandatory) {
|
||||
sa.resetTargets();
|
||||
}
|
||||
@@ -111,11 +107,10 @@ public abstract class TapAiBase extends SpellAbilityAi {
|
||||
* a boolean.
|
||||
* @return a boolean.
|
||||
*/
|
||||
protected boolean tapPrefTargeting(final Player ai, final Card source, final TargetRestrictions tgt, final SpellAbility sa, final boolean mandatory) {
|
||||
protected boolean tapPrefTargeting(final Player ai, final Card source, final SpellAbility sa, final boolean mandatory) {
|
||||
final Player opp = ai.getWeakestOpponent();
|
||||
final Game game = ai.getGame();
|
||||
CardCollection tapList = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents());
|
||||
tapList = CardLists.getValidCards(tapList, tgt.getValidTgts(), source.getController(), source, sa);
|
||||
tapList = CardLists.getTargetableCards(tapList, sa);
|
||||
tapList = CardLists.filter(tapList, Presets.UNTAPPED);
|
||||
tapList = CardLists.filter(tapList, new Predicate<Card>() {
|
||||
@@ -137,7 +132,6 @@ public abstract class TapAiBase extends SpellAbilityAi {
|
||||
//use broader approach when the cost is a positive thing
|
||||
if (tapList.isEmpty() && ComputerUtil.activateForCost(sa, ai)) {
|
||||
tapList = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents());
|
||||
tapList = CardLists.getValidCards(tapList, tgt.getValidTgts(), source.getController(), source, sa);
|
||||
tapList = CardLists.getTargetableCards(tapList, sa);
|
||||
tapList = CardLists.filter(tapList, new Predicate<Card>() {
|
||||
@Override
|
||||
@@ -166,11 +160,11 @@ public abstract class TapAiBase extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
boolean goodTargets = false;
|
||||
while (sa.getTargets().size() < tgt.getMaxTargets(source, sa)) {
|
||||
while (sa.canAddMoreTarget()) {
|
||||
Card choice = null;
|
||||
|
||||
if (tapList.isEmpty()) {
|
||||
if (sa.getTargets().size() < tgt.getMinTargets(source, sa) || sa.getTargets().size() == 0) {
|
||||
if (!sa.isMinTargetChosen() || sa.isZeroTargets()) {
|
||||
if (!mandatory) {
|
||||
sa.resetTargets();
|
||||
}
|
||||
@@ -231,7 +225,7 @@ public abstract class TapAiBase extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
if (choice == null) { // can't find anything left
|
||||
if (sa.getTargets().size() < tgt.getMinTargets(sa.getHostCard(), sa) || sa.getTargets().size() == 0) {
|
||||
if (!sa.isMinTargetChosen() || sa.isZeroTargets()) {
|
||||
if (!mandatory) {
|
||||
sa.resetTargets();
|
||||
}
|
||||
@@ -265,11 +259,9 @@ public abstract class TapAiBase extends SpellAbilityAi {
|
||||
*/
|
||||
protected boolean tapUnpreferredTargeting(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
||||
final Card source = sa.getHostCard();
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Game game = ai.getGame();
|
||||
|
||||
CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), source.getController(), source, sa);
|
||||
list = CardLists.getTargetableCards(list, sa);
|
||||
CardCollection list = CardLists.getTargetableCards(game.getCardsIn(ZoneType.Battlefield), sa);
|
||||
|
||||
// try to tap anything controlled by the computer
|
||||
CardCollection tapList = CardLists.filterControlledBy(list, ai.getOpponents());
|
||||
@@ -277,7 +269,7 @@ public abstract class TapAiBase extends SpellAbilityAi {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (sa.getTargets().size() >= tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||
if (sa.isMinTargetChosen()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -296,7 +288,7 @@ public abstract class TapAiBase extends SpellAbilityAi {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (sa.getTargets().size() >= tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||
if (sa.isMinTargetChosen()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -308,11 +300,9 @@ public abstract class TapAiBase extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
if (tgt == null) {
|
||||
if (!sa.usesTargeting()) {
|
||||
if (mandatory) {
|
||||
return true;
|
||||
}
|
||||
@@ -322,7 +312,7 @@ public abstract class TapAiBase extends SpellAbilityAi {
|
||||
return true;
|
||||
} else {
|
||||
sa.resetTargets();
|
||||
if (tapPrefTargeting(ai, source, tgt, sa, mandatory)) {
|
||||
if (tapPrefTargeting(ai, source, sa, mandatory)) {
|
||||
return true;
|
||||
} else if (mandatory) {
|
||||
// not enough preferred targets, but mandatory so keep going:
|
||||
@@ -335,17 +325,14 @@ public abstract class TapAiBase extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
boolean randomReturn = true;
|
||||
|
||||
if (tgt == null) {
|
||||
// either self or defined, either way should be fine
|
||||
} else {
|
||||
if (sa.usesTargeting()) {
|
||||
// target section, maybe pull this out?
|
||||
sa.resetTargets();
|
||||
if (!tapPrefTargeting(ai, source, tgt, sa, false)) {
|
||||
if (!tapPrefTargeting(ai, source, sa, false)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,11 +4,8 @@ import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class TapOrUntapAi extends TapAiBase {
|
||||
|
||||
/* (non-Javadoc)
|
||||
@@ -16,19 +13,17 @@ public class TapOrUntapAi extends TapAiBase {
|
||||
*/
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||
|
||||
if (tgt == null) {
|
||||
if (!sa.usesTargeting()) {
|
||||
// assume we are looking to tap human's stuff
|
||||
// TODO - check for things with untap abilities, and don't tap
|
||||
// those.
|
||||
final List<Card> defined = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
|
||||
|
||||
boolean bFlag = false;
|
||||
for (final Card c : defined) {
|
||||
for (final Card c : AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa)) {
|
||||
bFlag |= c.isUntapped();
|
||||
}
|
||||
|
||||
@@ -37,7 +32,7 @@ public class TapOrUntapAi extends TapAiBase {
|
||||
}
|
||||
} else {
|
||||
sa.resetTargets();
|
||||
if (!tapPrefTargeting(ai, source, tgt, sa, false)) {
|
||||
if (!tapPrefTargeting(ai, source, sa, false)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,10 +85,10 @@ public class TokenAi extends SpellAbilityAi {
|
||||
if (source.getSVar("X").equals("Count$Converge")) {
|
||||
x = ComputerUtilMana.getConvergeCount(sa, ai);
|
||||
}
|
||||
if (source.getSVar("X").equals("Count$xPaid")) {
|
||||
if (sa.getSVar("X").equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
x = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(x));
|
||||
sa.setSVar("PayX", Integer.toString(x));
|
||||
}
|
||||
if (x <= 0) {
|
||||
return false; // 0 tokens or 0 toughness token(s)
|
||||
@@ -261,10 +261,10 @@ public class TokenAi extends SpellAbilityAi {
|
||||
|
||||
if ("X".equals(tokenAmount) || "X".equals(tokenPower) || "X".equals(tokenToughness)) {
|
||||
int x = AbilityUtils.calculateAmount(source, tokenAmount, sa);
|
||||
if (source.getSVar("X").equals("Count$xPaid")) {
|
||||
if (sa.getSVar("X").equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
x = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(x));
|
||||
sa.setSVar("PayX", Integer.toString(x));
|
||||
}
|
||||
if (x <= 0) {
|
||||
return false;
|
||||
|
||||
@@ -46,7 +46,7 @@ public class UnattachAllAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
|
||||
source.setSVar("PayX", Integer.toString(xPay));
|
||||
sa.setSVar("PayX", Integer.toString(xPay));
|
||||
}
|
||||
|
||||
if (ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||
|
||||
@@ -51,27 +51,24 @@ public class UntapAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (tgt == null) {
|
||||
if (!sa.usesTargeting()) {
|
||||
final List<Card> pDefined = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
|
||||
return pDefined == null || !pDefined.get(0).isUntapped() || pDefined.get(0).getController() != ai;
|
||||
} else {
|
||||
return untapPrefTargeting(ai, tgt, sa, false);
|
||||
return untapPrefTargeting(ai, sa, false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
|
||||
if (tgt == null) {
|
||||
if (!sa.usesTargeting()) {
|
||||
if (mandatory) {
|
||||
return true;
|
||||
}
|
||||
@@ -80,7 +77,7 @@ public class UntapAi extends SpellAbilityAi {
|
||||
final List<Card> pDefined = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||
return pDefined == null || !pDefined.get(0).isUntapped() || pDefined.get(0).getController() != ai;
|
||||
} else {
|
||||
if (untapPrefTargeting(ai, tgt, sa, mandatory)) {
|
||||
if (untapPrefTargeting(ai, sa, mandatory)) {
|
||||
return true;
|
||||
} else if (mandatory) {
|
||||
// not enough preferred targets, but mandatory so keep going:
|
||||
@@ -100,7 +97,7 @@ public class UntapAi extends SpellAbilityAi {
|
||||
if (tgt == null) {
|
||||
// who cares if its already untapped, it's only a subability?
|
||||
} else {
|
||||
if (!untapPrefTargeting(ai, tgt, sa, false)) {
|
||||
if (!untapPrefTargeting(ai, sa, false)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -113,15 +110,13 @@ public class UntapAi extends SpellAbilityAi {
|
||||
* untapPrefTargeting.
|
||||
* </p>
|
||||
*
|
||||
* @param tgt
|
||||
* a {@link forge.game.spellability.TargetRestrictions} object.
|
||||
* @param sa
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
* @param mandatory
|
||||
* a boolean.
|
||||
* @return a boolean.
|
||||
*/
|
||||
private static boolean untapPrefTargeting(final Player ai, final TargetRestrictions tgt, final SpellAbility sa, final boolean mandatory) {
|
||||
private static boolean untapPrefTargeting(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
Player targetController = ai;
|
||||
@@ -131,7 +126,6 @@ public class UntapAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
CardCollection list = CardLists.getTargetableCards(targetController.getCardsIn(ZoneType.Battlefield), sa);
|
||||
list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, sa);
|
||||
|
||||
if (list.isEmpty()) {
|
||||
return false;
|
||||
@@ -175,7 +169,7 @@ public class UntapAi extends SpellAbilityAi {
|
||||
untapList.removeAll(toExclude);
|
||||
|
||||
sa.resetTargets();
|
||||
while (sa.getTargets().size() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
|
||||
while (sa.canAddMoreTarget()) {
|
||||
Card choice = null;
|
||||
|
||||
if (untapList.isEmpty()) {
|
||||
@@ -183,7 +177,7 @@ public class UntapAi extends SpellAbilityAi {
|
||||
if (sa.getSubAbility() != null && sa.getSubAbility().getApi() == ApiType.Animate && !list.isEmpty()
|
||||
&& ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
||||
choice = ComputerUtilCard.getWorstPermanentAI(list, false, false, false, false);
|
||||
} else if (sa.getTargets().size() < tgt.getMinTargets(sa.getHostCard(), sa) || sa.getTargets().size() == 0) {
|
||||
} else if (!sa.isMinTargetChosen() || sa.isZeroTargets()) {
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
} else {
|
||||
@@ -204,7 +198,7 @@ public class UntapAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
if (choice == null) { // can't find anything left
|
||||
if (sa.getTargets().size() < tgt.getMinTargets(sa.getHostCard(), sa) || sa.getTargets().size() == 0) {
|
||||
if (!sa.isMinTargetChosen() || sa.isZeroTargets()) {
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
} else {
|
||||
|
||||
@@ -50,8 +50,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
private final Map<String, String> alternateName = Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER);
|
||||
private final Map<String, Integer> artIds = new HashMap<>();
|
||||
|
||||
private final List<PaperCard> allCards = new ArrayList<>();
|
||||
private final List<PaperCard> roAllCards = Collections.unmodifiableList(allCards);
|
||||
private final Collection<PaperCard> roAllCards = Collections.unmodifiableCollection(allCardsByName.values());
|
||||
private final CardEdition.Collection editions;
|
||||
|
||||
public enum SetPreference {
|
||||
@@ -247,10 +246,8 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
|
||||
private void reIndex() {
|
||||
uniqueCardsByName.clear();
|
||||
allCards.clear();
|
||||
for (Entry<String, Collection<PaperCard>> kv : allCardsByName.asMap().entrySet()) {
|
||||
uniqueCardsByName.put(kv.getKey(), getFirstWithImage(kv.getValue()));
|
||||
allCards.addAll(kv.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -537,7 +534,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PaperCard> getAllCards() {
|
||||
public Collection<PaperCard> getAllCards() {
|
||||
return roAllCards;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,9 +24,9 @@ public interface ICardDatabase extends Iterable<PaperCard> {
|
||||
int getArtCount(String cardName, String edition);
|
||||
|
||||
Collection<PaperCard> getUniqueCards();
|
||||
List<PaperCard> getAllCards();
|
||||
List<PaperCard> getAllCards(String cardName);
|
||||
List<PaperCard> getAllCards(Predicate<PaperCard> predicate);
|
||||
Collection<PaperCard> getAllCards();
|
||||
Collection<PaperCard> getAllCards(String cardName);
|
||||
Collection<PaperCard> getAllCards(Predicate<PaperCard> predicate);
|
||||
|
||||
List<PaperCard> getAllCardsFromEdition(CardEdition edition);
|
||||
|
||||
|
||||
@@ -25,11 +25,13 @@ import com.google.common.collect.Maps;
|
||||
* Base class for Triggers,ReplacementEffects and StaticAbilities.
|
||||
*
|
||||
*/
|
||||
public abstract class CardTraitBase extends GameObject implements IHasCardView {
|
||||
public abstract class CardTraitBase extends GameObject implements IHasCardView, IHasSVars {
|
||||
|
||||
/** The host card. */
|
||||
protected Card hostCard;
|
||||
|
||||
private Card grantorCard = null; // card which grants the ability (equipment or owner of static ability that gave this one)
|
||||
|
||||
/** The map params. */
|
||||
protected Map<String, String> originalMapParams = Maps.newHashMap(),
|
||||
mapParams = Maps.newHashMap();
|
||||
@@ -40,7 +42,7 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView {
|
||||
/** The suppressed. */
|
||||
protected boolean suppressed = false;
|
||||
|
||||
protected Map<String, String> sVars = Maps.newHashMap();
|
||||
protected Map<String, String> sVars = Maps.newTreeMap();
|
||||
|
||||
protected Map<String, String> intrinsicChangedTextColors = Maps.newHashMap();
|
||||
protected Map<String, String> intrinsicChangedTextTypes = Maps.newHashMap();
|
||||
@@ -462,28 +464,29 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView {
|
||||
return CardView.get(hostCard);
|
||||
}
|
||||
|
||||
public String getSvarWithFallback(final String name) {
|
||||
String var = sVars.get(name);
|
||||
if (var == null) {
|
||||
var = hostCard.getSVar(name);
|
||||
protected IHasSVars getSVarFallback() {
|
||||
if (getOriginalHost() != null) {
|
||||
return getOriginalHost();
|
||||
}
|
||||
return var;
|
||||
return getHostCard();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSVar(final String name) {
|
||||
String var = sVars.get(name);
|
||||
if (var == null) {
|
||||
var = "";
|
||||
if (sVars.containsKey(name)) {
|
||||
return sVars.get(name);
|
||||
} else {
|
||||
return getSVarFallback().getSVar(name);
|
||||
}
|
||||
return var;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSVar(final String name) {
|
||||
return sVars.containsKey(name);
|
||||
return sVars.containsKey(name) || getSVarFallback().hasSVar(name);
|
||||
}
|
||||
|
||||
public Integer getSVarInt(final String name) {
|
||||
String var = sVars.get(name);
|
||||
String var = this.getSVar(name);
|
||||
if (var != null) {
|
||||
try {
|
||||
return Integer.parseInt(var);
|
||||
@@ -493,12 +496,32 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void setSVar(final String name, final String value) {
|
||||
sVars.put(name, value);
|
||||
}
|
||||
|
||||
public Set<String> getSVars() {
|
||||
return sVars.keySet();
|
||||
@Override
|
||||
public Map<String, String> getSVars() {
|
||||
return sVars;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSVars(Map<String, String> newSVars) {
|
||||
sVars = Maps.newTreeMap();
|
||||
sVars.putAll(newSVars);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeSVar(String var) {
|
||||
sVars.remove(var);
|
||||
}
|
||||
|
||||
public Card getOriginalHost() {
|
||||
return grantorCard;
|
||||
}
|
||||
public void setOriginalHost(final Card c) {
|
||||
grantorCard = c;
|
||||
}
|
||||
|
||||
public Map<String, String> getChangedTextColors() {
|
||||
@@ -553,7 +576,7 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView {
|
||||
protected void copyHelper(CardTraitBase copy, Card host) {
|
||||
copy.originalMapParams = Maps.newHashMap(originalMapParams);
|
||||
copy.mapParams = Maps.newHashMap(originalMapParams);
|
||||
copy.sVars = Maps.newHashMap(sVars);
|
||||
copy.setSVars(sVars);
|
||||
// dont use setHostCard to not trigger the not copied parts yet
|
||||
copy.hostCard = host;
|
||||
}
|
||||
|
||||
@@ -126,7 +126,7 @@ public class ForgeScript {
|
||||
} else if (property.equals("nonManaAbility")) {
|
||||
return !sa.isManaAbility();
|
||||
} else if (property.equals("withoutXCost")) {
|
||||
return !sa.isXCost();
|
||||
return !sa.costHasManaX();
|
||||
} else if (property.equals("Buyback")) {
|
||||
return sa.isBuyBackAbility();
|
||||
} else if (property.equals("Cycling")) {
|
||||
|
||||
@@ -439,10 +439,10 @@ public class Game {
|
||||
}
|
||||
|
||||
public synchronized void setGameOver(GameEndReason reason) {
|
||||
age = GameStage.GameOver;
|
||||
for (Player p : allPlayers) {
|
||||
p.clearController();
|
||||
}
|
||||
age = GameStage.GameOver;
|
||||
|
||||
for (Player p : getPlayers()) {
|
||||
p.onGameOver();
|
||||
|
||||
@@ -227,12 +227,6 @@ public class GameAction {
|
||||
// ensure that any leftover keyword/type changes are cleared in the state view
|
||||
copied.updateStateForView();
|
||||
|
||||
// Clean up temporary variables such as Sunburst value or announced PayX value
|
||||
if (!(zoneTo.is(ZoneType.Stack) || zoneTo.is(ZoneType.Battlefield))) {
|
||||
copied.clearTemporaryVars();
|
||||
}
|
||||
|
||||
|
||||
if (!suppress) {
|
||||
if (zoneFrom == null) {
|
||||
copied.getOwner().addInboundToken(copied);
|
||||
|
||||
@@ -31,15 +31,43 @@ public class GameEntityCounterTable extends ForwardingTable<GameEntity, CounterT
|
||||
*/
|
||||
@Override
|
||||
public Integer put(GameEntity rowKey, CounterType columnKey, Integer value) {
|
||||
Integer old = contains(rowKey, columnKey) ? get(rowKey, columnKey) : 0;
|
||||
return super.put(rowKey, columnKey, old + value);
|
||||
return super.put(rowKey, columnKey, get(rowKey, columnKey) + value);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Integer get(Object rowKey, Object columnKey) {
|
||||
if (!contains(rowKey, columnKey)) {
|
||||
return 0; // helper to not return null value
|
||||
}
|
||||
return super.get(rowKey, columnKey);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* returns the counters that can still be removed from game entity
|
||||
*/
|
||||
public Map<CounterType, Integer> filterToRemove(GameEntity ge) {
|
||||
Map<CounterType, Integer> result = Maps.newHashMap();
|
||||
if (!containsRow(ge)) {
|
||||
result.putAll(ge.getCounters());
|
||||
return result;
|
||||
}
|
||||
Map<CounterType, Integer> alreadyRemoved = row(ge);
|
||||
for (Map.Entry<CounterType, Integer> e : ge.getCounters().entrySet()) {
|
||||
Integer rest = e.getValue() - (alreadyRemoved.containsKey(e.getKey()) ? alreadyRemoved.get(e.getKey()) : 0);
|
||||
if (rest > 0) {
|
||||
result.put(e.getKey(), rest);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public Map<GameEntity, Integer> filterTable(CounterType type, String valid, Card host, SpellAbility sa) {
|
||||
Map<GameEntity, Integer> result = Maps.newHashMap();
|
||||
|
||||
for (Map.Entry<GameEntity, Integer> e : column(type).entrySet()) {
|
||||
if (e.getKey().isValid(valid, host.getController(), host, sa)) {
|
||||
if (e.getValue() > 0 && e.getKey().isValid(valid, host.getController(), host, sa)) {
|
||||
result.put(e.getKey(), e.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
20
forge-game/src/main/java/forge/game/IHasSVars.java
Normal file
20
forge-game/src/main/java/forge/game/IHasSVars.java
Normal file
@@ -0,0 +1,20 @@
|
||||
package forge.game;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public interface IHasSVars {
|
||||
|
||||
public String getSVar(final String name);
|
||||
|
||||
public boolean hasSVar(final String name);
|
||||
//public Integer getSVarInt(final String name);
|
||||
|
||||
public void setSVar(final String name, final String value);
|
||||
public void setSVars(final Map<String, String> newSVars);
|
||||
|
||||
//public Set<String> getSVars();
|
||||
|
||||
public Map<String, String> getSVars();
|
||||
|
||||
public void removeSVar(final String var);
|
||||
}
|
||||
@@ -19,13 +19,23 @@ public abstract class TriggerReplacementBase extends CardTraitBase implements II
|
||||
|
||||
@Override
|
||||
public void setHostCard(final Card c) {
|
||||
this.hostCard = c;
|
||||
super.setHostCard(c);
|
||||
|
||||
if (overridingAbility != null) {
|
||||
overridingAbility.setHostCard(c);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOriginalHost(Card c) {
|
||||
super.setOriginalHost(c);
|
||||
if (overridingAbility != null) {
|
||||
overridingAbility.setOriginalHost(c);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public Set<ZoneType> getActiveZone() {
|
||||
return validHostZones;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import com.google.common.base.Function;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import forge.card.CardStateName;
|
||||
import forge.game.IHasSVars;
|
||||
import forge.game.ability.effects.CharmEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardState;
|
||||
@@ -102,7 +103,10 @@ public final class AbilityFactory {
|
||||
}
|
||||
|
||||
public static final SpellAbility getAbility(final String abString, final Card card) {
|
||||
return getAbility(abString, card.getCurrentState(), null);
|
||||
return getAbility(abString, card, card.getCurrentState());
|
||||
}
|
||||
public static final SpellAbility getAbility(final String abString, final Card card, final IHasSVars sVarHolder) {
|
||||
return getAbility(abString, card.getCurrentState(), sVarHolder);
|
||||
}
|
||||
/**
|
||||
* <p>
|
||||
@@ -116,10 +120,10 @@ public final class AbilityFactory {
|
||||
* @return a {@link forge.game.spellability.SpellAbility} object.
|
||||
*/
|
||||
public static final SpellAbility getAbility(final String abString, final CardState state) {
|
||||
return getAbility(abString, state, null);
|
||||
return getAbility(abString, state, state);
|
||||
}
|
||||
|
||||
private static final SpellAbility getAbility(final String abString, final CardState state, final SpellAbility parent) {
|
||||
private static final SpellAbility getAbility(final String abString, final CardState state, final IHasSVars sVarHolder) {
|
||||
Map<String, String> mapParams;
|
||||
try {
|
||||
mapParams = AbilityFactory.getMapParams(abString);
|
||||
@@ -134,7 +138,7 @@ public final class AbilityFactory {
|
||||
throw new RuntimeException("AbilityFactory : getAbility -- no API in " + source + ": " + abString);
|
||||
}
|
||||
try {
|
||||
return getAbility(mapParams, type, state, parent);
|
||||
return getAbility(mapParams, type, state, sVarHolder);
|
||||
} catch (Error | Exception ex) {
|
||||
String msg = "AbilityFactory:getAbility: crash when trying to create ability ";
|
||||
Sentry.getContext().recordBreadcrumb(
|
||||
@@ -146,28 +150,24 @@ public final class AbilityFactory {
|
||||
}
|
||||
|
||||
public static final SpellAbility getAbility(final Card hostCard, final String svar) {
|
||||
return getAbility(hostCard.getCurrentState(), svar, null);
|
||||
return getAbility(hostCard, svar, hostCard.getCurrentState());
|
||||
}
|
||||
|
||||
public static final SpellAbility getAbility(final CardState state, final String svar) {
|
||||
return getAbility(state, svar, null);
|
||||
public static final SpellAbility getAbility(final Card hostCard, final String svar, final IHasSVars sVarHolder) {
|
||||
return getAbility(hostCard.getCurrentState(), svar, sVarHolder);
|
||||
}
|
||||
|
||||
private static final SpellAbility getAbility(final CardState state, final String svar, final SpellAbility parent) {
|
||||
if (!state.hasSVar(svar)) {
|
||||
public static final SpellAbility getAbility(final CardState state, final String svar, final IHasSVars sVarHolder) {
|
||||
if (!sVarHolder.hasSVar(svar)) {
|
||||
String source = state.getCard().getName();
|
||||
throw new RuntimeException("AbilityFactory : getAbility -- " + source + " has no SVar: " + svar);
|
||||
} else {
|
||||
return getAbility(state.getSVar(svar), state, parent);
|
||||
return getAbility(sVarHolder.getSVar(svar), state, sVarHolder);
|
||||
}
|
||||
}
|
||||
|
||||
public static final SpellAbility getAbility(final Map<String, String> mapParams, AbilityRecordType type, final Card card, final SpellAbility parent) {
|
||||
return getAbility(mapParams, type, card.getCurrentState(), parent);
|
||||
}
|
||||
|
||||
public static final SpellAbility getAbility(final Map<String, String> mapParams, AbilityRecordType type, final CardState state, final SpellAbility parent) {
|
||||
return getAbility(type, type.getApiTypeOf(mapParams), mapParams, parseAbilityCost(state, mapParams, type), state, parent);
|
||||
public static final SpellAbility getAbility(final Map<String, String> mapParams, AbilityRecordType type, final CardState state, final IHasSVars sVarHolder) {
|
||||
return getAbility(type, type.getApiTypeOf(mapParams), mapParams, parseAbilityCost(state, mapParams, type), state, sVarHolder);
|
||||
}
|
||||
|
||||
|
||||
@@ -184,12 +184,7 @@ public final class AbilityFactory {
|
||||
}
|
||||
|
||||
public static final SpellAbility getAbility(AbilityRecordType type, ApiType api, Map<String, String> mapParams,
|
||||
Cost abCost,final Card card, final SpellAbility parent) {
|
||||
return getAbility(type, api, mapParams, abCost, card.getCurrentState(), parent);
|
||||
}
|
||||
|
||||
public static final SpellAbility getAbility(AbilityRecordType type, ApiType api, Map<String, String> mapParams,
|
||||
Cost abCost,final CardState state, final SpellAbility parent) {
|
||||
Cost abCost,final CardState state, final IHasSVars sVarHolder) {
|
||||
final Card hostCard = state.getCard();
|
||||
TargetRestrictions abTgt = mapParams.containsKey("ValidTgts") ? readTarget(mapParams) : null;
|
||||
|
||||
@@ -218,41 +213,36 @@ public final class AbilityFactory {
|
||||
if (spellAbility == null) {
|
||||
final StringBuilder msg = new StringBuilder();
|
||||
msg.append("AbilityFactory : SpellAbility was not created for ");
|
||||
msg.append(state.getName());
|
||||
msg.append(state.toString());
|
||||
msg.append(". Looking for API: ").append(api);
|
||||
throw new RuntimeException(msg.toString());
|
||||
}
|
||||
|
||||
// need to set Parent Early
|
||||
if (parent != null && spellAbility instanceof AbilitySub) {
|
||||
((AbilitySub)spellAbility).setParent(parent);
|
||||
}
|
||||
|
||||
// *********************************************
|
||||
// set universal properties of the SpellAbility
|
||||
|
||||
if (mapParams.containsKey("References")) {
|
||||
for (String svar : mapParams.get("References").split(",")) {
|
||||
spellAbility.setSVar(svar, state.getSVar(svar));
|
||||
spellAbility.setSVar(svar, sVarHolder.getSVar(svar));
|
||||
}
|
||||
}
|
||||
|
||||
if (api == ApiType.DelayedTrigger && mapParams.containsKey("Execute")) {
|
||||
spellAbility.setSVar(mapParams.get("Execute"), state.getSVar(mapParams.get("Execute")));
|
||||
spellAbility.setSVar(mapParams.get("Execute"), sVarHolder.getSVar(mapParams.get("Execute")));
|
||||
}
|
||||
|
||||
if (mapParams.containsKey("PreventionSubAbility")) {
|
||||
spellAbility.setSVar(mapParams.get("PreventionSubAbility"), state.getSVar(mapParams.get("PreventionSubAbility")));
|
||||
spellAbility.setSVar(mapParams.get("PreventionSubAbility"), sVarHolder.getSVar(mapParams.get("PreventionSubAbility")));
|
||||
}
|
||||
|
||||
if (mapParams.containsKey("SubAbility")) {
|
||||
final String name = mapParams.get("SubAbility");
|
||||
spellAbility.setSubAbility(getSubAbility(state, name, spellAbility));
|
||||
spellAbility.setSubAbility(getSubAbility(state, name, sVarHolder));
|
||||
}
|
||||
|
||||
for (final String key : additionalAbilityKeys) {
|
||||
if (mapParams.containsKey(key) && spellAbility.getAdditionalAbility(key) == null) {
|
||||
spellAbility.setAdditionalAbility(key, getSubAbility(state, mapParams.get(key), spellAbility));
|
||||
spellAbility.setAdditionalAbility(key, getSubAbility(state, mapParams.get(key), sVarHolder));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,11 +250,10 @@ public final class AbilityFactory {
|
||||
final String key = "Choices";
|
||||
if (mapParams.containsKey(key)) {
|
||||
List<String> names = Lists.newArrayList(mapParams.get(key).split(","));
|
||||
final SpellAbility sap = spellAbility;
|
||||
spellAbility.setAdditionalAbilityList(key, Lists.transform(names, new Function<String, AbilitySub>() {
|
||||
@Override
|
||||
public AbilitySub apply(String input) {
|
||||
return getSubAbility(state, input, sap);
|
||||
return getSubAbility(state, input, sVarHolder);
|
||||
}
|
||||
}));
|
||||
}
|
||||
@@ -277,8 +266,8 @@ public final class AbilityFactory {
|
||||
} else if (mapParams.containsKey("SpellDescription")) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
|
||||
if (type != AbilityRecordType.SubAbility) { // SubAbilities don't have Costs or Cost
|
||||
// descriptors
|
||||
if (type != AbilityRecordType.SubAbility) {
|
||||
// SubAbilities don't have Costs or Cost descriptors
|
||||
sb.append(spellAbility.getCostDescription());
|
||||
}
|
||||
|
||||
@@ -418,10 +407,10 @@ public final class AbilityFactory {
|
||||
*
|
||||
* @return a {@link forge.game.spellability.AbilitySub} object.
|
||||
*/
|
||||
private static final AbilitySub getSubAbility(CardState state, String sSub, final SpellAbility parent) {
|
||||
private static final AbilitySub getSubAbility(CardState state, String sSub, final IHasSVars sVarHolder) {
|
||||
|
||||
if (state.hasSVar(sSub)) {
|
||||
return (AbilitySub) AbilityFactory.getAbility(state, sSub, parent);
|
||||
if (sVarHolder.hasSVar(sSub)) {
|
||||
return (AbilitySub) AbilityFactory.getAbility(state, sSub, sVarHolder);
|
||||
}
|
||||
System.out.println("SubAbility '"+ sSub +"' not found for: " + state.getName());
|
||||
|
||||
@@ -463,7 +452,7 @@ public final class AbilityFactory {
|
||||
SpellAbility rightAbility = rightState.getFirstAbility();
|
||||
Map<String, String> rightMap = Maps.newHashMap(rightAbility.getMapParams());
|
||||
|
||||
AbilityRecordType rightType = AbilityRecordType.getRecordType(leftMap);
|
||||
AbilityRecordType rightType = AbilityRecordType.getRecordType(rightMap);
|
||||
ApiType rightApi = leftType.getApiTypeOf(rightMap);
|
||||
rightMap.put("StackDescription", rightMap.get("SpellDescription"));
|
||||
rightMap.put("SpellDescription", "");
|
||||
@@ -471,8 +460,8 @@ public final class AbilityFactory {
|
||||
Cost totalCost = parseAbilityCost(leftState, leftMap, leftType);
|
||||
totalCost.add(parseAbilityCost(rightState, rightMap, rightType));
|
||||
|
||||
final SpellAbility left = getAbility(leftType, leftApi, leftMap, totalCost, leftState, null);
|
||||
final AbilitySub right = (AbilitySub) getAbility(AbilityRecordType.SubAbility, rightApi, rightMap, null, rightState, left);
|
||||
final SpellAbility left = getAbility(leftType, leftApi, leftMap, totalCost, leftState, leftState);
|
||||
final AbilitySub right = (AbilitySub) getAbility(AbilityRecordType.SubAbility, rightApi, rightMap, null, rightState, rightState);
|
||||
left.appendSubAbility(right);
|
||||
return left;
|
||||
}
|
||||
|
||||
@@ -1901,16 +1901,6 @@ public class AbilityUtils {
|
||||
|
||||
public static final String getSVar(final CardTraitBase ability, final String sVarName) {
|
||||
String val = ability.getSVar(sVarName);
|
||||
if (StringUtils.isEmpty(val)) {
|
||||
Card host = null;
|
||||
if (ability instanceof SpellAbility) {
|
||||
host = ((SpellAbility) ability).getOriginalHost();
|
||||
}
|
||||
if (host == null) {
|
||||
host = ability.getHostCard();
|
||||
}
|
||||
val = host.getSVar(sVarName);
|
||||
}
|
||||
if (!ability.isIntrinsic() || StringUtils.isEmpty(val)) {
|
||||
return val;
|
||||
}
|
||||
@@ -2016,9 +2006,8 @@ public class AbilityUtils {
|
||||
|
||||
SpellAbility firstSpell = c.getFirstSpellAbility();
|
||||
Map<String, String> params = Maps.newHashMap(firstSpell.getMapParams());
|
||||
AbilityRecordType rc = AbilityRecordType.getRecordType(params);
|
||||
ApiType api = rc.getApiTypeOf(params);
|
||||
AbilitySub subAbility = (AbilitySub) AbilityFactory.getAbility(AbilityRecordType.SubAbility, api, params, null, c, null);
|
||||
ApiType api = AbilityRecordType.getRecordType(params).getApiTypeOf(params);
|
||||
AbilitySub subAbility = (AbilitySub) AbilityFactory.getAbility(AbilityRecordType.SubAbility, api, params, null, c.getCurrentState(), null);
|
||||
|
||||
subAbility.setActivatingPlayer(sa.getActivatingPlayer());
|
||||
subAbility.setHostCard(sa.getHostCard());
|
||||
|
||||
@@ -151,14 +151,17 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
|
||||
// give abilities
|
||||
final List<SpellAbility> addedAbilities = Lists.newArrayList();
|
||||
for (final String s : abilities) {
|
||||
addedAbilities.add(AbilityFactory.getAbility(AbilityUtils.getSVar(sa, s), c));
|
||||
SpellAbility sSA = AbilityFactory.getAbility(c, s, sa);
|
||||
sSA.setOriginalHost(source);
|
||||
addedAbilities.add(sSA);
|
||||
}
|
||||
|
||||
// Grant triggers
|
||||
final List<Trigger> addedTriggers = Lists.newArrayList();
|
||||
for (final String s : triggers) {
|
||||
final Trigger parsedTrigger = TriggerHandler.parseTrigger(AbilityUtils.getSVar(sa, s), c, false);
|
||||
parsedTrigger.setOverridingAbility(AbilityFactory.getAbility(AbilityUtils.getSVar(sa, parsedTrigger.getParam("Execute")), c));
|
||||
parsedTrigger.setOverridingAbility(AbilityFactory.getAbility(c, parsedTrigger.getParam("Execute"), sa));
|
||||
parsedTrigger.setOriginalHost(source);
|
||||
addedTriggers.add(parsedTrigger);
|
||||
}
|
||||
|
||||
|
||||
@@ -226,8 +226,6 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect {
|
||||
}
|
||||
}
|
||||
|
||||
game.getTriggerHandler().resetActiveTriggers(false);
|
||||
|
||||
triggerList.triggerChangesZoneAll(game);
|
||||
|
||||
// if Shuffle parameter exists, and any amount of cards were owned by
|
||||
|
||||
@@ -947,10 +947,12 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
if (!decider.getController().confirmAction(tgtSA, null, Localizer.getInstance().getMessage("lblDoYouWantPlayCard", CardTranslation.getTranslatedName(tgtCard.getName())))) {
|
||||
continue;
|
||||
}
|
||||
tgtSA.setSVar("IsCastFromPlayEffect", "True");
|
||||
// if played, that card cannot be found
|
||||
if (decider.getController().playSaFromPlayEffect(tgtSA)) {
|
||||
fetchList.remove(tgtCard);
|
||||
}
|
||||
//some kind of reset here?
|
||||
}
|
||||
}
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
|
||||
|
||||
@@ -135,5 +135,10 @@ public class ChooseCardEffect extends SpellAbilityEffect {
|
||||
host.removeRemembered(rem);
|
||||
}
|
||||
}
|
||||
if (sa.hasParam("ImprintChosen")) {
|
||||
for (final Card imp : chosen) {
|
||||
host.addImprintedCard(imp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,9 +155,7 @@ public class EffectEffect extends SpellAbilityEffect {
|
||||
// Grant abilities
|
||||
if (effectAbilities != null) {
|
||||
for (final String s : effectAbilities) {
|
||||
final String actualAbility = AbilityUtils.getSVar(sa, s);
|
||||
|
||||
final SpellAbility grantedAbility = AbilityFactory.getAbility(actualAbility, eff);
|
||||
final SpellAbility grantedAbility = AbilityFactory.getAbility(eff, s, sa);
|
||||
eff.addSpellAbility(grantedAbility);
|
||||
grantedAbility.setIntrinsic(true);
|
||||
}
|
||||
@@ -166,11 +164,8 @@ public class EffectEffect extends SpellAbilityEffect {
|
||||
// Grant triggers
|
||||
if (effectTriggers != null) {
|
||||
for (final String s : effectTriggers) {
|
||||
final String actualTrigger = AbilityUtils.getSVar(sa, s);
|
||||
|
||||
final Trigger parsedTrigger = TriggerHandler.parseTrigger(actualTrigger, eff, true);
|
||||
final String ability = AbilityUtils.getSVar(sa, parsedTrigger.getParam("Execute"));
|
||||
parsedTrigger.setOverridingAbility(AbilityFactory.getAbility(ability, eff));
|
||||
final Trigger parsedTrigger = TriggerHandler.parseTrigger(AbilityUtils.getSVar(sa, s), eff, true);
|
||||
parsedTrigger.setOverridingAbility(AbilityFactory.getAbility(eff, parsedTrigger.getParam("Execute"), sa));
|
||||
parsedTrigger.setActiveZone(EnumSet.of(ZoneType.Command));
|
||||
parsedTrigger.setIntrinsic(true);
|
||||
eff.addTrigger(parsedTrigger);
|
||||
|
||||
@@ -224,12 +224,7 @@ public class FlipCoinEffect extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
public static int getFilpMultiplier(final Player flipper) {
|
||||
int i = 0;
|
||||
for (String kw : flipper.getKeywords()) {
|
||||
if (kw.startsWith("If you would flip a coin")) {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
return 1 << i;
|
||||
String str = "If you would flip a coin, instead flip two coins and ignore one.";
|
||||
return 1 + flipper.getKeywords().getAmount(str);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,6 @@ public class PermanentNoncreatureEffect extends PermanentEffect {
|
||||
public String getStackDescription(final SpellAbility sa) {
|
||||
final Card sourceCard = sa.getHostCard();
|
||||
//CardView toString return translated name,don't need call CardTranslation.getTranslatedName in this.
|
||||
return sourceCard.toString();
|
||||
return sourceCard.getName();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
@@ -76,15 +77,12 @@ public class PlayEffect extends SpellAbilityEffect {
|
||||
CardCollection showCards = new CardCollection();
|
||||
|
||||
if (sa.hasParam("Valid")) {
|
||||
ZoneType zone = ZoneType.Hand;
|
||||
if (sa.hasParam("ValidZone")) {
|
||||
zone = ZoneType.smartValueOf(sa.getParam("ValidZone"));
|
||||
}
|
||||
List<ZoneType> zones = sa.hasParam("ValidZone") ? ZoneType.listValueOf(sa.getParam("ValidZone")) : ImmutableList.of(ZoneType.Hand);
|
||||
tgtCards = new CardCollection(
|
||||
AbilityUtils.filterListByType(game.getCardsIn(zone), sa.getParam("Valid"), sa)
|
||||
AbilityUtils.filterListByType(game.getCardsIn(zones), sa.getParam("Valid"), sa)
|
||||
);
|
||||
if ( sa.hasParam("ShowCards") ) {
|
||||
showCards = new CardCollection(AbilityUtils.filterListByType(game.getCardsIn(zone), sa.getParam("ShowCards"), sa));
|
||||
showCards = new CardCollection(AbilityUtils.filterListByType(game.getCardsIn(zones), sa.getParam("ShowCards"), sa));
|
||||
}
|
||||
}
|
||||
else if (sa.hasParam("AnySupportedCard")) {
|
||||
|
||||
@@ -80,7 +80,7 @@ import io.sentry.event.BreadcrumbBuilder;
|
||||
* @author Forge
|
||||
* @version $Id$
|
||||
*/
|
||||
public class Card extends GameEntity implements Comparable<Card> {
|
||||
public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
private final Game game;
|
||||
private final IPaperCard paperCard;
|
||||
|
||||
@@ -124,7 +124,8 @@ public class Card extends GameEntity implements Comparable<Card> {
|
||||
private final PlayerCollection mayLookFaceDownExile = new PlayerCollection();
|
||||
private final PlayerCollection mayLookTemp = new PlayerCollection();
|
||||
|
||||
private final Multimap<Long, Keyword> cantHaveKeywords = MultimapBuilder.hashKeys().enumSetValues(Keyword.class).build();
|
||||
// don't use Enum Set Values or it causes a slow down
|
||||
private final Multimap<Long, Keyword> cantHaveKeywords = MultimapBuilder.hashKeys().hashSetValues().build();
|
||||
|
||||
private final Map<CounterType, Long> counterTypeTimestamps = Maps.newHashMap();
|
||||
|
||||
@@ -1437,6 +1438,7 @@ public class Card extends GameEntity implements Comparable<Card> {
|
||||
}
|
||||
public final void setChosenNumber(final int i) {
|
||||
chosenNumber = i;
|
||||
view.updateChosenNumber(this);
|
||||
}
|
||||
|
||||
public final Card getExiledWith() {
|
||||
@@ -6034,6 +6036,17 @@ public class Card extends GameEntity implements Comparable<Card> {
|
||||
abilities.addAll(GameActionUtil.getAlternativeCosts(sa, player));
|
||||
}
|
||||
}
|
||||
// Add Modal Spells
|
||||
if (isModal() && hasState(CardStateName.Modal)) {
|
||||
for (SpellAbility sa : getState(CardStateName.Modal).getSpellAbilities()) {
|
||||
//add alternative costs as additional spell abilities
|
||||
// only add Spells there
|
||||
if (sa.isSpell()) {
|
||||
abilities.add(sa);
|
||||
abilities.addAll(GameActionUtil.getAlternativeCosts(sa, player));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final Collection<SpellAbility> toRemove = Lists.newArrayListWithCapacity(abilities.size());
|
||||
for (final SpellAbility sa : abilities) {
|
||||
@@ -6049,7 +6062,11 @@ public class Card extends GameEntity implements Comparable<Card> {
|
||||
}
|
||||
abilities.removeAll(toRemove);
|
||||
|
||||
if (getState(CardStateName.Original).getType().isLand() && !getLastKnownZone().is(ZoneType.Battlefield)) {
|
||||
// Land Abilities below, move them to CardFactory after MayPlayRefactor
|
||||
if (getLastKnownZone().is(ZoneType.Battlefield)) {
|
||||
return abilities;
|
||||
}
|
||||
if (getState(CardStateName.Original).getType().isLand()) {
|
||||
LandAbility la = new LandAbility(this, player, null);
|
||||
if (la.canPlay()) {
|
||||
abilities.add(la);
|
||||
@@ -6091,7 +6108,7 @@ public class Card extends GameEntity implements Comparable<Card> {
|
||||
}
|
||||
|
||||
if (isModal() && hasState(CardStateName.Modal)) {
|
||||
if (getState(CardStateName.Modal).getType().isLand() && !getLastKnownZone().is(ZoneType.Battlefield)) {
|
||||
if (getState(CardStateName.Modal).getType().isLand()) {
|
||||
LandAbility la = new LandAbility(this, player, null);
|
||||
la.setCardState(CardStateName.Modal);
|
||||
|
||||
@@ -6409,17 +6426,6 @@ public class Card extends GameEntity implements Comparable<Card> {
|
||||
return changed;
|
||||
}
|
||||
|
||||
public final void clearTemporaryVars() {
|
||||
// Add cleanup for all variables that are set temporarily but that need
|
||||
// to be restored to their original value if a card changes zones
|
||||
|
||||
removeSVar("PayX"); // Temporary AI X announcement variable
|
||||
removeSVar("IsCastFromPlayEffect"); // Temporary SVar indicating that the spell is cast indirectly via AF Play
|
||||
setXManaCostPaidByColor(null);
|
||||
setKickerMagnitude(0);
|
||||
setPseudoMultiKickerMagnitude(0);
|
||||
}
|
||||
|
||||
public final int getFinalChapterNr() {
|
||||
int n = 0;
|
||||
for (final Trigger t : getTriggers()) {
|
||||
|
||||
@@ -392,11 +392,18 @@ public class CardFactory {
|
||||
}
|
||||
|
||||
// SpellPermanent only for Original State
|
||||
if (c.getCurrentStateName() == CardStateName.Original) {
|
||||
if (c.getCurrentStateName() == CardStateName.Original || c.getCurrentStateName() == CardStateName.Modal) {
|
||||
// this is the "default" spell for permanents like creatures and artifacts
|
||||
if (c.isPermanent() && !c.isAura() && !c.isLand()) {
|
||||
c.addSpellAbility(new SpellPermanent(c));
|
||||
SpellAbility sa = new SpellPermanent(c);
|
||||
|
||||
// Currently only for Modal, might react different when state is always set
|
||||
if (c.getCurrentStateName() == CardStateName.Modal) {
|
||||
sa.setCardState(c.getCurrentStateName());
|
||||
}
|
||||
c.addSpellAbility(sa);
|
||||
}
|
||||
// TODO add LandAbility there when refactor MayPlay
|
||||
}
|
||||
|
||||
CardFactoryUtil.addAbilityFactoryAbilities(c, face.getAbilities());
|
||||
@@ -540,9 +547,6 @@ public class CardFactory {
|
||||
to.setActivatingPlayer(p, lki);
|
||||
}
|
||||
|
||||
for (String sVar : from.getSVars()) {
|
||||
to.setSVar(sVar, from.getSVar(sVar));
|
||||
}
|
||||
//to.changeText();
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +51,6 @@ import forge.game.spellability.*;
|
||||
import forge.game.staticability.StaticAbility;
|
||||
import forge.game.trigger.Trigger;
|
||||
import forge.game.trigger.TriggerHandler;
|
||||
import forge.game.zone.Zone;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Aggregates;
|
||||
import forge.util.Expressions;
|
||||
@@ -227,50 +226,6 @@ public class CardFactoryUtil {
|
||||
return AbilityFactory.getAbility(ab, sourceCard);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* isTargetStillValid.
|
||||
* </p>
|
||||
*
|
||||
* @param ability
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
* @param target
|
||||
* a {@link forge.game.card.Card} object.
|
||||
* @return a boolean.
|
||||
*/
|
||||
public static boolean isTargetStillValid(final SpellAbility ability, final Card target) {
|
||||
Zone zone = target.getGame().getZoneOf(target);
|
||||
if (zone == null) {
|
||||
return false; // for tokens that disappeared
|
||||
}
|
||||
|
||||
final Card source = ability.getHostCard();
|
||||
final TargetRestrictions tgt = ability.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
// Reconfirm the Validity of a TgtValid, or if the Creature is still
|
||||
// a Creature
|
||||
if (tgt.doesTarget()
|
||||
&& !target.isValid(tgt.getValidTgts(), ability.getActivatingPlayer(), ability.getHostCard(), ability)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the target is in the zone it needs to be in to be targeted
|
||||
if (!tgt.getZone().contains(zone.getZoneType())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// If an Aura's target is removed before it resolves, the Aura
|
||||
// fizzles
|
||||
if (source.isAura() && !target.isInZone(ZoneType.Battlefield)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure it's still targetable as well
|
||||
return ability.canTarget(target);
|
||||
}
|
||||
|
||||
// does "target" have protection from "card"?
|
||||
/**
|
||||
* <p>
|
||||
@@ -1100,7 +1055,7 @@ public class CardFactoryUtil {
|
||||
if (sq[0].contains("ColorsCtrl")) {
|
||||
final String restriction = l[0].substring(11);
|
||||
final String[] rest = restriction.split(",");
|
||||
final CardCollection list = CardLists.getValidCards(cc.getGame().getCardsInGame(), rest, cc, c, null);
|
||||
final CardCollection list = CardLists.getValidCards(cc.getCardsIn(ZoneType.Battlefield), rest, cc, c, null);
|
||||
byte n = 0;
|
||||
for (final Card card : list) {
|
||||
n |= card.determineColor().getColor();
|
||||
@@ -1176,14 +1131,9 @@ public class CardFactoryUtil {
|
||||
return doXMath(Integer.parseInt(sq[cc.hasThreshold() ? 1 : 2]), m, c);
|
||||
}
|
||||
if (sq[0].contains("Averna")) {
|
||||
int kwcount = 0;
|
||||
for (String kw : cc.getKeywords()) {
|
||||
if (kw.equals("As you cascade, you may put a land card from among the exiled cards onto the " +
|
||||
"battlefield tapped.")) {
|
||||
kwcount++;
|
||||
}
|
||||
}
|
||||
return kwcount;
|
||||
String str = "As you cascade, you may put a land card from among the exiled cards onto the " +
|
||||
"battlefield tapped.";
|
||||
return cc.getKeywords().getAmount(str);
|
||||
}
|
||||
if (sq[0].startsWith("Kicked")) {
|
||||
return doXMath(Integer.parseInt(sq[c.getKickerMagnitude() > 0 ? 1 : 2]), m, c);
|
||||
@@ -1540,15 +1490,6 @@ public class CardFactoryUtil {
|
||||
SpellAbility castSA = c.getCastSA();
|
||||
return doXMath(castSA == null ? 0 : castSA.getPayingColors().countColors(), m, c);
|
||||
}
|
||||
// Count$ColoredCreatures *a DOMAIN for creatures*
|
||||
if (sq[0].contains("ColoredCreatures")) {
|
||||
int mask = 0;
|
||||
CardCollection someCards = CardLists.filter(cc.getCardsIn(ZoneType.Battlefield), Presets.CREATURES);
|
||||
for (final Card crd : someCards) {
|
||||
mask |= CardUtil.getColors(crd).getColor();
|
||||
}
|
||||
return doXMath(ColorSet.fromMask(mask).countColors(), m, c);
|
||||
}
|
||||
|
||||
// Count$CardMulticolor.<numMC>.<numNotMC>
|
||||
if (sq[0].contains("CardMulticolor")) {
|
||||
|
||||
@@ -1360,7 +1360,7 @@ public class CardProperty {
|
||||
}
|
||||
} else if (property.startsWith("hasXCost")) {
|
||||
SpellAbility sa1 = card.getFirstSpellAbility();
|
||||
if (sa1 != null && !sa1.isXCost()) {
|
||||
if (sa1 != null && !sa1.costHasManaX()) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.startsWith("suspended")) {
|
||||
|
||||
@@ -26,6 +26,7 @@ import forge.card.mana.ManaCostParser;
|
||||
import forge.game.CardTraitBase;
|
||||
import forge.game.ForgeScript;
|
||||
import forge.game.GameObject;
|
||||
import forge.game.IHasSVars;
|
||||
import forge.game.card.CardView.CardStateView;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.keyword.KeywordCollection;
|
||||
@@ -46,7 +47,7 @@ import java.util.Map;
|
||||
import io.sentry.Sentry;
|
||||
import io.sentry.event.BreadcrumbBuilder;
|
||||
|
||||
public class CardState extends GameObject {
|
||||
public class CardState extends GameObject implements IHasSVars {
|
||||
private String name = "";
|
||||
private CardType type = new CardType(false);
|
||||
private ManaCost manaCost = ManaCost.NO_COST;
|
||||
|
||||
@@ -306,6 +306,13 @@ public class CardView extends GameEntityView {
|
||||
set(TrackableProperty.ChosenType, c.getChosenType());
|
||||
}
|
||||
|
||||
public String getChosenNumber() {
|
||||
return get(TrackableProperty.ChosenNumber);
|
||||
}
|
||||
void updateChosenNumber(Card c) {
|
||||
set(TrackableProperty.ChosenNumber, c.getChosenNumber().toString());
|
||||
}
|
||||
|
||||
public List<String> getChosenColors() {
|
||||
return get(TrackableProperty.ChosenColors);
|
||||
}
|
||||
@@ -392,6 +399,7 @@ public class CardView extends GameEntityView {
|
||||
if (viewers == null || Iterables.isEmpty(viewers)) { return true; }
|
||||
|
||||
return Iterables.any(viewers, new Predicate<PlayerView>() {
|
||||
@Override
|
||||
public final boolean apply(final PlayerView input) {
|
||||
return canBeShownTo(input);
|
||||
}
|
||||
@@ -456,14 +464,17 @@ public class CardView extends GameEntityView {
|
||||
}
|
||||
|
||||
public boolean canFaceDownBeShownToAny(final Iterable<PlayerView> viewers) {
|
||||
if (viewers == null || Iterables.isEmpty(viewers)) { return true; }
|
||||
|
||||
return Iterables.any(viewers, new Predicate<PlayerView>() {
|
||||
@Override public final boolean apply(final PlayerView input) {
|
||||
return canFaceDownBeShownTo(input);
|
||||
@Override
|
||||
public final boolean apply(final PlayerView input) {
|
||||
return canFaceDownBeShownTo(input, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private boolean canFaceDownBeShownTo(final PlayerView viewer) {
|
||||
private boolean canFaceDownBeShownTo(final PlayerView viewer, boolean skip) {
|
||||
if (!isFaceDown()) {
|
||||
return true;
|
||||
}
|
||||
@@ -472,13 +483,15 @@ public class CardView extends GameEntityView {
|
||||
if (mayPlayerLook(viewer)) {
|
||||
return true;
|
||||
}
|
||||
final PlayerView controller = getController();
|
||||
if (!skip) {
|
||||
//if viewer is controlled by another player, also check if face can be shown to that player
|
||||
final PlayerView mindSlaveMaster = viewer.getMindSlaveMaster();
|
||||
if (mindSlaveMaster != null && mindSlaveMaster != controller && canFaceDownBeShownTo(mindSlaveMaster)) {
|
||||
return true;
|
||||
if (mindSlaveMaster != null) {
|
||||
return canFaceDownBeShownTo(mindSlaveMaster, true);
|
||||
}
|
||||
return isInZone(EnumSet.of(ZoneType.Battlefield, ZoneType.Stack, ZoneType.Sideboard)) && controller.equals(viewer);
|
||||
}
|
||||
|
||||
return isInZone(EnumSet.of(ZoneType.Battlefield, ZoneType.Stack, ZoneType.Sideboard)) && getController().equals(viewer);
|
||||
}
|
||||
|
||||
public FCollectionView<CardView> getEncodedCards() {
|
||||
@@ -1120,6 +1133,7 @@ public class CardView extends GameEntityView {
|
||||
public boolean hasDeathtouch() { return get(TrackableProperty.HasDeathtouch); }
|
||||
public boolean hasDevoid() { return get(TrackableProperty.HasDevoid); }
|
||||
public boolean hasDefender() { return get(TrackableProperty.HasDefender); }
|
||||
public boolean hasDivideDamage() { return get(TrackableProperty.HasDivideDamage); }
|
||||
public boolean hasDoubleStrike() { return get(TrackableProperty.HasDoubleStrike); }
|
||||
public boolean hasFirstStrike() { return get(TrackableProperty.HasFirstStrike); }
|
||||
public boolean hasFlying() { return get(TrackableProperty.HasFlying); }
|
||||
@@ -1145,6 +1159,9 @@ public class CardView extends GameEntityView {
|
||||
public boolean hasStorm() {
|
||||
return get(TrackableProperty.HasStorm);
|
||||
}
|
||||
public boolean hasLandwalk() {
|
||||
return get(TrackableProperty.HasLandwalk);
|
||||
}
|
||||
|
||||
public String getAbilityText() {
|
||||
return get(TrackableProperty.AbilityText);
|
||||
@@ -1157,6 +1174,8 @@ public class CardView extends GameEntityView {
|
||||
set(TrackableProperty.HasDeathtouch, c.hasKeyword(Keyword.DEATHTOUCH, state));
|
||||
set(TrackableProperty.HasDevoid, c.hasKeyword(Keyword.DEVOID, state));
|
||||
set(TrackableProperty.HasDefender, c.hasKeyword(Keyword.DEFENDER, state));
|
||||
set(TrackableProperty.HasDivideDamage, c.hasKeyword("You may assign CARDNAME's combat damage divided as " +
|
||||
"you choose among defending player and/or any number of creatures they control."));
|
||||
set(TrackableProperty.HasDoubleStrike, c.hasKeyword(Keyword.DOUBLE_STRIKE, state));
|
||||
set(TrackableProperty.HasFirstStrike, c.hasKeyword(Keyword.FIRST_STRIKE, state));
|
||||
set(TrackableProperty.HasFlying, c.hasKeyword(Keyword.FLYING, state));
|
||||
@@ -1175,6 +1194,7 @@ public class CardView extends GameEntityView {
|
||||
set(TrackableProperty.HasHaste, c.hasKeyword(Keyword.HASTE, state));
|
||||
set(TrackableProperty.HasInfect, c.hasKeyword(Keyword.INFECT, state));
|
||||
set(TrackableProperty.HasStorm, c.hasKeyword(Keyword.STORM, state));
|
||||
set(TrackableProperty.HasLandwalk, c.hasKeyword(Keyword.LANDWALK, state));
|
||||
updateAbilityText(c, state);
|
||||
//set protectionKey for Icons
|
||||
set(TrackableProperty.ProtectionKey, c.getProtectionKey());
|
||||
|
||||
@@ -123,6 +123,8 @@ public enum CounterEnumType {
|
||||
|
||||
GEM("GEM", 255, 99, 251),
|
||||
|
||||
GHOSTFORM("GHSTF", 223, 0, 254),
|
||||
|
||||
GLYPH("GLYPH", 184, 202, 199),
|
||||
|
||||
GOLD("GOLD", 248, 191, 0),
|
||||
|
||||
@@ -45,6 +45,9 @@ public class CounterType implements Comparable<CounterType>, Serializable {
|
||||
}
|
||||
|
||||
public static CounterType getType(String name) {
|
||||
if ("Any".equalsIgnoreCase(name)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return get(CounterEnumType.getType(name));
|
||||
} catch (final IllegalArgumentException ex) {
|
||||
|
||||
@@ -20,21 +20,18 @@ package forge.game.combat;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.*;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.GameEntityCounterTable;
|
||||
import forge.game.GameLogEntryType;
|
||||
import forge.game.GameObjectMap;
|
||||
import forge.game.*;
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardDamageMap;
|
||||
import forge.game.card.*;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbilityStackInstance;
|
||||
import forge.game.trigger.TriggerType;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.CardTranslation;
|
||||
import forge.util.collect.FCollection;
|
||||
import forge.util.collect.FCollectionView;
|
||||
import forge.util.Localizer;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import java.util.*;
|
||||
@@ -64,6 +61,7 @@ public class Combat {
|
||||
private Map<Card, CardCollection> blockersOrderedForDamageAssignment = Maps.newHashMap();
|
||||
private Map<GameEntity, CombatLki> lkiCache = Maps.newHashMap();
|
||||
private CardDamageMap dealtDamageTo = new CardDamageMap();
|
||||
private boolean dividedToPlayer = false;
|
||||
|
||||
// List holds creatures who have dealt 1st strike damage to disallow them deal damage on regular basis (unless they have double-strike KW)
|
||||
private CardCollection combatantsThatDealtFirstStrikeDamage = new CardCollection();
|
||||
@@ -373,7 +371,7 @@ public class Combat {
|
||||
blocker.updateBlockingForView();
|
||||
}
|
||||
|
||||
// remove blocked from specific attacker
|
||||
// remove blocker from specific attacker
|
||||
public final void removeBlockAssignment(final Card attacker, final Card blocker) {
|
||||
AttackingBand band = getBandOfAttackerNotNull(attacker);
|
||||
Collection<Card> cc = blockedBands.get(band);
|
||||
@@ -400,6 +398,15 @@ public class Combat {
|
||||
return result;
|
||||
}
|
||||
|
||||
public final CardCollection getDefendersCreatures() {
|
||||
CardCollection result = new CardCollection();
|
||||
for (Card attacker : getAttackers()) {
|
||||
CardCollection cc = getDefenderPlayerByAttacker(attacker).getCreaturesInPlay();
|
||||
result.addAll(cc);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public final CardCollection getBlockers(final AttackingBand band) {
|
||||
Collection<Card> blockers = blockedBands.get(band);
|
||||
return blockers == null ? new CardCollection() : new CardCollection(blockers);
|
||||
@@ -716,8 +723,26 @@ public class Combat {
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean divideCombatDamageAsChoose = (getDefendersCreatures().size() > 0 &&
|
||||
attacker.hasKeyword("You may assign CARDNAME's combat damage divided as you choose among " +
|
||||
"defending player and/or any number of creatures they control.")
|
||||
&& attacker.getController().getController().confirmAction(null, null,
|
||||
Localizer.getInstance().getMessage("lblAssignCombatDamageAsChoose",
|
||||
CardTranslation.getTranslatedName(attacker.getName()))));
|
||||
boolean trampler = attacker.hasKeyword(Keyword.TRAMPLE);
|
||||
orderedBlockers = blockersOrderedForDamageAssignment.get(attacker);
|
||||
if (divideCombatDamageAsChoose) {
|
||||
if (orderedBlockers == null || orderedBlockers.isEmpty()) {
|
||||
orderedBlockers = getDefendersCreatures();
|
||||
}
|
||||
else {
|
||||
for (Card c : getDefendersCreatures()) {
|
||||
if (!orderedBlockers.contains(c)) {
|
||||
orderedBlockers.add(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
assignedDamage = true;
|
||||
// If the Attacker is unblocked, or it's a trampler and has 0 blockers, deal damage to defender
|
||||
if (orderedBlockers == null || orderedBlockers.isEmpty()) {
|
||||
@@ -730,6 +755,10 @@ public class Combat {
|
||||
Player assigningPlayer = getAttackingPlayer();
|
||||
// Defensive Formation is very similar to Banding with Blockers
|
||||
// It allows the defending player to assign damage instead of the attacking player
|
||||
if (defender instanceof Card && divideCombatDamageAsChoose) {
|
||||
defender = getDefenderPlayerByAttacker(attacker);
|
||||
dividedToPlayer = true;
|
||||
}
|
||||
if (defender instanceof Player && defender.hasKeyword("You assign combat damage of each creature attacking you.")) {
|
||||
assigningPlayer = (Player)defender;
|
||||
}
|
||||
@@ -737,7 +766,8 @@ public class Combat {
|
||||
assigningPlayer = orderedBlockers.get(0).getController();
|
||||
}
|
||||
|
||||
Map<Card, Integer> map = assigningPlayer.getController().assignCombatDamage(attacker, orderedBlockers, damageDealt, defender, getAttackingPlayer() != assigningPlayer);
|
||||
Map<Card, Integer> map = assigningPlayer.getController().assignCombatDamage(attacker, orderedBlockers,
|
||||
damageDealt, defender, divideCombatDamageAsChoose || getAttackingPlayer() != assigningPlayer);
|
||||
for (Entry<Card, Integer> dt : map.entrySet()) {
|
||||
if (dt.getKey() == null) {
|
||||
if (dt.getValue() > 0)
|
||||
@@ -767,7 +797,7 @@ public class Combat {
|
||||
private final void addDefendingDamage(final int n, final Card source) {
|
||||
final GameEntity ge = getDefenderByAttacker(source);
|
||||
|
||||
if (ge instanceof Card) {
|
||||
if (ge instanceof Card && !dividedToPlayer) {
|
||||
final Card planeswalker = (Card) ge;
|
||||
planeswalker.addAssignedDamage(n, source);
|
||||
return;
|
||||
@@ -805,6 +835,9 @@ public class Combat {
|
||||
// This function handles both Regular and First Strike combat assignment
|
||||
for (final Entry<Card, Integer> entry : defendingDamageMap.entrySet()) {
|
||||
GameEntity defender = getDefenderByAttacker(entry.getKey());
|
||||
if (dividedToPlayer) {
|
||||
defender = getDefenderPlayerByAttacker(entry.getKey());
|
||||
}
|
||||
if (defender instanceof Player) { // player
|
||||
defender.addCombatDamage(entry.getValue(), entry.getKey(), dealtDamageTo, preventMap, counterTable);
|
||||
}
|
||||
@@ -819,6 +852,7 @@ public class Combat {
|
||||
combatants.addAll(getAttackers());
|
||||
combatants.addAll(getAllBlockers());
|
||||
combatants.addAll(getDefendingPlaneswalkers());
|
||||
combatants.addAll(getDefendersCreatures());
|
||||
|
||||
for (final Card c : combatants) {
|
||||
// if no assigned damage to resolve, move to next
|
||||
|
||||
@@ -22,6 +22,7 @@ import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
@@ -400,9 +401,9 @@ public class Cost implements Serializable {
|
||||
}
|
||||
|
||||
if (parse.startsWith("RemoveAnyCounter<")) {
|
||||
final String[] splitStr = abCostParse(parse, 3);
|
||||
final String description = splitStr.length > 2 ? splitStr[2] : null;
|
||||
return new CostRemoveAnyCounter(splitStr[0], splitStr[1], description);
|
||||
final String[] splitStr = abCostParse(parse, 4);
|
||||
final String description = splitStr.length > 3 ? splitStr[3] : null;
|
||||
return new CostRemoveAnyCounter(splitStr[0], CounterType.getType(splitStr[1]), splitStr[2], description);
|
||||
}
|
||||
|
||||
if (parse.startsWith("Exile<")) {
|
||||
@@ -959,5 +960,20 @@ public class Cost implements Serializable {
|
||||
return xCost;
|
||||
}
|
||||
|
||||
public Integer getMaxForNonManaX(final SpellAbility ability, final Player payer) {
|
||||
Integer val = null;
|
||||
for (CostPart p : getCostParts()) {
|
||||
if (!p.getAmount().equals("X")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
val = ObjectUtils.min(val, p.getMaxAmountX(ability, payer));
|
||||
}
|
||||
// extra 0 check
|
||||
if (val != null && val <= 0 && hasManaCost() && !getCostMana().canXbe0()) {
|
||||
val = null;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
public static final Cost Zero = new Cost(0);
|
||||
}
|
||||
|
||||
@@ -65,6 +65,16 @@ public class CostDiscard extends CostPartWithList {
|
||||
|
||||
public int paymentOrder() { return 10; }
|
||||
|
||||
@Override
|
||||
public Integer getMaxAmountX(SpellAbility ability, Player payer) {
|
||||
final Card source = ability.getHostCard();
|
||||
String type = this.getType();
|
||||
CardCollectionView handList = payer.canDiscardBy(ability) ? payer.getCardsIn(ZoneType.Hand) : CardCollection.EMPTY;
|
||||
|
||||
handList = CardLists.getValidCards(handList, type.split(";"), payer, source, ability);
|
||||
return handList.size();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
|
||||
@@ -74,6 +74,9 @@ public abstract class CostPart implements Comparable<CostPart>, Cloneable, Seria
|
||||
return this.amount;
|
||||
}
|
||||
|
||||
public Integer getMaxAmountX(final SpellAbility ability, final Player payer) {
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* Gets the type.
|
||||
*
|
||||
|
||||
@@ -47,6 +47,10 @@ public class CostPayEnergy extends CostPart {
|
||||
@Override
|
||||
public int paymentOrder() { return 7; }
|
||||
|
||||
public Integer getMaxAmountX(final SpellAbility ability, final Player payer) {
|
||||
return payer.getCounters(CounterEnumType.ENERGY);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
|
||||
@@ -17,19 +17,19 @@
|
||||
*/
|
||||
package forge.game.cost;
|
||||
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.*;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.TextUtil;
|
||||
|
||||
import java.util.Map;
|
||||
import com.google.common.collect.Table;
|
||||
|
||||
/**
|
||||
* The Class CostRemoveAnyCounter.
|
||||
*/
|
||||
public class CostRemoveAnyCounter extends CostPartWithList {
|
||||
public class CostRemoveAnyCounter extends CostPart {
|
||||
/**
|
||||
* Serializables need a version ID.
|
||||
*/
|
||||
@@ -37,13 +37,8 @@ public class CostRemoveAnyCounter extends CostPartWithList {
|
||||
// RemoveAnyCounter<Num/Type/{TypeDescription}>
|
||||
// Power Conduit and Chisei, Heart of Oceans
|
||||
// Both cards have "Remove a counter from a permanent you control"
|
||||
private CounterType counterType;
|
||||
/**
|
||||
* @param counterType the counterType to set
|
||||
*/
|
||||
public void setCounterType(CounterType counterType) {
|
||||
this.counterType = counterType;
|
||||
}
|
||||
|
||||
public final CounterType counter;
|
||||
|
||||
/**
|
||||
* Instantiates a new cost CostRemoveAnyCounter.
|
||||
@@ -51,33 +46,34 @@ public class CostRemoveAnyCounter extends CostPartWithList {
|
||||
* @param amount
|
||||
* the amount
|
||||
*/
|
||||
public CostRemoveAnyCounter(final String amount, final String type, final String description) {
|
||||
public CostRemoveAnyCounter(final String amount, final CounterType counter, final String type, final String description) {
|
||||
super(amount, type, description);
|
||||
this.counter = counter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int paymentOrder() { return 8; }
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.cost.CostPartWithList#getHashForList()
|
||||
*/
|
||||
@Override
|
||||
public String getHashForLKIList() {
|
||||
return "CounterRemove";
|
||||
public Integer getMaxAmountX(final SpellAbility ability, final Player payer) {
|
||||
|
||||
final Card source = ability.getHostCard();
|
||||
|
||||
CardCollectionView validCards = CardLists.getValidCards(payer.getCardsIn(ZoneType.Battlefield), this.getType().split(";"), payer, source, ability);
|
||||
int allCounters = 0;
|
||||
for (Card c : validCards) {
|
||||
if (this.counter != null) {
|
||||
allCounters += c.getCounters(this.counter);
|
||||
} else {
|
||||
for (Integer value : c.getCounters().values()) {
|
||||
allCounters += value;
|
||||
}
|
||||
@Override
|
||||
public String getHashForCardList() {
|
||||
return "CounterRemoveCards";
|
||||
}
|
||||
}
|
||||
return allCounters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the counter.
|
||||
*
|
||||
* @return the counter
|
||||
*/
|
||||
public CounterType getCounter() {
|
||||
return this.counterType;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
@@ -88,27 +84,7 @@ public class CostRemoveAnyCounter extends CostPartWithList {
|
||||
*/
|
||||
@Override
|
||||
public final boolean canPay(final SpellAbility ability, final Player payer) {
|
||||
final Card source = ability.getHostCard();
|
||||
CardCollectionView validCards = payer.getCardsIn(ZoneType.Battlefield);
|
||||
validCards = CardLists.getValidCards(validCards, this.getType().split(";"), payer, source, ability);
|
||||
validCards = CardLists.filter(validCards, CardPredicates.hasCounters());
|
||||
if (validCards.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
Integer i = this.convertAmount();
|
||||
|
||||
if (i == null) {
|
||||
i = AbilityUtils.calculateAmount(source, this.getAmount(), ability);
|
||||
}
|
||||
int allCounters = 0;
|
||||
for (Card c : validCards) {
|
||||
final Map<CounterType, Integer> tgtCounters = c.getCounters();
|
||||
for (Integer value : tgtCounters.values()) {
|
||||
allCounters += value;
|
||||
}
|
||||
}
|
||||
|
||||
return i <= allCounters;
|
||||
return AbilityUtils.calculateAmount(ability.getHostCard(), this.getAmount(), ability) <= getMaxAmountX(ability, payer);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -120,39 +96,29 @@ public class CostRemoveAnyCounter extends CostPartWithList {
|
||||
public final String toString() {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
|
||||
String counters = this.counter == null ? "counter" : this.counter.getName() + " counter";
|
||||
|
||||
sb.append("Remove ");
|
||||
sb.append(Cost.convertIntAndTypeToWords(this.convertAmount(), "counter"));
|
||||
sb.append(Cost.convertAmountTypeToWords(this.convertAmount(), this.getAmount(), counters));
|
||||
final String desc = this.getTypeDescription() == null ? this.getType() : this.getTypeDescription();
|
||||
sb.append(" from ").append(desc);
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean payAsDecided(Player ai, PaymentDecision decision, SpellAbility ability) {
|
||||
final String amount = this.getAmount();
|
||||
final Card source = ability.getHostCard();
|
||||
Integer c = this.convertAmount();
|
||||
if (c == null) {
|
||||
c = AbilityUtils.calculateAmount(source, amount, ability);
|
||||
}
|
||||
if (decision.cards.isEmpty()) {
|
||||
System.err.println(TextUtil.concatWithSpace("Warning: payment decision array was empty when paying CostRemoveAnyCounter for" , ability.getDescription(), "from", ability.getHostCard().toString()));
|
||||
return false;
|
||||
}
|
||||
Card valid = decision.cards.get(0);
|
||||
counterType = decision.ct;
|
||||
for (int i = 0; i < c; i++) {
|
||||
executePayment(ability, valid);
|
||||
}
|
||||
source.setSVar("CostCountersRemoved", Integer.toString(c));
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Card doPayment(SpellAbility ability, Card targetCard){
|
||||
targetCard.subtractCounter(this.getCounter(), 1);
|
||||
return targetCard;
|
||||
public boolean payAsDecided(Player ai, PaymentDecision decision, SpellAbility ability) {
|
||||
final Card source = ability.getHostCard();
|
||||
|
||||
int removed = 0;
|
||||
for (Table.Cell<GameEntity, CounterType, Integer> cell : decision.counterTable.cellSet()) {
|
||||
removed += cell.getValue();
|
||||
cell.getRowKey().subtractCounter(cell.getColumnKey(), cell.getValue());
|
||||
}
|
||||
|
||||
source.setSVar("CostCountersRemoved", Integer.toString(removed));
|
||||
return true;
|
||||
}
|
||||
|
||||
public <T> T accept(ICostVisitor<T> visitor) {
|
||||
|
||||
@@ -19,6 +19,7 @@ package forge.game.cost;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CounterEnumType;
|
||||
@@ -32,7 +33,7 @@ import java.util.List;
|
||||
/**
|
||||
* The Class CostRemoveCounter.
|
||||
*/
|
||||
public class CostRemoveCounter extends CostPartWithList {
|
||||
public class CostRemoveCounter extends CostPart {
|
||||
// SubCounter<Num/Counter/{Type/TypeDescription/Zone}>
|
||||
|
||||
// Here are the cards that have RemoveCounter<Type>
|
||||
@@ -47,7 +48,6 @@ public class CostRemoveCounter extends CostPartWithList {
|
||||
private static final long serialVersionUID = 1L;
|
||||
public final CounterType counter;
|
||||
public final ZoneType zone;
|
||||
private int cntRemoved;
|
||||
|
||||
/**
|
||||
* Instantiates a new cost remove counter.
|
||||
@@ -73,7 +73,28 @@ public class CostRemoveCounter extends CostPartWithList {
|
||||
public int paymentOrder() { return 8; }
|
||||
|
||||
@Override
|
||||
public boolean isUndoable() { return true; }
|
||||
public Integer getMaxAmountX(final SpellAbility ability, final Player payer) {
|
||||
final CounterType cntrs = this.counter;
|
||||
final Card source = ability.getHostCard();
|
||||
final String type = this.getType();
|
||||
if (this.payCostFromSource()) {
|
||||
return source.getCounters(cntrs);
|
||||
} else {
|
||||
List<Card> typeList;
|
||||
if (type.equals("OriginalHost")) {
|
||||
typeList = Lists.newArrayList(ability.getOriginalHost());
|
||||
} else {
|
||||
typeList = CardLists.getValidCards(payer.getCardsIn(this.zone), type.split(";"), payer, source, ability);
|
||||
}
|
||||
|
||||
// Single Target
|
||||
int maxcount = 0;
|
||||
for (Card c : typeList) {
|
||||
maxcount = Math.max(maxcount, c.getCounters(cntrs));
|
||||
}
|
||||
return maxcount;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
@@ -106,19 +127,6 @@ public class CostRemoveCounter extends CostPartWithList {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see forge.card.cost.CostPart#refund(forge.Card)
|
||||
*/
|
||||
@Override
|
||||
public final void refund(final Card source) {
|
||||
int refund = this.getCardList().size() == 1 ? this.cntRemoved : 1; // is wrong for Ooze Flux and Novijen Sages
|
||||
for (final Card c : this.getCardList()) {
|
||||
c.addCounter(this.counter, refund, source.getController(), false, false, null);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
@@ -144,23 +152,12 @@ public class CostRemoveCounter extends CostPartWithList {
|
||||
typeList = CardLists.getValidCards(payer.getCardsIn(this.zone), type.split(";"), payer, source, ability);
|
||||
}
|
||||
if (amount != null) {
|
||||
// TODO find better way than TypeDescription
|
||||
if (this.getTypeDescription().equals("among creatures you control")) {
|
||||
// remove X counters from among creatures you control
|
||||
int totalCounters = 0;
|
||||
for (Card c : typeList) {
|
||||
totalCounters += c.getCounters(cntrs);
|
||||
}
|
||||
return totalCounters >= amount;
|
||||
|
||||
} else {
|
||||
// (default logic) remove X counters from a single permanent
|
||||
for (Card c : typeList) {
|
||||
if (c.getCounters(cntrs) - amount >= 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -171,32 +168,19 @@ public class CostRemoveCounter extends CostPartWithList {
|
||||
@Override
|
||||
public boolean payAsDecided(Player ai, PaymentDecision decision, SpellAbility ability) {
|
||||
Card source = ability.getHostCard();
|
||||
cntRemoved = decision.c;
|
||||
for (final Card card : decision.cards) {
|
||||
executePayment(ability, card);
|
||||
|
||||
int removed = 0;
|
||||
int toRemove = AbilityUtils.calculateAmount(source, getAmount(), ability);
|
||||
// for this cost, the list should be only one
|
||||
for (Card c : decision.cards) {
|
||||
removed += toRemove;
|
||||
c.subtractCounter(counter, toRemove);
|
||||
}
|
||||
source.setSVar("CostCountersRemoved", Integer.toString(cntRemoved));
|
||||
|
||||
source.setSVar("CostCountersRemoved", Integer.toString(removed));
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Card doPayment(SpellAbility ability, Card targetCard){
|
||||
targetCard.subtractCounter(this.counter, cntRemoved);
|
||||
return targetCard;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.cost.CostPartWithList#getHashForList()
|
||||
*/
|
||||
@Override
|
||||
public String getHashForLKIList() {
|
||||
return "CounterRemove";
|
||||
}
|
||||
@Override
|
||||
public String getHashForCardList() {
|
||||
return "CounterRemoveCards";
|
||||
}
|
||||
|
||||
public <T> T accept(ICostVisitor<T> visitor) {
|
||||
return visitor.visit(this);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package forge.game.cost;
|
||||
|
||||
import forge.game.GameEntityCounterTable;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.mana.Mana;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
@@ -10,39 +10,41 @@ import forge.util.TextUtil;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
public class PaymentDecision {
|
||||
public int c = 0;
|
||||
public String type;
|
||||
public CounterType ct;
|
||||
|
||||
public final CardCollection cards = new CardCollection();
|
||||
public final List<Mana> mana;
|
||||
public final List<Player> players;
|
||||
public final List<SpellAbility> sp;
|
||||
|
||||
// used for CostRemoveAnyCounter
|
||||
public final GameEntityCounterTable counterTable;
|
||||
|
||||
public PaymentDecision(int cnt) {
|
||||
this(null, null, null, null);
|
||||
this(null, null, null, null, null);
|
||||
c = cnt;
|
||||
}
|
||||
|
||||
private PaymentDecision(Iterable<Card> chosen, List<Mana> manaProduced, List<Player> players,
|
||||
List<SpellAbility> sp) {
|
||||
List<SpellAbility> sp, GameEntityCounterTable counterTable) {
|
||||
if (chosen != null) {
|
||||
cards.addAll(chosen);
|
||||
}
|
||||
mana = manaProduced;
|
||||
this.players = players;
|
||||
this.sp = sp;
|
||||
this.counterTable = counterTable;
|
||||
}
|
||||
|
||||
private PaymentDecision(Card chosen) {
|
||||
this(null, null, null, null);
|
||||
this(null, null, null, null, null);
|
||||
cards.add(chosen);
|
||||
}
|
||||
|
||||
public PaymentDecision(String choice) {
|
||||
this(null, null, null, null);
|
||||
this(null, null, null, null, null);
|
||||
type = choice;
|
||||
}
|
||||
|
||||
@@ -62,17 +64,17 @@ public class PaymentDecision {
|
||||
}
|
||||
|
||||
public static PaymentDecision card(Iterable<Card> chosen) {
|
||||
return new PaymentDecision(chosen, null, null, null);
|
||||
return new PaymentDecision(chosen, null, null, null, null);
|
||||
}
|
||||
|
||||
public static PaymentDecision card(Iterable<Card> chosen, int n) {
|
||||
PaymentDecision res = new PaymentDecision(chosen, null, null, null);
|
||||
PaymentDecision res = new PaymentDecision(chosen, null, null, null, null);
|
||||
res.c = n;
|
||||
return res;
|
||||
}
|
||||
|
||||
public static PaymentDecision mana(List<Mana> manas) {
|
||||
return new PaymentDecision(null, manas, null, null);
|
||||
return new PaymentDecision(null, manas, null, null, null);
|
||||
}
|
||||
|
||||
|
||||
@@ -90,16 +92,14 @@ public class PaymentDecision {
|
||||
|
||||
public static PaymentDecision players(List<Player> players) {
|
||||
// TODO Auto-generated method stub
|
||||
return new PaymentDecision(null, null, players, null);
|
||||
return new PaymentDecision(null, null, players, null, null);
|
||||
}
|
||||
|
||||
public static PaymentDecision spellabilities(List<SpellAbility> sp) {
|
||||
return new PaymentDecision(null, null, null, sp);
|
||||
return new PaymentDecision(null, null, null, sp, null);
|
||||
}
|
||||
|
||||
public static PaymentDecision card(Card selected, CounterType counterType) {
|
||||
PaymentDecision res = card(selected);
|
||||
res.ct = counterType;
|
||||
return res;
|
||||
public static PaymentDecision counters(GameEntityCounterTable counterTable) {
|
||||
return new PaymentDecision(null, null, null, null, counterTable);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
package forge.game.keyword;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.common.collect.MultimapBuilder;
|
||||
|
||||
import forge.game.card.Card;
|
||||
|
||||
public class KeywordCollection implements Iterable<String>, Serializable {
|
||||
private static final long serialVersionUID = -2882986558147844702L;
|
||||
public class KeywordCollection implements Iterable<KeywordInterface> {
|
||||
|
||||
private boolean hidden = false;
|
||||
|
||||
private transient KeywordCollectionView view;
|
||||
private final Multimap<Keyword, KeywordInterface> map = MultimapBuilder.enumKeys(Keyword.class)
|
||||
// don't use enumKeys it causes a slow down
|
||||
private final Multimap<Keyword, KeywordInterface> map = MultimapBuilder.hashKeys()
|
||||
.arrayListValues().build();
|
||||
|
||||
public KeywordCollection() {
|
||||
@@ -157,35 +157,20 @@ public class KeywordCollection implements Iterable<String>, Serializable {
|
||||
return map.get(keyword);
|
||||
}
|
||||
|
||||
public List<String> asStringList() {
|
||||
List<String> result = Lists.newArrayList();
|
||||
for (KeywordInterface kw : getValues()) {
|
||||
result.add(kw.getOriginal());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public void setHostCard(final Card host) {
|
||||
for (KeywordInterface k : map.values()) {
|
||||
k.setHostCard(host);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<String> iterator() {
|
||||
return new Iterator<String>() {
|
||||
private final Iterator<KeywordInterface> iterator = map.values().iterator();
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return iterator.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String next() {
|
||||
KeywordInterface entry = iterator.next();
|
||||
return entry.getOriginal();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
//Don't support this
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@@ -204,8 +189,7 @@ public class KeywordCollection implements Iterable<String>, Serializable {
|
||||
return view;
|
||||
}
|
||||
|
||||
public class KeywordCollectionView implements Iterable<String>, Serializable {
|
||||
private static final long serialVersionUID = 7536969077044188264L;
|
||||
public class KeywordCollectionView implements Iterable<KeywordInterface> {
|
||||
|
||||
protected KeywordCollectionView() {
|
||||
}
|
||||
@@ -229,9 +213,18 @@ public class KeywordCollection implements Iterable<String>, Serializable {
|
||||
return KeywordCollection.this.contains(keyword);
|
||||
}
|
||||
|
||||
public List<String> asStringList() {
|
||||
return KeywordCollection.this.asStringList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<String> iterator() {
|
||||
public Iterator<KeywordInterface> iterator() {
|
||||
return KeywordCollection.this.iterator();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<KeywordInterface> iterator() {
|
||||
return this.map.values().iterator();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates.Presets;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.keyword.KeywordInterface;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerController.BinaryChoiceType;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
@@ -125,7 +126,8 @@ public class Untap extends Phase {
|
||||
|
||||
final Map<String, Integer> restrictUntap = Maps.newHashMap();
|
||||
boolean hasChosen = false;
|
||||
for (String kw : player.getKeywords()) {
|
||||
for (KeywordInterface ki : player.getKeywords()) {
|
||||
String kw = ki.getOriginal();
|
||||
if (kw.startsWith("UntapAdjust")) {
|
||||
String[] parse = kw.split(":");
|
||||
if (!restrictUntap.containsKey(parse[1])
|
||||
|
||||
@@ -1236,7 +1236,8 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
|
||||
public boolean hasProtectionFrom(final Card source, final boolean checkSBA, final boolean damageSource) {
|
||||
final boolean colorlessDamage = damageSource && source.hasKeyword("Colorless Damage Source");
|
||||
for (String kw : keywords) {
|
||||
for (KeywordInterface ki : keywords) {
|
||||
String kw = ki.getOriginal();
|
||||
if (kw.startsWith("Protection")) {
|
||||
if (kw.startsWith("Protection:")) { // uses isValid
|
||||
final String characteristic = kw.split(":")[1];
|
||||
@@ -2573,6 +2574,8 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
final PlayerController oldController = getController();
|
||||
|
||||
controlledBy.remove(timestamp);
|
||||
getView().updateMindSlaveMaster(this);
|
||||
|
||||
if (event) {
|
||||
game.fireEvent(new GameEventPlayerControl(this, oldLobbyPlayer, oldController, getLobbyPlayer(), getController()));
|
||||
}
|
||||
@@ -2580,9 +2583,9 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
|
||||
public void clearController() {
|
||||
controlledBy.clear();
|
||||
game.fireEvent(new GameEventPlayerControl(this, null, null, getLobbyPlayer(), getController()));
|
||||
}
|
||||
|
||||
|
||||
public Map.Entry<Long, Player> getControlledWhileSearching() {
|
||||
if (controlledWhileSearching.isEmpty()) {
|
||||
return null;
|
||||
@@ -3226,7 +3229,7 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
keywordEffect.updateAbilityTextForView();
|
||||
boolean headerAdded = false;
|
||||
StringBuilder kw = new StringBuilder();
|
||||
for(String k : keywords) {
|
||||
for(KeywordInterface k : keywords) {
|
||||
if(!headerAdded) {
|
||||
headerAdded = true;
|
||||
kw.append(this.getName()).append(" has: \n");
|
||||
|
||||
@@ -311,7 +311,7 @@ public class PlayerView extends GameEntityView {
|
||||
return getKeywords().contains(keyword);
|
||||
}
|
||||
void updateKeywords(Player p) {
|
||||
set(TrackableProperty.Keywords, ImmutableMultiset.copyOf(p.getKeywords()));
|
||||
set(TrackableProperty.Keywords, ImmutableMultiset.copyOf(p.getKeywords().asStringList()));
|
||||
}
|
||||
|
||||
public List<CardView> getCommanders() {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
package forge.game.replacement;
|
||||
|
||||
import forge.game.Game;
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
@@ -48,6 +49,7 @@ public class ReplaceDamage extends ReplacementEffect {
|
||||
*/
|
||||
@Override
|
||||
public boolean canReplace(Map<AbilityKey, Object> runParams) {
|
||||
final Game game = getHostCard().getGame();
|
||||
|
||||
if (!(runParams.containsKey(AbilityKey.Prevention) == (hasParam("PreventionEffect") || hasParam("Prevent")))) {
|
||||
return false;
|
||||
@@ -128,12 +130,27 @@ public class ReplaceDamage extends ReplacementEffect {
|
||||
}
|
||||
// check for DamageRedirection, the Thing where the damage is redirected to must be a creature or planeswalker or a player
|
||||
String def = getParam("DamageTarget");
|
||||
for (Player p : AbilityUtils.getDefinedPlayers(hostCard, def, null)) {
|
||||
if (!p.getGame().getPlayers().contains(p)) {
|
||||
if (def.startsWith("Replaced")) {
|
||||
// this can't work with the Defined below because the replaced objects aren't set to a possible SA yet
|
||||
if (def.equals("ReplacedSourceController")) {
|
||||
Card source = (Card) runParams.get(AbilityKey.DamageSource);
|
||||
if (!game.getPlayers().contains(source.getController())) {
|
||||
return false;
|
||||
}
|
||||
} else if (def.equals("ReplacedTargetController")) {
|
||||
if (!(affected instanceof Card) || !game.getPlayers().contains(((Card) affected).getController())) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
for (Player p : AbilityUtils.getDefinedPlayers(getHostCard(), def, null)) {
|
||||
if (!game.getPlayers().contains(p)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (Card c : AbilityUtils.getDefinedCards(hostCard, def, null)) {
|
||||
for (Card c : AbilityUtils.getDefinedCards(getHostCard(), def, null)) {
|
||||
if (!c.isCreature() && !c.isPlaneswalker()) {
|
||||
return false;
|
||||
}
|
||||
@@ -142,6 +159,7 @@ public class ReplaceDamage extends ReplacementEffect {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -288,34 +288,25 @@ public class ReplacementHandler {
|
||||
host = game.getCardState(host);
|
||||
}
|
||||
|
||||
if (mapParams.containsKey("ReplaceWith")) {
|
||||
if (replacementEffect.getOverridingAbility() == null && mapParams.containsKey("ReplaceWith")) {
|
||||
final String effectSVar = mapParams.get("ReplaceWith");
|
||||
final String effectAbString = host.getSVar(effectSVar);
|
||||
// TODO: the source of replacement effect should be the source of the original effect
|
||||
effectSA = AbilityFactory.getAbility(effectAbString, host);
|
||||
effectSA = AbilityFactory.getAbility(host, effectSVar, replacementEffect);
|
||||
//replacementEffect.setOverridingAbility(effectSA);
|
||||
//effectSA.setTrigger(true);
|
||||
|
||||
SpellAbility tailend = effectSA;
|
||||
do {
|
||||
replacementEffect.setReplacingObjects(runParams, tailend);
|
||||
//set original Params to update them later
|
||||
tailend.setReplacingObject(AbilityKey.OriginalParams, runParams);
|
||||
tailend = tailend.getSubAbility();
|
||||
} while(tailend != null);
|
||||
|
||||
}
|
||||
else if (replacementEffect.getOverridingAbility() != null) {
|
||||
} else if (replacementEffect.getOverridingAbility() != null) {
|
||||
effectSA = replacementEffect.getOverridingAbility();
|
||||
SpellAbility tailend = effectSA;
|
||||
do {
|
||||
replacementEffect.setReplacingObjects(runParams, tailend);
|
||||
//set original Params to update them later
|
||||
tailend.setReplacingObject(AbilityKey.OriginalParams, runParams);
|
||||
tailend = tailend.getSubAbility();
|
||||
} while(tailend != null);
|
||||
}
|
||||
|
||||
if (effectSA != null) {
|
||||
SpellAbility tailend = effectSA;
|
||||
do {
|
||||
replacementEffect.setReplacingObjects(runParams, tailend);
|
||||
//set original Params to update them later
|
||||
tailend.setReplacingObject(AbilityKey.OriginalParams, runParams);
|
||||
tailend = tailend.getSubAbility();
|
||||
} while(tailend != null);
|
||||
|
||||
effectSA.setLastStateBattlefield(game.getLastStateBattlefield());
|
||||
effectSA.setLastStateGraveyard(game.getLastStateGraveyard());
|
||||
if (replacementEffect.isIntrinsic()) {
|
||||
|
||||
@@ -64,10 +64,8 @@ public abstract class AbilityActivated extends SpellAbility implements Cloneable
|
||||
*/
|
||||
public AbilityActivated(final Card sourceCard, final Cost abCost, final TargetRestrictions tgt) {
|
||||
super(sourceCard, abCost);
|
||||
if ((tgt != null) && tgt.doesTarget()) {
|
||||
this.setTargetRestrictions(tgt);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isActivatedAbility() { return true; }
|
||||
|
||||
|
||||
@@ -348,7 +348,7 @@ public class AbilityManaPart implements java.io.Serializable {
|
||||
}
|
||||
|
||||
if (restriction.startsWith("CostContainsX")) {
|
||||
if (sa.isXCost()) {
|
||||
if (sa.costHasManaX()) {
|
||||
return true;
|
||||
}
|
||||
continue;
|
||||
|
||||
@@ -47,10 +47,8 @@ public abstract class AbilityStatic extends Ability implements Cloneable {
|
||||
|
||||
public AbilityStatic(final Card sourceCard, final Cost abCost, final TargetRestrictions tgt) {
|
||||
super(sourceCard, abCost);
|
||||
if ((tgt != null) && tgt.doesTarget()) {
|
||||
this.setTargetRestrictions(tgt);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public boolean canPlay() {
|
||||
Player player = getActivatingPlayer();
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
*/
|
||||
package forge.game.spellability;
|
||||
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
|
||||
import forge.card.CardStateName;
|
||||
import forge.card.mana.ManaCost;
|
||||
import forge.game.Game;
|
||||
@@ -100,7 +102,9 @@ public abstract class Spell extends SpellAbility implements java.io.Serializable
|
||||
card.setController(activator, 0);
|
||||
}
|
||||
|
||||
if (!this.getRestrictions().canPlay(getHostCard(), this)) {
|
||||
card = ObjectUtils.firstNonNull(getAlternateHost(card), card);
|
||||
|
||||
if (!this.getRestrictions().canPlay(card, this)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,6 @@ import forge.game.card.CardPredicates;
|
||||
import forge.game.card.CardZoneTable;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.cost.CostPart;
|
||||
import forge.game.cost.CostPartMana;
|
||||
import forge.game.cost.CostRemoveCounter;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.mana.Mana;
|
||||
@@ -47,6 +46,7 @@ import forge.game.player.Player;
|
||||
import forge.game.player.PlayerCollection;
|
||||
import forge.game.replacement.ReplacementEffect;
|
||||
import forge.game.staticability.StaticAbility;
|
||||
import forge.game.staticability.StaticAbilityCastWithFlash;
|
||||
import forge.game.trigger.Trigger;
|
||||
import forge.game.trigger.TriggerType;
|
||||
import forge.game.trigger.WrappedAbility;
|
||||
@@ -91,7 +91,6 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
||||
private Player activatingPlayer = null;
|
||||
private Player targetingPlayer = null;
|
||||
|
||||
private Card grantorCard = null; // card which grants the ability (equipment or owner of static ability that gave this one)
|
||||
private SpellAbility grantorOriginal = null;
|
||||
private StaticAbility grantorStatic = null;
|
||||
|
||||
@@ -114,8 +113,6 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
||||
|
||||
private CardStateName stateName = null;
|
||||
|
||||
private int totalManaSpent = 0;
|
||||
|
||||
/** The pay costs. */
|
||||
private Cost payCosts;
|
||||
private SpellAbilityRestriction restrictions = new SpellAbilityRestriction();
|
||||
@@ -368,11 +365,8 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
||||
return this.isAlternativeCost(AlternativeCost.Cycling);
|
||||
}
|
||||
|
||||
public Card getOriginalHost() {
|
||||
return grantorCard;
|
||||
}
|
||||
public void setOriginalHost(final Card c) {
|
||||
grantorCard = c;
|
||||
super.setOriginalHost(c);
|
||||
if (subAbility != null) {
|
||||
subAbility.setOriginalHost(c);
|
||||
}
|
||||
@@ -623,7 +617,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
||||
|
||||
public String getStackDescription() {
|
||||
String text = getHostCard().getView().getText();
|
||||
if (stackDescription.equals(text)) {
|
||||
if (stackDescription.equals(text) && !text.isEmpty()) {
|
||||
return getHostCard().getName() + " - " + text;
|
||||
}
|
||||
return TextUtil.fastReplace(stackDescription, "CARDNAME", getHostCard().getName());
|
||||
@@ -990,7 +984,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
||||
final TargetRestrictions tr = getTargetRestrictions();
|
||||
|
||||
// Restriction related to this ability
|
||||
if (tr != null) {
|
||||
if (usesTargeting()) {
|
||||
if (tr.isUniqueTargets() && getUniqueTargets().contains(entity))
|
||||
return false;
|
||||
|
||||
@@ -1327,11 +1321,6 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
||||
originalMapParams.put("Announce", announce + ";" + variable);
|
||||
}
|
||||
|
||||
public boolean isXCost() {
|
||||
CostPartMana cm = payCosts != null ? getPayCosts().getCostMana() : null;
|
||||
return cm != null && cm.getAmountOfX() > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canBeTargetedBy(SpellAbility sa) {
|
||||
return sa.canTargetSpellAbility(this);
|
||||
@@ -1402,21 +1391,37 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
||||
return false;
|
||||
}
|
||||
|
||||
return getTargets().size() < getTargetRestrictions().getMaxTargets(hostCard, this);
|
||||
return getTargets().size() < getMaxTargets();
|
||||
}
|
||||
|
||||
public boolean isZeroTargets() {
|
||||
return getTargetRestrictions().getMinTargets(hostCard, this) == 0 && getTargets().size() == 0;
|
||||
}
|
||||
|
||||
public boolean isMinTargetChosen() {
|
||||
return getTargetRestrictions().isMinTargetsChosen(hostCard, this);
|
||||
}
|
||||
public boolean isMaxTargetChosen() {
|
||||
return getTargetRestrictions().isMaxTargetsChosen(hostCard, this);
|
||||
}
|
||||
|
||||
public int getMinTargets() {
|
||||
return getTargetRestrictions().getMinTargets(getHostCard(), this);
|
||||
}
|
||||
|
||||
public int getMaxTargets() {
|
||||
return getTargetRestrictions().getMaxTargets(getHostCard(), this);
|
||||
}
|
||||
|
||||
public boolean isTargetNumberValid() {
|
||||
if (!this.usesTargeting()) {
|
||||
return getTargets().isEmpty();
|
||||
}
|
||||
|
||||
int minTargets = getTargetRestrictions().getMinTargets(hostCard, this);
|
||||
int maxTargets = getTargetRestrictions().getMaxTargets(hostCard, this);
|
||||
int numTargets = getTargets().size();
|
||||
if (!isMinTargetChosen()) {
|
||||
return false;
|
||||
}
|
||||
int maxTargets = getMaxTargets();
|
||||
|
||||
if (maxTargets == 0 && getPayCosts().hasSpecificCostType(CostRemoveCounter.class)
|
||||
&& hasSVar(getParam("TargetMax"))
|
||||
@@ -1429,7 +1434,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
||||
maxTargets = Integer.parseInt(getHostCard().getSVar("CostCountersRemoved"));
|
||||
}
|
||||
|
||||
return minTargets <= numTargets && maxTargets >= numTargets;
|
||||
return maxTargets >= getTargets().size();
|
||||
}
|
||||
/**
|
||||
* <p>
|
||||
@@ -1670,8 +1675,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
||||
SpellAbility currentAbility = this;
|
||||
final Card source = getHostCard();
|
||||
do {
|
||||
final TargetRestrictions tgt = currentAbility.getTargetRestrictions();
|
||||
if (tgt != null && tgt.doesTarget()) {
|
||||
if (currentAbility.usesTargeting()) {
|
||||
currentAbility.clearTargets();
|
||||
Player targetingPlayer;
|
||||
if (currentAbility.hasParam("TargetingPlayer")) {
|
||||
@@ -1788,12 +1792,8 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
||||
}
|
||||
}
|
||||
|
||||
public void setTotalManaSpent(int totManaSpent) {
|
||||
totalManaSpent = totManaSpent;
|
||||
}
|
||||
|
||||
public int getTotalManaSpent() {
|
||||
return totalManaSpent;
|
||||
return this.getPayingMana().size();
|
||||
}
|
||||
|
||||
public List<AbilitySub> getChosenList() {
|
||||
@@ -2059,17 +2059,8 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
||||
return true;
|
||||
}
|
||||
}
|
||||
final Game game = activator.getGame();
|
||||
final CardCollection allp = new CardCollection(game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES));
|
||||
allp.add(host);
|
||||
for (final Card ca : allp) {
|
||||
for (final StaticAbility stAb : ca.getStaticAbilities()) {
|
||||
if (stAb.applyAbility("CastWithFlash", host, this, activator)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
return StaticAbilityCastWithFlash.anyWithFlash(this, host, activator);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ import forge.game.GameType;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardFactoryUtil;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardUtil;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
@@ -271,7 +270,7 @@ public class SpellAbilityCondition extends SpellAbilityVariables {
|
||||
|
||||
if (this.isAllTargetsLegal()) {
|
||||
for (Card c : sa.getTargets().getTargetCards()) {
|
||||
if (!CardFactoryUtil.isTargetStillValid(sa, c)) {
|
||||
if (!sa.canTarget(c)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import forge.game.card.*;
|
||||
import forge.game.cost.IndividualCostPaymentInstance;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.staticability.StaticAbilityCastWithFlash;
|
||||
import forge.game.zone.Zone;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Expressions;
|
||||
@@ -503,6 +504,12 @@ public class SpellAbilityRestriction extends SpellAbilityVariables {
|
||||
System.out.println(c.getName() + " Did not have activator set in SpellAbilityRestriction.canPlay()");
|
||||
}
|
||||
|
||||
if (!StaticAbilityCastWithFlash.anyWithFlashNeedsTargeting(sa, c, activator)) {
|
||||
if (!sa.canCastTiming(c, activator)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!sa.hasSVar("IsCastFromPlayEffect")) {
|
||||
if (!checkTimingRestrictions(c, sa)) {
|
||||
return false;
|
||||
|
||||
@@ -39,6 +39,7 @@ import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@@ -141,10 +142,10 @@ public class SpellAbilityStackInstance implements IIdentifiable, IHasCardView {
|
||||
}
|
||||
// We probably should be storing SA svars too right?
|
||||
if (!sa.isWrapper()) {
|
||||
for (final String store : sa.getSVars()) {
|
||||
final String value = source.getSVar(store);
|
||||
for (final Entry<String, String> e : sa.getSVars().entrySet()) {
|
||||
final String value = e.getValue();
|
||||
if (!StringUtils.isEmpty(value)) {
|
||||
storedSVars.put(store, value);
|
||||
storedSVars.put(e.getKey(), value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,6 @@ public class TargetRestrictions {
|
||||
// Target Choices (which is specific for the StackInstance)
|
||||
|
||||
// What this Object is restricted to targeting
|
||||
private boolean tgtValid = false;
|
||||
private String[] originalValidTgts,
|
||||
validTgts;
|
||||
private String uiPrompt = "";
|
||||
@@ -93,7 +92,6 @@ public class TargetRestrictions {
|
||||
* a {@link forge.game.spellability.TargetRestrictions} object.
|
||||
*/
|
||||
public TargetRestrictions(final TargetRestrictions target) {
|
||||
this.tgtValid = true;
|
||||
this.uiPrompt = target.getVTSelection();
|
||||
this.originalValidTgts = target.getValidTgts();
|
||||
this.validTgts = this.originalValidTgts.clone();
|
||||
@@ -129,7 +127,6 @@ public class TargetRestrictions {
|
||||
* a {@link java.lang.String} object.
|
||||
*/
|
||||
public TargetRestrictions(final String prompt, final String[] valid, final String min, final String max) {
|
||||
this.tgtValid = true;
|
||||
this.uiPrompt = prompt;
|
||||
this.originalValidTgts = valid;
|
||||
this.validTgts = this.originalValidTgts.clone();
|
||||
@@ -172,17 +169,6 @@ public class TargetRestrictions {
|
||||
this.maxTotalCMC = cmc;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* doesTarget.
|
||||
* </p>
|
||||
*
|
||||
* @return a boolean.
|
||||
*/
|
||||
public final boolean doesTarget() {
|
||||
return this.tgtValid;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getValidTgts.
|
||||
@@ -210,7 +196,7 @@ public class TargetRestrictions {
|
||||
*
|
||||
* @return the min targets
|
||||
*/
|
||||
private final String getMinTargets() {
|
||||
public final String getMinTargets() {
|
||||
return this.minTargets;
|
||||
}
|
||||
|
||||
@@ -219,7 +205,7 @@ public class TargetRestrictions {
|
||||
*
|
||||
* @return the max targets
|
||||
*/
|
||||
private final String getMaxTargets() {
|
||||
public final String getMaxTargets() {
|
||||
return this.maxTargets;
|
||||
}
|
||||
|
||||
@@ -278,8 +264,7 @@ public class TargetRestrictions {
|
||||
* @return a boolean.
|
||||
*/
|
||||
public final boolean isMaxTargetsChosen(final Card c, final SpellAbility sa) {
|
||||
TargetChoices choice = sa.getTargets();
|
||||
return this.getMaxTargets(c, sa) == choice.size();
|
||||
return this.getMaxTargets(c, sa) == sa.getTargets().size();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -294,11 +279,11 @@ public class TargetRestrictions {
|
||||
* @return a boolean.
|
||||
*/
|
||||
public final boolean isMinTargetsChosen(final Card c, final SpellAbility sa) {
|
||||
if (this.getMinTargets(c, sa) == 0) {
|
||||
int min = getMinTargets(c, sa);
|
||||
if (min == 0) {
|
||||
return true;
|
||||
}
|
||||
TargetChoices choice = sa.getTargets();
|
||||
return this.getMinTargets(c, sa) <= choice.size();
|
||||
return min <= sa.getTargets().size();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user