mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-08-18 17:53:29 +00:00
wip
This commit is contained in:
+3
-3
@@ -226,11 +226,11 @@ export const SpawnIntentSchema = BaseIntentSchema.extend({
|
|||||||
export const BoatAttackIntentSchema = BaseIntentSchema.extend({
|
export const BoatAttackIntentSchema = BaseIntentSchema.extend({
|
||||||
type: z.literal("boat"),
|
type: z.literal("boat"),
|
||||||
targetID: ID.nullable(),
|
targetID: ID.nullable(),
|
||||||
troops: z.number().nullable(),
|
troops: z.number(),
|
||||||
dstX: z.number(),
|
dstX: z.number(),
|
||||||
dstY: z.number(),
|
dstY: z.number(),
|
||||||
srcX: z.number().nullable().optional(),
|
srcX: z.number().nullable(),
|
||||||
srcY: z.number().nullable().optional(),
|
srcY: z.number().nullable(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const AllianceRequestIntentSchema = BaseIntentSchema.extend({
|
export const AllianceRequestIntentSchema = BaseIntentSchema.extend({
|
||||||
|
|||||||
@@ -283,7 +283,7 @@ export class AttackExecution implements Execution {
|
|||||||
this.border.add(neighbor);
|
this.border.add(neighbor);
|
||||||
const numOwnedByMe = this.mg
|
const numOwnedByMe = this.mg
|
||||||
.neighbors(neighbor)
|
.neighbors(neighbor)
|
||||||
.filter((t) => this.mg.owner(t) == this._owner).length;
|
.filter((t) => this.mg.owner(t) === this._owner).length;
|
||||||
let mag = 0;
|
let mag = 0;
|
||||||
switch (this.mg.terrainType(tile)) {
|
switch (this.mg.terrainType(tile)) {
|
||||||
case TerrainType.Plains:
|
case TerrainType.Plains:
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export class BotExecution implements Execution {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tick(ticks: number) {
|
tick(ticks: number) {
|
||||||
if (ticks % this.attackRate != this.attackTick) return;
|
if (ticks % this.attackRate !== this.attackTick) return;
|
||||||
|
|
||||||
if (!this.bot.isAlive()) {
|
if (!this.bot.isAlive()) {
|
||||||
this.active = false;
|
this.active = false;
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export class DefensePostExecution implements Execution {
|
|||||||
private post: Unit | null = null;
|
private post: Unit | null = null;
|
||||||
private active: boolean = true;
|
private active: boolean = true;
|
||||||
|
|
||||||
private target: Unit = null;
|
private target: Unit | null = null;
|
||||||
private lastShellAttack = 0;
|
private lastShellAttack = 0;
|
||||||
|
|
||||||
private alreadySentShell = new Set<Unit>();
|
private alreadySentShell = new Set<Unit>();
|
||||||
@@ -37,6 +37,8 @@ export class DefensePostExecution implements Execution {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private shoot() {
|
private shoot() {
|
||||||
|
if (this.post === null) return;
|
||||||
|
if (this.target === null) return;
|
||||||
const shellAttackRate = this.mg.config().defensePostShellAttackRate();
|
const shellAttackRate = this.mg.config().defensePostShellAttackRate();
|
||||||
if (this.mg.ticks() - this.lastShellAttack > shellAttackRate) {
|
if (this.mg.ticks() - this.lastShellAttack > shellAttackRate) {
|
||||||
this.lastShellAttack = this.mg.ticks();
|
this.lastShellAttack = this.mg.ticks();
|
||||||
@@ -76,7 +78,7 @@ export class DefensePostExecution implements Execution {
|
|||||||
this.player = this.post.owner();
|
this.player = this.post.owner();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.target != null && !this.target.isActive()) {
|
if (this.target !== null && !this.target.isActive()) {
|
||||||
this.target = null;
|
this.target = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,7 +119,7 @@ export class DefensePostExecution implements Execution {
|
|||||||
return distA - distB;
|
return distA - distB;
|
||||||
})[0]?.unit ?? null;
|
})[0]?.unit ?? null;
|
||||||
|
|
||||||
if (this.target == null || !this.target.isActive()) {
|
if (this.target === null || !this.target.isActive()) {
|
||||||
this.target = null;
|
this.target = null;
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -42,13 +42,16 @@ export class EmojiExecution implements Execution {
|
|||||||
|
|
||||||
tick(ticks: number): void {
|
tick(ticks: number): void {
|
||||||
const emojiString = flattenedEmojiTable.at(this.emoji);
|
const emojiString = flattenedEmojiTable.at(this.emoji);
|
||||||
|
if (emojiString === undefined) {
|
||||||
if (this.requestor.canSendEmoji(this.recipient)) {
|
consolex.warn(
|
||||||
|
`cannot send emoji ${this.emoji} from ${this.requestor} to ${this.recipient}`,
|
||||||
|
);
|
||||||
|
} else if (this.requestor.canSendEmoji(this.recipient)) {
|
||||||
this.requestor.sendEmoji(this.recipient, emojiString);
|
this.requestor.sendEmoji(this.recipient, emojiString);
|
||||||
if (
|
if (
|
||||||
emojiString == "🖕" &&
|
emojiString === "🖕" &&
|
||||||
this.recipient != AllPlayers &&
|
this.recipient !== AllPlayers &&
|
||||||
this.recipient.type() == PlayerType.FakeHuman
|
this.recipient.type() === PlayerType.FakeHuman
|
||||||
) {
|
) {
|
||||||
this.recipient.updateRelation(this.requestor, -100);
|
this.recipient.updateRelation(this.requestor, -100);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Execution, Game } from "../game/Game";
|
import { Execution, Game } from "../game/Game";
|
||||||
|
import { TileRef } from "../game/GameMap";
|
||||||
import { PseudoRandom } from "../PseudoRandom";
|
import { PseudoRandom } from "../PseudoRandom";
|
||||||
import { ClientID, GameID, Intent, Turn } from "../Schemas";
|
import { ClientID, GameID, Intent, Turn } from "../Schemas";
|
||||||
import { simpleHash } from "../Util";
|
import { simpleHash } from "../Util";
|
||||||
@@ -66,8 +67,8 @@ export class Executor {
|
|||||||
this.mg.ref(intent.x, intent.y),
|
this.mg.ref(intent.x, intent.y),
|
||||||
);
|
);
|
||||||
case "boat":
|
case "boat":
|
||||||
let src = null;
|
let src: TileRef | null = null;
|
||||||
if (intent.srcX != null || intent.srcY != null) {
|
if (intent.srcX !== null && intent.srcY !== null) {
|
||||||
src = this.mg.ref(intent.srcX, intent.srcY);
|
src = this.mg.ref(intent.srcX, intent.srcY);
|
||||||
}
|
}
|
||||||
return new TransportShipExecution(
|
return new TransportShipExecution(
|
||||||
|
|||||||
@@ -111,11 +111,11 @@ export class FakeHumanExecution implements Execution {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tick(ticks: number) {
|
tick(ticks: number) {
|
||||||
if (ticks % this.attackRate != this.attackTick) return;
|
if (ticks % this.attackRate !== this.attackTick) return;
|
||||||
|
|
||||||
if (this.mg.inSpawnPhase()) {
|
if (this.mg.inSpawnPhase()) {
|
||||||
const rl = this.randomLand();
|
const rl = this.randomLand();
|
||||||
if (rl == null) {
|
if (rl === null) {
|
||||||
consolex.warn(`cannot spawn ${this.nation.playerInfo.name}`);
|
consolex.warn(`cannot spawn ${this.nation.playerInfo.name}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -123,11 +123,11 @@ export class FakeHumanExecution implements Execution {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.player == null) {
|
if (this.player === null) {
|
||||||
this.player = this.mg
|
this.player =
|
||||||
.players()
|
this.mg.players().find((p) => p.id() === this.nation.playerInfo.id) ??
|
||||||
.find((p) => p.id() == this.nation.playerInfo.id);
|
null;
|
||||||
if (this.player == null) {
|
if (this.player === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -170,6 +170,9 @@ export class FakeHumanExecution implements Execution {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private maybeAttack() {
|
private maybeAttack() {
|
||||||
|
if (this.player === null || this.behavior === null) {
|
||||||
|
throw new Error("not initialized");
|
||||||
|
}
|
||||||
const enemyborder = Array.from(this.player.borderTiles())
|
const enemyborder = Array.from(this.player.borderTiles())
|
||||||
.flatMap((t) => this.mg.neighbors(t))
|
.flatMap((t) => this.mg.neighbors(t))
|
||||||
.filter(
|
.filter(
|
||||||
@@ -255,6 +258,9 @@ export class FakeHumanExecution implements Execution {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleEnemies() {
|
handleEnemies() {
|
||||||
|
if (this.player === null || this.behavior === null) {
|
||||||
|
throw new Error("not initialized");
|
||||||
|
}
|
||||||
this.behavior.forgetOldEnemies();
|
this.behavior.forgetOldEnemies();
|
||||||
this.behavior.checkIncomingAttacks();
|
this.behavior.checkIncomingAttacks();
|
||||||
this.behavior.assistAllies();
|
this.behavior.assistAllies();
|
||||||
@@ -270,7 +276,8 @@ export class FakeHumanExecution implements Execution {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private maybeSendEmoji(enemy: Player) {
|
private maybeSendEmoji(enemy: Player) {
|
||||||
if (enemy.type() != PlayerType.Human) return;
|
if (this.player === null) throw new Error("not initialized");
|
||||||
|
if (enemy.type() !== PlayerType.Human) return;
|
||||||
const lastSent = this.lastEmojiSent.get(enemy) ?? -300;
|
const lastSent = this.lastEmojiSent.get(enemy) ?? -300;
|
||||||
if (this.mg.ticks() - lastSent <= 300) return;
|
if (this.mg.ticks() - lastSent <= 300) return;
|
||||||
this.lastEmojiSent.set(enemy, this.mg.ticks());
|
this.lastEmojiSent.set(enemy, this.mg.ticks());
|
||||||
@@ -284,11 +291,12 @@ export class FakeHumanExecution implements Execution {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private maybeSendNuke(other: Player) {
|
private maybeSendNuke(other: Player) {
|
||||||
|
if (this.player === null) throw new Error("not initialized");
|
||||||
const silos = this.player.units(UnitType.MissileSilo);
|
const silos = this.player.units(UnitType.MissileSilo);
|
||||||
if (
|
if (
|
||||||
silos.length == 0 ||
|
silos.length === 0 ||
|
||||||
this.player.gold() < this.cost(UnitType.AtomBomb) ||
|
this.player.gold() < this.cost(UnitType.AtomBomb) ||
|
||||||
other.type() == PlayerType.Bot ||
|
other.type() === PlayerType.Bot ||
|
||||||
this.player.isOnSameTeam(other)
|
this.player.isOnSameTeam(other)
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
@@ -302,17 +310,17 @@ export class FakeHumanExecution implements Execution {
|
|||||||
UnitType.SAMLauncher,
|
UnitType.SAMLauncher,
|
||||||
);
|
);
|
||||||
const structureTiles = structures.map((u) => u.tile());
|
const structureTiles = structures.map((u) => u.tile());
|
||||||
const randomTiles: TileRef[] = new Array(10);
|
const randomTiles: (TileRef | null)[] = new Array(10);
|
||||||
for (let i = 0; i < randomTiles.length; i++) {
|
for (let i = 0; i < randomTiles.length; i++) {
|
||||||
randomTiles[i] = this.randTerritoryTile(other);
|
randomTiles[i] = this.randTerritoryTile(other);
|
||||||
}
|
}
|
||||||
const allTiles = randomTiles.concat(structureTiles);
|
const allTiles = randomTiles.concat(structureTiles);
|
||||||
|
|
||||||
let bestTile = null;
|
let bestTile: TileRef | null = null;
|
||||||
let bestValue = 0;
|
let bestValue = 0;
|
||||||
this.removeOldNukeEvents();
|
this.removeOldNukeEvents();
|
||||||
outer: for (const tile of new Set(allTiles)) {
|
outer: for (const tile of new Set(allTiles)) {
|
||||||
if (tile == null) continue;
|
if (tile === null) continue;
|
||||||
for (const t of this.mg.bfs(tile, manhattanDistFN(tile, 15))) {
|
for (const t of this.mg.bfs(tile, manhattanDistFN(tile, 15))) {
|
||||||
// Make sure we nuke at least 15 tiles in border
|
// Make sure we nuke at least 15 tiles in border
|
||||||
if (this.mg.owner(t) !== other) {
|
if (this.mg.owner(t) !== other) {
|
||||||
@@ -321,12 +329,12 @@ export class FakeHumanExecution implements Execution {
|
|||||||
}
|
}
|
||||||
if (!this.player.canBuild(UnitType.AtomBomb, tile)) continue;
|
if (!this.player.canBuild(UnitType.AtomBomb, tile)) continue;
|
||||||
const value = this.nukeTileScore(tile, silos, structures);
|
const value = this.nukeTileScore(tile, silos, structures);
|
||||||
if (value > bestTile) {
|
if (value > bestValue) {
|
||||||
bestTile = tile;
|
bestTile = tile;
|
||||||
bestValue = value;
|
bestValue = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (bestTile != null) {
|
if (bestTile !== null) {
|
||||||
this.sendNuke(bestTile);
|
this.sendNuke(bestTile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -343,6 +351,7 @@ export class FakeHumanExecution implements Execution {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private sendNuke(tile: TileRef) {
|
private sendNuke(tile: TileRef) {
|
||||||
|
if (this.player === null) throw new Error("not initialized");
|
||||||
const tick = this.mg.ticks();
|
const tick = this.mg.ticks();
|
||||||
this.lastNukeSent.push([tick, tile]);
|
this.lastNukeSent.push([tick, tile]);
|
||||||
this.mg.addExecution(
|
this.mg.addExecution(
|
||||||
@@ -375,7 +384,9 @@ export class FakeHumanExecution implements Execution {
|
|||||||
|
|
||||||
// Prefer tiles that are closer to a silo
|
// Prefer tiles that are closer to a silo
|
||||||
const siloTiles = silos.map((u) => u.tile());
|
const siloTiles = silos.map((u) => u.tile());
|
||||||
const { x: closestSilo } = closestTwoTiles(this.mg, siloTiles, [tile]);
|
const result = closestTwoTiles(this.mg, siloTiles, [tile]);
|
||||||
|
if (result === null) throw new Error("Missing result");
|
||||||
|
const { x: closestSilo } = result;
|
||||||
const distanceSquared = this.mg.euclideanDistSquared(tile, closestSilo);
|
const distanceSquared = this.mg.euclideanDistSquared(tile, closestSilo);
|
||||||
const distanceToClosestSilo = Math.sqrt(distanceSquared);
|
const distanceToClosestSilo = Math.sqrt(distanceSquared);
|
||||||
tileValue -= distanceToClosestSilo * 30;
|
tileValue -= distanceToClosestSilo * 30;
|
||||||
@@ -437,6 +448,7 @@ export class FakeHumanExecution implements Execution {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private maybeSpawnStructure(type: UnitType, maxNum: number) {
|
private maybeSpawnStructure(type: UnitType, maxNum: number) {
|
||||||
|
if (this.player === null) throw new Error("not initialized");
|
||||||
const units = this.player.units(type);
|
const units = this.player.units(type);
|
||||||
if (units.length >= maxNum) {
|
if (units.length >= maxNum) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -32,7 +32,10 @@ export class MissileSiloExecution implements Execution {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tick(ticks: number): void {
|
tick(ticks: number): void {
|
||||||
if (this.silo == null) {
|
if (this.player === null || this.mg === null) {
|
||||||
|
throw new Error("Not initialized");
|
||||||
|
}
|
||||||
|
if (this.silo === null) {
|
||||||
const spawn = this.player.canBuild(UnitType.MissileSilo, this.tile);
|
const spawn = this.player.canBuild(UnitType.MissileSilo, this.tile);
|
||||||
if (spawn === false) {
|
if (spawn === false) {
|
||||||
consolex.warn(
|
consolex.warn(
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ export class NukeExecution implements Execution {
|
|||||||
this.pathFinder.computeControlPoints(
|
this.pathFinder.computeControlPoints(
|
||||||
spawn,
|
spawn,
|
||||||
this.dst,
|
this.dst,
|
||||||
this.type != UnitType.MIRVWarhead,
|
this.type !== UnitType.MIRVWarhead,
|
||||||
);
|
);
|
||||||
this.nuke = this.player.buildUnit(this.type, spawn, {
|
this.nuke = this.player.buildUnit(this.type, spawn, {
|
||||||
detonationDst: this.dst,
|
detonationDst: this.dst,
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export class SAMLauncherExecution implements Execution {
|
|||||||
private tile: TileRef,
|
private tile: TileRef,
|
||||||
private sam: Unit | null = null,
|
private sam: Unit | null = null,
|
||||||
) {
|
) {
|
||||||
if (sam != null) {
|
if (sam !== null) {
|
||||||
this.tile = sam.tile();
|
this.tile = sam.tile();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -46,6 +46,7 @@ export class SAMLauncherExecution implements Execution {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private getSingleTarget(): Unit | null {
|
private getSingleTarget(): Unit | null {
|
||||||
|
if (this.sam === null) return null;
|
||||||
const nukes = this.mg
|
const nukes = this.mg
|
||||||
.nearbyUnits(this.sam.tile(), this.searchRangeRadius, [
|
.nearbyUnits(this.sam.tile(), this.searchRangeRadius, [
|
||||||
UnitType.AtomBomb,
|
UnitType.AtomBomb,
|
||||||
@@ -80,11 +81,11 @@ export class SAMLauncherExecution implements Execution {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private isHit(type: UnitType, random: number): boolean {
|
private isHit(type: UnitType, random: number): boolean {
|
||||||
if (type == UnitType.AtomBomb) {
|
if (type === UnitType.AtomBomb) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type == UnitType.MIRVWarhead) {
|
if (type === UnitType.MIRVWarhead) {
|
||||||
return random < this.mg.config().samWarheadHittingChance();
|
return random < this.mg.config().samWarheadHittingChance();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,14 +131,18 @@ export class SAMLauncherExecution implements Execution {
|
|||||||
(unit) =>
|
(unit) =>
|
||||||
unit.owner() !== this.player && !this.player.isFriendly(unit.owner()),
|
unit.owner() !== this.player && !this.player.isFriendly(unit.owner()),
|
||||||
)
|
)
|
||||||
.filter(
|
.filter((unit) => {
|
||||||
(unit) =>
|
const dst = unit.detonationDst();
|
||||||
this.mg.manhattanDist(unit.detonationDst(), this.sam.tile()) <
|
return (
|
||||||
this.MIRVWarheadProtectionRadius,
|
this.sam !== null &&
|
||||||
);
|
dst !== null &&
|
||||||
|
this.mg.manhattanDist(dst, this.sam.tile()) <
|
||||||
|
this.MIRVWarheadProtectionRadius
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
let target: Unit | null = null;
|
let target: Unit | null = null;
|
||||||
if (mirvWarheadTargets.length == 0) {
|
if (mirvWarheadTargets.length === 0) {
|
||||||
target = this.getSingleTarget();
|
target = this.getSingleTarget();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,7 +160,8 @@ export class SAMLauncherExecution implements Execution {
|
|||||||
) {
|
) {
|
||||||
this.sam.setCooldown(true);
|
this.sam.setCooldown(true);
|
||||||
const type =
|
const type =
|
||||||
mirvWarheadTargets.length > 0 ? UnitType.MIRVWarhead : target.type();
|
mirvWarheadTargets.length > 0 ? UnitType.MIRVWarhead : target?.type();
|
||||||
|
if (type === undefined) throw new Error("Unknown unit type");
|
||||||
const random = this.pseudoRandom.next();
|
const random = this.pseudoRandom.next();
|
||||||
const hit = this.isHit(type, random);
|
const hit = this.isHit(type, random);
|
||||||
if (!hit) {
|
if (!hit) {
|
||||||
@@ -174,7 +180,7 @@ export class SAMLauncherExecution implements Execution {
|
|||||||
);
|
);
|
||||||
// Delete warheads
|
// Delete warheads
|
||||||
mirvWarheadTargets.forEach((u) => u.delete());
|
mirvWarheadTargets.forEach((u) => u.delete());
|
||||||
} else {
|
} else if (target !== null) {
|
||||||
target.setTargetedBySAM(true);
|
target.setTargetedBySAM(true);
|
||||||
this.mg.addExecution(
|
this.mg.addExecution(
|
||||||
new SAMMissileExecution(
|
new SAMMissileExecution(
|
||||||
@@ -184,6 +190,8 @@ export class SAMLauncherExecution implements Execution {
|
|||||||
target,
|
target,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
throw new Error("target is null");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { PseudoRandom } from "../PseudoRandom";
|
|||||||
export class ShellExecution implements Execution {
|
export class ShellExecution implements Execution {
|
||||||
private active = true;
|
private active = true;
|
||||||
private pathFinder: AirPathFinder;
|
private pathFinder: AirPathFinder;
|
||||||
private shell: Unit;
|
private shell: Unit | undefined;
|
||||||
private mg: Game;
|
private mg: Game;
|
||||||
private destroyAtTick: number = -1;
|
private destroyAtTick: number = -1;
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@ export class ShellExecution implements Execution {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tick(ticks: number): void {
|
tick(ticks: number): void {
|
||||||
if (this.shell == null) {
|
if (this.shell === undefined) {
|
||||||
this.shell = this._owner.buildUnit(UnitType.Shell, this.spawn, {});
|
this.shell = this._owner.buildUnit(UnitType.Shell, this.spawn, {});
|
||||||
}
|
}
|
||||||
if (!this.shell.isActive()) {
|
if (!this.shell.isActive()) {
|
||||||
@@ -40,7 +40,7 @@ export class ShellExecution implements Execution {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.destroyAtTick == -1 && !this.ownerUnit.isActive()) {
|
if (this.destroyAtTick === -1 && !this.ownerUnit.isActive()) {
|
||||||
this.destroyAtTick = this.mg.ticks() + this.mg.config().shellLifetime();
|
this.destroyAtTick = this.mg.ticks() + this.mg.config().shellLifetime();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,8 +61,8 @@ export class ShellExecution implements Execution {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private effectOnTarget(): number {
|
private effectOnTarget(): number {
|
||||||
const baseDamage: number = this.mg.config().unitInfo(UnitType.Shell).damage;
|
const { damage } = this.mg.config().unitInfo(UnitType.Shell);
|
||||||
return baseDamage;
|
return damage ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
isActive(): boolean {
|
isActive(): boolean {
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ export class TradeShipExecution implements Execution {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.origOwner != this.tradeShip.owner()) {
|
if (this.origOwner !== this.tradeShip.owner()) {
|
||||||
// Store as variable in case ship is recaptured by previous owner
|
// Store as variable in case ship is recaptured by previous owner
|
||||||
this.wasCaptured = true;
|
this.wasCaptured = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export class TransportShipExecution implements Execution {
|
|||||||
private attackerID: PlayerID,
|
private attackerID: PlayerID,
|
||||||
private targetID: PlayerID | null,
|
private targetID: PlayerID | null,
|
||||||
private ref: TileRef,
|
private ref: TileRef,
|
||||||
private troops: number | null,
|
private troops: number,
|
||||||
private src: TileRef | null,
|
private src: TileRef | null,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@@ -120,19 +120,19 @@ export class TransportShipExecution implements Execution {
|
|||||||
UnitType.TransportShip,
|
UnitType.TransportShip,
|
||||||
this.dst,
|
this.dst,
|
||||||
);
|
);
|
||||||
if (closestTileSrc == false) {
|
if (closestTileSrc === false) {
|
||||||
consolex.warn(`can't build transport ship`);
|
consolex.warn(`can't build transport ship`);
|
||||||
this.active = false;
|
this.active = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.src == null) {
|
if (this.src === null) {
|
||||||
// Only update the src if it's not already set
|
// Only update the src if it's not already set
|
||||||
// because we assume that the src is set to the best spawn tile
|
// because we assume that the src is set to the best spawn tile
|
||||||
this.src = closestTileSrc;
|
this.src = closestTileSrc;
|
||||||
} else {
|
} else {
|
||||||
if (
|
if (
|
||||||
this.mg.owner(this.src) != this.attacker ||
|
this.mg.owner(this.src) !== this.attacker ||
|
||||||
!this.mg.isShore(this.src)
|
!this.mg.isShore(this.src)
|
||||||
) {
|
) {
|
||||||
console.warn(
|
console.warn(
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ export class WinCheckExecution implements Execution {
|
|||||||
this.mg.numLandTiles() - this.mg.numTilesWithFallout();
|
this.mg.numLandTiles() - this.mg.numTilesWithFallout();
|
||||||
const percentage = (max[1] / numTilesWithoutFallout) * 100;
|
const percentage = (max[1] / numTilesWithoutFallout) * 100;
|
||||||
if (percentage > this.mg.config().percentageTilesOwnedToWin()) {
|
if (percentage > this.mg.config().percentageTilesOwnedToWin()) {
|
||||||
if (max[0] == ColoredTeams.Bot) return;
|
if (max[0] === ColoredTeams.Bot) return;
|
||||||
this.mg.setWinner(max[0], this.mg.stats().stats());
|
this.mg.setWinner(max[0], this.mg.stats().stats());
|
||||||
console.log(`${max[0]} has won the game`);
|
console.log(`${max[0]} has won the game`);
|
||||||
this.active = false;
|
this.active = false;
|
||||||
|
|||||||
@@ -111,8 +111,8 @@ export class BotBehavior {
|
|||||||
|
|
||||||
// Select the most hated player
|
// Select the most hated player
|
||||||
if (this.enemy === null) {
|
if (this.enemy === null) {
|
||||||
const mostHated = this.player.allRelationsSorted()[0] ?? null;
|
const mostHated = this.player.allRelationsSorted()[0];
|
||||||
if (mostHated != null && mostHated.relation === Relation.Hostile) {
|
if (mostHated !== undefined && mostHated.relation === Relation.Hostile) {
|
||||||
this.enemy = mostHated.player;
|
this.enemy = mostHated.player;
|
||||||
this.enemyUpdated = this.game.ticks();
|
this.enemyUpdated = this.game.ticks();
|
||||||
}
|
}
|
||||||
@@ -137,7 +137,7 @@ export class BotBehavior {
|
|||||||
for (const neighbor of this.random.shuffleArray(neighbors)) {
|
for (const neighbor of this.random.shuffleArray(neighbors)) {
|
||||||
if (!neighbor.isPlayer()) continue;
|
if (!neighbor.isPlayer()) continue;
|
||||||
if (this.player.isFriendly(neighbor)) continue;
|
if (this.player.isFriendly(neighbor)) continue;
|
||||||
if (neighbor.type() == PlayerType.FakeHuman) {
|
if (neighbor.type() === PlayerType.FakeHuman) {
|
||||||
if (this.random.chance(2)) {
|
if (this.random.chance(2)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -762,6 +762,7 @@ export class PlayerImpl implements Player {
|
|||||||
case UnitType.MIRVWarhead:
|
case UnitType.MIRVWarhead:
|
||||||
return targetTile;
|
return targetTile;
|
||||||
case UnitType.Port:
|
case UnitType.Port:
|
||||||
|
if (validTiles === null) throw new Error("validTiles is required");
|
||||||
return this.portSpawn(targetTile, validTiles);
|
return this.portSpawn(targetTile, validTiles);
|
||||||
case UnitType.Warship:
|
case UnitType.Warship:
|
||||||
return this.warshipSpawn(targetTile);
|
return this.warshipSpawn(targetTile);
|
||||||
|
|||||||
+16
-15
@@ -16,13 +16,12 @@ import { PlayerImpl } from "./PlayerImpl";
|
|||||||
export class UnitImpl implements Unit {
|
export class UnitImpl implements Unit {
|
||||||
private _active = true;
|
private _active = true;
|
||||||
private _health: bigint;
|
private _health: bigint;
|
||||||
private _lastTile: TileRef = null;
|
private _lastTile: TileRef;
|
||||||
private _target: Unit = null;
|
private _moveTarget: TileRef | null = null;
|
||||||
private _moveTarget: TileRef = null;
|
|
||||||
private _targetedBySAM = false;
|
private _targetedBySAM = false;
|
||||||
private _safeFromPiratesCooldown: number; // Only for trade ships
|
private _safeFromPiratesCooldown: number; // Only for trade ships
|
||||||
private _lastSetSafeFromPirates: number; // Only for trade ships
|
private _lastSetSafeFromPirates: number; // Only for trade ships
|
||||||
private _constructionType: UnitType = undefined;
|
private _constructionType: UnitType | undefined;
|
||||||
|
|
||||||
private _troops: number;
|
private _troops: number;
|
||||||
private _cooldownTick: Tick | null = null;
|
private _cooldownTick: Tick | null = null;
|
||||||
@@ -45,12 +44,14 @@ export class UnitImpl implements Unit {
|
|||||||
.config()
|
.config()
|
||||||
.safeFromPiratesCooldownMax();
|
.safeFromPiratesCooldownMax();
|
||||||
|
|
||||||
this._troops = "troops" in params ? params.troops : 0;
|
this._troops = "troops" in params ? (params.troops ?? 0) : 0;
|
||||||
this._dstPort = "dstPort" in params ? params.dstPort : null;
|
this._dstPort = "dstPort" in params ? params.dstPort : undefined;
|
||||||
this._cooldownDuration =
|
this._cooldownDuration =
|
||||||
"cooldownDuration" in params ? params.cooldownDuration : null;
|
"cooldownDuration" in params ? params.cooldownDuration : undefined;
|
||||||
this._lastSetSafeFromPirates =
|
this._lastSetSafeFromPirates =
|
||||||
"lastSetSafeFromPirates" in params ? params.lastSetSafeFromPirates : 0;
|
"lastSetSafeFromPirates" in params
|
||||||
|
? (params.lastSetSafeFromPirates ?? 0)
|
||||||
|
: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
id() {
|
id() {
|
||||||
@@ -93,7 +94,7 @@ export class UnitImpl implements Unit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
move(tile: TileRef): void {
|
move(tile: TileRef): void {
|
||||||
if (tile == null) {
|
if (tile === null) {
|
||||||
throw new Error("tile cannot be null");
|
throw new Error("tile cannot be null");
|
||||||
}
|
}
|
||||||
this.mg.removeUnit(this);
|
this.mg.removeUnit(this);
|
||||||
@@ -112,7 +113,7 @@ export class UnitImpl implements Unit {
|
|||||||
return Number(this._health);
|
return Number(this._health);
|
||||||
}
|
}
|
||||||
hasHealth(): boolean {
|
hasHealth(): boolean {
|
||||||
return this.info().maxHealth != undefined;
|
return this.info().maxHealth !== undefined;
|
||||||
}
|
}
|
||||||
tile(): TileRef {
|
tile(): TileRef {
|
||||||
return this._tile;
|
return this._tile;
|
||||||
@@ -127,7 +128,7 @@ export class UnitImpl implements Unit {
|
|||||||
|
|
||||||
setOwner(newOwner: Player): void {
|
setOwner(newOwner: Player): void {
|
||||||
const oldOwner = this._owner;
|
const oldOwner = this._owner;
|
||||||
oldOwner._units = oldOwner._units.filter((u) => u != this);
|
oldOwner._units = oldOwner._units.filter((u) => u !== this);
|
||||||
this._owner = newOwner as PlayerImpl;
|
this._owner = newOwner as PlayerImpl;
|
||||||
this.mg.addUpdate(this.toUpdate());
|
this.mg.addUpdate(this.toUpdate());
|
||||||
this.mg.displayMessage(
|
this.mg.displayMessage(
|
||||||
@@ -149,11 +150,11 @@ export class UnitImpl implements Unit {
|
|||||||
if (!this.isActive()) {
|
if (!this.isActive()) {
|
||||||
throw new Error(`cannot delete ${this} not active`);
|
throw new Error(`cannot delete ${this} not active`);
|
||||||
}
|
}
|
||||||
this._owner._units = this._owner._units.filter((b) => b != this);
|
this._owner._units = this._owner._units.filter((b) => b !== this);
|
||||||
this._active = false;
|
this._active = false;
|
||||||
this.mg.addUpdate(this.toUpdate());
|
this.mg.addUpdate(this.toUpdate());
|
||||||
this.mg.removeUnit(this);
|
this.mg.removeUnit(this);
|
||||||
if (displayMessage && this.type() != UnitType.MIRVWarhead) {
|
if (displayMessage && this.type() !== UnitType.MIRVWarhead) {
|
||||||
this.mg.displayMessage(
|
this.mg.displayMessage(
|
||||||
`Your ${this.type()} was destroyed`,
|
`Your ${this.type()} was destroyed`,
|
||||||
MessageType.ERROR,
|
MessageType.ERROR,
|
||||||
@@ -166,14 +167,14 @@ export class UnitImpl implements Unit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
constructionType(): UnitType | null {
|
constructionType(): UnitType | null {
|
||||||
if (this.type() != UnitType.Construction) {
|
if (this.type() !== UnitType.Construction) {
|
||||||
throw new Error(`Cannot get construction type on ${this.type()}`);
|
throw new Error(`Cannot get construction type on ${this.type()}`);
|
||||||
}
|
}
|
||||||
return this._constructionType ?? null;
|
return this._constructionType ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
setConstructionType(type: UnitType): void {
|
setConstructionType(type: UnitType): void {
|
||||||
if (this.type() != UnitType.Construction) {
|
if (this.type() !== UnitType.Construction) {
|
||||||
throw new Error(`Cannot set construction type on ${this.type()}`);
|
throw new Error(`Cannot set construction type on ${this.type()}`);
|
||||||
}
|
}
|
||||||
this._constructionType = type;
|
this._constructionType = type;
|
||||||
|
|||||||
Reference in New Issue
Block a user