mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 15:30:43 +00:00
7d79c299f4
feat: WarShips red color outside if target current player
221 lines
6.1 KiB
TypeScript
221 lines
6.1 KiB
TypeScript
import {
|
|
Cell,
|
|
Execution,
|
|
Game,
|
|
Player,
|
|
Unit,
|
|
PlayerID,
|
|
TerrainType,
|
|
UnitType,
|
|
} from "../game/Game";
|
|
import { PathFinder } from "../pathfinding/PathFinding";
|
|
import { PathFindResultType } from "../pathfinding/AStar";
|
|
import { PseudoRandom } from "../PseudoRandom";
|
|
import { distSort, distSortUnit } from "../Util";
|
|
import { consolex } from "../Consolex";
|
|
import { TileRef } from "../game/GameMap";
|
|
import { ShellExecution } from "./ShellExecution";
|
|
|
|
export class WarshipExecution implements Execution {
|
|
private random: PseudoRandom;
|
|
|
|
private _owner: Player;
|
|
private active = true;
|
|
private warship: Unit = null;
|
|
private mg: Game = null;
|
|
|
|
private target: Unit = null;
|
|
private pathfinder: PathFinder;
|
|
|
|
private patrolTile: TileRef;
|
|
|
|
// TODO: put in config
|
|
private searchRange = 100;
|
|
|
|
private shellAttackRate = 5;
|
|
private lastShellAttack = 0;
|
|
|
|
private alreadySentShell = new Set<Unit>();
|
|
|
|
constructor(
|
|
private playerID: PlayerID,
|
|
private patrolCenterTile: TileRef,
|
|
) {}
|
|
|
|
init(mg: Game, ticks: number): void {
|
|
if (!mg.hasPlayer(this.playerID)) {
|
|
console.log(`WarshipExecution: player ${this.playerID} not found`);
|
|
this.active = false;
|
|
return;
|
|
}
|
|
this.pathfinder = PathFinder.Mini(mg, 5000, false);
|
|
this._owner = mg.player(this.playerID);
|
|
this.mg = mg;
|
|
this.patrolTile = this.patrolCenterTile;
|
|
this.random = new PseudoRandom(mg.ticks());
|
|
}
|
|
|
|
tick(ticks: number): void {
|
|
if (this.warship == null) {
|
|
const spawn = this._owner.canBuild(UnitType.Warship, this.patrolTile);
|
|
if (spawn == false) {
|
|
this.active = false;
|
|
return;
|
|
}
|
|
this.warship = this._owner.buildUnit(UnitType.Warship, 0, spawn);
|
|
return;
|
|
}
|
|
if (!this.warship.isActive()) {
|
|
this.active = false;
|
|
return;
|
|
}
|
|
if (this.target != null && !this.target.isActive()) {
|
|
this.target = null;
|
|
}
|
|
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))
|
|
.filter(
|
|
(u) =>
|
|
u.type() != UnitType.TradeShip || u.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
|
|
if (
|
|
a.type() === UnitType.TransportShip &&
|
|
b.type() !== UnitType.TransportShip
|
|
) {
|
|
return -1;
|
|
}
|
|
if (
|
|
a.type() !== UnitType.TransportShip &&
|
|
b.type() === UnitType.TransportShip
|
|
) {
|
|
return 1;
|
|
}
|
|
// If both are same type, sort by distance
|
|
return distSortUnit(this.mg, this.warship)(a, b);
|
|
})[0] ?? null;
|
|
|
|
this.warship.setTarget(this.target);
|
|
if (this.target == null || this.target.type() != UnitType.TradeShip) {
|
|
// Patrol unless we are hunting down a tradeship
|
|
const result = this.pathfinder.nextTile(
|
|
this.warship.tile(),
|
|
this.patrolTile,
|
|
);
|
|
switch (result.type) {
|
|
case PathFindResultType.Completed:
|
|
this.patrolTile = this.randomTile();
|
|
break;
|
|
case PathFindResultType.NextTile:
|
|
this.warship.move(result.tile);
|
|
break;
|
|
case PathFindResultType.Pending:
|
|
return;
|
|
case PathFindResultType.PathNotFound:
|
|
consolex.log(`path not found to patrol tile`);
|
|
this.patrolTile = this.randomTile();
|
|
break;
|
|
}
|
|
}
|
|
if (
|
|
this.target == null ||
|
|
!this.target.isActive() ||
|
|
this.target.owner() == this._owner
|
|
) {
|
|
// In case another destroyer captured or destroyed target
|
|
this.target = null;
|
|
return;
|
|
}
|
|
if (this.target.type() != UnitType.TradeShip) {
|
|
if (this.mg.ticks() - this.lastShellAttack > this.shellAttackRate) {
|
|
this.lastShellAttack = this.mg.ticks();
|
|
this.mg.addExecution(
|
|
new ShellExecution(
|
|
this.warship.tile(),
|
|
this.warship.owner(),
|
|
this.warship,
|
|
this.target,
|
|
),
|
|
);
|
|
if (!this.target.hasHealth()) {
|
|
// Don't send multiple shells to target that can be oneshotted
|
|
this.alreadySentShell.add(this.target);
|
|
this.target = null;
|
|
return;
|
|
}
|
|
}
|
|
// Only hunt down tradeships
|
|
return;
|
|
}
|
|
|
|
for (let i = 0; i < 2; i++) {
|
|
// target is trade ship so capture it.
|
|
const result = this.pathfinder.nextTile(
|
|
this.warship.tile(),
|
|
this.target.tile(),
|
|
5,
|
|
);
|
|
switch (result.type) {
|
|
case PathFindResultType.Completed:
|
|
this.owner().captureUnit(this.target);
|
|
this.target = null;
|
|
return;
|
|
case PathFindResultType.NextTile:
|
|
this.warship.move(result.tile);
|
|
break;
|
|
case PathFindResultType.Pending:
|
|
break;
|
|
case PathFindResultType.PathNotFound:
|
|
consolex.log(`path not found to target`);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
owner(): Player {
|
|
return this._owner;
|
|
}
|
|
|
|
isActive(): boolean {
|
|
return this.active;
|
|
}
|
|
|
|
activeDuringSpawnPhase(): boolean {
|
|
return false;
|
|
}
|
|
|
|
randomTile(): TileRef {
|
|
while (true) {
|
|
const x =
|
|
this.mg.x(this.patrolCenterTile) +
|
|
this.random.nextInt(-this.searchRange / 2, this.searchRange / 2);
|
|
const y =
|
|
this.mg.y(this.patrolCenterTile) +
|
|
this.random.nextInt(-this.searchRange / 2, this.searchRange / 2);
|
|
if (!this.mg.isValidCoord(x, y)) {
|
|
continue;
|
|
}
|
|
const tile = this.mg.ref(x, y);
|
|
if (!this.mg.isOcean(tile)) {
|
|
continue;
|
|
}
|
|
return tile;
|
|
}
|
|
}
|
|
}
|