Skip to content

Commit 9666c9b

Browse files
authored
Merge branch 'master' into wiki
2 parents 61815f1 + 15fca42 commit 9666c9b

File tree

393 files changed

+4056
-1991
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

393 files changed

+4056
-1991
lines changed

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

Lines changed: 20 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -275,27 +275,6 @@ public final boolean isEffectiveAttacker(final Player ai, final Card attacker, f
275275
return false;
276276
}
277277

278-
// the attacker will die to a triggered ability (e.g. Sarkhan the Masterless)
279-
for (Card c : ai.getOpponents().getCardsIn(ZoneType.Battlefield)) {
280-
for (Trigger t : c.getTriggers()) {
281-
if (t.getMode() == TriggerType.Attacks) {
282-
SpellAbility sa = t.ensureAbility();
283-
if (sa == null) {
284-
continue;
285-
}
286-
287-
if (sa.getApi() == ApiType.EachDamage && "TriggeredAttacker".equals(sa.getParam("Defined"))) {
288-
List<Card> valid = CardLists.getValidCards(c.getController().getCreaturesInPlay(), sa.getParam("ValidCards"), c.getController(), c, sa);
289-
// TODO: this assumes that 1 damage is dealt per creature. Improve this to check the parameter/X to determine
290-
// how much damage is dealt by each of the creatures in the valid list.
291-
if (attacker.getNetToughness() <= valid.size()) {
292-
return false;
293-
}
294-
}
295-
}
296-
}
297-
}
298-
299278
if ("TRUE".equals(attacker.getSVar("HasAttackEffect"))) {
300279
return true;
301280
}
@@ -414,7 +393,7 @@ public final List<Card> notNeededAsBlockers(final List<Card> currentAttackers, f
414393
if (ai.getController().isAI()) {
415394
PlayerControllerAi aic = ((PlayerControllerAi) ai.getController());
416395
pilotsNonAggroDeck = aic.pilotsNonAggroDeck();
417-
playAggro = !pilotsNonAggroDeck || aic.getAi().getBooleanProperty(AiProps.PLAY_AGGRO);
396+
playAggro = !pilotsNonAggroDeck || aic.getAi().getBoolProperty(AiProps.PLAY_AGGRO);
418397
}
419398
// TODO make switchable via AI property
420399
int thresholdMod = 0;
@@ -635,13 +614,7 @@ private boolean doAssault() {
635614

636615
// if true, the AI will attempt to identify which blockers will already be taken,
637616
// thus attempting to predict how many creatures with evasion can actively block
638-
boolean predictEvasion = false;
639-
if (ai.getController().isAI()) {
640-
AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
641-
if (aic.getBooleanProperty(AiProps.COMBAT_ASSAULT_ATTACK_EVASION_PREDICTION)) {
642-
predictEvasion = true;
643-
}
644-
}
617+
boolean predictEvasion = AiProfileUtil.getBoolProperty(ai, AiProps.COMBAT_ASSAULT_ATTACK_EVASION_PREDICTION);
645618

646619
CardCollection accountedBlockers = new CardCollection(this.blockers);
647620
CardCollection categorizedAttackers = new CardCollection();
@@ -881,12 +854,12 @@ public final int declareAttackers(final Combat combat) {
881854
AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
882855
simAI = aic.usesSimulation();
883856
if (!simAI) {
884-
playAggro = aic.getBooleanProperty(AiProps.PLAY_AGGRO);
857+
playAggro = aic.getBoolProperty(AiProps.PLAY_AGGRO);
885858
chanceToAttackToTrade = aic.getIntProperty(AiProps.CHANCE_TO_ATTACK_INTO_TRADE);
886-
tradeIfTappedOut = aic.getBooleanProperty(AiProps.ATTACK_INTO_TRADE_WHEN_TAPPED_OUT);
859+
tradeIfTappedOut = aic.getBoolProperty(AiProps.ATTACK_INTO_TRADE_WHEN_TAPPED_OUT);
887860
extraChanceIfOppHasMana = aic.getIntProperty(AiProps.CHANCE_TO_ATKTRADE_WHEN_OPP_HAS_MANA);
888-
tradeIfLowerLifePressure = aic.getBooleanProperty(AiProps.RANDOMLY_ATKTRADE_ONLY_ON_LOWER_LIFE_PRESSURE);
889-
predictEvasion = aic.getBooleanProperty(AiProps.COMBAT_ATTRITION_ATTACK_EVASION_PREDICTION);
861+
tradeIfLowerLifePressure = aic.getBoolProperty(AiProps.RANDOMLY_ATKTRADE_ONLY_ON_LOWER_LIFE_PRESSURE);
862+
predictEvasion = aic.getBoolProperty(AiProps.COMBAT_ATTRITION_ATTACK_EVASION_PREDICTION);
890863
}
891864
}
892865

@@ -995,6 +968,16 @@ && isEffectiveAttacker(ai, attacker, combat, finalDefender)) {
995968
return aiAggression;
996969
}
997970

971+
// Only do decisive attacks against token-generating players
972+
if (!bAssault && defender instanceof Player) {
973+
Player opponent = (Player)defender;
974+
if (CardLists.count(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.nameEquals("Rabble Rousing"))
975+
- CardLists.count(opponent.getCardsIn(ZoneType.Battlefield), CardPredicates.nameEquals("Darien, King of Kjeldor"))
976+
- CardLists.count(opponent.getCardsIn(ZoneType.Battlefield), CardPredicates.nameEquals("Kazuul, Tyrant of the Cliffs")) < 0) {
977+
return aiAggression;
978+
}
979+
}
980+
998981
if (bAssault && defender == defendingOpponent) { // in case we are forced to attack someone else
999982
if (LOG_AI_ATTACKS)
1000983
System.out.println("Assault");
@@ -1005,7 +988,7 @@ && isEffectiveAttacker(ai, attacker, combat, finalDefender)) {
1005988
if (attackMax != -1 && combat.getAttackers().size() >= attackMax)
1006989
return aiAggression;
1007990

1008-
// TODO if lifeInDanger use chance to hold back some
991+
// TODO if lifeInDanger use chance to hold back some (especially in multiplayer)
1009992
if (canAttackWrapper(attacker, defender) && isEffectiveAttacker(ai, attacker, combat, defender)) {
1010993
combat.addAttacker(attacker, defender);
1011994
}
@@ -1102,7 +1085,8 @@ && isEffectiveAttacker(ai, attacker, combat, finalDefender)) {
11021085
for (final Card pCard : myList) {
11031086
// if the creature can attack then it's a potential attacker this
11041087
// turn, assume summoning sickness creatures will be able to
1105-
if (ComputerUtilCombat.canAttackNextTurn(pCard) && pCard.getNetCombatDamage() > 0) {
1088+
// TODO: Account for triggered power boosts.
1089+
if (ComputerUtilCombat.canAttackNextTurn(pCard) && (pCard.getNetCombatDamage() > 0 || "TRUE".equals(pCard.getSVar("HasAttackEffect")))) {
11061090
candidateAttackers.add(pCard);
11071091
candidateUnblockedDamage += ComputerUtilCombat.damageIfUnblocked(pCard, defendingOpponent, null, false);
11081092
computerForces++;
@@ -1433,7 +1417,7 @@ private void calculate(final List<Card> defenders, final Combat combat) {
14331417
// Check if maybe we are too reckless in adding this attacker
14341418
if (canKillAllDangerous) {
14351419
boolean avoidAttackingIntoBlock = ai.getController().isAI()
1436-
&& ((PlayerControllerAi) ai.getController()).getAi().getBooleanProperty(AiProps.TRY_TO_AVOID_ATTACKING_INTO_CERTAIN_BLOCK);
1420+
&& ((PlayerControllerAi) ai.getController()).getAi().getBoolProperty(AiProps.TRY_TO_AVOID_ATTACKING_INTO_CERTAIN_BLOCK);
14371421
boolean attackerWillDie = defPower >= attacker.getNetToughness();
14381422
boolean uselessAttack = !hasCombatEffect && !hasAttackEffect;
14391423
boolean noContributionToAttack = attackers.size() <= defenders.size() || attacker.getNetPower() <= 0;

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

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -861,10 +861,9 @@ private void makeChumpBlocksToSavePW(Combat combat) {
861861
return;
862862
}
863863

864-
AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
865-
final int evalThresholdToken = aic.getIntProperty(AiProps.THRESHOLD_TOKEN_CHUMP_TO_SAVE_PLANESWALKER);
866-
final int evalThresholdNonToken = aic.getIntProperty(AiProps.THRESHOLD_NONTOKEN_CHUMP_TO_SAVE_PLANESWALKER);
867-
final boolean onlyIfLethal = aic.getBooleanProperty(AiProps.CHUMP_TO_SAVE_PLANESWALKER_ONLY_ON_LETHAL);
864+
final int evalThresholdToken = AiProfileUtil.getIntProperty(ai, AiProps.THRESHOLD_TOKEN_CHUMP_TO_SAVE_PLANESWALKER);
865+
final int evalThresholdNonToken = AiProfileUtil.getIntProperty(ai, AiProps.THRESHOLD_NONTOKEN_CHUMP_TO_SAVE_PLANESWALKER);
866+
final boolean onlyIfLethal = AiProfileUtil.getBoolProperty(ai, AiProps.CHUMP_TO_SAVE_PLANESWALKER_ONLY_ON_LETHAL);
868867

869868
if (evalThresholdToken > 0 || evalThresholdNonToken > 0) {
870869
// detect how much damage is threatened to each of the planeswalkers, see which ones would be
@@ -1047,7 +1046,7 @@ private void assignBlockers(final Combat combat, List<Card> possibleBlockers) {
10471046
clearBlockers(combat, possibleBlockers);
10481047

10491048
diff = (ai.getLife() * 2) - 5; // This is the minimal gain for an unnecessary trade
1050-
if (ai.getController().isAI() && diff > 0 && ((PlayerControllerAi) ai.getController()).getAi().getBooleanProperty(AiProps.PLAY_AGGRO)) {
1049+
if (diff > 0 && AiProfileUtil.getBoolProperty(ai, AiProps.PLAY_AGGRO)) {
10511050
diff = 0;
10521051
}
10531052

@@ -1284,9 +1283,9 @@ private boolean wouldLikeToRandomlyTrade(Card attacker, Card blocker, Combat com
12841283
AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
12851284
// simulation must get same results or it may crash
12861285
if (!aic.usesSimulation()) {
1287-
enableRandomTrades = aic.getBooleanProperty(AiProps.ENABLE_RANDOM_FAVORABLE_TRADES_ON_BLOCK);
1288-
randomTradeIfBehindOnBoard = aic.getBooleanProperty(AiProps.RANDOMLY_TRADE_EVEN_WHEN_HAVE_LESS_CREATS);
1289-
randomTradeIfCreatInHand = aic.getBooleanProperty(AiProps.ALSO_TRADE_WHEN_HAVE_A_REPLACEMENT_CREAT);
1286+
enableRandomTrades = aic.getBoolProperty(AiProps.ENABLE_RANDOM_FAVORABLE_TRADES_ON_BLOCK);
1287+
randomTradeIfBehindOnBoard = aic.getBoolProperty(AiProps.RANDOMLY_TRADE_EVEN_WHEN_HAVE_LESS_CREATS);
1288+
randomTradeIfCreatInHand = aic.getBoolProperty(AiProps.ALSO_TRADE_WHEN_HAVE_A_REPLACEMENT_CREAT);
12901289
minRandomTradeChance = aic.getIntProperty(AiProps.MIN_CHANCE_TO_RANDOMLY_TRADE_ON_BLOCK);
12911290
maxRandomTradeChance = aic.getIntProperty(AiProps.MAX_CHANCE_TO_RANDOMLY_TRADE_ON_BLOCK);
12921291
chanceModForEmbalm = aic.getIntProperty(AiProps.CHANCE_DECREASE_TO_TRADE_VS_EMBALM);

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

Lines changed: 10 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,7 @@ private boolean checkETBEffectsPreparedCard(final Card card, final SpellAbility
377377

378378
if (card.isSaga()) {
379379
for (final Trigger tr : card.getTriggers()) {
380-
if (tr.getMode() != TriggerType.CounterAdded || !tr.isChapter()) {
380+
if (!tr.isChapter()) {
381381
continue;
382382
}
383383

@@ -393,6 +393,7 @@ private boolean checkETBEffectsPreparedCard(final Card card, final SpellAbility
393393
return false;
394394
}
395395

396+
// usually later chapters make use of an earlier one
396397
break;
397398
}
398399
}
@@ -763,7 +764,7 @@ public SpellAbility predictSpellToCastInMain2(ApiType exceptSA) {
763764
return predictSpellToCastInMain2(exceptSA, true);
764765
}
765766
private SpellAbility predictSpellToCastInMain2(ApiType exceptSA, boolean handOnly) {
766-
if (!getBooleanProperty(AiProps.PREDICT_SPELLS_FOR_MAIN2)) {
767+
if (!getBoolProperty(AiProps.PREDICT_SPELLS_FOR_MAIN2)) {
767768
return null;
768769
}
769770

@@ -929,7 +930,7 @@ public AiPlayDecision canPlaySa(SpellAbility sa) {
929930
final Card card = sa.getHostCard();
930931

931932
// Trying to play a card that has Buyback without a Buyback cost, look for possible additional considerations
932-
if (getBooleanProperty(AiProps.TRY_TO_PRESERVE_BUYBACK_SPELLS)) {
933+
if (getBoolProperty(AiProps.TRY_TO_PRESERVE_BUYBACK_SPELLS)) {
933934
if (card.hasKeyword(Keyword.BUYBACK) && !sa.isBuyback() && !canPlaySpellWithoutBuyback(card, sa)) {
934935
return AiPlayDecision.NeedsToPlayCriteriaNotMet;
935936
}
@@ -1298,27 +1299,13 @@ public boolean confirmStaticApplication(Card hostCard, String logic) {
12981299
}
12991300

13001301
public String getProperty(AiProps propName) {
1301-
return AiProfileUtil.getAIProp(getPlayer().getLobbyPlayer(), propName);
1302+
return AiProfileUtil.getProperty(getPlayer(), propName);
13021303
}
1303-
13041304
public int getIntProperty(AiProps propName) {
1305-
String prop = AiProfileUtil.getAIProp(getPlayer().getLobbyPlayer(), propName);
1306-
1307-
if (prop == null || prop.isEmpty()) {
1308-
return Integer.parseInt(propName.getDefault());
1309-
}
1310-
1311-
return Integer.parseInt(prop);
1305+
return AiProfileUtil.getIntProperty(getPlayer(), propName);
13121306
}
1313-
1314-
public boolean getBooleanProperty(AiProps propName) {
1315-
String prop = AiProfileUtil.getAIProp(getPlayer().getLobbyPlayer(), propName);
1316-
1317-
if (prop == null || prop.isEmpty()) {
1318-
return Boolean.parseBoolean(propName.getDefault());
1319-
}
1320-
1321-
return Boolean.parseBoolean(prop);
1307+
public boolean getBoolProperty(AiProps propName) {
1308+
return AiProfileUtil.getBoolProperty(getPlayer(), propName);
13221309
}
13231310

13241311
public AiPlayDecision canPlayFromEffectAI(Spell spell, boolean mandatory, boolean withoutPayingManaCost) {
@@ -1329,7 +1316,7 @@ public AiPlayDecision canPlayFromEffectAI(Spell spell, boolean mandatory, boolea
13291316

13301317
final Card card = spell.getHostCard();
13311318
if (spell instanceof SpellApiBased) {
1332-
boolean chance = false;
1319+
boolean chance;
13331320
if (withoutPayingManaCost) {
13341321
chance = SpellApiToAi.Converter.get(spell).doTriggerNoCostWithSubs(player, spell, mandatory).willingToPlay();
13351322
} else {
@@ -1467,7 +1454,7 @@ private boolean isSafeToHoldLandDropForMain2(Card landToPlay) {
14671454
CardCollection inHand = CardLists.filter(player.getCardsIn(ZoneType.Hand), CardPredicates.NON_LANDS);
14681455
CardCollectionView otb = player.getCardsIn(ZoneType.Battlefield);
14691456

1470-
if (getBooleanProperty(AiProps.HOLD_LAND_DROP_ONLY_IF_HAVE_OTHER_PERMS)) {
1457+
if (getBoolProperty(AiProps.HOLD_LAND_DROP_ONLY_IF_HAVE_OTHER_PERMS)) {
14711458
if (!otb.anyMatch(CardPredicates.NON_LANDS)) {
14721459
return false;
14731460
}

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
package forge.ai;
1919

2020
import forge.LobbyPlayer;
21+
import forge.game.player.Player;
2122
import forge.util.Aggregates;
2223
import forge.util.FileUtil;
2324
import forge.util.TextUtil;
@@ -113,6 +114,23 @@ private static Map<AiProps, String> loadProfile(final String profileName) {
113114
return profileMap;
114115
}
115116

117+
public static String getProperty(final Player p, final AiProps propName) {
118+
String prop = AiProfileUtil.getAIProp(p.getLobbyPlayer(), propName);
119+
120+
if (prop == null || prop.isEmpty()) {
121+
// TODO if p is human try to predict some values from previous plays or something
122+
return propName.getDefault();
123+
}
124+
125+
return prop;
126+
}
127+
public static int getIntProperty(final Player p, final AiProps propName) {
128+
return Integer.parseInt(getProperty(p, propName));
129+
}
130+
public static boolean getBoolProperty(final Player p, final AiProps propName) {
131+
return Boolean.parseBoolean(getProperty(p, propName));
132+
}
133+
116134
/**
117135
* Returns an AI property value for the current profile.
118136
*

0 commit comments

Comments
 (0)