CountersRemove Effect and AI: update to new scripting

make the AI use of RemoveCounter abilities
This commit is contained in:
Hanmac
2017-01-28 14:43:06 +00:00
parent 02ac27ba64
commit 4b06eaf490
2 changed files with 352 additions and 42 deletions

View File

@@ -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);
}
}

View File

@@ -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));
}