give Battleships & Destroyers health, make shells more frequent & larger

This commit is contained in:
Evan
2024-12-20 16:43:24 -08:00
parent 17d75324f8
commit 5307285d8b
11 changed files with 96 additions and 33 deletions
+7 -4
View File
@@ -53,22 +53,25 @@ export abstract class DefaultConfig implements Config {
case UnitType.TransportShip:
return {
cost: () => 0,
territoryBound: false
territoryBound: false,
}
case UnitType.Destroyer:
return {
cost: (p: Player) => (p.units(UnitType.Destroyer).length + 1) * 250_000,
territoryBound: false
territoryBound: false,
maxHealth: 1000,
}
case UnitType.Battleship:
return {
cost: (p: Player) => (p.units(UnitType.Battleship).length + 1) * 500_000,
territoryBound: false
territoryBound: false,
maxHealth: 5000
}
case UnitType.Shell:
return {
cost: () => 0,
territoryBound: false
territoryBound: false,
damage: 250
}
case UnitType.Port:
return {
+1 -1
View File
@@ -16,7 +16,7 @@ export const devConfig = new class extends DefaultConfig {
return 95
}
numSpawnPhaseTurns(gameType: GameType): number {
return gameType == GameType.Singleplayer ? 40 : 100
return gameType == GameType.Singleplayer ? 40 : 200
// return 100
}
gameCreationRate(): number {
+16 -3
View File
@@ -1,4 +1,4 @@
import { Cell, Execution, MutableGame, MutablePlayer, MutableUnit, PlayerID, TerrainType, Tile, UnitType } from "../game/Game";
import { Cell, Execution, MutableGame, MutablePlayer, MutableUnit, PlayerID, TerrainType, Tile, Unit, UnitType } from "../game/Game";
import { PathFinder } from "../pathfinding/PathFinding";
import { PathFindResultType } from "../pathfinding/AStar";
import { SerialAStar } from "../pathfinding/SerialAStar";
@@ -22,9 +22,11 @@ export class BattleshipExecution implements Execution {
// TODO: put in config
private searchRange = 100
private attackRate = 20
private attackRate = 5
private lastAttack = 0
private alreadyTargeted = new Set<Unit>()
constructor(
private playerID: PlayerID,
private cell: Cell,
@@ -41,6 +43,11 @@ export class BattleshipExecution implements Execution {
}
tick(ticks: number): void {
this.alreadyTargeted.forEach(u => {
if (!u.isActive()) {
this.alreadyTargeted.delete(u)
}
})
if (this.battleship == null) {
const spawn = this._owner.canBuild(UnitType.Battleship, this.patrolTile)
if (spawn == false) {
@@ -82,11 +89,17 @@ export class BattleshipExecution implements Execution {
.filter(u => u.owner() != this.battleship.owner())
.filter(u => u != this.battleship)
.filter(u => !u.owner().isAlliedWith(this.battleship.owner()))
.filter(u => !this.alreadyTargeted.has(u))
.sort(distSortUnit(this.battleship));
if (ships.length > 0) {
const toAttack = ships[0]
if (!toAttack.hasHealth()) {
// Don't send multiple shells to target if it can be one-shotted.
this.alreadyTargeted.add(toAttack)
}
this.lastAttack = this.mg.ticks()
this.mg.addExecution(new ShellExecution(this.battleship.tile(), this.battleship.owner(), ships[0]))
this.mg.addExecution(new ShellExecution(this.battleship.tile(), this.battleship.owner(), this.battleship, toAttack))
}
}
+5 -2
View File
@@ -58,6 +58,7 @@ export class DestroyerExecution implements Execution {
if (this.target == null) {
const ships = this.mg.units(UnitType.TransportShip, UnitType.Destroyer, UnitType.TradeShip, UnitType.Battleship)
.filter(u => manhattanDist(u.tile().cell(), this.destroyer.tile().cell()) < 100)
.filter(u => u.type() != UnitType.Destroyer || u.health() < this.destroyer.health()) // only attack Destroyers weaker than it.
.filter(u => u.owner() != this.destroyer.owner())
.filter(u => u != this.destroyer)
.filter(u => !u.owner().isAlliedWith(this.destroyer.owner()))
@@ -93,14 +94,16 @@ export class DestroyerExecution implements Execution {
case PathFindResultType.Completed:
switch (this.target.type()) {
case UnitType.TransportShip:
case UnitType.Battleship:
this.target.delete()
break
case UnitType.TradeShip:
this.owner().captureUnit(this.target)
break
case UnitType.Destroyer:
this.target.delete()
this.destroyer.delete()
const health = this.target.health()
this.target.modifyHealth(-this.destroyer.health())
this.destroyer.modifyHealth(-health)
break
}
this.target = null
+5
View File
@@ -30,6 +30,11 @@ export class PlayerExecution implements Execution {
tick(ticks: number) {
this.player.units().forEach(u => {
if (u.health() <= 0) {
u.delete()
return
}
u.modifyHealth(1)
const tileOwner = u.tile().owner()
if (u.info().territoryBound) {
if (tileOwner.isPlayer()) {
+4 -4
View File
@@ -9,7 +9,7 @@ export class ShellExecution implements Execution {
private pathFinder: PathFinder
private shell: MutableUnit
constructor(private spawn: Tile, private _owner: MutablePlayer, private target: MutableUnit) {
constructor(private spawn: Tile, private _owner: MutablePlayer, private ownerUnit: Unit, private target: MutableUnit) {
}
@@ -25,17 +25,17 @@ export class ShellExecution implements Execution {
this.active = false
return
}
if (!this.target.isActive()) {
if (!this.target.isActive() || !this.ownerUnit.isActive()) {
this.shell.delete(false)
this.active = false
return
}
for (let i = 0; i < 3; i++) {
const result = this.pathFinder.nextTile(this.shell.tile(), this.target.tile())
const result = this.pathFinder.nextTile(this.shell.tile(), this.target.tile(), 3)
switch (result.type) {
case PathFindResultType.Completed:
this.active = false
this.target.delete()
this.target.modifyHealth(-this.shell.info().damage)
this.shell.delete(false)
return
case PathFindResultType.NextTile:
+5
View File
@@ -35,6 +35,8 @@ export interface UnitInfo {
cost: (player: Player) => Gold
// Determines if its owner changes when its tile is conquered.
territoryBound: boolean
maxHealth?: number,
damage?: number
}
export enum UnitType {
@@ -193,6 +195,8 @@ export interface Unit {
owner(): Player
isActive(): boolean
info(): UnitInfo
hasHealth(): boolean
health(): number
}
export interface MutableUnit extends Unit {
@@ -200,6 +204,7 @@ export interface MutableUnit extends Unit {
owner(): MutablePlayer
setTroops(troops: number): void
delete(displayerMessage?: boolean): void
modifyHealth(delta: number): void
}
export interface TerraNullius {
+21 -2
View File
@@ -1,5 +1,5 @@
import { MessageType } from "../../client/graphics/layers/EventsDisplay";
import { simpleHash } from "../Util";
import { simpleHash, within } from "../Util";
import { MutableUnit, Tile, TerraNullius, UnitType, Player, UnitInfo } from "./Game";
import { GameImpl } from "./GameImpl";
import { PlayerImpl } from "./PlayerImpl";
@@ -8,6 +8,7 @@ import { TerraNulliusImpl } from "./TerraNulliusImpl";
export class UnitImpl implements MutableUnit {
private _active = true;
private _health: number
constructor(
private _type: UnitType,
@@ -15,7 +16,10 @@ export class UnitImpl implements MutableUnit {
private _tile: Tile,
private _troops: number,
public _owner: PlayerImpl,
) { }
) {
// default to half health (or 1 is no health specified)
this._health = (this.g.unitInfo(_type).maxHealth ?? 2) / 2
}
type(): UnitType {
return this._type
@@ -35,6 +39,12 @@ export class UnitImpl implements MutableUnit {
troops(): number {
return this._troops;
}
health(): number {
return this._health
}
hasHealth(): boolean {
return this.info().maxHealth != undefined
}
tile(): Tile {
return this._tile;
}
@@ -57,6 +67,15 @@ export class UnitImpl implements MutableUnit {
)
}
modifyHealth(delta: number): void {
this._health = within(
this._health + delta,
0,
this.info().maxHealth ?? 1
)
}
delete(displayMessage: boolean = true): void {
if (!this.isActive()) {
throw new Error(`cannot delete ${this} not active`)