feat: move warship (#196)

This commit is contained in:
Ilan Schemoul
2025-03-15 00:30:30 +01:00
committed by GitHub
parent baf91db288
commit 275de50fcd
8 changed files with 182 additions and 52 deletions
+11 -1
View File
@@ -24,7 +24,8 @@ export type Intent =
| DonateIntent
| TargetTroopRatioIntent
| BuildUnitIntent
| EmbargoIntent;
| EmbargoIntent
| MoveWarshipIntent;
export type AttackIntent = z.infer<typeof AttackIntentSchema>;
export type CancelAttackIntent = z.infer<typeof CancelAttackIntentSchema>;
@@ -43,6 +44,7 @@ export type TargetTroopRatioIntent = z.infer<
typeof TargetTroopRatioIntentSchema
>;
export type BuildUnitIntent = z.infer<typeof BuildUnitIntentSchema>;
export type MoveWarshipIntent = z.infer<typeof MoveWarshipIntentSchema>;
export type Turn = z.infer<typeof TurnSchema>;
export type GameConfig = z.infer<typeof GameConfigSchema>;
@@ -164,6 +166,7 @@ const BaseIntentSchema = z.object({
"troop_ratio",
"build_unit",
"embargo",
"move_warship",
]),
clientID: ID,
playerID: ID,
@@ -261,6 +264,12 @@ export const CancelAttackIntentSchema = BaseIntentSchema.extend({
attackID: z.string(),
});
export const MoveWarshipIntentSchema = BaseIntentSchema.extend({
type: z.literal("move_warship"),
unitId: z.number(),
tile: z.number(),
});
const IntentSchema = z.union([
AttackIntentSchema,
CancelAttackIntentSchema,
@@ -275,6 +284,7 @@ const IntentSchema = z.union([
TargetTroopRatioIntentSchema,
BuildUnitIntentSchema,
EmbargoIntentSchema,
MoveWarshipIntentSchema,
]);
export const TurnSchema = z.object({
+3
View File
@@ -36,6 +36,7 @@ import { fixProfaneUsername, isProfaneUsername } from "../validations/username";
import { NoOpExecution } from "./NoOpExecution";
import { EmbargoExecution } from "./EmbargoExecution";
import { RetreatExecution } from "./RetreatExecution";
import { MoveWarshipExecution } from "./MoveWarshipExecution";
export class Executor {
// private random = new PseudoRandom(999)
@@ -83,6 +84,8 @@ export class Executor {
}
case "cancel_attack":
return new RetreatExecution(intent.playerID, intent.attackID);
case "move_warship":
return new MoveWarshipExecution(intent.unitId, intent.tile);
case "spawn":
return new SpawnExecution(
new PlayerInfo(
@@ -0,0 +1,40 @@
import { Execution, Game, Player, PlayerID } from "../game/Game";
const cancelDelay = 2;
export class MoveWarshipExecution implements Execution {
private active = true;
private mg: Game;
constructor(
public readonly unitId: number,
public readonly position: number,
) {}
init(mg: Game, ticks: number): void {
this.mg = mg;
}
tick(ticks: number): void {
const warship = this.mg.units().find((u) => u.id() == this.unitId);
if (!warship) {
console.log("MoveWarshipExecution: warship is already dead");
return;
}
warship.setMoveTarget(this.position);
this.active = false;
}
owner(): Player {
const warship = this.mg.units().find((u) => u.id() == this.unitId);
return warship ? warship.owner() : null;
}
isActive(): boolean {
return this.active;
}
activeDuringSpawnPhase(): boolean {
return false;
}
}
+82 -38
View File
@@ -55,6 +55,70 @@ export class WarshipExecution implements Execution {
this.random = new PseudoRandom(mg.ticks());
}
// Only for warships with "moveTarget" set
goToMoveTarget(target: TileRef): boolean {
// Patrol unless we are hunting down a tradeship
const result = this.pathfinder.nextTile(this.warship.tile(), target);
switch (result.type) {
case PathFindResultType.Completed:
this.warship.setMoveTarget(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;
}
}
private shoot() {
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;
}
}
}
private patrol() {
this.warship.setWarshipTarget(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;
}
}
}
tick(ticks: number): void {
if (this.warship == null) {
const spawn = this._owner.canBuild(UnitType.Warship, this.patrolTile);
@@ -110,28 +174,17 @@ export class WarshipExecution implements Execution {
return distSortUnit(this.mg, this.warship)(a, b);
})[0] ?? null;
this.warship.setWarshipTarget(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.warship.moveTarget()) {
this.goToMoveTarget(this.warship.moveTarget());
// If we have a "move target" then we cannot target trade ships as it
// requires moving.
if (this.target && this.target.type() == UnitType.TradeShip) {
this.target = null;
}
} else if (!this.target || this.target.type() != UnitType.TradeShip) {
this.patrol();
}
if (
this.target == null ||
!this.target.isActive() ||
@@ -141,25 +194,16 @@ export class WarshipExecution implements Execution {
this.target = null;
return;
}
this.warship.setWarshipTarget(this.target);
// If we have a move target we do not want to go after trading ships
if (!this.target) {
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
this.shoot();
return;
}
+3
View File
@@ -238,6 +238,9 @@ export interface Unit {
dstPort(): Unit; // Only for trade ships
detonationDst(): TileRef; // Only for nukes
setMoveTarget(cell: TileRef): void;
moveTarget(): TileRef | null;
// Mutations
setTroops(troops: number): void;
delete(displayerMessage?: boolean): void;
+10 -1
View File
@@ -1,4 +1,4 @@
import { MessageType, nukeTypes, UnitSpecificInfos } from "./Game";
import { MessageType, UnitSpecificInfos } from "./Game";
import { UnitUpdate } from "./GameUpdates";
import { GameUpdateType } from "./GameUpdates";
import { simpleHash, toInt, within, withinInt } from "../Util";
@@ -14,6 +14,7 @@ export class UnitImpl implements Unit {
private _lastTile: TileRef = null;
// Currently only warship use it
private _target: Unit = null;
private _moveTarget: TileRef = null;
private _constructionType: UnitType = undefined;
@@ -184,4 +185,12 @@ export class UnitImpl implements Unit {
setDstPort(dstPort: Unit): void {
this._dstPort = dstPort;
}
setMoveTarget(moveTarget: TileRef) {
this._moveTarget = moveTarget;
}
moveTarget(): TileRef | null {
return this._moveTarget;
}
}