mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-08-08 11:33:48 +00:00
unitgrid instead of defensepostgrid (#265)
added a unitgrid instead of a defensepostgrid for all units to be able to see if a tile is inside the range of a unit by a custom distance this is used for the sam launcher to draw a circle around the area it protects removed all parts that use the defensepostgrid and made it use the unitgrid instead
This commit is contained in:
@@ -381,8 +381,12 @@ export class DefaultConfig implements Config {
|
||||
throw new Error(`terrain type ${type} not supported`);
|
||||
}
|
||||
if (defender.isPlayer()) {
|
||||
for (const dp of gm.nearbyDefensePosts(tileToConquer)) {
|
||||
if (dp.owner() == defender) {
|
||||
for (const dp of gm.nearbyUnits(
|
||||
tileToConquer,
|
||||
gm.config().defensePostRange(),
|
||||
UnitType.DefensePost,
|
||||
)) {
|
||||
if (dp.unit.owner() == defender) {
|
||||
mag *= this.defensePostDefenseBonus();
|
||||
speed *= this.defensePostDefenseBonus();
|
||||
break;
|
||||
|
||||
@@ -62,41 +62,36 @@ export class SAMLauncherExecution implements Execution {
|
||||
}
|
||||
|
||||
const nukes = this.mg
|
||||
.units(UnitType.AtomBomb, UnitType.HydrogenBomb)
|
||||
.filter((u) => {
|
||||
// (x - center_x)² + (y - center_y)² < radius²
|
||||
const x = this.mg.x(u.tile());
|
||||
const y = this.mg.y(u.tile());
|
||||
const centerX = this.mg.x(this.post.tile());
|
||||
const centerY = this.mg.y(this.post.tile());
|
||||
const isInRange =
|
||||
(x - centerX) ** 2 + (y - centerY) ** 2 < this.searchRangeRadius ** 2;
|
||||
return isInRange;
|
||||
})
|
||||
.filter((u) => u.owner() !== this.player)
|
||||
.filter((u) => !u.owner().isAlliedWith(this.player));
|
||||
.nearbyUnits(this.post.tile(), this.searchRangeRadius, [
|
||||
UnitType.AtomBomb,
|
||||
UnitType.HydrogenBomb,
|
||||
])
|
||||
.filter(
|
||||
({ unit }) =>
|
||||
unit.owner() !== this.player &&
|
||||
!unit.owner().isAlliedWith(this.player),
|
||||
);
|
||||
|
||||
this.target =
|
||||
nukes.sort((a, b) => {
|
||||
// Prioritize HydrogenBombs first
|
||||
const { unit: unitA, distSquared: distA } = a;
|
||||
const { unit: unitB, distSquared: distB } = b;
|
||||
|
||||
// Prioritize Hydrogen Bombs
|
||||
if (
|
||||
a.type() === UnitType.HydrogenBomb &&
|
||||
b.type() !== UnitType.HydrogenBomb
|
||||
) {
|
||||
unitA.type() === UnitType.HydrogenBomb &&
|
||||
unitB.type() !== UnitType.HydrogenBomb
|
||||
)
|
||||
return -1;
|
||||
}
|
||||
if (
|
||||
a.type() !== UnitType.HydrogenBomb &&
|
||||
b.type() === UnitType.HydrogenBomb
|
||||
) {
|
||||
unitA.type() !== UnitType.HydrogenBomb &&
|
||||
unitB.type() === UnitType.HydrogenBomb
|
||||
)
|
||||
return 1;
|
||||
}
|
||||
// If both are the same type, sort by distance
|
||||
return (
|
||||
this.mg.manhattanDist(this.post.tile(), a.tile()) -
|
||||
this.mg.manhattanDist(this.post.tile(), b.tile())
|
||||
);
|
||||
})[0] ?? null;
|
||||
|
||||
// If both are the same type, sort by distance (lower `distSquared` means closer)
|
||||
return distA - distB;
|
||||
})[0]?.unit ?? null;
|
||||
|
||||
const cooldown =
|
||||
this.lastMissileAttack != 0 &&
|
||||
|
||||
@@ -138,44 +138,54 @@ export class WarshipExecution implements Execution {
|
||||
}
|
||||
const hasPort = this._owner.units(UnitType.Port).length > 0;
|
||||
const ships = this.mg
|
||||
.units(UnitType.TransportShip, UnitType.Warship, UnitType.TradeShip)
|
||||
.filter((u) => this.mg.manhattanDist(u.tile(), this.warship.tile()) < 130)
|
||||
.filter((u) => u.owner() != this.warship.owner())
|
||||
.filter((u) => u != this.warship)
|
||||
.filter((u) => !u.owner().isAlliedWith(this.warship.owner()))
|
||||
.filter((u) => !this.alreadySentShell.has(u))
|
||||
// Do not target trade ships if we don't have port
|
||||
.filter((u) => u.type() != UnitType.TradeShip || hasPort)
|
||||
.filter((u) => {
|
||||
const portOwner = u.dstPort() ? u.dstPort().owner() : null;
|
||||
return u.type() != UnitType.TradeShip || portOwner != this.owner();
|
||||
});
|
||||
.nearbyUnits(
|
||||
this.warship.tile(),
|
||||
130, // Search range
|
||||
[UnitType.TransportShip, UnitType.Warship, UnitType.TradeShip],
|
||||
)
|
||||
.filter(
|
||||
({ unit }) =>
|
||||
unit.owner() !== this.warship.owner() &&
|
||||
unit !== this.warship &&
|
||||
!unit.owner().isAlliedWith(this.warship.owner()) &&
|
||||
!this.alreadySentShell.has(unit) &&
|
||||
(unit.type() !== UnitType.TradeShip || hasPort) &&
|
||||
(unit.type() !== UnitType.TradeShip ||
|
||||
unit.dstPort()?.owner() !== this.owner()),
|
||||
);
|
||||
|
||||
this.target =
|
||||
ships.sort((a, b) => {
|
||||
// First compare by Warship type
|
||||
if (a.type() === UnitType.Warship && b.type() !== UnitType.Warship) {
|
||||
return -1;
|
||||
}
|
||||
if (a.type() !== UnitType.Warship && b.type() === UnitType.Warship) {
|
||||
return 1;
|
||||
}
|
||||
// Then favor transport ship
|
||||
const { unit: unitA, distSquared: distA } = a;
|
||||
const { unit: unitB, distSquared: distB } = b;
|
||||
|
||||
// Prioritize Warships
|
||||
if (
|
||||
a.type() === UnitType.TransportShip &&
|
||||
b.type() !== UnitType.TransportShip
|
||||
) {
|
||||
unitA.type() === UnitType.Warship &&
|
||||
unitB.type() !== UnitType.Warship
|
||||
)
|
||||
return -1;
|
||||
}
|
||||
if (
|
||||
a.type() !== UnitType.TransportShip &&
|
||||
b.type() === UnitType.TransportShip
|
||||
) {
|
||||
unitA.type() !== UnitType.Warship &&
|
||||
unitB.type() === UnitType.Warship
|
||||
)
|
||||
return 1;
|
||||
}
|
||||
// If both are same type, sort by distance
|
||||
return distSortUnit(this.mg, this.warship)(a, b);
|
||||
})[0] ?? null;
|
||||
|
||||
// Then favor Transport Ships over Trade Ships
|
||||
if (
|
||||
unitA.type() === UnitType.TransportShip &&
|
||||
unitB.type() !== UnitType.TransportShip
|
||||
)
|
||||
return -1;
|
||||
if (
|
||||
unitA.type() !== UnitType.TransportShip &&
|
||||
unitB.type() === UnitType.TransportShip
|
||||
)
|
||||
return 1;
|
||||
|
||||
// If both are the same type, sort by distance (lower `distSquared` means closer)
|
||||
return distA - distB;
|
||||
})[0]?.unit ?? null;
|
||||
|
||||
if (this.warship.moveTarget()) {
|
||||
this.goToMoveTarget(this.warship.moveTarget());
|
||||
|
||||
@@ -411,7 +411,11 @@ export interface Game extends GameMap {
|
||||
// Units
|
||||
units(...types: UnitType[]): Unit[];
|
||||
unitInfo(type: UnitType): UnitInfo;
|
||||
nearbyDefensePosts(tile: TileRef): Unit[];
|
||||
nearbyUnits(
|
||||
tile: TileRef,
|
||||
searchRange: number,
|
||||
types: UnitType | UnitType[],
|
||||
): Array<{ unit: Unit; distSquared: number }>;
|
||||
|
||||
addExecution(...exec: Execution[]): void;
|
||||
displayMessage(
|
||||
|
||||
+16
-12
@@ -29,7 +29,7 @@ import { MessageType } from "./Game";
|
||||
import { UnitImpl } from "./UnitImpl";
|
||||
import { consolex } from "../Consolex";
|
||||
import { GameMap, GameMapImpl, TileRef, TileUpdate } from "./GameMap";
|
||||
import { DefenseGrid } from "./DefensePostGrid";
|
||||
import { UnitGrid } from "./UnitGrid";
|
||||
import { StatsImpl } from "./StatsImpl";
|
||||
import { Stats } from "./Stats";
|
||||
|
||||
@@ -66,7 +66,7 @@ export class GameImpl implements Game {
|
||||
private _nextUnitID = 1;
|
||||
|
||||
private updates: GameUpdates = createGameUpdatesMap();
|
||||
private defenseGrid: DefenseGrid;
|
||||
private unitGrid: UnitGrid;
|
||||
|
||||
private _stats: StatsImpl = new StatsImpl();
|
||||
|
||||
@@ -88,10 +88,7 @@ export class GameImpl implements Game {
|
||||
n.strength,
|
||||
),
|
||||
);
|
||||
this.defenseGrid = new DefenseGrid(
|
||||
this._map,
|
||||
this._config.defensePostRange(),
|
||||
);
|
||||
this.unitGrid = new UnitGrid(this._map);
|
||||
}
|
||||
isOnEdgeOfMap(ref: TileRef): boolean {
|
||||
return this._map.isOnEdgeOfMap(ref);
|
||||
@@ -541,15 +538,22 @@ export class GameImpl implements Game {
|
||||
});
|
||||
}
|
||||
|
||||
addDefensePost(dp: Unit) {
|
||||
this.defenseGrid.addDefense(dp);
|
||||
addUnit(u: Unit) {
|
||||
this.unitGrid.addUnit(u);
|
||||
}
|
||||
removeDefensePost(dp: Unit) {
|
||||
this.defenseGrid.removeDefense(dp);
|
||||
removeUnit(u: Unit) {
|
||||
this.unitGrid.removeUnit(u);
|
||||
}
|
||||
|
||||
nearbyDefensePosts(tile: TileRef): Unit[] {
|
||||
return this.defenseGrid.nearbyDefenses(tile) as Unit[];
|
||||
nearbyUnits(
|
||||
tile: TileRef,
|
||||
searchRange: number,
|
||||
types: UnitType | UnitType[],
|
||||
): Array<{ unit: Unit; distSquared: number }> {
|
||||
return this.unitGrid.nearbyUnits(tile, searchRange, types) as Array<{
|
||||
unit: Unit;
|
||||
distSquared: number;
|
||||
}>;
|
||||
}
|
||||
|
||||
ref(x: number, y: number): TileRef {
|
||||
|
||||
+17
-11
@@ -30,8 +30,9 @@ import { TerraNulliusImpl } from "./TerraNulliusImpl";
|
||||
import { WorkerClient } from "../worker/WorkerClient";
|
||||
import { GameMap, GameMapImpl, TileRef, TileUpdate } from "./GameMap";
|
||||
import { GameUpdateViewData } from "./GameUpdates";
|
||||
import { DefenseGrid } from "./DefensePostGrid";
|
||||
import { UnitGrid } from "./UnitGrid";
|
||||
import { consolex } from "../Consolex";
|
||||
import { SAMLauncherExecution } from "../execution/SAMLauncherExecution";
|
||||
|
||||
export class UnitView {
|
||||
public _wasUpdated = true;
|
||||
@@ -254,7 +255,7 @@ export class GameView implements GameMap {
|
||||
|
||||
private _myPlayer: PlayerView | null = null;
|
||||
|
||||
private defensePostGrid: DefenseGrid;
|
||||
private unitGrid: UnitGrid;
|
||||
|
||||
private toDelete = new Set<number>();
|
||||
|
||||
@@ -272,7 +273,7 @@ export class GameView implements GameMap {
|
||||
updates: null,
|
||||
playerNameViewData: {},
|
||||
};
|
||||
this.defensePostGrid = new DefenseGrid(_map, _config.defensePostRange());
|
||||
this.unitGrid = new UnitGrid(_map);
|
||||
}
|
||||
isOnEdgeOfMap(ref: TileRef): boolean {
|
||||
return this._map.isOnEdgeOfMap(ref);
|
||||
@@ -318,12 +319,10 @@ export class GameView implements GameMap {
|
||||
unit = new UnitView(this, update);
|
||||
this._units.set(update.id, unit);
|
||||
}
|
||||
if (update.unitType == UnitType.DefensePost) {
|
||||
if (update.isActive) {
|
||||
this.defensePostGrid.addDefense(unit);
|
||||
} else {
|
||||
this.defensePostGrid.removeDefense(unit);
|
||||
}
|
||||
if (update.isActive) {
|
||||
this.unitGrid.addUnit(unit);
|
||||
} else {
|
||||
this.unitGrid.removeUnit(unit);
|
||||
}
|
||||
if (!unit.isActive()) {
|
||||
// Wait until next tick to delete the unit.
|
||||
@@ -336,8 +335,15 @@ export class GameView implements GameMap {
|
||||
return this.updatedTiles;
|
||||
}
|
||||
|
||||
nearbyDefenses(tile: TileRef): UnitView[] {
|
||||
return this.defensePostGrid.nearbyDefenses(tile) as UnitView[];
|
||||
nearbyUnits(
|
||||
tile: TileRef,
|
||||
searchRange: number,
|
||||
types: UnitType | UnitType[],
|
||||
): Array<{ unit: UnitView; distSquared: number }> {
|
||||
return this.unitGrid.nearbyUnits(tile, searchRange, types) as Array<{
|
||||
unit: UnitView;
|
||||
distSquared: number;
|
||||
}>;
|
||||
}
|
||||
|
||||
myClientID(): ClientID {
|
||||
|
||||
@@ -666,9 +666,8 @@ export class PlayerImpl implements Player {
|
||||
this.removeGold(cost);
|
||||
this.removeTroops(troops);
|
||||
this.mg.addUpdate(b.toUpdate());
|
||||
if (type == UnitType.DefensePost) {
|
||||
this.mg.addDefensePost(b);
|
||||
}
|
||||
this.mg.addUnit(b);
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
import { Unit } from "./Game";
|
||||
import { Unit, UnitType } from "./Game";
|
||||
import { GameMap, TileRef } from "./GameMap";
|
||||
import { UnitView } from "./GameView";
|
||||
|
||||
export class DefenseGrid {
|
||||
export class UnitGrid {
|
||||
private grid: Set<Unit | UnitView>[][];
|
||||
private readonly cellSize = 100;
|
||||
|
||||
constructor(
|
||||
private gm: GameMap,
|
||||
private searchRange: number,
|
||||
) {
|
||||
constructor(private gm: GameMap) {
|
||||
this.grid = Array(Math.ceil(gm.height() / this.cellSize))
|
||||
.fill(null)
|
||||
.map(() =>
|
||||
@@ -24,8 +21,8 @@ export class DefenseGrid {
|
||||
return [Math.floor(x / this.cellSize), Math.floor(y / this.cellSize)];
|
||||
}
|
||||
|
||||
// Add a defense unit to the grid
|
||||
addDefense(unit: Unit | UnitView) {
|
||||
// Add a unit to the grid
|
||||
addUnit(unit: Unit | UnitView) {
|
||||
const tile = unit.tile();
|
||||
const [gridX, gridY] = this.getGridCoords(this.gm.x(tile), this.gm.y(tile));
|
||||
|
||||
@@ -34,8 +31,8 @@ export class DefenseGrid {
|
||||
}
|
||||
}
|
||||
|
||||
// Remove a defense unit from the grid
|
||||
removeDefense(unit: Unit | UnitView) {
|
||||
// Remove a unit from the grid
|
||||
removeUnit(unit: Unit | UnitView) {
|
||||
const tile = unit.tile();
|
||||
const [gridX, gridY] = this.getGridCoords(this.gm.x(tile), this.gm.y(tile));
|
||||
|
||||
@@ -53,35 +50,40 @@ export class DefenseGrid {
|
||||
);
|
||||
}
|
||||
|
||||
// Get all defense units within range of a point
|
||||
// Get all units within range of a point
|
||||
// Returns [unit, distanceSquared] pairs for efficient filtering
|
||||
nearbyDefenses(tile: TileRef): Array<Unit | UnitView> {
|
||||
nearbyUnits(
|
||||
tile: TileRef,
|
||||
searchRange: number,
|
||||
types: UnitType | UnitType[],
|
||||
): Array<{ unit: Unit | UnitView; distSquared: number }> {
|
||||
const x = this.gm.x(tile);
|
||||
const y = this.gm.y(tile);
|
||||
const [gridX, gridY] = this.getGridCoords(x, y);
|
||||
const cellsToCheck = Math.ceil(this.searchRange / this.cellSize);
|
||||
const nearby: Array<Unit | UnitView> = [];
|
||||
const cellsToCheck = Math.ceil(searchRange / this.cellSize);
|
||||
const nearby: Array<{ unit: Unit | UnitView; distSquared: number }> = [];
|
||||
|
||||
// Pre-calculate range bounds for efficiency
|
||||
const startGridX = Math.max(0, gridX - cellsToCheck);
|
||||
const endGridX = Math.min(this.grid[0].length - 1, gridX + cellsToCheck);
|
||||
const startGridY = Math.max(0, gridY - cellsToCheck);
|
||||
const endGridY = Math.min(this.grid.length - 1, gridY + cellsToCheck);
|
||||
|
||||
// Squared range for faster comparison (avoid sqrt)
|
||||
const rangeSquared = this.searchRange * this.searchRange;
|
||||
const rangeSquared = searchRange * searchRange;
|
||||
const typeSet = Array.isArray(types) ? new Set(types) : new Set([types]);
|
||||
|
||||
for (let cy = startGridY; cy <= endGridY; cy++) {
|
||||
for (let cx = startGridX; cx <= endGridX; cx++) {
|
||||
for (const unit of this.grid[cy][cx]) {
|
||||
const tileX = this.gm.x(unit.tile());
|
||||
const tileY = this.gm.y(unit.tile());
|
||||
const dx = tileX - x;
|
||||
const dy = tileY - y;
|
||||
const distSquared = dx * dx + dy * dy;
|
||||
if (unit.isActive()) {
|
||||
const tileX = this.gm.x(unit.tile());
|
||||
const tileY = this.gm.y(unit.tile());
|
||||
const dx = tileX - x;
|
||||
const dy = tileY - y;
|
||||
const distSquared = dx * dx + dy * dy;
|
||||
|
||||
if (distSquared <= rangeSquared) {
|
||||
nearby.push(unit);
|
||||
if (distSquared <= rangeSquared && typeSet.has(unit.type())) {
|
||||
nearby.push({ unit, distSquared });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -131,9 +131,7 @@ export class UnitImpl implements Unit {
|
||||
this._owner._units = this._owner._units.filter((b) => b != this);
|
||||
this._active = false;
|
||||
this.mg.addUpdate(this.toUpdate());
|
||||
if (this.type() == UnitType.DefensePost) {
|
||||
this.mg.removeDefensePost(this);
|
||||
}
|
||||
this.mg.removeUnit(this);
|
||||
if (displayMessage) {
|
||||
this.mg.displayMessage(
|
||||
`Your ${this.type()} was destroyed`,
|
||||
|
||||
Reference in New Issue
Block a user