Skip to content

Commit 01ac625

Browse files
authored
Improve assault calculation (Card-Forge#9368)
1 parent 1bdbca1 commit 01ac625

File tree

13 files changed

+154
-174
lines changed

13 files changed

+154
-174
lines changed

docs/Card-scripting-API/Targeting.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,7 @@ Examples:
9090
For the full list of all available properties see the classes [CardProperty](https://github.com/Card-Forge/forge/blob/master/forge-game/src/main/java/forge/game/card/CardProperty.java) or respectively [PlayerProperty](https://github.com/Card-Forge/forge/blob/master/forge-game/src/main/java/forge/game/player/PlayerProperty.java).
9191

9292
## TgtZone$ {ZoneType}
93-
94-
Redundant for AF where the specified zone already matches with a `Origin$` parameter.
93+
Redundant for AF that can only target the Stack or where the specified zone already matches with a `Origin$` parameter.
9594

9695
## TargetType
9796

forge-ai/src/main/java/forge/ai/AiAttackController.java

Lines changed: 123 additions & 127 deletions
Large diffs are not rendered by default.

forge-ai/src/main/java/forge/ai/ComputerUtil.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3161,11 +3161,12 @@ public static boolean shouldSacrificeThreatenedCard(Player ai, Card c, SpellAbil
31613161
}
31623162
Combat combat = ai.getGame().getCombat();
31633163
boolean isThreatened = (c.isCreature() && ComputerUtil.predictCreatureWillDieThisTurn(ai, c, sa, false)
3164-
&& (!ComputerUtilCombat.willOpposingCreatureDieInCombat(ai, c, combat) && !ComputerUtilCombat.isDangerousToSacInCombat(ai, c, combat)))
3164+
&& !ComputerUtilCombat.willOpposingCreatureDieInCombat(ai, c, combat) && !ComputerUtilCombat.isDangerousToSacInCombat(ai, c, combat))
31653165
|| (!c.isCreature() && ComputerUtil.predictThreatenedObjects(ai, sa).contains(c));
31663166
return isThreatened;
31673167
}
31683168

3169+
// some AI checks can lead to loops depending on the boardstate
31693170
public static <T> T protectRecursion(SpellAbility sa, Supplier<T> loopableMethod, T fallback) {
31703171
boolean unskip = false;
31713172
if (sa != null) {

forge-ai/src/main/java/forge/ai/ComputerUtilCard.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,6 @@ public static void sortByEvaluateCreature(final CardCollection list) {
7575
list.sort(ComputerUtilCard.EvaluateCreatureComparator);
7676
}
7777

78-
// The AI doesn't really pick the best artifact, just the most expensive.
79-
8078
/**
8179
* <p>
8280
* getBestArtifactAI.

forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -206,18 +206,17 @@ public static int poisonIfUnblocked(final Card attacker, final Player attacked)
206206
if (!attacked.canReceiveCounters(CounterEnumType.POISON)) {
207207
return 0;
208208
}
209-
int damage = attacker.getNetCombatDamage();
209+
int damage = attacker.getNetCombatDamage() +
210+
predictPowerBonusOfAttacker(attacker, null, null, false);
210211
int poison = 0;
211-
damage += predictPowerBonusOfAttacker(attacker, null, null, false);
212212
if (attacker.isInfectDamage(attacked)) {
213213
int pd = predictDamageTo(attacked, damage, attacker, true);
214214
// opponent can always order it so that he gets 0
215-
if (pd == 1 && attacker.getController().getOpponents().getCardsIn(ZoneType.Battlefield).anyMatch(CardPredicates.nameEquals("Vorinclex, Monstrous Raider"))) {
216-
pd = 0;
217-
}
218-
poison += pd;
219-
if (attacker.hasDoubleStrike()) {
220-
poison += pd;
215+
if (pd > 1 || !attacker.getController().getOpponents().getCardsIn(ZoneType.Battlefield).anyMatch(CardPredicates.nameEquals("Vorinclex, Monstrous Raider"))) {
216+
poison = pd;
217+
if (attacker.hasDoubleStrike()) {
218+
poison *= 2;
219+
}
221220
}
222221
}
223222
if (damage > 0) {
@@ -346,7 +345,7 @@ public static int resultingPoison(final Player ai, final Combat combat) {
346345
for (final Card attacker : attackers) {
347346
final List<Card> blockers = combat.getBlockers(attacker);
348347

349-
if (blockers.size() == 0
348+
if (blockers.isEmpty()
350349
|| StaticAbilityAssignCombatDamageAsUnblocked.assignCombatDamageAsUnblocked(attacker)) {
351350
unblocked.add(attacker);
352351
} else if (attacker.hasKeyword(Keyword.TRAMPLE)) {
@@ -2501,20 +2500,18 @@ public static boolean willKillAtLeastOne(final Player ai, final Card c, final Co
25012500
}
25022501

25032502
public static int predictExtraPoisonWithDamage(Card attacker, Player attacked, int damage) {
2504-
int pd = 0, poison = 0;
2503+
int poison = 0;
25052504
int damageAfterRepl = predictDamageTo(attacked, damage, attacker, true);
25062505
if (damageAfterRepl > 0) {
2507-
CardCollectionView trigCards = attacker.getController().getCardsIn(ZoneType.Battlefield);
2508-
for (Card c : trigCards) {
2506+
for (Card c : attacker.getController().getCardsIn(ZoneType.Battlefield)) {
25092507
for (Trigger t : c.getTriggers()) {
25102508
if (t.getMode() == TriggerType.DamageDone && !"False".equals(t.getParam("CombatDamage")) && t.matchesValidParam("ValidSource", attacker)) {
25112509
SpellAbility ab = t.getOverridingAbility();
25122510
if (ab.getApi() == ApiType.Poison && "TriggeredTarget".equals(ab.getParam("Defined"))) {
2513-
pd += AbilityUtils.calculateAmount(attacker, ab.getParam("Num"), ab);
2511+
poison += AbilityUtils.calculateAmount(attacker, ab.getParam("Num"), ab);
25142512
}
25152513
}
25162514
}
2517-
poison += pd;
25182515
// TODO: Predict replacement effects for counters (doubled, reduced, additional counters, etc.)
25192516
}
25202517
// intern toxic effect

forge-ai/src/main/java/forge/ai/SpecialCardAi.java

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
*/
1818
package forge.ai;
1919

20-
import com.google.common.collect.Iterables;
2120
import com.google.common.collect.Lists;
2221
import forge.ai.ability.AnimateAi;
2322
import forge.ai.ability.FightAi;
@@ -38,6 +37,7 @@
3837
import forge.game.phase.PhaseHandler;
3938
import forge.game.phase.PhaseType;
4039
import forge.game.player.Player;
40+
import forge.game.player.PlayerCollection;
4141
import forge.game.player.PlayerPredicates;
4242
import forge.game.spellability.SpellAbility;
4343
import forge.game.spellability.SpellAbilityPredicates;
@@ -405,24 +405,22 @@ public static AiAbilityDecision considerTargetingOpponent(final Player ai, final
405405
ai.getCardsIn(ZoneType.Battlefield).threadSafeIterable(), CardPredicates.hasSVar("DonateMe")));
406406
if (donateTarget != null) {
407407
// first filter for opponents which can be targeted by SA
408-
final Iterable<Player> oppList = IterableUtil.filter(ai.getOpponents(),
409-
PlayerPredicates.isTargetableBy(sa));
408+
PlayerCollection oppList = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
410409

411410
// All opponents have hexproof or something like that
412-
if (Iterables.isEmpty(oppList)) {
411+
if (oppList.isEmpty()) {
413412
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
414413
}
415414

416415
// filter for player who does not have donate target already
417-
Iterable<Player> oppTarget = IterableUtil.filter(oppList,
418-
PlayerPredicates.isNotCardInPlay(donateTarget.getName()));
416+
PlayerCollection oppTarget = oppList.filter(PlayerPredicates.isNotCardInPlay(donateTarget.getName()));
419417
// fall back to previous list
420-
if (Iterables.isEmpty(oppTarget)) {
418+
if (oppTarget.isEmpty()) {
421419
oppTarget = oppList;
422420
}
423421

424422
// select player with less lands on the field (helpful for Illusions of Grandeur and probably Pacts too)
425-
Player opp = Collections.min(Lists.newArrayList(oppTarget),
423+
Player opp = Collections.min(oppTarget,
426424
PlayerPredicates.compareByZoneSize(ZoneType.Battlefield, CardPredicates.LANDS));
427425

428426
if (opp != null) {

forge-core/src/main/java/forge/card/CardRules.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -794,7 +794,7 @@ private void parseLine(final String line, CardFace face) {
794794
face.addTrigger(value);
795795
} else if ("Types".equals(key)) {
796796
face.setType(CardType.parse(value, false));
797-
} else if ("Text".equals(key) && !"no text".equals(value) && StringUtils.isNotBlank(value)) {
797+
} else if ("Text".equals(key) && StringUtils.isNotBlank(value)) {
798798
face.setNonAbilityText(value);
799799
}
800800
break;

forge-game/src/main/java/forge/game/card/CardFactory.java

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -368,16 +368,16 @@ private static void readCardFace(Card c, ICardFace face) {
368368
}
369369
}
370370

371+
// Set name for Sentry reports to be identifiable
372+
c.setName(face.getName());
373+
374+
c.getCurrentState().setFlavorName(face.getFlavorName());
375+
371376
// Negative card Id's are for view purposes only
372377
if (c.getId() >= 0) {
373378
// Build English oracle and translated oracle mapping
374379
CardTranslation.buildOracleMapping(face.getName(), face.getOracleText(), variantName);
375-
}
376-
377-
// Set name for Sentry reports to be identifiable
378-
c.setName(face.getName());
379380

380-
if (c.getId() >= 0) { // Set Triggers & Abilities if not for view
381381
for (Entry<String, String> v : face.getVariables())
382382
c.setSVar(v.getKey(), v.getValue());
383383
for (String r : face.getReplacements())
@@ -397,13 +397,8 @@ private static void readCardFace(Card c, ICardFace face) {
397397
c.setManaCost(face.getManaCost());
398398
c.setText(face.getNonAbilityText());
399399

400-
c.getCurrentState().setBaseLoyalty(face.getInitialLoyalty());
401-
c.getCurrentState().setBaseDefense(face.getDefense());
402-
403400
c.getCurrentState().setOracleText(face.getOracleText());
404401

405-
c.getCurrentState().setFlavorName(face.getFlavorName());
406-
407402
// Super and 'middle' types should use enums.
408403
c.setType(new CardType(face.getType()));
409404

@@ -418,6 +413,9 @@ private static void readCardFace(Card c, ICardFace face) {
418413
c.setBaseToughnessString(face.getToughness());
419414
}
420415

416+
c.getCurrentState().setBaseLoyalty(face.getInitialLoyalty());
417+
c.getCurrentState().setBaseDefense(face.getDefense());
418+
421419
c.setAttractionLights(face.getAttractionLights());
422420

423421
if (c.getId() > 0) // Set FactoryAbilities if not for view

forge-game/src/main/java/forge/game/card/CardFactoryUtil.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -526,8 +526,6 @@ public static List<String> getSharedKeywords(final Iterable<String> kw, final Ca
526526
* the card
527527
*/
528528
public static final void addAbilityFactoryAbilities(final Card card, final Iterable<String> abilities) {
529-
// **************************************************
530-
// AbilityFactory cards
531529
for (String rawAbility : abilities) {
532530
try {
533531
final SpellAbility intrinsicAbility = AbilityFactory.getAbility(rawAbility, card);

forge-game/src/main/java/forge/game/player/Player.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3724,10 +3724,6 @@ public final boolean sameTeam(final Player other) {
37243724
return this.teamNumber == other.getTeam();
37253725
}
37263726

3727-
public final int countExaltedBonus() {
3728-
return CardLists.getAmountOfKeyword(this.getCardsIn(ZoneType.Battlefield), Keyword.EXALTED);
3729-
}
3730-
37313727
public final boolean isCursed() {
37323728
return CardLists.count(getAttachedCards(), Card::isCurse) > 0;
37333729
}

0 commit comments

Comments
 (0)