mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-20 20:58:03 +00:00
CountersPutOrRemoveEffect: refactor to use better methods
CountersPutOrRemoveAI: use better logic to check when its good to remove a counter and when not PlayerController: add chooseBinary with Params, use it for AddOrRemove. no need for chooseAndRemoveOrPutCounter
This commit is contained in:
@@ -496,6 +496,24 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* (non-Javadoc)
|
||||||
|
*
|
||||||
|
* @see
|
||||||
|
* forge.game.player.PlayerController#chooseBinary(forge.game.spellability.
|
||||||
|
* SpellAbility, java.lang.String,
|
||||||
|
* forge.game.player.PlayerController.BinaryChoiceType, java.util.Map)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean chooseBinary(SpellAbility sa, String question, BinaryChoiceType kindOfChoice,
|
||||||
|
Map<String, Object> params) {
|
||||||
|
ApiType api = sa.getApi();
|
||||||
|
if (null == api) {
|
||||||
|
throw new InvalidParameterException("SA is not api-based, this is not supported yet");
|
||||||
|
}
|
||||||
|
return SpellApiToAi.Converter.get(api).chooseBinary(kindOfChoice, sa, params);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Card chooseProtectionShield(GameEntity entityBeingDamaged, List<String> options, Map<String, Card> choiceMap) {
|
public Card chooseProtectionShield(GameEntity entityBeingDamaged, List<String> options, Map<String, Card> choiceMap) {
|
||||||
int i = MyRandom.getRandom().nextInt(options.size());
|
int i = MyRandom.getRandom().nextInt(options.size());
|
||||||
@@ -522,46 +540,6 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
return sa.getChosenList();
|
return sa.getChosenList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Pair<CounterType,String> chooseAndRemoveOrPutCounter(Card cardWithCounter) {
|
|
||||||
if (!cardWithCounter.hasCounters()) {
|
|
||||||
System.out.println("chooseCounterType was reached with a card with no counters on it. Consider filtering this card out earlier");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
final Player controller = cardWithCounter.getController();
|
|
||||||
final List<Player> enemies = player.getOpponents();
|
|
||||||
final List<Player> allies = player.getAllies();
|
|
||||||
allies.add(player);
|
|
||||||
|
|
||||||
List<CounterType> countersToIncrease = new ArrayList<CounterType>();
|
|
||||||
List<CounterType> countersToDecrease = new ArrayList<CounterType>();
|
|
||||||
|
|
||||||
for (final CounterType counter : cardWithCounter.getCounters().keySet()) {
|
|
||||||
if ((!ComputerUtil.isNegativeCounter(counter, cardWithCounter) && allies.contains(controller))
|
|
||||||
|| (ComputerUtil.isNegativeCounter(counter, cardWithCounter) && enemies.contains(controller))) {
|
|
||||||
countersToIncrease.add(counter);
|
|
||||||
} else {
|
|
||||||
countersToDecrease.add(counter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!countersToIncrease.isEmpty()) {
|
|
||||||
int random = MyRandom.getRandom().nextInt(countersToIncrease.size());
|
|
||||||
return new ImmutablePair<CounterType,String>(countersToIncrease.get(random),"Put");
|
|
||||||
}
|
|
||||||
else if (!countersToDecrease.isEmpty()) {
|
|
||||||
int random = MyRandom.getRandom().nextInt(countersToDecrease.size());
|
|
||||||
return new ImmutablePair<CounterType,String>(countersToDecrease.get(random),"Remove");
|
|
||||||
}
|
|
||||||
|
|
||||||
// shouldn't reach here but just in case, remove random counter
|
|
||||||
List<CounterType> countersOnCard = new ArrayList<CounterType>();
|
|
||||||
int random = MyRandom.getRandom().nextInt(countersOnCard.size());
|
|
||||||
return new ImmutablePair<CounterType,String>(countersOnCard.get(random),"Remove");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte chooseColorAllowColorless(String message, Card card, ColorSet colors) {
|
public byte chooseColorAllowColorless(String message, Card card, ColorSet colors) {
|
||||||
final String c = ComputerUtilCard.getMostProminentColor(player.getCardsIn(ZoneType.Hand));
|
final String c = ComputerUtilCard.getMostProminentColor(player.getCardsIn(ZoneType.Hand));
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import forge.game.phase.PhaseHandler;
|
|||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.player.PlayerActionConfirmMode;
|
import forge.game.player.PlayerActionConfirmMode;
|
||||||
|
import forge.game.player.PlayerController.BinaryChoiceType;
|
||||||
import forge.game.spellability.AbilitySub;
|
import forge.game.spellability.AbilitySub;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.util.MyRandom;
|
import forge.util.MyRandom;
|
||||||
@@ -329,4 +330,8 @@ public abstract class SpellAbilityAi {
|
|||||||
public CounterType chooseCounterType(List<CounterType> options, SpellAbility sa, Map<String, Object> params) {
|
public CounterType chooseCounterType(List<CounterType> options, SpellAbility sa, Map<String, Object> params) {
|
||||||
return Iterables.getFirst(options, null);
|
return Iterables.getFirst(options, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean chooseBinary(BinaryChoiceType kindOfChoice, SpellAbility sa, Map<String, Object> params) {
|
||||||
|
return MyRandom.getRandom().nextBoolean();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,17 +17,24 @@
|
|||||||
*/
|
*/
|
||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import forge.ai.ComputerUtil;
|
||||||
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
|
import forge.game.Game;
|
||||||
|
import forge.game.GlobalRuleChange;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
|
import forge.game.card.CardCollection;
|
||||||
|
import forge.game.card.CardCollectionView;
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
|
import forge.game.card.CardPredicates;
|
||||||
|
import forge.game.card.CounterType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
|
import forge.game.player.PlayerController.BinaryChoiceType;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
import forge.game.spellability.TargetRestrictions;
|
||||||
import forge.game.zone.ZoneType;
|
|
||||||
import forge.util.MyRandom;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
@@ -39,54 +46,252 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public class CountersPutOrRemoveAi extends SpellAbilityAi {
|
public class CountersPutOrRemoveAi extends SpellAbilityAi {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* (non-Javadoc)
|
||||||
|
*
|
||||||
|
* @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player,
|
||||||
|
* forge.game.spellability.SpellAbility)
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
||||||
return doTriggerAINoCost(ai, sa, false);
|
if (sa.usesTargeting()) {
|
||||||
|
return doTgt(ai, sa, false);
|
||||||
|
}
|
||||||
|
return super.checkApiLogic(ai, sa);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean doTgt(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
|
final Card source = sa.getHostCard();
|
||||||
|
final Game game = ai.getGame();
|
||||||
|
|
||||||
|
final int amount = Integer.valueOf(sa.getParam("CounterNum"));
|
||||||
|
|
||||||
|
// remove counter with Time might use Exile Zone too
|
||||||
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
|
// need to targetable
|
||||||
|
CardCollection list = CardLists.getTargetableCards(game.getCardsIn(tgt.getZone()), sa);
|
||||||
|
|
||||||
|
if (list.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sa.hasParam("CounterType")) {
|
||||||
|
// currently only Jhoira's Timebug
|
||||||
|
final CounterType type = CounterType.valueOf(sa.getParam("CounterType"));
|
||||||
|
|
||||||
|
CardCollection countersList = CardLists.filter(list, CardPredicates.hasCounter(type, amount));
|
||||||
|
|
||||||
|
if (countersList.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// currently can only target cards you control or you own
|
||||||
|
final Card best = ComputerUtilCard.getBestAI(countersList);
|
||||||
|
|
||||||
|
// currently both cards only has one target
|
||||||
|
sa.getTargets().add(best);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
// currently only Clockspinning
|
||||||
|
boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule);
|
||||||
|
|
||||||
|
// logic to remove some counter
|
||||||
|
CardCollection countersList = CardLists.filter(list, CardPredicates.hasCounters());
|
||||||
|
|
||||||
|
if (!countersList.isEmpty()) {
|
||||||
|
|
||||||
|
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
|
||||||
|
CardCollectionView depthsList = CardLists.filter(countersList,
|
||||||
|
CardPredicates.nameEquals("Dark Depths"), CardPredicates.hasCounter(CounterType.ICE));
|
||||||
|
|
||||||
|
if (!depthsList.isEmpty()) {
|
||||||
|
sa.getTargets().add(depthsList.getFirst());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get rid of Planeswalkers, currently only if it can kill them
|
||||||
|
// with one touch
|
||||||
|
CardCollection planeswalkerList = CardLists.filter(
|
||||||
|
CardLists.filterControlledBy(countersList, ai.getOpponents()),
|
||||||
|
CardPredicates.Presets.PLANEWALKERS,
|
||||||
|
CardPredicates.hasLessCounter(CounterType.LOYALTY, amount));
|
||||||
|
|
||||||
|
if (!planeswalkerList.isEmpty()) {
|
||||||
|
sa.getTargets().add(ComputerUtilCard.getBestPlaneswalkerAI(planeswalkerList));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// do as M1M1 part
|
||||||
|
CardCollection aiList = CardLists.filterControlledBy(countersList, 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(countersList, 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 (!ComputerUtil.isUselessCounter(aType)) {
|
||||||
|
// whould remove positive counter
|
||||||
|
if (best.getCounters(aType) <= amount) {
|
||||||
|
sa.getTargets().add(best);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mandatory) {
|
||||||
|
sa.getTargets().add(ComputerUtilCard.getWorstAI(list));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
return doTgt(ai, sa, true);
|
||||||
// if Defined, don't worry about targeting
|
}
|
||||||
|
|
||||||
List<ZoneType> zones = ZoneType.listValueOf(sa.getParamOrDefault("TgtZones", "Battlefield"));
|
/*
|
||||||
List<Card> validCards = CardLists.getValidCards(ai.getGame().getCardsIn(zones),
|
* (non-Javadoc)
|
||||||
tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getHostCard(), sa);
|
*
|
||||||
|
* @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 (validCards.isEmpty()) {
|
if (options.size() > 1) {
|
||||||
return false;
|
final Player ai = sa.getActivatingPlayer();
|
||||||
}
|
final Game game = ai.getGame();
|
||||||
|
|
||||||
List<Card> cWithCounters = CardLists.filter(validCards, new Predicate<Card>() {
|
boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule);
|
||||||
@Override
|
|
||||||
public boolean apply(final Card crd) {
|
Card tgt = (Card) params.get("Target");
|
||||||
return crd.hasCounters();
|
|
||||||
|
// planeswalker has high priority for loyalty counters
|
||||||
|
if (tgt.isPlaneswalker() && options.contains(CounterType.LOYALTY)) {
|
||||||
|
return CounterType.LOYALTY;
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
if (cWithCounters.isEmpty()) {
|
if (tgt.getController().isOpponentOf(ai)) {
|
||||||
if (mandatory) {
|
// creatures with BaseToughness below or equal zero might be
|
||||||
cWithCounters = validCards;
|
// killed if their counters are removed
|
||||||
|
if (tgt.isCreature() && tgt.getBaseToughness() <= 0) {
|
||||||
|
if (options.contains(CounterType.P1P1)) {
|
||||||
|
return CounterType.P1P1;
|
||||||
|
} else if (options.contains(CounterType.M1M1)) {
|
||||||
|
return CounterType.M1M1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fallback logic, select positive counter to remove it
|
||||||
|
for (final CounterType type : options) {
|
||||||
|
if (!ComputerUtil.isNegativeCounter(type, tgt)) {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return false;
|
// this counters are treat first to be removed
|
||||||
|
if ("Dark Depths".equals(tgt.getName()) && options.contains(CounterType.ICE)) {
|
||||||
|
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
|
||||||
|
return CounterType.ICE;
|
||||||
|
}
|
||||||
|
} else if (tgt.hasKeyword("Undying") && options.contains(CounterType.P1P1)) {
|
||||||
|
return CounterType.P1P1;
|
||||||
|
} else if (tgt.hasKeyword("Persist") && options.contains(CounterType.M1M1)) {
|
||||||
|
return CounterType.M1M1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// fallback logic, select positive counter to add more
|
||||||
|
for (final CounterType type : options) {
|
||||||
|
if (!ComputerUtil.isNegativeCounter(type, tgt)) {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
|
return super.chooseCounterType(options, sa, params);
|
||||||
Card targetCard = null;
|
}
|
||||||
if (cWithCounters.isEmpty() && ((sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa))
|
|
||||||
|| (sa.getTargets().getNumTargeted() == 0))) {
|
/*
|
||||||
sa.resetTargets();
|
* (non-Javadoc)
|
||||||
return false;
|
*
|
||||||
|
* @see
|
||||||
|
* forge.ai.SpellAbilityAi#chooseBinary(forge.game.player.PlayerController.
|
||||||
|
* BinaryChoiceType, forge.game.spellability.SpellAbility, java.util.Map)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean chooseBinary(BinaryChoiceType kindOfChoice, SpellAbility sa, Map<String, Object> params) {
|
||||||
|
if (kindOfChoice.equals(BinaryChoiceType.AddOrRemove)) {
|
||||||
|
final Player ai = sa.getActivatingPlayer();
|
||||||
|
final Game game = ai.getGame();
|
||||||
|
Card tgt = (Card) params.get("Target");
|
||||||
|
CounterType type = (CounterType) params.get("CounterType");
|
||||||
|
|
||||||
|
boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule);
|
||||||
|
|
||||||
|
if (tgt.getController().isOpponentOf(ai)) {
|
||||||
|
if (type.equals(CounterType.LOYALTY) && tgt.isPlaneswalker()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ComputerUtil.isNegativeCounter(type, tgt);
|
||||||
|
} else {
|
||||||
|
if (type.equals(CounterType.ICE) && "Dark Depths".equals(tgt.getName())) {
|
||||||
|
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (type.equals(CounterType.M1M1) && tgt.hasKeyword("Persist")) {
|
||||||
|
return false;
|
||||||
|
} else if (type.equals(CounterType.P1P1) && tgt.hasKeyword("Undying")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !ComputerUtil.isNegativeCounter(type, tgt);
|
||||||
}
|
}
|
||||||
|
|
||||||
int random = MyRandom.getRandom().nextInt(cWithCounters.size());
|
|
||||||
targetCard = cWithCounters.get(random);
|
|
||||||
|
|
||||||
sa.getTargets().add(targetCard);
|
|
||||||
cWithCounters.remove(targetCard);
|
|
||||||
}
|
}
|
||||||
return true;
|
return super.chooseBinary(kindOfChoice, sa, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,22 @@
|
|||||||
package forge.game.ability.effects;
|
package forge.game.ability.effects;
|
||||||
|
|
||||||
import forge.game.GameObject;
|
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.ability.SpellAbilityEffect;
|
import forge.game.ability.SpellAbilityEffect;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CounterType;
|
import forge.game.card.CounterType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
|
import forge.game.player.PlayerController;
|
||||||
|
import forge.game.player.PlayerController.BinaryChoiceType;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.zone.Zone;
|
import forge.game.zone.Zone;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.Lang;
|
import forge.util.Lang;
|
||||||
import org.apache.commons.lang3.tuple.Pair;
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* API for adding to or subtracting from existing counters on a target.
|
* API for adding to or subtracting from existing counters on a target.
|
||||||
@@ -23,10 +27,16 @@ public class CountersPutOrRemoveEffect extends SpellAbilityEffect {
|
|||||||
protected String getStackDescription(SpellAbility sa) {
|
protected String getStackDescription(SpellAbility sa) {
|
||||||
final StringBuilder sb = new StringBuilder();
|
final StringBuilder sb = new StringBuilder();
|
||||||
sb.append(sa.getActivatingPlayer().getName());
|
sb.append(sa.getActivatingPlayer().getName());
|
||||||
sb.append(" removes a counter from or puts another of those counters on ");
|
|
||||||
|
|
||||||
final List<GameObject> targets = getTargets(sa);
|
if (sa.hasParam("CounterType")) {
|
||||||
sb.append(Lang.joinHomogenous(targets));
|
CounterType ctype = CounterType.valueOf(sa.getParam("CounterType"));
|
||||||
|
sb.append(" removes a ").append(ctype.getName());
|
||||||
|
sb.append(" counter from or put another ").append(ctype.getName()).append(" counter on ");
|
||||||
|
} else {
|
||||||
|
sb.append(" removes a counter from or puts another of those counters on ");
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append(Lang.joinHomogenous(getTargets(sa)));
|
||||||
|
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
@@ -34,28 +44,47 @@ public class CountersPutOrRemoveEffect extends SpellAbilityEffect {
|
|||||||
@Override
|
@Override
|
||||||
public void resolve(SpellAbility sa) {
|
public void resolve(SpellAbility sa) {
|
||||||
final Player activator = sa.getActivatingPlayer();
|
final Player activator = sa.getActivatingPlayer();
|
||||||
final int counterAmount = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("CounterNum"), sa);
|
final Card source = sa.getHostCard();
|
||||||
|
final int counterAmount = AbilityUtils.calculateAmount(source, sa.getParam("CounterNum"), sa);
|
||||||
|
|
||||||
List<Card> tgtCards = getDefinedCardsOrTargeted(sa);
|
CounterType ctype = null;
|
||||||
|
if (sa.hasParam("CounterType")) {
|
||||||
|
ctype = CounterType.valueOf(sa.getParam("CounterType"));
|
||||||
|
}
|
||||||
|
|
||||||
for (final Card tgtCard : tgtCards) {
|
PlayerController pc = activator.getController();
|
||||||
|
|
||||||
|
for (final Card tgtCard : getDefinedCardsOrTargeted(sa)) {
|
||||||
if (!sa.usesTargeting() || tgtCard.canBeTargetedBy(sa)) {
|
if (!sa.usesTargeting() || tgtCard.canBeTargetedBy(sa)) {
|
||||||
if (tgtCard.hasCounters()) {
|
if (tgtCard.hasCounters()) {
|
||||||
Pair<CounterType,String> selection = activator.getController().chooseAndRemoveOrPutCounter(tgtCard);
|
|
||||||
final CounterType chosenCounter = selection.getLeft();
|
final Map<CounterType, Integer> tgtCounters = tgtCard.getCounters();
|
||||||
final boolean putCounter = selection.getRight().startsWith("Put");
|
Map<String, Object> params = Maps.newHashMap();
|
||||||
|
params.put("Target", tgtCard);
|
||||||
|
|
||||||
|
List<CounterType> list = Lists.newArrayList(tgtCounters.keySet());
|
||||||
|
if (ctype != null) {
|
||||||
|
list = Lists.newArrayList(ctype);
|
||||||
|
}
|
||||||
|
|
||||||
|
String prompt = "Select type of counters to add or remove";
|
||||||
|
CounterType chosenType = pc.chooseCounterType(list, sa, prompt, params);
|
||||||
|
|
||||||
|
params.put("CounterType", chosenType);
|
||||||
|
prompt = "What to do with that '" + chosenType.getName() + "' counter ";
|
||||||
|
Boolean putCounter = pc.chooseBinary(sa, prompt, BinaryChoiceType.AddOrRemove, params);
|
||||||
|
|
||||||
if (putCounter) {
|
if (putCounter) {
|
||||||
// Put another of the chosen counter on card
|
// Put another of the chosen counter on card
|
||||||
final Zone zone = tgtCard.getGame().getZoneOf(tgtCard);
|
final Zone zone = tgtCard.getGame().getZoneOf(tgtCard);
|
||||||
if (zone == null || zone.is(ZoneType.Battlefield) || zone.is(ZoneType.Stack)) {
|
if (zone == null || zone.is(ZoneType.Battlefield) || zone.is(ZoneType.Stack)) {
|
||||||
tgtCard.addCounter(chosenCounter, counterAmount, true);
|
tgtCard.addCounter(chosenType, counterAmount, true);
|
||||||
} else {
|
} else {
|
||||||
// adding counters to something like re-suspend cards
|
// adding counters to something like re-suspend cards
|
||||||
tgtCard.addCounter(chosenCounter, counterAmount, false);
|
tgtCard.addCounter(chosenType, counterAmount, false);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
tgtCard.subtractCounter(chosenCounter, counterAmount);
|
tgtCard.subtractCounter(chosenType, counterAmount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ import forge.game.spellability.AbilitySub;
|
|||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.SpellAbilityStackInstance;
|
import forge.game.spellability.SpellAbilityStackInstance;
|
||||||
import forge.game.spellability.TargetChoices;
|
import forge.game.spellability.TargetChoices;
|
||||||
import forge.game.trigger.Trigger;
|
|
||||||
import forge.game.trigger.WrappedAbility;
|
import forge.game.trigger.WrappedAbility;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.item.PaperCard;
|
import forge.item.PaperCard;
|
||||||
@@ -69,6 +68,7 @@ public abstract class PlayerController {
|
|||||||
UntapOrLeaveTapped,
|
UntapOrLeaveTapped,
|
||||||
UntapTimeVault,
|
UntapTimeVault,
|
||||||
LeftOrRight,
|
LeftOrRight,
|
||||||
|
AddOrRemove,
|
||||||
}
|
}
|
||||||
|
|
||||||
protected final Game game;
|
protected final Game game;
|
||||||
@@ -164,7 +164,6 @@ public abstract class PlayerController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public abstract Object vote(SpellAbility sa, String prompt, List<Object> options, ListMultimap<Object, Player> votes);
|
public abstract Object vote(SpellAbility sa, String prompt, List<Object> options, ListMultimap<Object, Player> votes);
|
||||||
public abstract Pair<CounterType,String> chooseAndRemoveOrPutCounter(Card cardWithCounter);
|
|
||||||
public abstract boolean confirmReplacementEffect(ReplacementEffect replacementEffect, SpellAbility effectSA, String question);
|
public abstract boolean confirmReplacementEffect(ReplacementEffect replacementEffect, SpellAbility effectSA, String question);
|
||||||
public abstract CardCollectionView getCardsToMulligan(Player firstPlayer);
|
public abstract CardCollectionView getCardsToMulligan(Player firstPlayer);
|
||||||
|
|
||||||
@@ -182,8 +181,10 @@ public abstract class PlayerController {
|
|||||||
return chooseNumber(sa, string, min, max);
|
return chooseNumber(sa, string, min, max);
|
||||||
};
|
};
|
||||||
|
|
||||||
public final boolean chooseBinary(SpellAbility sa, String question, BinaryChoiceType kindOfChoice) { return chooseBinary(sa, question, kindOfChoice, null); }
|
public final boolean chooseBinary(SpellAbility sa, String question, BinaryChoiceType kindOfChoice) { return chooseBinary(sa, question, kindOfChoice, (Boolean) null); }
|
||||||
public abstract boolean chooseBinary(SpellAbility sa, String question, BinaryChoiceType kindOfChoice, Boolean defaultChioce);
|
public abstract boolean chooseBinary(SpellAbility sa, String question, BinaryChoiceType kindOfChoice, Boolean defaultChioce);
|
||||||
|
public boolean chooseBinary(SpellAbility sa, String question, BinaryChoiceType kindOfChoice, Map<String, Object> params) { return chooseBinary(sa, question, kindOfChoice); }
|
||||||
|
|
||||||
public abstract boolean chooseFlipResult(SpellAbility sa, Player flipper, boolean[] results, boolean call);
|
public abstract boolean chooseFlipResult(SpellAbility sa, Player flipper, boolean[] results, boolean call);
|
||||||
public abstract Card chooseProtectionShield(GameEntity entityBeingDamaged, List<String> options, Map<String, Card> choiceMap);
|
public abstract Card chooseProtectionShield(GameEntity entityBeingDamaged, List<String> options, Map<String, Card> choiceMap);
|
||||||
|
|
||||||
|
|||||||
@@ -57,7 +57,6 @@ import forge.game.spellability.Spell;
|
|||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.SpellAbilityStackInstance;
|
import forge.game.spellability.SpellAbilityStackInstance;
|
||||||
import forge.game.spellability.TargetChoices;
|
import forge.game.spellability.TargetChoices;
|
||||||
import forge.game.trigger.Trigger;
|
|
||||||
import forge.game.trigger.WrappedAbility;
|
import forge.game.trigger.WrappedAbility;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.gamesimulationtests.util.card.CardSpecification;
|
import forge.gamesimulationtests.util.card.CardSpecification;
|
||||||
@@ -294,11 +293,6 @@ public class PlayerControllerForTests extends PlayerController {
|
|||||||
return chooseItem(manaChoices);
|
return chooseItem(manaChoices);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Pair<CounterType, String> chooseAndRemoveOrPutCounter(Card cardWithCounter) {
|
|
||||||
throw new IllegalStateException("Erring on the side of caution here...");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean confirmReplacementEffect(ReplacementEffect replacementEffect, SpellAbility effectSA, String question) {
|
public boolean confirmReplacementEffect(ReplacementEffect replacementEffect, SpellAbility effectSA, String question) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -2,9 +2,7 @@ Name:Jhoira's Timebug
|
|||||||
ManaCost:2
|
ManaCost:2
|
||||||
Types:Artifact Creature Insect
|
Types:Artifact Creature Insect
|
||||||
PT:1/2
|
PT:1/2
|
||||||
A:AB$ GenericChoice | Cost$ T | ValidTgts$ Permanent.inZoneBattlefield+YouCtrl,Card.suspended+YouOwn | TgtPrompt$ Choose target permanent you control or suspended card you own | Defined$ You | Choices$ LoseTime,AddTime | TgtZone$ Battlefield,Exile | ConditionDefined$ Targeted | ConditionPresent$ Card.counters_GE1_TIME | ConditionCompare$ GE1 | SpellDescription$ Choose target permanent you control or suspended card you own. If that permanent or card has a time counter on it, you may remove a time counter from it or put another time counter on it.
|
A:AB$ AddOrRemoveCounter | Cost$ T | CounterNum$ 1 | ValidTgts$ Permanent.inZoneBattlefield+YouCtrl,Card.suspended+YouOwn | TgtPrompt$ Choose target permanent you control or suspended card you own | TgtZone$ Battlefield,Exile | ConditionDefined$ Targeted | ConditionPresent$ Card.counters_GE1_TIME | SpellDescription$ Choose target permanent you control or suspended card you own. If that permanent or card has a time counter on it, you may remove a time counter from it or put another time counter on it.
|
||||||
SVar:LoseTime:DB$ RemoveCounter | Defined$ Targeted | TgtPrompt$ Select target permanent or suspended card. | CounterType$ TIME | CounterNum$ 1 | SpellDescription$ Remove a time counter.
|
DeckNeeds:Keyword$Suspend & Keyword$Vanishing
|
||||||
SVar:AddTime:DB$ PutCounter | Defined$ Targeted | TgtPrompt$ Select target permanent with a time counter on it or suspended card. | CounterType$ TIME | CounterNum$ 1 | TgtZone$ Battlefield,Exile | SpellDescription$ Add a time counter.
|
|
||||||
SVar:RemAIDeck:True
|
|
||||||
SVar:Picture:http://www.wizards.com/global/images/magic/general/jhoiras_timebug.jpg
|
SVar:Picture:http://www.wizards.com/global/images/magic/general/jhoiras_timebug.jpg
|
||||||
Oracle:{T}: Choose target permanent you control or suspended card you own. If that permanent or card has a time counter on it, you may remove a time counter from it or put another time counter on it.
|
Oracle:{T}: Choose target permanent you control or suspended card you own. If that permanent or card has a time counter on it, you may remove a time counter from it or put another time counter on it.
|
||||||
|
|||||||
@@ -1009,13 +1009,14 @@ public class PlayerControllerHuman
|
|||||||
public boolean chooseBinary(final SpellAbility sa, final String question, final BinaryChoiceType kindOfChoice, final Boolean defaultVal) {
|
public boolean chooseBinary(final SpellAbility sa, final String question, final BinaryChoiceType kindOfChoice, final Boolean defaultVal) {
|
||||||
final List<String> labels;
|
final List<String> labels;
|
||||||
switch (kindOfChoice) {
|
switch (kindOfChoice) {
|
||||||
case HeadsOrTails: labels = ImmutableList.of("Heads", "Tails"); break;
|
case HeadsOrTails: labels = ImmutableList.of("Heads", "Tails"); break;
|
||||||
case TapOrUntap: labels = ImmutableList.of("Tap", "Untap"); break;
|
case TapOrUntap: labels = ImmutableList.of("Tap", "Untap"); break;
|
||||||
case OddsOrEvens: labels = ImmutableList.of("Odds", "Evens"); break;
|
case OddsOrEvens: labels = ImmutableList.of("Odds", "Evens"); break;
|
||||||
case UntapOrLeaveTapped: labels = ImmutableList.of("Untap", "Leave tapped"); break;
|
case UntapOrLeaveTapped: labels = ImmutableList.of("Untap", "Leave tapped"); break;
|
||||||
case UntapTimeVault: labels = ImmutableList.of("Untap (and skip this turn)", "Leave tapped"); break;
|
case UntapTimeVault: labels = ImmutableList.of("Untap (and skip this turn)", "Leave tapped"); break;
|
||||||
case PlayOrDraw: labels = ImmutableList.of("Play", "Draw"); break;
|
case PlayOrDraw: labels = ImmutableList.of("Play", "Draw"); break;
|
||||||
case LeftOrRight: labels = ImmutableList.of("Left", "Right"); break;
|
case LeftOrRight: labels = ImmutableList.of("Left", "Right"); break;
|
||||||
|
case AddOrRemove: labels = ImmutableList.of("Add Counter", "Remove Counter"); break;
|
||||||
default: labels = ImmutableList.copyOf(kindOfChoice.toString().split("Or"));
|
default: labels = ImmutableList.copyOf(kindOfChoice.toString().split("Or"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1038,24 +1039,6 @@ public class PlayerControllerHuman
|
|||||||
return choiceMap.get(getGui().one(title, options));
|
return choiceMap.get(getGui().one(title, options));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Pair<CounterType,String> chooseAndRemoveOrPutCounter(final Card cardWithCounter) {
|
|
||||||
if (!cardWithCounter.hasCounters()) {
|
|
||||||
System.out.println("chooseCounterType was reached with a card with no counters on it. Consider filtering this card out earlier");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
final String counterChoiceTitle = "Choose a counter type on " + cardWithCounter;
|
|
||||||
final CounterType chosen = getGui().one(counterChoiceTitle, ImmutableList.copyOf(cardWithCounter.getCounters().keySet()));
|
|
||||||
|
|
||||||
final String putOrRemoveTitle = "What to do with that '" + chosen.getName() + "' counter ";
|
|
||||||
final String putString = "Put another " + chosen.getName() + " counter on " + cardWithCounter;
|
|
||||||
final String removeString = "Remove a " + chosen.getName() + " counter from " + cardWithCounter;
|
|
||||||
final String addOrRemove = getGui().one(putOrRemoveTitle, ImmutableList.of(putString, removeString));
|
|
||||||
|
|
||||||
return new ImmutablePair<CounterType,String>(chosen,addOrRemove);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Pair<SpellAbilityStackInstance, GameObject> chooseTarget(final SpellAbility saSpellskite, final List<Pair<SpellAbilityStackInstance, GameObject>> allTargets) {
|
public Pair<SpellAbilityStackInstance, GameObject> chooseTarget(final SpellAbility saSpellskite, final List<Pair<SpellAbilityStackInstance, GameObject>> allTargets) {
|
||||||
if (allTargets.size() < 2) {
|
if (allTargets.size() < 2) {
|
||||||
|
|||||||
Reference in New Issue
Block a user