Skip to content

Commit

Permalink
Merge branch 'Card-Forge:master' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
jjayers99 authored Jan 3, 2024
2 parents b65e3a3 + 13cf081 commit 80ada76
Show file tree
Hide file tree
Showing 50 changed files with 386 additions and 248 deletions.
5 changes: 2 additions & 3 deletions forge-ai/src/main/java/forge/ai/ComputerUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
import com.google.common.collect.Multimap;

import forge.ai.AiCardMemory.MemorySet;
import forge.ai.ability.ChooseGenericEffectAi;
import forge.ai.ability.ProtectAi;
import forge.ai.ability.TokenAi;
import forge.card.CardStateName;
Expand Down Expand Up @@ -290,7 +289,7 @@ public static final boolean playSpellAbilityWithoutPayingManaCost(final Player a
SpellAbility newSA = sa.copyWithNoManaCost();
newSA.setActivatingPlayer(ai, true);

if (!CostPayment.canPayAdditionalCosts(newSA.getPayCosts(), newSA) || !ComputerUtilMana.canPayManaCost(newSA, ai, 0, false)) {
if (!CostPayment.canPayAdditionalCosts(newSA.getPayCosts(), newSA, false) || !ComputerUtilMana.canPayManaCost(newSA, ai, 0, false)) {
return false;
}

Expand Down Expand Up @@ -1176,7 +1175,7 @@ public static boolean castPermanentInMain1(final Player ai, final SpellAbility s
return true;
}

if (cardState.hasKeyword(Keyword.RIOT) && ChooseGenericEffectAi.preferHasteForRiot(sa, ai)) {
if (cardState.hasKeyword(Keyword.RIOT) && SpecialAiLogic.preferHasteForRiot(sa, ai)) {
// Planning to choose Haste for Riot, so do this in Main 1
return true;
}
Expand Down
23 changes: 19 additions & 4 deletions forge-ai/src/main/java/forge/ai/ComputerUtilAbility.java
Original file line number Diff line number Diff line change
Expand Up @@ -255,10 +255,25 @@ public int compareEvaluator(final SpellAbility a, final SpellAbility b, boolean
}

// deprioritize planar die roll marked with AIRollPlanarDieParams:LowPriority$ True
if (ApiType.RollPlanarDice == a.getApi() && a.getHostCard() != null && a.getHostCard().hasSVar("AIRollPlanarDieParams") && a.getHostCard().getSVar("AIRollPlanarDieParams").toLowerCase().matches(".*lowpriority\\$\\s*true.*")) {
return 1;
} else if (ApiType.RollPlanarDice == b.getApi() && b.getHostCard() != null && b.getHostCard().hasSVar("AIRollPlanarDieParams") && b.getHostCard().getSVar("AIRollPlanarDieParams").toLowerCase().matches(".*lowpriority\\$\\s*true.*")) {
return -1;
if (ApiType.RollPlanarDice == a.getApi() || ApiType.RollPlanarDice == b.getApi()) {
Card hostCardForGame = a.getHostCard();
if (hostCardForGame == null) {
if (b.getHostCard() != null) {
hostCardForGame = b.getHostCard();
} else {
return 0; // fallback if neither SA have a host card somehow
}
}
Game game = hostCardForGame.getGame();
for (Card c : game.getActivePlanes()) {
if (c.hasSVar("AIRollPlanarDieParams") && c.getSVar("AIRollPlanarDieParams").toLowerCase().matches(".*lowpriority\\$\\s*true.*")) {
if (ApiType.RollPlanarDice == a.getApi()) {
return 1;
} else {
return -1;
}
}
}
}

// deprioritize pump spells with pure energy cost (can be activated last,
Expand Down
6 changes: 3 additions & 3 deletions forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java
Original file line number Diff line number Diff line change
Expand Up @@ -1257,7 +1257,7 @@ public static int predictPowerBonusOfAttacker(final Card attacker, final Card bl
sa.setActivatingPlayer(source.getController(), true);

if (sa.hasParam("Cost")) {
if (!CostPayment.canPayAdditionalCosts(sa.getPayCosts(), sa)) {
if (!CostPayment.canPayAdditionalCosts(sa.getPayCosts(), sa, true)) {
continue;
}
}
Expand Down Expand Up @@ -1454,7 +1454,7 @@ public static int predictToughnessBonusOfAttacker(final Card attacker, final Car
continue;
}
if (sa.hasParam("Cost")) {
if (!CostPayment.canPayAdditionalCosts(sa.getPayCosts(), sa)) {
if (!CostPayment.canPayAdditionalCosts(sa.getPayCosts(), sa, true)) {
continue;
}
}
Expand Down Expand Up @@ -1488,7 +1488,7 @@ public static int predictToughnessBonusOfAttacker(final Card attacker, final Car
continue;
}
if (sa.hasParam("Cost")) {
if (!CostPayment.canPayAdditionalCosts(sa.getPayCosts(), sa)) {
if (!CostPayment.canPayAdditionalCosts(sa.getPayCosts(), sa, true)) {
continue;
}
}
Expand Down
4 changes: 3 additions & 1 deletion forge-ai/src/main/java/forge/ai/ComputerUtilCost.java
Original file line number Diff line number Diff line change
Expand Up @@ -636,7 +636,7 @@ public boolean apply(Card card) {
}

return ComputerUtilMana.canPayManaCost(sa, player, extraManaNeeded, effect)
&& CostPayment.canPayAdditionalCosts(sa.getPayCosts(), sa);
&& CostPayment.canPayAdditionalCosts(sa.getPayCosts(), sa, effect);
}

public static boolean willPayUnlessCost(SpellAbility sa, Player payer, Cost cost, boolean alreadyPaid, FCollectionView<Player> payers) {
Expand Down Expand Up @@ -760,6 +760,8 @@ else if (payer.getLife() <= AbilityUtils.calculateAmount(source, aiLogic.substri
int evalToken = ComputerUtilCard.evaluateCreatureList(tokenList);

return evalToken < evalCounter;
} else if ("Riot".equals(aiLogic)) {
return !SpecialAiLogic.preferHasteForRiot(sa, payer);
}

// Check for shocklands and similar ETB replacement effects
Expand Down
4 changes: 2 additions & 2 deletions forge-ai/src/main/java/forge/ai/ComputerUtilMana.java
Original file line number Diff line number Diff line change
Expand Up @@ -976,7 +976,7 @@ private static boolean canPayShardWithSpellAbility(ManaCostShard toPay, Player a
// Check if AI can still play this mana ability
ma.setActivatingPlayer(ai, true);
// if the AI can't pay the additional costs skip the mana ability
if (!CostPayment.canPayAdditionalCosts(ma.getPayCosts(), ma)) {
if (!CostPayment.canPayAdditionalCosts(ma.getPayCosts(), ma, false)) {
return false;
} else if (ma.getRestrictions() != null && ma.getRestrictions().isInstantSpeed()) {
return false;
Expand Down Expand Up @@ -1517,7 +1517,7 @@ public boolean apply(final Card c) {
if (cost != null) {
// if the AI can't pay the additional costs skip the mana ability
m.setActivatingPlayer(ai, true);
if (!CostPayment.canPayAdditionalCosts(m.getPayCosts(), m)) {
if (!CostPayment.canPayAdditionalCosts(m.getPayCosts(), m, false)) {
continue;
}

Expand Down
3 changes: 3 additions & 0 deletions forge-ai/src/main/java/forge/ai/PlayerControllerAi.java
Original file line number Diff line number Diff line change
Expand Up @@ -1063,6 +1063,9 @@ public boolean payCostToPreventEffect(Cost cost, SpellAbility sa, boolean alread
final Ability emptyAbility = new AbilityStatic(source, cost, sa.getTargetRestrictions()) { @Override public void resolve() { } };
emptyAbility.setActivatingPlayer(player, true);
emptyAbility.setTriggeringObjects(sa.getTriggeringObjects());
emptyAbility.setReplacingObjects(sa.getReplacingObjects());
emptyAbility.setTrigger(sa.getTrigger());
emptyAbility.setReplacementEffect(sa.getReplacementEffect());
emptyAbility.setSVars(sa.getSVars());
emptyAbility.setCardState(sa.getCardState());
emptyAbility.setXManaCostPaid(sa.getRootAbility().getXManaCostPaid());
Expand Down
47 changes: 47 additions & 0 deletions forge-ai/src/main/java/forge/ai/SpecialAiLogic.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;

import forge.ai.ability.TokenAi;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
Expand All @@ -13,6 +15,7 @@
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.Aggregates;
import forge.util.Expressions;

Expand Down Expand Up @@ -398,4 +401,48 @@ public static boolean doBranchCounterspellLogic(final Player ai, final SpellAbil
}
return willPlay;
}

public static boolean preferHasteForRiot(SpellAbility sa, Player player) {
// returning true means preferring Haste, returning false means preferring a +1/+1 counter
final Card host = sa.getHostCard();
final Game game = host.getGame();
final Card copy = CardUtil.getLKICopy(host);
copy.setLastKnownZone(player.getZone(ZoneType.Battlefield));

// check state it would have on the battlefield
CardCollection preList = new CardCollection(copy);
game.getAction().checkStaticAbilities(false, Sets.newHashSet(copy), preList);
// reset again?
game.getAction().checkStaticAbilities(false);

// can't gain counters, use Haste
if (!copy.canReceiveCounters(CounterEnumType.P1P1)) {
return true;
}

// already has Haste, use counter
if (copy.hasKeyword(Keyword.HASTE)) {
return false;
}

// not AI turn
if (!game.getPhaseHandler().isPlayerTurn(player)) {
return false;
}

// not before Combat
if (!game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
return false;
}

// TODO check other opponents too if able
final Player opp = player.getWeakestOpponent();
if (opp != null) {
// TODO add predict Combat Damage?
return opp.getLife() < copy.getNetPower();
}

// haste might not be good enough?
return false;
}
}
54 changes: 0 additions & 54 deletions forge-ai/src/main/java/forge/ai/ability/ChooseGenericEffectAi.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,14 @@
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilCost;
import forge.ai.SpellAbilityAi;
import forge.ai.SpellApiToAi;
import forge.card.MagicColor;
import forge.game.Game;
import forge.game.card.*;
import forge.game.combat.Combat;
import forge.game.cost.Cost;
import forge.game.keyword.Keyword;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
Expand All @@ -33,8 +30,6 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
if ("Khans".equals(aiLogic) || "Dragons".equals(aiLogic)) {
return true;
} else if ("Riot".equals(aiLogic)) {
return true;
} else if ("Pump".equals(aiLogic) || "BestOption".equals(aiLogic)) {
for (AbilitySub sb : sa.getAdditionalAbilityList("Choices")) {
if (SpellApiToAi.Converter.get(sb.getApi()).canPlayAIWithSubs(ai, sb)) {
Expand Down Expand Up @@ -86,9 +81,7 @@ protected boolean doTriggerAINoCost(final Player aiPlayer, final SpellAbility sa
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells,
Map<String, Object> params) {
Card host = sa.getHostCard();
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
final Game game = host.getGame();
final Combat combat = game.getCombat();
final String logic = sa.getParam("AILogic");
if (logic == null) {
return spells.get(0);
Expand Down Expand Up @@ -287,54 +280,7 @@ public boolean apply(final SpellAbility sp) {
if (!filtered.isEmpty()) {
return filtered.get(0);
}
} else if ("Riot".equals(logic)) {
SpellAbility counterSA = spells.get(0), hasteSA = spells.get(1);
return preferHasteForRiot(sa, player) ? hasteSA : counterSA;
}
return spells.get(0); // return first choice if no logic found
}

public static boolean preferHasteForRiot(SpellAbility sa, Player player) {
// returning true means preferring Haste, returning false means preferring a +1/+1 counter
final Card host = sa.getHostCard();
final Game game = host.getGame();
final Card copy = CardUtil.getLKICopy(host);
copy.setLastKnownZone(player.getZone(ZoneType.Battlefield));

// check state it would have on the battlefield
CardCollection preList = new CardCollection(copy);
game.getAction().checkStaticAbilities(false, Sets.newHashSet(copy), preList);
// reset again?
game.getAction().checkStaticAbilities(false);

// can't gain counters, use Haste
if (!copy.canReceiveCounters(CounterEnumType.P1P1)) {
return true;
}

// already has Haste, use counter
if (copy.hasKeyword(Keyword.HASTE)) {
return false;
}

// not AI turn
if (!game.getPhaseHandler().isPlayerTurn(player)) {
return false;
}

// not before Combat
if (!game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
return false;
}

// TODO check other opponents too if able
final Player opp = player.getWeakestOpponent();
if (opp != null) {
// TODO add predict Combat Damage?
return opp.getLife() < copy.getNetPower();
}

// haste might not be good enough?
return false;
}
}
11 changes: 9 additions & 2 deletions forge-ai/src/main/java/forge/ai/ability/RollPlanarDiceAi.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,16 @@ public class RollPlanarDiceAi extends SpellAbilityAi {
*/
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
Card plane = sa.getHostCard();
for (Card c : ai.getGame().getActivePlanes()) {
if (willRollOnPlane(ai, c)) {
return true;
}
}
return false;
}

private boolean willRollOnPlane(Player ai, Card plane) {
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
boolean decideToRoll = false;
boolean rollInMain1 = false;
String modeName = "never";
Expand Down
2 changes: 0 additions & 2 deletions forge-game/src/main/java/forge/game/Game.java
Original file line number Diff line number Diff line change
Expand Up @@ -253,15 +253,13 @@ public void addLeftGraveyardThisTurn(Card lki) {
public List<Card> getLeftBattlefieldThisTurn() {
return leftBattlefieldThisTurn;
}

public List<Card> getLeftGraveyardThisTurn() {
return leftGraveyardThisTurn;
}

public void clearLeftBattlefieldThisTurn() {
leftBattlefieldThisTurn.clear();
}

public void clearLeftGraveyardThisTurn() {
leftGraveyardThisTurn.clear();
}
Expand Down
18 changes: 13 additions & 5 deletions forge-game/src/main/java/forge/game/GameAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -583,14 +583,19 @@ private Card changeZone(final Zone zoneFrom, Zone zoneTo, final Card c, Integer
}
}

// Need to apply any static effects to produce correct triggers
checkStaticAbilities();

if (table.replaceCounterEffect(game, null, true, true, params)) {
// update static abilities after etb counters have been placed
if (!table.isEmpty()) {
// we don't want always trigger before counters are placed
game.getTriggerHandler().suppressMode(TriggerType.Always);
// Need to apply any static effects to produce correct triggers
checkStaticAbilities();
}

table.replaceCounterEffect(game, null, true, true, params);

// update static abilities after etb counters have been placed
game.getTriggerHandler().clearSuppression(TriggerType.Always);
checkStaticAbilities();

// 400.7g try adding keyword back into card if it doesn't already have it
if (zoneTo.is(ZoneType.Stack) && cause != null && cause.isSpell() && !cause.isIntrinsic() && c.equals(cause.getHostCard())) {
if (cause.getKeyword() != null && !copied.getKeywords().contains(cause.getKeyword())) {
Expand Down Expand Up @@ -2085,6 +2090,9 @@ public void startGame(GameOutcome lastGameOutcome, Runnable startGameHook) {
//<THIS CODE WILL WORK WITH PHASE = NULL>
if (game.getRules().hasAppliedVariant(GameType.Planechase)) {
first.initPlane();
for (final Player p1 : game.getPlayers()) {
p1.createPlanechaseEffects(game);
}
}

first = runOpeningHandActions(first);
Expand Down
5 changes: 5 additions & 0 deletions forge-game/src/main/java/forge/game/PlanarDice.java
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,11 @@ else if (i == 1)
runParams.put(AbilityKey.Result, Arrays.asList(0));
roller.getGame().getTriggerHandler().runTrigger(TriggerType.RolledDieOnce, runParams, false);

if (res == Chaos) {
runParams = AbilityKey.mapFromPlayer(roller);
roller.getGame().getTriggerHandler().runTrigger(TriggerType.ChaosEnsues, runParams, false);
}

return res;
}

Expand Down
Loading

0 comments on commit 80ada76

Please sign in to comment.