mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-17 11:18:01 +00:00
CountersMultiply: add new API type for double counters on cards
remove it from RepeatEachAi add more AI logic into it. it does support Strive Spell
This commit is contained in:
2
.gitattributes
vendored
2
.gitattributes
vendored
@@ -68,6 +68,7 @@ forge-ai/src/main/java/forge/ai/ability/CopyPermanentAi.java -text
|
||||
forge-ai/src/main/java/forge/ai/ability/CounterAi.java -text
|
||||
forge-ai/src/main/java/forge/ai/ability/CountersAi.java svneol=native#text/plain
|
||||
forge-ai/src/main/java/forge/ai/ability/CountersMoveAi.java -text
|
||||
forge-ai/src/main/java/forge/ai/ability/CountersMultiplyAi.java -text svneol=unset#text/plain
|
||||
forge-ai/src/main/java/forge/ai/ability/CountersProliferateAi.java -text
|
||||
forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java -text
|
||||
forge-ai/src/main/java/forge/ai/ability/CountersPutAllAi.java -text
|
||||
@@ -369,6 +370,7 @@ forge-game/src/main/java/forge/game/ability/effects/CopyPermanentEffect.java -te
|
||||
forge-game/src/main/java/forge/game/ability/effects/CopySpellAbilityEffect.java -text
|
||||
forge-game/src/main/java/forge/game/ability/effects/CounterEffect.java -text
|
||||
forge-game/src/main/java/forge/game/ability/effects/CountersMoveEffect.java -text
|
||||
forge-game/src/main/java/forge/game/ability/effects/CountersMultiplyEffect.java -text svneol=unset#text/plain
|
||||
forge-game/src/main/java/forge/game/ability/effects/CountersNoteEffect.java -text
|
||||
forge-game/src/main/java/forge/game/ability/effects/CountersProliferateEffect.java -text
|
||||
forge-game/src/main/java/forge/game/ability/effects/CountersPutAllEffect.java -text
|
||||
|
||||
@@ -43,7 +43,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.zone.MagicStack;
|
||||
import forge.game.zone.ZoneType;
|
||||
@@ -1381,9 +1380,8 @@ public class ComputerUtilCard {
|
||||
public static boolean canPumpAgainstRemoval(Player ai, SpellAbility sa) {
|
||||
final List<GameObject> objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa, true);
|
||||
final CardCollection threatenedTargets = new CardCollection();
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
|
||||
if (tgt == null) {
|
||||
if (!sa.usesTargeting()) {
|
||||
final List<Card> cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||
for (final Card card : cards) {
|
||||
if (objects.contains(card)) {
|
||||
@@ -1393,9 +1391,8 @@ public class ComputerUtilCard {
|
||||
// For pumps without targeting restrictions, just return immediately until this is fleshed out.
|
||||
return false;
|
||||
}
|
||||
CardCollection targetables = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa);
|
||||
|
||||
CardCollection targetables = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa);
|
||||
targetables = CardLists.getTargetableCards(targetables, sa);
|
||||
targetables = ComputerUtil.getSafeTargets(ai, sa, targetables);
|
||||
for (final Card c : targetables) {
|
||||
if (objects.contains(c)) {
|
||||
@@ -1405,13 +1402,14 @@ public class ComputerUtilCard {
|
||||
if (!threatenedTargets.isEmpty()) {
|
||||
ComputerUtilCard.sortByEvaluateCreature(threatenedTargets);
|
||||
for (Card c : threatenedTargets) {
|
||||
sa.getTargets().add(c);
|
||||
if (sa.getTargets().getNumTargeted() >= tgt.getMaxTargets(sa.getHostCard(), sa)) {
|
||||
break;
|
||||
if (sa.canAddMoreTarget()) {
|
||||
sa.getTargets().add(c);
|
||||
if (!sa.canAddMoreTarget()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sa.getTargets().getNumTargeted() > tgt.getMaxTargets(sa.getHostCard(), sa)
|
||||
|| sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||
if (!sa.isTargetNumberValid()) {
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -88,6 +88,7 @@ public enum SpellApiToAi {
|
||||
.put(ApiType.Mill, MillAi.class)
|
||||
.put(ApiType.MoveCounter, CountersMoveAi.class)
|
||||
.put(ApiType.MultiplePiles, CannotPlayAi.class)
|
||||
.put(ApiType.MultiplyCounter, CountersMultiplyAi.class)
|
||||
.put(ApiType.MustAttack, MustAttackAi.class)
|
||||
.put(ApiType.MustBlock, MustBlockAi.class)
|
||||
.put(ApiType.NameCard, ChooseCardNameAi.class)
|
||||
|
||||
212
forge-ai/src/main/java/forge/ai/ability/CountersMultiplyAi.java
Normal file
212
forge-ai/src/main/java/forge/ai/ability/CountersMultiplyAi.java
Normal file
@@ -0,0 +1,212 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
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.zone.ZoneType;
|
||||
|
||||
public class CountersMultiplyAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
||||
final CounterType counterType = getCounterType(sa);
|
||||
|
||||
if (!sa.usesTargeting()) {
|
||||
// defined are mostly Self or Creatures you control
|
||||
CardCollection list = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||
|
||||
list = CardLists.filter(list, new Predicate<Card>() {
|
||||
|
||||
@Override
|
||||
public boolean apply(Card c) {
|
||||
if (!c.hasCounters()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (counterType != null) {
|
||||
if (c.getCounters(counterType) <= 0) {
|
||||
return false;
|
||||
}
|
||||
if (!c.canReceiveCounters(counterType)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
for (Map.Entry<CounterType, Integer> e : c.getCounters().entrySet()) {
|
||||
// has negative counter it would double
|
||||
if (ComputerUtil.isNegativeCounter(e.getKey(), c)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
if (list.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return setTargets(ai, sa);
|
||||
}
|
||||
|
||||
return super.checkApiLogic(ai, sa);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
|
||||
final CounterType counterType = getCounterType(sa);
|
||||
|
||||
if (!CounterType.P1P1.equals(counterType) && counterType != null) {
|
||||
if (!sa.hasParam("ActivationPhases")) {
|
||||
// Don't use non P1P1/M1M1 counters before main 2 if possible
|
||||
if (ph.getPhase().isBefore(PhaseType.MAIN2) && !ComputerUtil.castSpellInMain1(ai, sa)) {
|
||||
return false;
|
||||
}
|
||||
if (ph.isPlayerTurn(ai) && !isSorcerySpeed(sa)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ComputerUtil.waitForBlocking(sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
if (sa.usesTargeting() && !setTargets(ai, sa) && !mandatory) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private CounterType getCounterType(SpellAbility sa) {
|
||||
if (sa.hasParam("CounterType")) {
|
||||
try {
|
||||
return AbilityUtils.getCounterType(sa.getParam("CounterType"), sa);
|
||||
} catch (Exception e) {
|
||||
System.out.println("Counter type doesn't match, nor does an SVar exist with the type name.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean setTargets(Player ai, SpellAbility sa) {
|
||||
final CounterType counterType = getCounterType(sa);
|
||||
|
||||
final Game game = ai.getGame();
|
||||
|
||||
CardCollection list = CardLists.getTargetableCards(game.getCardsIn(ZoneType.Battlefield), sa);
|
||||
|
||||
// pre filter targetable cards with counters and can receive one of
|
||||
// them
|
||||
list = CardLists.filter(list, new Predicate<Card>() {
|
||||
|
||||
@Override
|
||||
public boolean apply(Card c) {
|
||||
if (!c.hasCounters()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (counterType != null) {
|
||||
if (c.getCounters(counterType) <= 0) {
|
||||
return false;
|
||||
}
|
||||
if (!c.canReceiveCounters(counterType)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
CardCollection aiList = CardLists.filterControlledBy(list, ai);
|
||||
if (!aiList.isEmpty()) {
|
||||
// counter type list to check
|
||||
// first loyalty, then P1P!, then Charge Counter
|
||||
List<CounterType> typeList = Lists.newArrayList(CounterType.LOYALTY, CounterType.P1P1, CounterType.CHARGE);
|
||||
for (CounterType type : typeList) {
|
||||
// enough targets
|
||||
if (!sa.canAddMoreTarget()) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (counterType == null || counterType == type) {
|
||||
addTargetsByCounterType(ai, sa, aiList, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CardCollection oppList = CardLists.filterControlledBy(list, ai.getOpponents());
|
||||
if (!oppList.isEmpty()) {
|
||||
// not enough targets
|
||||
if (sa.canAddMoreTarget()) {
|
||||
final CounterType type = CounterType.M1M1;
|
||||
if (counterType == null || counterType == type) {
|
||||
addTargetsByCounterType(ai, sa, oppList, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// targeting does failed
|
||||
if (!sa.isTargetNumberValid()) {
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void addTargetsByCounterType(final Player ai, final SpellAbility sa, final CardCollection list,
|
||||
final CounterType type) {
|
||||
|
||||
CardCollection newList = CardLists.filter(list, CardPredicates.hasCounter(type));
|
||||
if (newList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
newList.sort(Collections.reverseOrder(CardPredicates.compareByCounterType(type)));
|
||||
while (sa.canAddMoreTarget()) {
|
||||
if (newList.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
Card c = newList.remove(0);
|
||||
sa.getTargets().add(c);
|
||||
|
||||
// check if Spell with Strive is still playable
|
||||
if (sa.isSpell() && sa.getHostCard().hasStartOfKeyword("Strive")) {
|
||||
// if not remove target again and break list
|
||||
if (!ComputerUtilCost.canPayCost(sa, ai)) {
|
||||
sa.getTargets().remove(c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
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.Presets;
|
||||
@@ -14,8 +15,6 @@ import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
public class RepeatEachAi extends SpellAbilityAi {
|
||||
|
||||
@@ -38,21 +37,6 @@ public class RepeatEachAi extends SpellAbilityAi {
|
||||
if (compTokenCreats.size() <= humTokenCreats.size()) {
|
||||
return false;
|
||||
}
|
||||
} else if ("DoubleCounters".equals(logic)) {
|
||||
// TODO Improve this logic, double Planeswalker counters first, then +1/+1 on Useful creatures
|
||||
// Then Charge Counters, then -1/-1 on Opposing Creatures
|
||||
CardCollection perms = new CardCollection(aiPlayer.getCardsIn(ZoneType.Battlefield));
|
||||
perms = CardLists.filter(CardLists.getTargetableCards(perms, sa), new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return c.hasCounters();
|
||||
}
|
||||
});
|
||||
if (perms.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
CardLists.shuffle(perms);
|
||||
sa.setTargetCard(perms.get(0));
|
||||
} else if ("RemoveAllCounters".equals(logic)) {
|
||||
// Break Dark Depths
|
||||
CardCollectionView depthsList = aiPlayer.getCardsIn(ZoneType.Battlefield, "Dark Depths");
|
||||
|
||||
@@ -86,6 +86,7 @@ public enum ApiType {
|
||||
Mill (MillEffect.class),
|
||||
MoveCounter (CountersMoveEffect.class),
|
||||
MultiplePiles (MultiplePilesEffect.class),
|
||||
MultiplyCounter (CountersMultiplyEffect.class),
|
||||
MustAttack (MustAttackEffect.class),
|
||||
MustBlock (MustBlockEffect.class),
|
||||
NameCard (ChooseCardNameEffect.class),
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
package forge.game.ability.effects;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.util.Lang;
|
||||
|
||||
public class CountersMultiplyEffect extends SpellAbilityEffect {
|
||||
|
||||
@Override
|
||||
protected String getStackDescription(SpellAbility sa) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
final CounterType counterType = getCounterType(sa);
|
||||
|
||||
sb.append("Double the number of ");
|
||||
|
||||
if (counterType != null) {
|
||||
sb.append(counterType.getName());
|
||||
sb.append(" counters");
|
||||
} else {
|
||||
sb.append("each kind of counter");
|
||||
}
|
||||
sb.append(" on ");
|
||||
|
||||
sb.append(Lang.joinHomogenous(getTargetCards(sa)));
|
||||
|
||||
sb.append(".");
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
@Override
|
||||
public void resolve(SpellAbility sa) {
|
||||
|
||||
final CounterType counterType = getCounterType(sa);
|
||||
final int n = Integer.valueOf(sa.getParamOrDefault("Multiplier", "2")) - 1;
|
||||
|
||||
for (final Card tgtCard : getTargetCards(sa)) {
|
||||
if (counterType != null) {
|
||||
tgtCard.addCounter(counterType, tgtCard.getCounters(counterType) * n, false);
|
||||
} else {
|
||||
for (Map.Entry<CounterType, Integer> e : tgtCard.getCounters().entrySet()) {
|
||||
tgtCard.addCounter(e.getKey(), e.getValue() * n, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private CounterType getCounterType(SpellAbility sa) {
|
||||
if (sa.hasParam("CounterType")) {
|
||||
try {
|
||||
return AbilityUtils.getCounterType(sa.getParam("CounterType"), sa);
|
||||
} catch (Exception e) {
|
||||
System.out.println("Counter type doesn't match, nor does an SVar exist with the type name.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user