Refactor tactical overview calculation into TacticalAnalysis class

This commit is contained in:
Alexis King 2022-12-11 12:27:13 -06:00
parent f7e1dc1b91
commit 17d675bdb4
7 changed files with 266 additions and 191 deletions

View File

@ -3214,17 +3214,17 @@ public final class GameUI {
this.gameSession.gameView.stopCombatAnimations(); this.gameSession.gameView.stopCombatAnimations();
this.gameSession.recalculateSystemState(); this.gameSession.recalculateSystemState();
if (this.gameSession.localPlayer != null) { if (this.gameSession.localPlayer != null) {
final StatusPanelState var2 = (StatusPanelState) this.statusPanel.state; final StatusPanelState statusState = (StatusPanelState) this.statusPanel.state;
if (var2.icon.isEmpty() && this.gameSession.placementMode != PlacementMode.BUILD_FLEET) { if (statusState.icon.isEmpty() && this.gameSession.placementMode != PlacementMode.BUILD_FLEET) {
if (this.gameSession.gameState.gameOptions.unifiedTerritories) { if (this.gameSession.gameState.gameOptions.unifiedTerritories) {
if (this.gameSession.localPlayer.combinedForce.fleetsAvailableToBuild > 0) { if (this.gameSession.localPlayer.combinedForce.fleetsAvailableToBuild > 0) {
this.activateFleetPlacement(this.gameSession.localPlayer.combinedForce, true); this.activateFleetPlacement(this.gameSession.localPlayer.combinedForce, true);
return; return;
} }
} else { } else {
for (final ContiguousForce var3 : this.gameSession.localPlayer.contiguousForces) { for (final ContiguousForce force : this.gameSession.localPlayer.contiguousForces) {
if (var3.fleetsAvailableToBuild > 0) { if (force.fleetsAvailableToBuild > 0) {
this.activateFleetPlacement(var3, true); this.activateFleetPlacement(force, true);
return; return;
} }
} }
@ -3233,7 +3233,7 @@ public final class GameUI {
this.gameSession.endTurn(); this.gameSession.endTurn();
this.endTurnButton.activate(); this.endTurnButton.activate();
var2.icon.setSprite(null); statusState.icon.setSprite(null);
if (this.gameSession.playersWaitingOn == 1) { if (this.gameSession.playersWaitingOn == 1) {
this.setActionHint(StringConstants.TEXT_WAITING_FOR_PLAYER); this.setActionHint(StringConstants.TEXT_WAITING_FOR_PLAYER);
} else { } else {

View File

@ -45,16 +45,13 @@ public abstract class AbstractGameView {
public boolean isAnimatingViewport; public boolean isAnimatingViewport;
public StarSystem targetedSystem; public StarSystem targetedSystem;
public MoveFleetsOrder selectedFleetOrder; public MoveFleetsOrder selectedFleetOrder;
protected int[] possibleSystemCollapseStages;
protected Random random; protected Random random;
protected int animationTick; protected int animationTick;
public SystemHighlight[] highlightedSystems; public SystemHighlight[] highlightedSystems;
protected int[] systemDrawX; protected int[] systemDrawX;
protected List<CombatEngagementAnimationState> combatEngagements; protected List<CombatEngagementAnimationState> combatEngagements;
protected int[] clonedRemainingGarrisons; protected int[] clonedRemainingGarrisons;
protected boolean[] willOwnSystem;
protected int _n; protected int _n;
protected int[] systemCollapseStages;
private int largestFleetMovement; private int largestFleetMovement;
protected int largestFleetBuildQuantity; protected int largestFleetBuildQuantity;
protected List<MoveFleetsAnimationState> fleetMovements; protected List<MoveFleetsAnimationState> fleetMovements;
@ -66,7 +63,6 @@ public abstract class AbstractGameView {
protected Player[] clonedSystemOwners; protected Player[] clonedSystemOwners;
protected Collection<ProjectOrder> projectOrders; protected Collection<ProjectOrder> projectOrders;
protected List<MoveFleetsAnimationState> combatRetreats; protected List<MoveFleetsAnimationState> combatRetreats;
protected boolean[] canOwnSystem;
protected int[] systemDrawY; protected int[] systemDrawY;
protected Player[] systemOwners; protected Player[] systemOwners;
@MagicConstant(valuesFromClass = GameView.AnimationPhase.class) @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) { private void addBuildTannhauserEvent(final Player player, final StarSystem source, final StarSystem target) {
final int phase = this.nextTannhauserPhase(source, target); final int phase = this.nextTannhauserPhase(source, target);
this.buildEvents.add(new BuildEvent(target, player, GameState.ResourceType.EXOTICS, phase)); this.buildEvents.add(new BuildEvent(target, player, GameState.ResourceType.EXOTICS, phase));

View File

@ -65,6 +65,7 @@ public final class ClientGameSession extends GameSession {
private Player[] systemOwners; private Player[] systemOwners;
private ContiguousForce[] systemForces; private ContiguousForce[] systemForces;
private int[] remainingGarrisons; private int[] remainingGarrisons;
private TacticalAnalysis tacticalAnalysis;
public boolean desynced; public boolean desynced;
public int turnNumberWhenJoined; public int turnNumberWhenJoined;
@ -93,15 +94,6 @@ public final class ClientGameSession extends GameSession {
private TickTimer recentlyPlayedBuildSfxQueue; private TickTimer recentlyPlayedBuildSfxQueue;
private int recentlyPlayedBuildSfxCounter; 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, public ClientGameSession(final boolean isMultiplayer,
final boolean isTutorial, final boolean isTutorial,
final int turnLengthIndex, 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.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) { if (this.localPlayerIndex < 0) {
isAutoPlaying = false; isAutoPlaying = false;
@ -300,115 +285,12 @@ public final class ClientGameSession extends GameSession {
ShatteredPlansClient.saveProfile(); ShatteredPlansClient.saveProfile();
} }
/** private void recalculateTacticalAnalysis() {
* Recalculates the hatching overlay used to display which systems are this.tacticalAnalysis.analyze(this.gameState, this.localPlayer);
* 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;
}
for (final MoveFleetsOrder incomingOrder : system.incomingOrders) { public boolean isSystemOwnershipGuaranteed(final StarSystem system) {
if (incomingOrder.player == this.localPlayer) { return this.tacticalAnalysis.isOwnershipGuaranteed(system);
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);
} }
private void readTurnOrdersAndUpdate(final CipheredBuffer packet, final int len) { private void readTurnOrdersAndUpdate(final CipheredBuffer packet, final int len) {
@ -427,22 +309,18 @@ public final class ClientGameSession extends GameSession {
order.source.remainingGarrison -= order.quantity; order.source.remainingGarrison -= order.quantity;
} }
this.recalculateTacticalOverlay(); this.recalculateTacticalAnalysis();
} }
// only used by the tutorial // only used by the tutorial
public void setMap(final Map map) { public void setMap(final Map map) {
this.gameState.setMap(map); this.gameState.setMap(map);
this.tacticalAnalysis = new TacticalAnalysis(map.systems.length);
this.recalculateTacticalAnalysis();
this.gameView.setMap(map); this.gameView.setMap(map);
this.maxGarrisonsAtTurnEnd = new int[map.systems.length]; this.gameView.setTacticalAnalysis(this.tacticalAnalysis);
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();
} }
public void draw() { public void draw() {
@ -1412,7 +1290,7 @@ public final class ClientGameSession extends GameSession {
this.gameView.stopCombatAnimations(); this.gameView.stopCombatAnimations();
this.recalculateSystemState(); this.recalculateSystemState();
this.handleBuildProject(GameState.ResourceType.ENERGY, target); this.handleBuildProject(GameState.ResourceType.ENERGY, target);
this.recalculateTacticalOverlay(); this.recalculateTacticalAnalysis();
this.ui.setPlacementMode(PlacementMode.NONE); this.ui.setPlacementMode(PlacementMode.NONE);
this.ui.markProjectPending(GameState.ResourceType.ENERGY); this.ui.markProjectPending(GameState.ResourceType.ENERGY);
} }
@ -1481,7 +1359,7 @@ public final class ClientGameSession extends GameSession {
this.gameState.projectOrders.remove(order); this.gameState.projectOrders.remove(order);
this.ui.handleProjectOrderCanceled(order.type); this.ui.handleProjectOrderCanceled(order.type);
if (order.type == GameState.ResourceType.ENERGY) { if (order.type == GameState.ResourceType.ENERGY) {
this.recalculateTacticalOverlay(); this.recalculateTacticalAnalysis();
} }
} }
@ -1534,7 +1412,7 @@ public final class ClientGameSession extends GameSession {
this.cancelOrdersToAttackPlayer(offerer); this.cancelOrdersToAttackPlayer(offerer);
} }
this.recalculateTacticalOverlay(); this.recalculateTacticalAnalysis();
super.handlePactAccepted(offerer, offeree); super.handlePactAccepted(offerer, offeree);
} }
@ -1590,15 +1468,6 @@ public final class ClientGameSession extends GameSession {
for (final StarSystem system : this.gameState.map.systems) { for (final StarSystem system : this.gameState.map.systems) {
system.remainingGarrison = system.garrison; 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) { 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.assignSystemState(this.remainingGarrisons, this.systemForces, this.systemOwners, false);
this.gameView.setTacticalOverlay(this.systemsCanOwn, this.systemsWillOwn, this.guaranteedCollapseStages, this.possibleCollapseStages); this.recalculateTacticalAnalysis();
this.recalculateTacticalOverlay();
if (this.isMultiplayer || this.ais[this.localPlayerIndex] == null) { if (this.isMultiplayer || this.ais[this.localPlayerIndex] == null) {
if (isAutoPlaying && !this.desynced && this.localPlayerIsAlive) { if (isAutoPlaying && !this.desynced && this.localPlayerIsAlive) {
this.ais[this.localPlayerIndex].makeDesiredPactOffers(); this.ais[this.localPlayerIndex].makeDesiredPactOffers();
this.ais[this.localPlayerIndex].planTurnOrders(); this.ais[this.localPlayerIndex].planTurnOrders();
this.recalculateTacticalOverlay(); this.recalculateTacticalAnalysis();
this.unsentMoveOrders.addAll(this.gameState.moveOrders); this.unsentMoveOrders.addAll(this.gameState.moveOrders);
this.unsentBuildOrders.addAll(this.gameState.buildOrders); 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"); JagexApplet.clientError(var6, "AI has errored in single player game");
} }
this.recalculateTacticalOverlay(); this.recalculateTacticalAnalysis();
this.advanceTurnSinglePlayer(); 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() { private void resendAllTurnOrders() {
C2SPacket.Type.ALL_TURN_ORDERS.write(C2SPacket.buffer); C2SPacket.Type.ALL_TURN_ORDERS.write(C2SPacket.buffer);
C2SPacket.buffer.withLengthShort(() -> { C2SPacket.buffer.withLengthShort(() -> {
@ -1681,7 +1544,7 @@ public final class ClientGameSession extends GameSession {
orders.projectOrders.forEach(this::addOrder); orders.projectOrders.forEach(this::addOrder);
orders.buildOrders.forEach(this::addOrder); orders.buildOrders.forEach(this::addOrder);
orders.moveOrders.forEach(this::addOrder); orders.moveOrders.forEach(this::addOrder);
this.recalculateTacticalOverlay(); this.recalculateTacticalAnalysis();
this.ui.updateAvailableFleetCounters(); this.ui.updateAvailableFleetCounters();
} }
@ -1707,8 +1570,6 @@ public final class ClientGameSession extends GameSession {
order.system.remainingGarrison += order.quantity; order.system.remainingGarrison += order.quantity;
this.remainingGarrisons[order.system.index] += 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; force.fleetsAvailableToBuild -= order.quantity;
} }
@ -1762,7 +1623,7 @@ public final class ClientGameSession extends GameSession {
this.gameState.moveOrders.remove(order); this.gameState.moveOrders.remove(order);
} }
this.recalculateTacticalOverlay(); this.recalculateTacticalAnalysis();
if (this.isMultiplayer) { if (this.isMultiplayer) {
this.unsentMoveOrders.remove(order); this.unsentMoveOrders.remove(order);
this.unsentMoveOrders.add(order); this.unsentMoveOrders.add(order);
@ -1819,12 +1680,12 @@ public final class ClientGameSession extends GameSession {
this.gameView.highlightedSystems[system.index] = SystemHighlight.GRAY; this.gameView.highlightedSystems[system.index] = SystemHighlight.GRAY;
} }
} }
this.recalculateTacticalOverlay(); this.recalculateTacticalAnalysis();
} }
private void handleMoveFleets(final StarSystem source, final StarSystem target, final int quantity) { private void handleMoveFleets(final StarSystem source, final StarSystem target, final int quantity) {
this.addOrder(new MoveFleetsOrder(source, target, 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) { private void handleBuildProject(@MagicConstant(valuesFromClass = GameState.ResourceType.class) final int type, final StarSystem target) {

View File

@ -74,6 +74,7 @@ public final class GameView extends AbstractGameView {
public boolean _Gb; public boolean _Gb;
public boolean _Ab; public boolean _Ab;
public MoveFleetsOrder _rb; public MoveFleetsOrder _rb;
protected TacticalAnalysis tacticalAnalysis;
private Sprite[] _Mb; private Sprite[] _Mb;
private ArgbSprite _K; private ArgbSprite _K;
private double _wb; private double _wb;
@ -3055,9 +3056,9 @@ public final class GameView extends AbstractGameView {
final boolean isAttacked = system.incomingOrders.stream() final boolean isAttacked = system.incomingOrders.stream()
.anyMatch(order -> order.player == this.localPlayer); .anyMatch(order -> order.player == this.localPlayer);
if (isAttacked && this.localPlayer != null) { if (isAttacked && this.localPlayer != null) {
if (!this.canOwnSystem[system.index]) { if (!this.tacticalAnalysis.isOwnershipPossible(system)) {
drawSystemHatching(this.systemHexes[system.index], Drawing.RED, 96); 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); drawSystemHatching(this.systemHexes[system.index], Drawing.GREEN, 64);
} else { } else {
drawSystemHatching(this.systemHexes[system.index], Drawing.YELLOW, 64); 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 (owner == this.localPlayer && this.localPlayer != null) {
if (!this.canOwnSystem[system.index]) { if (!this.tacticalAnalysis.isOwnershipPossible(system)) {
drawSystemHatching(var6, Drawing.RED, this.systemCollapseStages[system.index] == 0 ? 192 : 96); drawSystemHatching(var6, Drawing.RED, this.tacticalAnalysis.getEarliestGuaranteedCollapseWave(system) == 0 ? 192 : 96);
} else if (!this.willOwnSystem[system.index]) { } else if (!this.tacticalAnalysis.isOwnershipGuaranteed(system)) {
drawSystemHatching(var6, Drawing.YELLOW, this.possibleSystemCollapseStages[system.index] == 0 ? 128 : 64); 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;
}
} }

View File

@ -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 <i>guaranteed</i> to own
* after this turn. Implies {@link #systemCanOwn}.
*/
private final boolean[] systemWillOwn;
/**
* {@code true} for systems that the local play <i>may</i> 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 <i>guarantee</i> 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 <i>might</i>
* 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
* <i>guaranteed</i> 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;
}
}
}
}
}
}
}
}

View File

@ -262,7 +262,7 @@ public final class TutorialState {
for (var3 = 0; var2.length > var3; ++var3) { for (var3 = 0; var2.length > var3; ++var3) {
var4 = var2[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); session.gameView.a021(92, var4, 300.0F);
return; return;
} }

View File

@ -1333,6 +1333,11 @@ public final class GameState {
return kills; 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() { public void recalculateFleetProduction() {
for (final Player player : this.players) { for (final Player player : this.players) {
this.recalculateFleetProduction(player); this.recalculateFleetProduction(player);