mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-20 12:48:00 +00:00
CountersRemove Effect and AI: update to new scripting
make the AI use of RemoveCounter abilities
This commit is contained in:
@@ -1,11 +1,27 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.GlobalRuleChange;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.CounterType;
|
||||
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 CountersRemoveAi extends SpellAbilityAi {
|
||||
|
||||
@@ -55,12 +71,11 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
||||
|
||||
final String type = sa.getParam("CounterType");
|
||||
|
||||
// TODO currently, not targeted
|
||||
if (sa.usesTargeting()) {
|
||||
return false;
|
||||
return doTgt(ai, sa, false);
|
||||
}
|
||||
|
||||
if (!type.matches("Any")) {
|
||||
if (!type.matches("Any") && !type.matches("All")) {
|
||||
final int currCounters = sa.getHostCard().getCounters(CounterType.valueOf(type));
|
||||
if (currCounters < 1) {
|
||||
return false;
|
||||
@@ -70,21 +85,295 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
||||
return super.checkApiLogic(ai, sa);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
// AI needs to be expanded, since this function can be pretty complex
|
||||
// based on what the
|
||||
// expected targets could be
|
||||
boolean chance = true;
|
||||
private boolean doTgt(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final Card source = sa.getHostCard();
|
||||
final Game game = ai.getGame();
|
||||
|
||||
// TODO - currently, not targeted, only for Self
|
||||
final String type = sa.getParam("CounterType");
|
||||
final String amountStr = sa.getParam("CounterNum");
|
||||
|
||||
// Note: Not many cards even use Trigger and Remove Counters. And even
|
||||
// fewer are not mandatory
|
||||
// Since the targeting portion of this would be what
|
||||
// remove counter with Time might use Exile Zone too
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
CardCollection list = new CardCollection(game.getCardsIn(tgt.getZone()));
|
||||
// need to targetable
|
||||
list = CardLists.getTargetableCards(list, sa);
|
||||
|
||||
if (list.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return chance;
|
||||
if (sa.hasParam("AITgts")) {
|
||||
String aiTgts = sa.getParam("AITgts");
|
||||
CardCollection prefList = CardLists.getValidCards(list, aiTgts.split(","), ai, source, sa);
|
||||
if (!prefList.isEmpty() || sa.hasParam("AITgtsStrict")) {
|
||||
list = prefList;
|
||||
}
|
||||
}
|
||||
|
||||
boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule);
|
||||
|
||||
if (type.matches("All")) {
|
||||
// Logic Part for Vampire Hexmage
|
||||
// Break Dark Depths
|
||||
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
|
||||
CardCollectionView depthsList = ai.getCardsIn(ZoneType.Battlefield, "Dark Depths");
|
||||
depthsList = CardLists.filter(depthsList, CardPredicates.isTargetableBy(sa),
|
||||
CardPredicates.hasCounter(CounterType.ICE, 3));
|
||||
|
||||
if (!depthsList.isEmpty()) {
|
||||
sa.getTargets().add(depthsList.getFirst());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Get rid of Planeswalkers:
|
||||
list = ai.getOpponents().getCardsIn(ZoneType.Battlefield);
|
||||
list = CardLists.filter(list, CardPredicates.isTargetableBy(sa));
|
||||
|
||||
CardCollection planeswalkerList = CardLists.filter(list, CardPredicates.Presets.PLANEWALKERS,
|
||||
CardPredicates.hasCounter(CounterType.LOYALTY, 5));
|
||||
|
||||
if (!planeswalkerList.isEmpty()) {
|
||||
sa.getTargets().add(ComputerUtilCard.getBestPlaneswalkerAI(planeswalkerList));
|
||||
return true;
|
||||
}
|
||||
|
||||
} else if (type.matches("Any")) {
|
||||
// variable amount for Hex Parasite
|
||||
int amount;
|
||||
boolean xPay = false;
|
||||
if (amountStr.equals("X") && source.getSVar("X").equals("Count$xPaid")) {
|
||||
final int manaLeft = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
|
||||
if (manaLeft == 0) {
|
||||
return false;
|
||||
}
|
||||
amount = manaLeft;
|
||||
xPay = true;
|
||||
} else {
|
||||
amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
||||
}
|
||||
// try to remove them from Dark Depths and Planeswalkers too
|
||||
|
||||
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
|
||||
CardCollectionView depthsList = ai.getCardsIn(ZoneType.Battlefield, "Dark Depths");
|
||||
depthsList = CardLists.filter(depthsList, CardPredicates.isTargetableBy(sa),
|
||||
CardPredicates.hasCounter(CounterType.ICE));
|
||||
|
||||
if (!depthsList.isEmpty()) {
|
||||
Card depth = depthsList.getFirst();
|
||||
int ice = depth.getCounters(CounterType.ICE);
|
||||
if (amount >= ice) {
|
||||
sa.getTargets().add(depth);
|
||||
if (xPay) {
|
||||
source.setSVar("PayX", Integer.toString(ice));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get rid of Planeswalkers:
|
||||
list = ai.getOpponents().getCardsIn(ZoneType.Battlefield);
|
||||
list = CardLists.filter(list, CardPredicates.isTargetableBy(sa));
|
||||
|
||||
CardCollection planeswalkerList = CardLists.filter(list, CardPredicates.Presets.PLANEWALKERS,
|
||||
CardPredicates.hasLessCounter(CounterType.LOYALTY, amount));
|
||||
|
||||
if (!planeswalkerList.isEmpty()) {
|
||||
Card best = ComputerUtilCard.getBestPlaneswalkerAI(planeswalkerList);
|
||||
sa.getTargets().add(best);
|
||||
if (xPay) {
|
||||
source.setSVar("PayX", Integer.toString(best.getCurrentLoyalty()));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// some rules only for amount = 1
|
||||
if (!xPay) {
|
||||
// do as M1M1 part
|
||||
CardCollection aiList = CardLists.filterControlledBy(list, ai);
|
||||
|
||||
CardCollection aiM1M1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.M1M1));
|
||||
|
||||
CardCollection aiPersistList = CardLists.getKeyword(aiM1M1List, "Persist");
|
||||
if (!aiPersistList.isEmpty()) {
|
||||
aiM1M1List = aiPersistList;
|
||||
}
|
||||
|
||||
if (!aiM1M1List.isEmpty()) {
|
||||
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiM1M1List));
|
||||
return true;
|
||||
}
|
||||
|
||||
// do as P1P1 part
|
||||
CardCollection aiP1P1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.P1P1));
|
||||
CardCollection aiUndyingList = CardLists.getKeyword(aiM1M1List, "Undying");
|
||||
|
||||
if (!aiUndyingList.isEmpty()) {
|
||||
aiP1P1List = aiUndyingList;
|
||||
}
|
||||
if (!aiP1P1List.isEmpty()) {
|
||||
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiP1P1List));
|
||||
return true;
|
||||
}
|
||||
|
||||
// fallback to remove any counter from opponent
|
||||
CardCollection oppList = CardLists.filterControlledBy(list, ai.getOpponents());
|
||||
oppList = CardLists.filter(oppList, CardPredicates.hasCounters());
|
||||
if (!oppList.isEmpty()) {
|
||||
final Card best = ComputerUtilCard.getBestAI(oppList);
|
||||
|
||||
for (final CounterType aType : best.getCounters().keySet()) {
|
||||
if (!ComputerUtil.isNegativeCounter(aType, best)) {
|
||||
sa.getTargets().add(best);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (type.equals("M1M1")) {
|
||||
// no special amount for that one yet
|
||||
int amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
||||
CardCollection aiList = CardLists.filterControlledBy(list, ai);
|
||||
aiList = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.M1M1, amount));
|
||||
|
||||
CardCollection aiPersist = CardLists.getKeyword(aiList, "Persist");
|
||||
if (!aiPersist.isEmpty()) {
|
||||
aiList = aiPersist;
|
||||
}
|
||||
|
||||
// TODO do not remove -1/-1 counters from cards which does need
|
||||
// them for abilities
|
||||
|
||||
if (!aiList.isEmpty()) {
|
||||
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiList));
|
||||
return true;
|
||||
}
|
||||
|
||||
} else if (type.equals("P1P1")) {
|
||||
// no special amount for that one yet
|
||||
int amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
||||
|
||||
list = CardLists.filter(list, CardPredicates.hasCounter(CounterType.P1P1, amount));
|
||||
|
||||
// currently only logic for Bloodcrazed Hoplite, but add logic for
|
||||
// targeting ai creatures too
|
||||
CardCollection aiList = CardLists.filterControlledBy(list, ai);
|
||||
if (!aiList.isEmpty()) {
|
||||
CardCollection aiListUndying = CardLists.getKeyword(aiList, "Undying");
|
||||
if (!aiListUndying.isEmpty()) {
|
||||
aiList = aiListUndying;
|
||||
}
|
||||
if (!aiList.isEmpty()) {
|
||||
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiList));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// need to target opponent creatures
|
||||
CardCollection oppList = CardLists.filterControlledBy(list, ai.getOpponents());
|
||||
if (!oppList.isEmpty()) {
|
||||
CardCollection oppListNotUndying = CardLists.getNotKeyword(oppList, "Undying");
|
||||
if (!oppListNotUndying.isEmpty()) {
|
||||
oppList = oppListNotUndying;
|
||||
}
|
||||
|
||||
if (!oppList.isEmpty()) {
|
||||
sa.getTargets().add(ComputerUtilCard.getWorstCreatureAI(oppList));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
} else if (type.equals("TIME")) {
|
||||
int amount;
|
||||
boolean xPay = false;
|
||||
// Timecrafting has X R
|
||||
if (amountStr.equals("X") && source.getSVar("X").equals("Count$xPaid")) {
|
||||
final int manaLeft = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
|
||||
if (manaLeft == 0) {
|
||||
return false;
|
||||
}
|
||||
amount = manaLeft;
|
||||
xPay = true;
|
||||
} else {
|
||||
amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
||||
}
|
||||
|
||||
CardCollection timeList = CardLists.filter(list, CardPredicates.hasLessCounter(CounterType.TIME, amount));
|
||||
|
||||
if (!timeList.isEmpty()) {
|
||||
Card best = ComputerUtilCard.getBestAI(timeList);
|
||||
|
||||
int timeCount = best.getCounters(CounterType.TIME);
|
||||
sa.getTargets().add(best);
|
||||
if (xPay) {
|
||||
source.setSVar("PayX", Integer.toString(timeCount));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (mandatory) {
|
||||
sa.getTargets().add(ComputerUtilCard.getWorstAI(list));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
return doTgt(aiPlayer, sa, mandatory);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see forge.ai.SpellAbilityAi#chooseNumber(forge.game.player.Player,
|
||||
* forge.game.spellability.SpellAbility, int, int, java.util.Map)
|
||||
*/
|
||||
@Override
|
||||
public int chooseNumber(Player player, SpellAbility sa, int min, int max, Map<String, Object> params) {
|
||||
// TODO Auto-generated method stub
|
||||
return super.chooseNumber(player, sa, min, max, params);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see forge.ai.SpellAbilityAi#chooseCounterType(java.util.List,
|
||||
* forge.game.spellability.SpellAbility, java.util.Map)
|
||||
*/
|
||||
@Override
|
||||
public CounterType chooseCounterType(List<CounterType> options, SpellAbility sa, Map<String, Object> params) {
|
||||
if (options.size() <= 1) {
|
||||
return super.chooseCounterType(options, sa, params);
|
||||
}
|
||||
Player ai = sa.getActivatingPlayer();
|
||||
Card target = (Card) params.get("Target");
|
||||
|
||||
if (target.getController().isOpponentOf(ai)) {
|
||||
// if its a Planeswalker try to remove Loyality first
|
||||
if (target.isPlaneswalker()) {
|
||||
return CounterType.LOYALTY;
|
||||
}
|
||||
for (CounterType type : options) {
|
||||
if (!ComputerUtil.isNegativeCounter(type, target)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (options.contains(CounterType.M1M1) && target.hasKeyword("Persist")) {
|
||||
return CounterType.M1M1;
|
||||
} else if (options.contains(CounterType.P1P1) && target.hasKeyword("Undying")) {
|
||||
return CounterType.M1M1;
|
||||
}
|
||||
for (CounterType type : options) {
|
||||
if (ComputerUtil.isNegativeCounter(type, target)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.chooseCounterType(options, sa, params);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import forge.game.card.CounterType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerController;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.Zone;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
@@ -17,6 +16,7 @@ import java.util.Map;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
public class CountersRemoveEffect extends SpellAbilityEffect {
|
||||
@Override
|
||||
@@ -24,14 +24,20 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
|
||||
final String counterName = sa.getParam("CounterType");
|
||||
final String num = sa.getParam("CounterNum");
|
||||
|
||||
final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("CounterNum"), sa);
|
||||
int amount = 0;
|
||||
if (!num.equals("All") && !num.equals("Remembered")) {
|
||||
amount = AbilityUtils.calculateAmount(sa.getHostCard(), num, sa);
|
||||
};
|
||||
|
||||
sb.append("Remove ");
|
||||
if (sa.hasParam("UpTo")) {
|
||||
sb.append("up to ");
|
||||
}
|
||||
if ("Any".matches(counterName)) {
|
||||
if ("All".matches(counterName)) {
|
||||
sb.append("all counter");
|
||||
} else if ("Any".matches(counterName)) {
|
||||
if (amount == 1) {
|
||||
sb.append("a counter");
|
||||
} else {
|
||||
@@ -64,32 +70,29 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
|
||||
final Card card = sa.getHostCard();
|
||||
final Game game = card.getGame();
|
||||
final String type = sa.getParam("CounterType");
|
||||
final String num = sa.getParam("CounterNum");
|
||||
int cntToRemove = 0;
|
||||
if (!sa.getParam("CounterNum").equals("All") && !sa.getParam("CounterNum").equals("Remembered")) {
|
||||
cntToRemove = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("CounterNum"), sa);
|
||||
if (!num.equals("All") && !num.equals("Remembered")) {
|
||||
cntToRemove = AbilityUtils.calculateAmount(sa.getHostCard(), num, sa);
|
||||
}
|
||||
|
||||
CounterType counterType = null;
|
||||
|
||||
try {
|
||||
counterType = AbilityUtils.getCounterType(type, sa);
|
||||
} catch (Exception e) {
|
||||
if (!type.matches("Any")) {
|
||||
if (!type.equals("Any") && !type.equals("All")) {
|
||||
try {
|
||||
counterType = AbilityUtils.getCounterType(type, sa);
|
||||
} catch (Exception e) {
|
||||
System.out.println("Counter type doesn't match, nor does an SVar exist with the type name.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
boolean rememberRemoved = sa.hasParam("RememberRemoved");
|
||||
|
||||
boolean rememberRemoved = false;
|
||||
if (sa.hasParam("RememberRemoved")) {
|
||||
rememberRemoved = true;
|
||||
}
|
||||
for (final Player tgtPlayer : getTargetPlayers(sa)) {
|
||||
// Removing energy
|
||||
if ((tgt == null) || tgtPlayer.canBeTargetedBy(sa)) {
|
||||
if (sa.getParam("CounterNum").equals("All")) {
|
||||
if (!sa.usesTargeting() || tgtPlayer.canBeTargetedBy(sa)) {
|
||||
if (num.equals("All")) {
|
||||
cntToRemove = tgtPlayer.getCounters(counterType);
|
||||
}
|
||||
tgtPlayer.subtractCounter(counterType, cntToRemove);
|
||||
@@ -97,9 +100,14 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
for (final Card tgtCard : getTargetCards(sa)) {
|
||||
if ((tgt == null) || tgtCard.canBeTargetedBy(sa)) {
|
||||
if (!sa.usesTargeting() || tgtCard.canBeTargetedBy(sa)) {
|
||||
final Zone zone = game.getZoneOf(tgtCard);
|
||||
if (sa.getParam("CounterNum").equals("All")) {
|
||||
if (type.equals("All")) {
|
||||
for (Map.Entry<CounterType, Integer> e : tgtCard.getCounters().entrySet()) {
|
||||
tgtCard.subtractCounter(e.getKey(), e.getValue());
|
||||
}
|
||||
continue;
|
||||
} else if (num.equals("All")) {
|
||||
cntToRemove = tgtCard.getCounters(counterType);
|
||||
} else if (sa.getParam("CounterNum").equals("Remembered")) {
|
||||
cntToRemove = tgtCard.getCountersAddedBy(card, counterType);
|
||||
@@ -107,13 +115,21 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
|
||||
|
||||
PlayerController pc = sa.getActivatingPlayer().getController();
|
||||
|
||||
if (type.matches("Any")) {
|
||||
if (type.equals("Any")) {
|
||||
while (cntToRemove > 0 && tgtCard.hasCounters()) {
|
||||
final Map<CounterType, Integer> tgtCounters = tgtCard.getCounters();
|
||||
Map<String, Object> params = Maps.newHashMap();
|
||||
params.put("Target", tgtCard);
|
||||
|
||||
CounterType chosenType = pc.chooseCounterType(ImmutableList.copyOf(tgtCounters.keySet()), sa, "Select type of counters to remove");
|
||||
String prompt = "Select the number of " + chosenType.getName() + " counters to remove";
|
||||
int chosenAmount = pc.chooseNumber(sa, prompt, 1, Math.min(cntToRemove, tgtCounters.get(chosenType)));
|
||||
String prompt = "Select type of counters to remove";
|
||||
CounterType chosenType = pc.chooseCounterType(
|
||||
ImmutableList.copyOf(tgtCounters.keySet()), sa, prompt, params);
|
||||
prompt = "Select the number of " + chosenType.getName() + " counters to remove";
|
||||
int max = Math.min(cntToRemove, tgtCounters.get(chosenType));
|
||||
params = Maps.newHashMap();
|
||||
params.put("Target", tgtCard);
|
||||
params.put("CounterType", chosenType);
|
||||
int chosenAmount = pc.chooseNumber(sa, prompt, 1, max, params);
|
||||
|
||||
tgtCard.subtractCounter(chosenType, chosenAmount);
|
||||
if (rememberRemoved) {
|
||||
@@ -124,14 +140,19 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
|
||||
cntToRemove -= chosenAmount;
|
||||
}
|
||||
} else {
|
||||
cntToRemove = Math.min(cntToRemove, tgtCard.getCounters(counterType));
|
||||
|
||||
if (zone.is(ZoneType.Battlefield) || zone.is(ZoneType.Exile)) {
|
||||
if (sa.hasParam("UpTo"))
|
||||
cntToRemove = pc.chooseNumber(sa, "Select the number of " + type + " counters to remove", 0, cntToRemove);
|
||||
if (sa.hasParam("UpTo")) {
|
||||
Map<String, Object> params = Maps.newHashMap();
|
||||
params.put("Target", tgtCard);
|
||||
params.put("CounterType", type);
|
||||
String title = "Select the number of " + type + " counters to remove";
|
||||
cntToRemove = pc.chooseNumber(sa, title, 0, cntToRemove, params);
|
||||
}
|
||||
|
||||
}
|
||||
if (rememberRemoved) {
|
||||
if (cntToRemove > tgtCard.getCounters(counterType)) {
|
||||
cntToRemove = tgtCard.getCounters(counterType);
|
||||
}
|
||||
for (int i = 0; i < cntToRemove; i++) {
|
||||
card.addRemembered(Pair.of(counterType, i));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user