mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-18 23:22:44 +00:00
fix: allies cannot annex your clusters (#2158)
## Description: Fixes #1685 Continuation from #1924, which was auto-closed after the upstream repo force-pushed main and I synced my fork. This change ensures that allies are excluded from the `getMode()` call made by `getCapturingPlayer()` inside `removeCluster()`. - Previously, friendly neighbors were treated as candidates for capturing, leading to incorrect annexations in certain edge cases. - Added a small efficiency improvement by filtering out non-player and friendly neighbors up front to reduce total computations down-the-line. - Important: we can’t simply check if the `getMode(neighborsIDs)` result is a friendly. Doing so would cause the territory to go to nobody until the user is attacked. I believe the expected behavior is the largest neighboring enemy should take it automatically. Here's an example of the new behavior in an extreme edge case: <img width="622" height="422" alt="Screenshot 2025-08-24 at 4 56 46 PM" src="https://github.com/user-attachments/assets/c5dd9c0d-0a3c-4657-8154-e114fa920689" /> ## Please complete the following: - [X] I have added screenshots for all UI updates - [X] I process any text displayed to the user through translateText() and I've added it to the en.json file - [X] I have added relevant tests to the test directory - [X] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: nottirb
This commit is contained in:
@@ -114,27 +114,6 @@ export function inscribed(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMode(list: Set<number>): number {
|
|
||||||
// Count occurrences
|
|
||||||
const counts = new Map<number, number>();
|
|
||||||
for (const item of list) {
|
|
||||||
counts.set(item, (counts.get(item) ?? 0) + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the item with the highest count
|
|
||||||
let mode = 0;
|
|
||||||
let maxCount = 0;
|
|
||||||
|
|
||||||
for (const [item, count] of counts) {
|
|
||||||
if (count > maxCount) {
|
|
||||||
maxCount = count;
|
|
||||||
mode = item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return mode;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function sanitize(name: string): string {
|
export function sanitize(name: string): string {
|
||||||
return Array.from(name)
|
return Array.from(name)
|
||||||
.join("")
|
.join("")
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Config } from "../configuration/Config";
|
|||||||
import { Execution, Game, Player, UnitType } from "../game/Game";
|
import { Execution, Game, Player, UnitType } from "../game/Game";
|
||||||
import { GameImpl } from "../game/GameImpl";
|
import { GameImpl } from "../game/GameImpl";
|
||||||
import { GameMap, TileRef } from "../game/GameMap";
|
import { GameMap, TileRef } from "../game/GameMap";
|
||||||
import { calculateBoundingBox, getMode, inscribed, simpleHash } from "../Util";
|
import { calculateBoundingBox, inscribed, simpleHash } from "../Util";
|
||||||
|
|
||||||
export class PlayerExecution implements Execution {
|
export class PlayerExecution implements Execution {
|
||||||
private readonly ticksPerClusterCalc = 20;
|
private readonly ticksPerClusterCalc = 20;
|
||||||
@@ -214,22 +214,37 @@ export class PlayerExecution implements Execution {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private getCapturingPlayer(cluster: Set<TileRef>): Player | null {
|
private getCapturingPlayer(cluster: Set<TileRef>): Player | null {
|
||||||
const neighborsIDs = new Set<number>();
|
// Collect unique neighbor IDs (excluding self) as candidates
|
||||||
|
const candidatesIDs = new Set<number>();
|
||||||
|
const selfID = this.player.smallID();
|
||||||
|
|
||||||
for (const t of cluster) {
|
for (const t of cluster) {
|
||||||
for (const neighbor of this.mg.neighbors(t)) {
|
for (const neighbor of this.mg.neighbors(t)) {
|
||||||
if (this.mg.ownerID(neighbor) !== this.player.smallID()) {
|
if (this.mg.ownerID(neighbor) !== selfID) {
|
||||||
neighborsIDs.add(this.mg.ownerID(neighbor));
|
candidatesIDs.add(this.mg.ownerID(neighbor));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let largestNeighborAttack: Player | null = null;
|
// Filter out friendly and non-player candidates
|
||||||
let largestTroopCount: number = 0;
|
const neighbors = new Set<Player>();
|
||||||
for (const id of neighborsIDs) {
|
for (const id of candidatesIDs) {
|
||||||
const neighbor = this.mg.playerBySmallID(id);
|
const neighbor = this.mg.playerBySmallID(id);
|
||||||
if (!neighbor.isPlayer() || this.player.isFriendly(neighbor)) {
|
if (!neighbor.isPlayer() || neighbor.isFriendly(this.player)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
neighbors.add(neighbor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there are no enemies, return null
|
||||||
|
if (neighbors.size === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the largest attack from the neighbors
|
||||||
|
let largestNeighborAttack: Player | null = null;
|
||||||
|
let largestTroopCount = 0;
|
||||||
|
for (const neighbor of neighbors) {
|
||||||
for (const attack of neighbor.outgoingAttacks()) {
|
for (const attack of neighbor.outgoingAttacks()) {
|
||||||
if (attack.target() === this.player) {
|
if (attack.target() === this.player) {
|
||||||
if (attack.troops() > largestTroopCount) {
|
if (attack.troops() > largestTroopCount) {
|
||||||
@@ -239,20 +254,10 @@ export class PlayerExecution implements Execution {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (largestNeighborAttack !== null) {
|
|
||||||
return largestNeighborAttack;
|
|
||||||
}
|
|
||||||
|
|
||||||
// fall back to getting mode if no attacks
|
// Return the largest neighbor attack
|
||||||
const mode = getMode(neighborsIDs);
|
// If there is no largest neighbor attack, this will return null
|
||||||
if (!this.mg.playerBySmallID(mode).isPlayer()) {
|
return largestNeighborAttack;
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const capturing = this.mg.playerBySmallID(mode);
|
|
||||||
if (!capturing.isPlayer()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return capturing;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private calculateClusters(): Set<TileRef>[] {
|
private calculateClusters(): Set<TileRef>[] {
|
||||||
|
|||||||
Reference in New Issue
Block a user