diff --git a/src/main/java/funorb/shatteredplans/client/GameUI.java b/src/main/java/funorb/shatteredplans/client/GameUI.java index 968be35..8e5b1f6 100644 --- a/src/main/java/funorb/shatteredplans/client/GameUI.java +++ b/src/main/java/funorb/shatteredplans/client/GameUI.java @@ -3214,17 +3214,17 @@ public final class GameUI { this.gameSession.gameView.stopCombatAnimations(); this.gameSession.recalculateSystemState(); if (this.gameSession.localPlayer != null) { - final StatusPanelState var2 = (StatusPanelState) this.statusPanel.state; - if (var2.icon.isEmpty() && this.gameSession.placementMode != PlacementMode.BUILD_FLEET) { + final StatusPanelState statusState = (StatusPanelState) this.statusPanel.state; + if (statusState.icon.isEmpty() && this.gameSession.placementMode != PlacementMode.BUILD_FLEET) { if (this.gameSession.gameState.gameOptions.unifiedTerritories) { if (this.gameSession.localPlayer.combinedForce.fleetsAvailableToBuild > 0) { this.activateFleetPlacement(this.gameSession.localPlayer.combinedForce, true); return; } } else { - for (final ContiguousForce var3 : this.gameSession.localPlayer.contiguousForces) { - if (var3.fleetsAvailableToBuild > 0) { - this.activateFleetPlacement(var3, true); + for (final ContiguousForce force : this.gameSession.localPlayer.contiguousForces) { + if (force.fleetsAvailableToBuild > 0) { + this.activateFleetPlacement(force, true); return; } } @@ -3233,7 +3233,7 @@ public final class GameUI { this.gameSession.endTurn(); this.endTurnButton.activate(); - var2.icon.setSprite(null); + statusState.icon.setSprite(null); if (this.gameSession.playersWaitingOn == 1) { this.setActionHint(StringConstants.TEXT_WAITING_FOR_PLAYER); } else { diff --git a/src/main/java/funorb/shatteredplans/client/game/AbstractGameView.java b/src/main/java/funorb/shatteredplans/client/game/AbstractGameView.java index dea9e1d..deba8ac 100644 --- a/src/main/java/funorb/shatteredplans/client/game/AbstractGameView.java +++ b/src/main/java/funorb/shatteredplans/client/game/AbstractGameView.java @@ -45,16 +45,13 @@ public abstract class AbstractGameView { public boolean isAnimatingViewport; public StarSystem targetedSystem; public MoveFleetsOrder selectedFleetOrder; - protected int[] possibleSystemCollapseStages; protected Random random; protected int animationTick; public SystemHighlight[] highlightedSystems; protected int[] systemDrawX; protected List combatEngagements; protected int[] clonedRemainingGarrisons; - protected boolean[] willOwnSystem; protected int _n; - protected int[] systemCollapseStages; private int largestFleetMovement; protected int largestFleetBuildQuantity; protected List fleetMovements; @@ -66,7 +63,6 @@ public abstract class AbstractGameView { protected Player[] clonedSystemOwners; protected Collection projectOrders; protected List combatRetreats; - protected boolean[] canOwnSystem; protected int[] systemDrawY; protected Player[] systemOwners; @MagicConstant(valuesFromClass = GameView.AnimationPhase.class) @@ -243,16 +239,6 @@ public abstract class AbstractGameView { } } - public final void setTacticalOverlay(final boolean[] canOwnSystem, - final boolean[] willOwnSystem, - final int[] collapseStages, - final int[] possibleCollapseStages) { - this.possibleSystemCollapseStages = possibleCollapseStages; - this.systemCollapseStages = collapseStages; - this.canOwnSystem = canOwnSystem; - this.willOwnSystem = willOwnSystem; - } - private void addBuildTannhauserEvent(final Player player, final StarSystem source, final StarSystem target) { final int phase = this.nextTannhauserPhase(source, target); this.buildEvents.add(new BuildEvent(target, player, GameState.ResourceType.EXOTICS, phase)); diff --git a/src/main/java/funorb/shatteredplans/client/game/ClientGameSession.java b/src/main/java/funorb/shatteredplans/client/game/ClientGameSession.java index 87134c9..af86e91 100644 --- a/src/main/java/funorb/shatteredplans/client/game/ClientGameSession.java +++ b/src/main/java/funorb/shatteredplans/client/game/ClientGameSession.java @@ -65,6 +65,7 @@ public final class ClientGameSession extends GameSession { private Player[] systemOwners; private ContiguousForce[] systemForces; private int[] remainingGarrisons; + private TacticalAnalysis tacticalAnalysis; public boolean desynced; public int turnNumberWhenJoined; @@ -93,15 +94,6 @@ public final class ClientGameSession extends GameSession { private TickTimer recentlyPlayedBuildSfxQueue; private int recentlyPlayedBuildSfxCounter; - public boolean[] systemsWillOwn; - private boolean[] systemsCanOwn; - private int[] possibleCollapseStages; - private int[] minGarrisonsAtTurnEnd; - private int[] guaranteedCollapseStages; - private int[] maxGarrisonsAtTurnEnd; - private int[] safeGarrisonsToHold; - private int[] minGarrisonsToHold; - public ClientGameSession(final boolean isMultiplayer, final boolean isTutorial, final int turnLengthIndex, @@ -202,17 +194,10 @@ public final class ClientGameSession extends GameSession { } } - final int systemCount = this.gameState.map.systems.length; - this.systemsCanOwn = new boolean[systemCount]; - this.systemsWillOwn = new boolean[systemCount]; - this.guaranteedCollapseStages = new int[systemCount]; - this.possibleCollapseStages = new int[systemCount]; - this.minGarrisonsAtTurnEnd = new int[systemCount]; - this.safeGarrisonsToHold = new int[systemCount]; - this.maxGarrisonsAtTurnEnd = new int[systemCount]; - this.minGarrisonsToHold = new int[systemCount]; this.gameState.recalculatePlayerFleetProduction(); - this.gameView.setTacticalOverlay(this.systemsCanOwn, this.systemsWillOwn, this.guaranteedCollapseStages, this.possibleCollapseStages); + + this.tacticalAnalysis = new TacticalAnalysis(this.gameState.map.systems.length); + this.gameView.setTacticalAnalysis(this.tacticalAnalysis); if (this.localPlayerIndex < 0) { isAutoPlaying = false; @@ -300,115 +285,12 @@ public final class ClientGameSession extends GameSession { ShatteredPlansClient.saveProfile(); } - /** - * Recalculates the “hatching” overlay used to display which systems are - * expected or in danger of being captured or lost. - */ - private void recalculateTacticalOverlay() { - for (final StarSystem system : this.gameState.map.systems) { - this.guaranteedCollapseStages[system.index] = 0; - this.possibleCollapseStages[system.index] = 0; - if (system.owner == this.localPlayer) { - this.systemsCanOwn[system.index] = true; - this.maxGarrisonsAtTurnEnd[system.index] = system.remainingGarrison; - this.systemsWillOwn[system.index] = true; - this.minGarrisonsAtTurnEnd[system.index] = system.remainingGarrison; - } else { - this.systemsCanOwn[system.index] = false; - this.maxGarrisonsAtTurnEnd[system.index] = 0; - this.systemsWillOwn[system.index] = false; - this.minGarrisonsAtTurnEnd[system.index] = 0; - } + private void recalculateTacticalAnalysis() { + this.tacticalAnalysis.analyze(this.gameState, this.localPlayer); + } - for (final MoveFleetsOrder incomingOrder : system.incomingOrders) { - if (incomingOrder.player == this.localPlayer) { - this.maxGarrisonsAtTurnEnd[system.index] += incomingOrder.quantity; - this.systemsCanOwn[system.index] = true; - if (incomingOrder.target.owner == this.localPlayer || incomingOrder.target.garrison == 0) { - this.minGarrisonsAtTurnEnd[system.index] += incomingOrder.quantity; - this.systemsWillOwn[system.index] = true; - } - } - } - - if (this.localPlayer != null) { - for (final StarSystem neighbor : system.neighbors) { - if (neighbor.owner != null && neighbor.owner != this.localPlayer - && (system.owner != this.localPlayer || !neighbor.owner.allies[this.localPlayer.index]) - && !this.isStellarBombTarget(this.localPlayer, neighbor)) { - this.systemsWillOwn[system.index] = false; - this.minGarrisonsAtTurnEnd[system.index] = 0; - break; - } - } - } - } - - if (this.gameState.gameOptions.noChainCollapsing) { - for (final StarSystem system : this.gameState.map.systems) { - this.minGarrisonsToHold[system.index] = 0; - this.safeGarrisonsToHold[system.index] = 0; - } - } else if (this.gameState.gameOptions.simpleGarrisoning) { - for (final StarSystem system : this.gameState.map.systems) { - this.minGarrisonsToHold[system.index] = 1; - this.safeGarrisonsToHold[system.index] = 1; - } - } else { - for (final StarSystem system : this.gameState.map.systems) { - this.minGarrisonsToHold[system.index] = (int) Arrays.stream(system.neighbors).filter(neighbor -> !this.systemsCanOwn[neighbor.index]).count(); - this.safeGarrisonsToHold[system.index] = (int) Arrays.stream(system.neighbors).filter(neighbor -> !this.systemsWillOwn[neighbor.index]).count(); - } - } - - if (!this.gameState.gameOptions.noChainCollapsing) { - boolean goAgain = true; - while (goAgain) { - goAgain = false; - - for (final StarSystem system : this.gameState.map.systems) { - final int index = system.index; - if (this.systemsCanOwn[index] && this.minGarrisonsToHold[index] > this.maxGarrisonsAtTurnEnd[index]) { - goAgain = true; - this.systemsCanOwn[index] = false; - final int nextStage = this.guaranteedCollapseStages[index] + 1; - - for (final StarSystem neighbor : system.neighbors) { - if (this.gameState.gameOptions.simpleGarrisoning) { - this.minGarrisonsToHold[neighbor.index] = 1; - } else { - this.minGarrisonsToHold[neighbor.index]++; - } - - if (this.guaranteedCollapseStages[neighbor.index] > nextStage || this.systemsCanOwn[neighbor.index]) { - this.guaranteedCollapseStages[neighbor.index] = nextStage; - } - } - } - - if (this.systemsWillOwn[index] && this.safeGarrisonsToHold[index] > this.minGarrisonsAtTurnEnd[index]) { - goAgain = true; - this.systemsWillOwn[index] = false; - this.minGarrisonsAtTurnEnd[index] = 0; - final int nextStage = this.possibleCollapseStages[index] + 1; - - for (final StarSystem neighbor : system.neighbors) { - if (this.gameState.gameOptions.simpleGarrisoning) { - this.safeGarrisonsToHold[neighbor.index] = 1; - } else { - this.safeGarrisonsToHold[neighbor.index]++; - } - - if (nextStage < this.possibleCollapseStages[neighbor.index] || this.systemsWillOwn[neighbor.index]) { - this.possibleCollapseStages[neighbor.index] = nextStage; - } - } - } - } - } - } - - this.gameView.setTacticalOverlay(this.systemsCanOwn, this.systemsWillOwn, this.guaranteedCollapseStages, this.possibleCollapseStages); + public boolean isSystemOwnershipGuaranteed(final StarSystem system) { + return this.tacticalAnalysis.isOwnershipGuaranteed(system); } private void readTurnOrdersAndUpdate(final CipheredBuffer packet, final int len) { @@ -427,22 +309,18 @@ public final class ClientGameSession extends GameSession { order.source.remainingGarrison -= order.quantity; } - this.recalculateTacticalOverlay(); + this.recalculateTacticalAnalysis(); } // only used by the tutorial public void setMap(final Map map) { this.gameState.setMap(map); + + this.tacticalAnalysis = new TacticalAnalysis(map.systems.length); + this.recalculateTacticalAnalysis(); + this.gameView.setMap(map); - this.maxGarrisonsAtTurnEnd = new int[map.systems.length]; - this.minGarrisonsToHold = new int[map.systems.length]; - this.systemsCanOwn = new boolean[map.systems.length]; - this.guaranteedCollapseStages = new int[map.systems.length]; - this.minGarrisonsAtTurnEnd = new int[map.systems.length]; - this.safeGarrisonsToHold = new int[map.systems.length]; - this.systemsWillOwn = new boolean[map.systems.length]; - this.possibleCollapseStages = new int[map.systems.length]; - this.recalculateTacticalOverlay(); + this.gameView.setTacticalAnalysis(this.tacticalAnalysis); } public void draw() { @@ -1412,7 +1290,7 @@ public final class ClientGameSession extends GameSession { this.gameView.stopCombatAnimations(); this.recalculateSystemState(); this.handleBuildProject(GameState.ResourceType.ENERGY, target); - this.recalculateTacticalOverlay(); + this.recalculateTacticalAnalysis(); this.ui.setPlacementMode(PlacementMode.NONE); this.ui.markProjectPending(GameState.ResourceType.ENERGY); } @@ -1481,7 +1359,7 @@ public final class ClientGameSession extends GameSession { this.gameState.projectOrders.remove(order); this.ui.handleProjectOrderCanceled(order.type); if (order.type == GameState.ResourceType.ENERGY) { - this.recalculateTacticalOverlay(); + this.recalculateTacticalAnalysis(); } } @@ -1534,7 +1412,7 @@ public final class ClientGameSession extends GameSession { this.cancelOrdersToAttackPlayer(offerer); } - this.recalculateTacticalOverlay(); + this.recalculateTacticalAnalysis(); super.handlePactAccepted(offerer, offeree); } @@ -1590,15 +1468,6 @@ public final class ClientGameSession extends GameSession { for (final StarSystem system : this.gameState.map.systems) { system.remainingGarrison = system.garrison; - if (system.owner == this.localPlayer) { - this.maxGarrisonsAtTurnEnd[system.index] = system.garrison; - this.minGarrisonsAtTurnEnd[system.index] = system.garrison; - } else { - this.systemsCanOwn[system.index] = false; - this.systemsWillOwn[system.index] = false; - this.maxGarrisonsAtTurnEnd[system.index] = 0; - this.minGarrisonsAtTurnEnd[system.index] = 0; - } } if (this.systemOwners == null || this.systemOwners.length < this.gameState.map.systems.length) { @@ -1615,13 +1484,12 @@ public final class ClientGameSession extends GameSession { } this.gameView.assignSystemState(this.remainingGarrisons, this.systemForces, this.systemOwners, false); - this.gameView.setTacticalOverlay(this.systemsCanOwn, this.systemsWillOwn, this.guaranteedCollapseStages, this.possibleCollapseStages); - this.recalculateTacticalOverlay(); + this.recalculateTacticalAnalysis(); if (this.isMultiplayer || this.ais[this.localPlayerIndex] == null) { if (isAutoPlaying && !this.desynced && this.localPlayerIsAlive) { this.ais[this.localPlayerIndex].makeDesiredPactOffers(); this.ais[this.localPlayerIndex].planTurnOrders(); - this.recalculateTacticalOverlay(); + this.recalculateTacticalAnalysis(); this.unsentMoveOrders.addAll(this.gameState.moveOrders); this.unsentBuildOrders.addAll(this.gameState.buildOrders); @@ -1650,16 +1518,11 @@ public final class ClientGameSession extends GameSession { JagexApplet.clientError(var6, "AI has errored in single player game"); } - this.recalculateTacticalOverlay(); + this.recalculateTacticalAnalysis(); this.advanceTurnSinglePlayer(); } } - private boolean isStellarBombTarget(final Player player, final StarSystem system) { - return this.gameState.projectOrders.stream().anyMatch(order -> - order.player == player && order.type == GameState.ResourceType.ENERGY && order.target == system); - } - private void resendAllTurnOrders() { C2SPacket.Type.ALL_TURN_ORDERS.write(C2SPacket.buffer); C2SPacket.buffer.withLengthShort(() -> { @@ -1681,7 +1544,7 @@ public final class ClientGameSession extends GameSession { orders.projectOrders.forEach(this::addOrder); orders.buildOrders.forEach(this::addOrder); orders.moveOrders.forEach(this::addOrder); - this.recalculateTacticalOverlay(); + this.recalculateTacticalAnalysis(); this.ui.updateAvailableFleetCounters(); } @@ -1707,8 +1570,6 @@ public final class ClientGameSession extends GameSession { order.system.remainingGarrison += order.quantity; this.remainingGarrisons[order.system.index] += order.quantity; - this.maxGarrisonsAtTurnEnd[order.system.index] += order.quantity; - this.minGarrisonsAtTurnEnd[order.system.index] += order.quantity; force.fleetsAvailableToBuild -= order.quantity; } @@ -1762,7 +1623,7 @@ public final class ClientGameSession extends GameSession { this.gameState.moveOrders.remove(order); } - this.recalculateTacticalOverlay(); + this.recalculateTacticalAnalysis(); if (this.isMultiplayer) { this.unsentMoveOrders.remove(order); this.unsentMoveOrders.add(order); @@ -1819,12 +1680,12 @@ public final class ClientGameSession extends GameSession { this.gameView.highlightedSystems[system.index] = SystemHighlight.GRAY; } } - this.recalculateTacticalOverlay(); + this.recalculateTacticalAnalysis(); } private void handleMoveFleets(final StarSystem source, final StarSystem target, final int quantity) { this.addOrder(new MoveFleetsOrder(source, target, quantity)); - this.recalculateTacticalOverlay(); + this.recalculateTacticalAnalysis(); } private void handleBuildProject(@MagicConstant(valuesFromClass = GameState.ResourceType.class) final int type, final StarSystem target) { diff --git a/src/main/java/funorb/shatteredplans/client/game/GameView.java b/src/main/java/funorb/shatteredplans/client/game/GameView.java index ba8a2e8..9b5b756 100644 --- a/src/main/java/funorb/shatteredplans/client/game/GameView.java +++ b/src/main/java/funorb/shatteredplans/client/game/GameView.java @@ -74,6 +74,7 @@ public final class GameView extends AbstractGameView { public boolean _Gb; public boolean _Ab; public MoveFleetsOrder _rb; + protected TacticalAnalysis tacticalAnalysis; private Sprite[] _Mb; private ArgbSprite _K; private double _wb; @@ -3055,9 +3056,9 @@ public final class GameView extends AbstractGameView { final boolean isAttacked = system.incomingOrders.stream() .anyMatch(order -> order.player == this.localPlayer); if (isAttacked && this.localPlayer != null) { - if (!this.canOwnSystem[system.index]) { + if (!this.tacticalAnalysis.isOwnershipPossible(system)) { drawSystemHatching(this.systemHexes[system.index], Drawing.RED, 96); - } else if (this.willOwnSystem[system.index]) { + } else if (this.tacticalAnalysis.isOwnershipGuaranteed(system)) { drawSystemHatching(this.systemHexes[system.index], Drawing.GREEN, 64); } else { drawSystemHatching(this.systemHexes[system.index], Drawing.YELLOW, 64); @@ -3079,10 +3080,10 @@ public final class GameView extends AbstractGameView { } if (owner == this.localPlayer && this.localPlayer != null) { - if (!this.canOwnSystem[system.index]) { - drawSystemHatching(var6, Drawing.RED, this.systemCollapseStages[system.index] == 0 ? 192 : 96); - } else if (!this.willOwnSystem[system.index]) { - drawSystemHatching(var6, Drawing.YELLOW, this.possibleSystemCollapseStages[system.index] == 0 ? 128 : 64); + if (!this.tacticalAnalysis.isOwnershipPossible(system)) { + drawSystemHatching(var6, Drawing.RED, this.tacticalAnalysis.getEarliestGuaranteedCollapseWave(system) == 0 ? 192 : 96); + } else if (!this.tacticalAnalysis.isOwnershipGuaranteed(system)) { + drawSystemHatching(var6, Drawing.YELLOW, this.tacticalAnalysis.getEarliestPossibleCollapseWave(system) == 0 ? 128 : 64); } } } @@ -3701,4 +3702,8 @@ public final class GameView extends AbstractGameView { } } } + + public final void setTacticalAnalysis(final TacticalAnalysis tacticalAnalysis) { + this.tacticalAnalysis = tacticalAnalysis; + } } diff --git a/src/main/java/funorb/shatteredplans/client/game/TacticalAnalysis.java b/src/main/java/funorb/shatteredplans/client/game/TacticalAnalysis.java new file mode 100644 index 0000000..c07b882 --- /dev/null +++ b/src/main/java/funorb/shatteredplans/client/game/TacticalAnalysis.java @@ -0,0 +1,218 @@ +package funorb.shatteredplans.client.game; + +import funorb.shatteredplans.game.GameState; +import funorb.shatteredplans.game.MoveFleetsOrder; +import funorb.shatteredplans.game.Player; +import funorb.shatteredplans.map.StarSystem; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Arrays; + +/** + * This class performs some simple analyses of the current map from the + * perspective of the local player. The resulting information is used to drive + * the “tactical overlay” in the user interface, which renders the information + * using cross-hatching of various colors. + */ +public final class TacticalAnalysis { + /** + * {@code true} for systems owned by the local player that could be attacked + * by a hostile force on this turn (and therefore could be lost regardless of + * its garrison at the start of combat on this turn). + */ + private final boolean[] systemThreatened; + + /** + * {@code true} for systems that the local player is guaranteed to own + * after this turn. Implies {@link #systemCanOwn}. + */ + private final boolean[] systemWillOwn; + + /** + * {@code true} for systems that the local play may own after this + * turn. Implied by {@link #systemWillOwn}. + */ + private final boolean[] systemCanOwn; + + /** + * The minimum number of fleets (owned by the local player) that could be + * garrisoned in each system after this turn, assuming the worst possible + * combat outcomes. + */ + private final int[] minGarrisonAtTurnEnd; + + /** + * The maximum number of fleets (owned by the local player) that could be + * garrisoned in each system after this turn, assuming the best possible + * combat outcomes. + */ + private final int[] maxGarrisonAtTurnEnd; + + /** + * The absolute minimum number of fleets needed to be garrisoned in each + * system at the end of this turn to have any chance of holding it, assuming + * the best possible combat outcomes in neighboring systems. + */ + private final int[] minGarrisonToHold; + + /** + * The minimum number of fleets needed to be garrisoned in each system at the + * end of this turn to guarantee it will be held, assuming the worst + * possible combat outcomes in neighboring systems. + */ + private final int[] safeGarrisonToHold; + + /** + * Tracks the index of the earliest “wave” in which each system might + * collapse. For systems that cannot be lost (see {@link #systemWillOwn}), + * the value is arbitrary. + */ + private final int[] possibleCollapseWave; + + /** + * Tracks the index of the earliest “wave” in which each system is + * guaranteed to collapse. For systems that might not be lost (see + * {@link #systemCanOwn}), the value is arbitrary. + */ + private final int[] guaranteedCollapseWave; + + public TacticalAnalysis(final int systemCount) { + this.systemThreatened = new boolean[systemCount]; + this.systemCanOwn = new boolean[systemCount]; + this.systemWillOwn = new boolean[systemCount]; + this.guaranteedCollapseWave = new int[systemCount]; + this.possibleCollapseWave = new int[systemCount]; + this.minGarrisonAtTurnEnd = new int[systemCount]; + this.safeGarrisonToHold = new int[systemCount]; + this.maxGarrisonAtTurnEnd = new int[systemCount]; + this.minGarrisonToHold = new int[systemCount]; + } + + public boolean isThreatened(final @NotNull StarSystem system) { + return this.systemThreatened[system.index]; + } + + public boolean isOwnershipGuaranteed(final @NotNull StarSystem system) { + return this.systemWillOwn[system.index]; + } + + public boolean isOwnershipPossible(final @NotNull StarSystem system) { + return this.systemCanOwn[system.index]; + } + + public int getEarliestPossibleCollapseWave(final @NotNull StarSystem system) { + return this.possibleCollapseWave[system.index]; + } + + public int getEarliestGuaranteedCollapseWave(final @NotNull StarSystem system) { + return this.guaranteedCollapseWave[system.index]; + } + + public void analyze(final @NotNull GameState gameState, final @Nullable Player localPlayer) { + for (final StarSystem system : gameState.map.systems) { + this.systemThreatened[system.index] = false; + this.guaranteedCollapseWave[system.index] = 0; + this.possibleCollapseWave[system.index] = 0; + if (system.owner == localPlayer) { + this.systemCanOwn[system.index] = true; + this.maxGarrisonAtTurnEnd[system.index] = system.remainingGarrison; + this.systemWillOwn[system.index] = true; + this.minGarrisonAtTurnEnd[system.index] = system.remainingGarrison; + } else { + this.systemCanOwn[system.index] = false; + this.maxGarrisonAtTurnEnd[system.index] = 0; + this.systemWillOwn[system.index] = false; + this.minGarrisonAtTurnEnd[system.index] = 0; + } + + for (final MoveFleetsOrder incomingOrder : system.incomingOrders) { + if (incomingOrder.player == localPlayer) { + this.maxGarrisonAtTurnEnd[system.index] += incomingOrder.quantity; + this.systemCanOwn[system.index] = true; + if (incomingOrder.target.owner == localPlayer || incomingOrder.target.garrison == 0) { + this.minGarrisonAtTurnEnd[system.index] += incomingOrder.quantity; + this.systemWillOwn[system.index] = true; + } + } + } + + if (localPlayer != null) { + for (final StarSystem neighbor : system.neighbors) { + if (neighbor.owner != null && neighbor.owner != localPlayer + && (system.owner != localPlayer || !neighbor.owner.allies[localPlayer.index]) + && !gameState.isStellarBombTarget(localPlayer, neighbor)) { + this.systemThreatened[system.index] = true; + this.systemWillOwn[system.index] = false; + this.minGarrisonAtTurnEnd[system.index] = 0; + break; + } + } + } + } + + if (gameState.gameOptions.noChainCollapsing) { + for (final StarSystem system : gameState.map.systems) { + this.minGarrisonToHold[system.index] = 0; + this.safeGarrisonToHold[system.index] = 0; + } + } else if (gameState.gameOptions.simpleGarrisoning) { + for (final StarSystem system : gameState.map.systems) { + this.minGarrisonToHold[system.index] = 1; + this.safeGarrisonToHold[system.index] = 1; + } + } else { + for (final StarSystem system : gameState.map.systems) { + this.minGarrisonToHold[system.index] = (int) Arrays.stream(system.neighbors).filter(neighbor -> !this.systemCanOwn[neighbor.index]).count(); + this.safeGarrisonToHold[system.index] = (int) Arrays.stream(system.neighbors).filter(neighbor -> !this.systemWillOwn[neighbor.index]).count(); + } + } + + if (!gameState.gameOptions.noChainCollapsing) { + boolean goAgain = true; + while (goAgain) { + goAgain = false; + + for (final StarSystem system : gameState.map.systems) { + final int index = system.index; + if (this.systemCanOwn[index] && this.minGarrisonToHold[index] > this.maxGarrisonAtTurnEnd[index]) { + goAgain = true; + this.systemCanOwn[index] = false; + final int nextStage = this.guaranteedCollapseWave[index] + 1; + + for (final StarSystem neighbor : system.neighbors) { + if (gameState.gameOptions.simpleGarrisoning) { + this.minGarrisonToHold[neighbor.index] = 1; + } else { + this.minGarrisonToHold[neighbor.index]++; + } + + if (this.guaranteedCollapseWave[neighbor.index] > nextStage || this.systemCanOwn[neighbor.index]) { + this.guaranteedCollapseWave[neighbor.index] = nextStage; + } + } + } + + if (this.systemWillOwn[index] && this.safeGarrisonToHold[index] > this.minGarrisonAtTurnEnd[index]) { + goAgain = true; + this.systemWillOwn[index] = false; + this.minGarrisonAtTurnEnd[index] = 0; + final int nextStage = this.possibleCollapseWave[index] + 1; + + for (final StarSystem neighbor : system.neighbors) { + if (gameState.gameOptions.simpleGarrisoning) { + this.safeGarrisonToHold[neighbor.index] = 1; + } else { + this.safeGarrisonToHold[neighbor.index]++; + } + + if (nextStage < this.possibleCollapseWave[neighbor.index] || this.systemWillOwn[neighbor.index]) { + this.possibleCollapseWave[neighbor.index] = nextStage; + } + } + } + } + } + } + } +} diff --git a/src/main/java/funorb/shatteredplans/client/game/TutorialState.java b/src/main/java/funorb/shatteredplans/client/game/TutorialState.java index f416e24..d88f5a3 100644 --- a/src/main/java/funorb/shatteredplans/client/game/TutorialState.java +++ b/src/main/java/funorb/shatteredplans/client/game/TutorialState.java @@ -262,7 +262,7 @@ public final class TutorialState { for (var3 = 0; var2.length > var3; ++var3) { var4 = var2[var3]; - if (var4.owner == localPlayer && !session.systemsWillOwn[var4.index]) { + if (var4.owner == localPlayer && !session.isSystemOwnershipGuaranteed(var4)) { session.gameView.a021(92, var4, 300.0F); return; } diff --git a/src/main/java/funorb/shatteredplans/game/GameState.java b/src/main/java/funorb/shatteredplans/game/GameState.java index 0416a8f..073aee7 100644 --- a/src/main/java/funorb/shatteredplans/game/GameState.java +++ b/src/main/java/funorb/shatteredplans/game/GameState.java @@ -1333,6 +1333,11 @@ public final class GameState { return kills; } + public boolean isStellarBombTarget(final Player player, final StarSystem system) { + return this.projectOrders.stream().anyMatch(order -> + order.player == player && order.type == ResourceType.ENERGY && order.target == system); + } + public void recalculateFleetProduction() { for (final Player player : this.players) { this.recalculateFleetProduction(player);