diff --git a/forge-ai/src/main/java/forge/ai/AiAttackController.java b/forge-ai/src/main/java/forge/ai/AiAttackController.java index fa9d32a5645..43ac68318ca 100644 --- a/forge-ai/src/main/java/forge/ai/AiAttackController.java +++ b/forge-ai/src/main/java/forge/ai/AiAttackController.java @@ -79,6 +79,7 @@ public class AiAttackController { private int aiAggression = 0; // how aggressive the ai is attack will be depending on circumstances private final boolean nextTurn; // include creature that can only attack/block next turn private final int timeOut; + private final boolean canUseTimeout; private List> futures = new ArrayList<>(); /** @@ -98,6 +99,7 @@ public class AiAttackController { this.nextTurn = nextTurn; refreshCombatants(defendingOpponent); this.timeOut = ai.getGame().getAITimeout(); + this.canUseTimeout = ai.getGame().canUseTimeout(); } // overloaded constructor to evaluate attackers that should attack next turn public AiAttackController(final Player ai, Card attacker) { @@ -112,6 +114,7 @@ public class AiAttackController { } this.blockers = getPossibleBlockers(oppList, this.attackers, this.nextTurn); this.timeOut = ai.getGame().getAITimeout(); + this.canUseTimeout = ai.getGame().canUseTimeout(); } // overloaded constructor to evaluate single specified attacker private void refreshCombatants(GameEntity defender) { @@ -967,10 +970,16 @@ public class AiAttackController { numForcedAttackers.incrementAndGet(); } return 0; + }).exceptionally(ex -> { + ex.printStackTrace(); + return 0; })); } CompletableFuture[] futuresArray = futures.toArray(new CompletableFuture[0]); - CompletableFuture.allOf(futuresArray).completeOnTimeout(null, timeOut, TimeUnit.SECONDS).join(); + if (canUseTimeout) + CompletableFuture.allOf(futuresArray).completeOnTimeout(null, timeOut, TimeUnit.SECONDS).join(); + else + CompletableFuture.allOf(futuresArray).join(); futures.clear(); if (attackersLeft.isEmpty()) { return aiAggression; diff --git a/forge-ai/src/main/java/forge/ai/AiController.java b/forge-ai/src/main/java/forge/ai/AiController.java index 1688adbf056..64c08e7574e 100644 --- a/forge-ai/src/main/java/forge/ai/AiController.java +++ b/forge-ai/src/main/java/forge/ai/AiController.java @@ -1684,7 +1684,10 @@ public class AiController { // instead of computing all available concurrently just add a simple timeout depending on the user prefs try { - return future.completeOnTimeout(null, game.getAITimeout(), TimeUnit.SECONDS).get(); + if (game.AI_CAN_USE_TIMEOUT) + return future.completeOnTimeout(null, game.getAITimeout(), TimeUnit.SECONDS).get(); + else + return future.get(); } catch (InterruptedException | ExecutionException e) { return null; } diff --git a/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java b/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java index 2108118976b..38a90a59112 100644 --- a/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java +++ b/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java @@ -216,6 +216,7 @@ public class GameCopier { private void copyGameState(Game newGame, Player aiPlayer) { newGame.EXPERIMENTAL_RESTORE_SNAPSHOT = origGame.EXPERIMENTAL_RESTORE_SNAPSHOT; newGame.AI_TIMEOUT = origGame.AI_TIMEOUT; + newGame.AI_CAN_USE_TIMEOUT = origGame.AI_CAN_USE_TIMEOUT; newGame.setAge(origGame.getAge()); // TODO countersAddedThisTurn diff --git a/forge-game/src/main/java/forge/game/Game.java b/forge-game/src/main/java/forge/game/Game.java index 640638135e2..489672d2a04 100644 --- a/forge-game/src/main/java/forge/game/Game.java +++ b/forge-game/src/main/java/forge/game/Game.java @@ -90,6 +90,7 @@ public class Game { private final Zone stackZone = new Zone(ZoneType.Stack, this); public int AI_TIMEOUT = 5; + public boolean AI_CAN_USE_TIMEOUT = true; public boolean EXPERIMENTAL_RESTORE_SNAPSHOT = false; // While this is false here, its really set by the Match/Preferences @@ -1359,4 +1360,7 @@ public class Game { public int getAITimeout() { return AI_TIMEOUT; } + public boolean canUseTimeout() { + return AI_CAN_USE_TIMEOUT; + } } diff --git a/forge-gui-desktop/src/test/java/forge/ai/simulation/SimulationTest.java b/forge-gui-desktop/src/test/java/forge/ai/simulation/SimulationTest.java index a4d9ecf6b9f..d3481d2b46b 100644 --- a/forge-gui-desktop/src/test/java/forge/ai/simulation/SimulationTest.java +++ b/forge-gui-desktop/src/test/java/forge/ai/simulation/SimulationTest.java @@ -48,6 +48,7 @@ public class SimulationTest { game.setAge(GameStage.Play); game.EXPERIMENTAL_RESTORE_SNAPSHOT = false; game.AI_TIMEOUT = FModel.getPreferences().getPrefInt(FPref.MATCH_AI_TIMEOUT); + game.AI_CAN_USE_TIMEOUT = true; //Only Android is restricted according to API Level return game; } diff --git a/forge-gui-mobile/src/forge/screens/settings/SettingsPage.java b/forge-gui-mobile/src/forge/screens/settings/SettingsPage.java index ca1252e12c4..5738c1497ef 100644 --- a/forge-gui-mobile/src/forge/screens/settings/SettingsPage.java +++ b/forge-gui-mobile/src/forge/screens/settings/SettingsPage.java @@ -244,10 +244,12 @@ public class SettingsPage extends TabPage { Forge.getLocalizer().getMessage("cbExperimentalRestore"), Forge.getLocalizer().getMessage("nlExperimentalRestore")), 1); - lstSettings.addItem(new CustomSelectSetting(FPref.MATCH_AI_TIMEOUT, Forge.getLocalizer().getMessage("cbAITimeout"), - Forge.getLocalizer().getMessage("nlAITimeout"), - Lists.newArrayList("5", "10", "60", "120", "240", "300", "600")), - 1); + if (!GuiBase.isAndroid() || GuiBase.getAndroidAPILevel() > 30) { + lstSettings.addItem(new CustomSelectSetting(FPref.MATCH_AI_TIMEOUT, Forge.getLocalizer().getMessage("cbAITimeout"), + Forge.getLocalizer().getMessage("nlAITimeout"), + Lists.newArrayList("5", "10", "60", "120", "240", "300", "600")), + 1); + } lstSettings.addItem(new BooleanSetting(FPref.FILTERED_HANDS, Forge.getLocalizer().getMessage("cbFilteredHands"), Forge.getLocalizer().getMessage("nlFilteredHands")), diff --git a/forge-gui/src/main/java/forge/gamemodes/match/HostedMatch.java b/forge-gui/src/main/java/forge/gamemodes/match/HostedMatch.java index fc04e5afecb..901e446ec96 100644 --- a/forge-gui/src/main/java/forge/gamemodes/match/HostedMatch.java +++ b/forge-gui/src/main/java/forge/gamemodes/match/HostedMatch.java @@ -156,6 +156,9 @@ public class HostedMatch { game = match.createGame(); game.EXPERIMENTAL_RESTORE_SNAPSHOT = FModel.getPreferences().getPrefBoolean(FPref.MATCH_EXPERIMENTAL_RESTORE); game.AI_TIMEOUT = FModel.getPreferences().getPrefInt(FPref.MATCH_AI_TIMEOUT); + // Android API 31 and above can use completeOnTimeout -> CompletableFuture: + //https://developer.android.com/reference/java/util/concurrent/CompletableFuture#completeOnTimeout(T,%20long,%20java.util.concurrent.TimeUnit) + game.AI_CAN_USE_TIMEOUT = !GuiBase.isAndroid() || GuiBase.getAndroidAPILevel() > 30; StaticData.instance().setSourceImageForClone(FModel.getPreferences().getPrefBoolean(FPref.UI_CLONE_MODE_SOURCE));