mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-19 12:18:00 +00:00
Merge branch 'master' into 'master'
Reimplement the AI logic for Astral cards again. See merge request core-developers/forge!5696
This commit is contained in:
@@ -21,6 +21,7 @@ import java.util.Arrays;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import forge.game.GameEntity;
|
||||||
import org.apache.commons.lang3.tuple.Pair;
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
@@ -616,6 +617,28 @@ public class SpecialCardAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Goblin Polka Band
|
||||||
|
public static class GoblinPolkaBand {
|
||||||
|
public static boolean consider(final Player ai, final SpellAbility sa) {
|
||||||
|
int maxPotentialTgts = Lists.newArrayList(Iterables.filter(ai.getOpponents().getCreaturesInPlay(), CardPredicates.Presets.UNTAPPED)).size();
|
||||||
|
int maxPotentialPayment = ComputerUtilMana.determineLeftoverMana(sa, ai, "R");
|
||||||
|
|
||||||
|
int numTgts = Math.min(maxPotentialPayment, maxPotentialTgts);
|
||||||
|
if (numTgts == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set Announce
|
||||||
|
sa.getHostCard().setSVar("TgtNum", String.valueOf(numTgts));
|
||||||
|
|
||||||
|
// Simulate random targeting
|
||||||
|
List<GameEntity> validTgts = sa.getTargetRestrictions().getAllCandidates(sa, true);
|
||||||
|
sa.resetTargets();
|
||||||
|
sa.getTargets().addAll(Aggregates.random(validTgts, numTgts));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Guilty Conscience
|
// Guilty Conscience
|
||||||
public static class GuiltyConscience {
|
public static class GuiltyConscience {
|
||||||
public static Card getBestAttachTarget(final Player ai, final SpellAbility sa, final List<Card> list) {
|
public static Card getBestAttachTarget(final Player ai, final SpellAbility sa, final List<Card> list) {
|
||||||
@@ -1157,6 +1180,33 @@ public class SpecialCardAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Power Struggle
|
||||||
|
public static class PowerStruggle {
|
||||||
|
public static boolean considerFirstTarget(final Player ai, final SpellAbility sa) {
|
||||||
|
Card firstTgt = (Card)Aggregates.random(sa.getTargetRestrictions().getAllCandidates(sa, true));
|
||||||
|
if (firstTgt != null) {
|
||||||
|
sa.getTargets().add(firstTgt);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean considerSecondTarget(final Player ai, final SpellAbility sa) {
|
||||||
|
Card firstTgt = sa.getParent().getTargetCard();
|
||||||
|
Iterable<Card> candidates = Iterables.filter(ai.getOpponents().getCardsIn(ZoneType.Battlefield),
|
||||||
|
Predicates.and(CardPredicates.sharesCardTypeWith(firstTgt), CardPredicates.isTargetableBy(sa)));
|
||||||
|
Card secondTgt = Aggregates.random(candidates);
|
||||||
|
if (secondTgt != null) {
|
||||||
|
sa.resetTargets();
|
||||||
|
sa.getTargets().add(secondTgt);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Price of Progress
|
// Price of Progress
|
||||||
public static class PriceOfProgress {
|
public static class PriceOfProgress {
|
||||||
public static boolean consider(final Player ai, final SpellAbility sa) {
|
public static boolean consider(final Player ai, final SpellAbility sa) {
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import forge.game.card.CardPredicates.Presets;
|
|||||||
import forge.game.card.CounterEnumType;
|
import forge.game.card.CounterEnumType;
|
||||||
import forge.game.combat.Combat;
|
import forge.game.combat.Combat;
|
||||||
import forge.game.keyword.Keyword;
|
import forge.game.keyword.Keyword;
|
||||||
|
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.PlayerPredicates;
|
import forge.game.player.PlayerPredicates;
|
||||||
@@ -146,6 +147,16 @@ public class ChooseCardAi extends SpellAbilityAi {
|
|||||||
return checkApiLogic(ai, sa);
|
return checkApiLogic(ai, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected boolean checkPhaseRestrictions(Player ai, SpellAbility sa, PhaseHandler ph) {
|
||||||
|
String aiLogic = sa.getParamOrDefault("AILogic", "");
|
||||||
|
|
||||||
|
if (aiLogic.equals("AtOppEOT")) {
|
||||||
|
return ph.getNextTurn().equals(ai) && ph.is(PhaseType.END_OF_TURN);
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.checkPhaseRestrictions(ai, sa, ph);
|
||||||
|
}
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.card.spellability.SpellAbility, java.util.List, boolean)
|
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.card.spellability.SpellAbility, java.util.List, boolean)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import com.google.common.base.Predicate;
|
|||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
|
import forge.ai.SpecialCardAi;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
@@ -80,6 +81,10 @@ public class ControlExchangeAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
|
|
||||||
|
if ("PowerStruggle".equals(sa.getParam("AILogic"))) {
|
||||||
|
return SpecialCardAi.PowerStruggle.considerSecondTarget(aiPlayer, sa);
|
||||||
|
}
|
||||||
|
|
||||||
// for TrigTwoTargets logic, only get the opponents' cards for the first target
|
// for TrigTwoTargets logic, only get the opponents' cards for the first target
|
||||||
CardCollectionView unfilteredList = "TrigTwoTargets".equals(sa.getParam("AILogic")) ?
|
CardCollectionView unfilteredList = "TrigTwoTargets".equals(sa.getParam("AILogic")) ?
|
||||||
aiPlayer.getOpponents().getCardsIn(ZoneType.Battlefield) :
|
aiPlayer.getOpponents().getCardsIn(ZoneType.Battlefield) :
|
||||||
|
|||||||
@@ -436,6 +436,11 @@ public class PumpAi extends PumpAiBase {
|
|||||||
|
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
|
|
||||||
|
if ("PowerStruggle".equals(sa.getParam("AILogic"))) {
|
||||||
|
return SpecialCardAi.PowerStruggle.considerFirstTarget(ai, sa);
|
||||||
|
}
|
||||||
|
|
||||||
if (sa.hasParam("TargetingPlayer") && sa.getActivatingPlayer().equals(ai) && !sa.isTrigger()) {
|
if (sa.hasParam("TargetingPlayer") && sa.getActivatingPlayer().equals(ai) && !sa.isTrigger()) {
|
||||||
Player targetingPlayer = AbilityUtils.getDefinedPlayers(source, sa.getParam("TargetingPlayer"), sa).get(0);
|
Player targetingPlayer = AbilityUtils.getDefinedPlayers(source, sa.getParam("TargetingPlayer"), sa).get(0);
|
||||||
sa.setTargetingPlayer(targetingPlayer);
|
sa.setTargetingPlayer(targetingPlayer);
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import forge.ai.AiController;
|
import forge.ai.*;
|
||||||
import forge.ai.AiProps;
|
|
||||||
import forge.ai.ComputerUtil;
|
|
||||||
import forge.ai.ComputerUtilCost;
|
|
||||||
import forge.ai.PlayerControllerAi;
|
|
||||||
import forge.ai.SpellAbilityAi;
|
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.cost.Cost;
|
import forge.game.cost.Cost;
|
||||||
@@ -48,6 +43,10 @@ public class TapAi extends TapAiBase {
|
|||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final Cost abCost = sa.getPayCosts();
|
final Cost abCost = sa.getPayCosts();
|
||||||
|
|
||||||
|
if ("GoblinPolkaBand".equals(sa.getParam("AILogic"))) {
|
||||||
|
return SpecialCardAi.GoblinPolkaBand.consider(ai, sa);
|
||||||
|
}
|
||||||
|
|
||||||
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source, sa)) {
|
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source, sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,7 +98,12 @@ public class TokenAi extends SpellAbilityAi {
|
|||||||
sa.getRootAbility().setXManaCostPaid(x);
|
sa.getRootAbility().setXManaCostPaid(x);
|
||||||
}
|
}
|
||||||
if (x <= 0) {
|
if (x <= 0) {
|
||||||
return false; // 0 tokens or 0 toughness token(s)
|
if ("RandomPT".equals(sa.getParam("AILogic"))) {
|
||||||
|
// e.g. Necropolis of Azar - we're guaranteed at least 1 toughness from the ability
|
||||||
|
x = 1;
|
||||||
|
} else {
|
||||||
|
return false; // 0 tokens or 0 toughness token(s)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ Name:Goblin Polka Band
|
|||||||
ManaCost:R R
|
ManaCost:R R
|
||||||
Types:Creature Goblin
|
Types:Creature Goblin
|
||||||
PT:1/1
|
PT:1/1
|
||||||
A:AB$ Tap | Announce$ TgtNum | AnnounceTitle$ any number of creatures to target | Cost$ X 2 T | XColor$ R | CostDesc$ {2}, {T}: | ValidTgts$ Creature.untapped | TargetMin$ TgtNum | TargetMax$ TgtNum | TargetsAtRandom$ True | RememberTargets$ True | SubAbility$ GoblinHangover | SpellDescription$ Tap any number of random target creatures. Goblins tapped in this way do not untap during their controllers' next untap phases. This ability costs {R} more to activate for each target.
|
A:AB$ Tap | Announce$ TgtNum | AnnounceTitle$ any number of creatures to target | Cost$ X 2 T | XColor$ R | CostDesc$ {2}, {T}: | ValidTgts$ Creature.untapped | TargetMin$ TgtNum | TargetMax$ TgtNum | TargetsAtRandom$ True | RememberTargets$ True | AILogic$ GoblinPolkaBand | SubAbility$ GoblinHangover | SpellDescription$ Tap any number of random target creatures. Goblins tapped in this way do not untap during their controllers' next untap phases. This ability costs {R} more to activate for each target.
|
||||||
SVar:GoblinHangover:DB$ PumpAll | ValidCards$ Goblin.IsRemembered | KW$ HIDDEN This card doesn't untap during your next untap step. | Duration$ Permanent
|
SVar:GoblinHangover:DB$ PumpAll | ValidCards$ Goblin.IsRemembered | KW$ HIDDEN This card doesn't untap during your next untap step. | Duration$ Permanent
|
||||||
SVar:TgtNum:Number$0
|
SVar:TgtNum:Number$0
|
||||||
SVar:X:SVar$TgtNum
|
SVar:X:SVar$TgtNum
|
||||||
|
|||||||
@@ -2,6 +2,6 @@ Name:Power Struggle
|
|||||||
ManaCost:2 U U U
|
ManaCost:2 U U U
|
||||||
Types:Enchantment
|
Types:Enchantment
|
||||||
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ Player | TriggerZones$ Battlefield | Execute$ TrigPump | TriggerController$ TriggeredPlayer | TriggerDescription$ At the beginning of each player's upkeep, that player exchanges control of random target artifact, creature or land he or she controls, for control of random target permanent of the same type that a random opponent controls.
|
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ Player | TriggerZones$ Battlefield | Execute$ TrigPump | TriggerController$ TriggeredPlayer | TriggerDescription$ At the beginning of each player's upkeep, that player exchanges control of random target artifact, creature or land he or she controls, for control of random target permanent of the same type that a random opponent controls.
|
||||||
SVar:TrigPump:DB$ Pump | TargetsWithDefinedController$ TriggeredPlayer | ValidTgts$ Artifact,Creature,Land | TargetsAtRandom$ True | SubAbility$ DBExchangeControl
|
SVar:TrigPump:DB$ Pump | TargetsWithDefinedController$ TriggeredPlayer | ValidTgts$ Artifact,Creature,Land | TargetsAtRandom$ True | AILogic$ PowerStruggle | SubAbility$ DBExchangeControl
|
||||||
SVar:DBExchangeControl:DB$ ExchangeControl | Defined$ ParentTarget | ValidTgts$ Artifact,Creature,Land | TargetsWithDefinedController$ Player.OpponentOf TriggeredPlayer | TargetsWithSharedCardType$ ParentTarget | TargetsAtRandom$ True
|
SVar:DBExchangeControl:DB$ ExchangeControl | Defined$ ParentTarget | ValidTgts$ Artifact,Creature,Land | TargetsWithDefinedController$ Player.OpponentOf TriggeredPlayer | TargetsWithSharedCardType$ ParentTarget | TargetsAtRandom$ True | AILogic$ PowerStruggle
|
||||||
Oracle:At the beginning of each player's upkeep, that player exchanges control of random target artifact, creature or land he or she controls, for control of random target permanent of the same type that a random opponent controls.
|
Oracle:At the beginning of each player's upkeep, that player exchanges control of random target artifact, creature or land he or she controls, for control of random target permanent of the same type that a random opponent controls.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ ManaCost:2 B B
|
|||||||
Types:Enchantment
|
Types:Enchantment
|
||||||
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Creature.nonBlack | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ Whenever a nonblack creature dies, put a husk counter on CARDNAME.
|
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Creature.nonBlack | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ Whenever a nonblack creature dies, put a husk counter on CARDNAME.
|
||||||
SVar:TrigPutCounter:DB$ PutCounter | CounterType$ HUSK
|
SVar:TrigPutCounter:DB$ PutCounter | CounterType$ HUSK
|
||||||
A:AB$ Token | Cost$ 5 SubCounter<1/HUSK> | TokenScript$ spawn_of_azar | TokenPower$ X | TokenToughness$ Y | SpellDescription$ Create an X/Y black Spawn creature token with swampwalk named Spawn of Azar, where X and Y are numbers chosen at random from 1 to 3.
|
A:AB$ Token | Cost$ 5 SubCounter<1/HUSK> | TokenScript$ spawn_of_azar | TokenPower$ X | TokenToughness$ Y | AILogic$ RandomPT | SpellDescription$ Create an X/Y black Spawn creature token with swampwalk named Spawn of Azar, where X and Y are numbers chosen at random from 1 to 3.
|
||||||
SVar:X:Count$Random.1.3
|
SVar:X:Count$Random.1.3
|
||||||
SVar:Y:Count$Random.1.3
|
SVar:Y:Count$Random.1.3
|
||||||
DeckHas:Ability$Counters & Ability$Token
|
DeckHas:Ability$Counters & Ability$Token
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
Name:Pandora's Box
|
Name:Pandora's Box
|
||||||
ManaCost:5
|
ManaCost:5
|
||||||
Types:Artifact
|
Types:Artifact
|
||||||
A:AB$ ChooseCard | Cost$ 3 T | Choices$ Creature | AtRandom$ True | AllCards$ True | SubAbility$ DBRepeatEach | StackDescription$ SpellDescription | SpellDescription$ Choose a creature card at random from all players' decklists. Each player flips a coin. Each player whose coin comes up heads creates a token that's a copy of that card.
|
A:AB$ ChooseCard | Cost$ 3 T | Choices$ Creature | AtRandom$ True | AllCards$ True | SubAbility$ DBRepeatEach | AILogic$ AtOppEOT | StackDescription$ SpellDescription | SpellDescription$ Choose a creature card at random from all players' decklists. Each player flips a coin. Each player whose coin comes up heads creates a token that's a copy of that card.
|
||||||
SVar:DBRepeatEach:DB$ RepeatEach | RepeatPlayers$ Player | RepeatSubAbility$ DBFlip | SubAbility$ DBCleanup
|
SVar:DBRepeatEach:DB$ RepeatEach | RepeatPlayers$ Player | RepeatSubAbility$ DBFlip | SubAbility$ DBCleanup
|
||||||
SVar:DBFlip:DB$ FlipACoin | Flipper$ Remembered | NoCall$ True | HeadsSubAbility$ DBCopyPermanent
|
SVar:DBFlip:DB$ FlipACoin | Flipper$ Remembered | NoCall$ True | HeadsSubAbility$ DBCopyPermanent
|
||||||
SVar:DBCopyPermanent:DB$ CopyPermanent | Defined$ ChosenCard | Controller$ Remembered
|
SVar:DBCopyPermanent:DB$ CopyPermanent | Defined$ ChosenCard | Controller$ Remembered
|
||||||
|
|||||||
Reference in New Issue
Block a user