create defense post

This commit is contained in:
Evan
2024-12-01 19:54:04 -08:00
parent e479c15ef9
commit d133092317
12 changed files with 127 additions and 29 deletions
+2 -1
View File
@@ -202,7 +202,8 @@
* add info view on top right DONE 11/30/2024
* add info view for units DONE 11/30/2024
* add defense post
* record single player games
* use mini A* for all pathfinding
* record single player game stats
* add radiation from nuke
* add cities
* create alternate view to show friendly & enemy units
Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 B

After

Width:  |  Height:  |  Size: 126 B

+14 -1
View File
@@ -1,4 +1,4 @@
import { Colord } from "colord";
import { colord, Colord } from "colord";
import { Theme } from "../../../core/configuration/Config";
import { Unit, UnitEvent, Cell, Game, Tile, UnitType } from "../../../core/game/Game";
import { bfs, dist, euclDist } from "../../../core/Util";
@@ -160,6 +160,19 @@ export class StructureLayer implements Layer {
onUnitEvent(event: UnitEvent) {
this.handleUnitRendering(event);
if (event.unit.type() == UnitType.DefensePost) {
if (!event.unit.isActive()) {
return
}
// Array.from(
// bfs(
// event.unit.tile(),
// dist(event.unit.tile(), this.game.config().defensePostRange())
// )
// ).filter(t => t.isBorder() && t.owner() == event.unit.owner()).forEach(t => {
// this.paintCell(t.cell(), colord({ r: 255, g: 255, b: 255 }), 255)
// })
}
}
paintCell(cell: Cell, color: Colord, alpha: number) {
+30 -14
View File
@@ -1,7 +1,7 @@
import { PriorityQueue } from "@datastructures-js/priority-queue";
import { Cell, Game, Player, Tile, TileEvent } from "../../../core/game/Game";
import { Cell, Game, Player, Tile, TileEvent, UnitEvent, UnitType } from "../../../core/game/Game";
import { PseudoRandom } from "../../../core/PseudoRandom";
import { Colord } from "colord";
import { colord, Colord } from "colord";
import { bfs, dist } from "../../../core/Util";
import { Theme } from "../../../core/configuration/Config";
import { Layer } from "./Layer";
@@ -13,12 +13,10 @@ export class TerritoryLayer implements Layer {
private context: CanvasRenderingContext2D
private imageData: ImageData
private tileToRenderQueue: PriorityQueue<{ tileEvent: TileEvent, lastUpdate: number }> = new PriorityQueue((a, b) => { return a.lastUpdate - b.lastUpdate })
private tileToRenderQueue: PriorityQueue<{ tile: Tile, lastUpdate: number }> = new PriorityQueue((a, b) => { return a.lastUpdate - b.lastUpdate })
private random = new PseudoRandom(123)
private theme: Theme = null
constructor(private game: Game, eventBus: EventBus) {
this.theme = game.config().theme()
eventBus.on(TileEvent, e => this.tileUpdate(e))
@@ -70,9 +68,9 @@ export class TerritoryLayer implements Layer {
while (numToRender > 0) {
numToRender--
const event = this.tileToRenderQueue.pop().tileEvent
this.paintTerritory(event.tile)
event.tile.neighbors().forEach(t => this.paintTerritory(t))
const tile = this.tileToRenderQueue.pop().tile
this.paintTerritory(tile)
tile.neighbors().forEach(t => this.paintTerritory(t))
}
}
@@ -83,11 +81,19 @@ export class TerritoryLayer implements Layer {
}
const owner = tile.owner() as Player
if (tile.isBorder()) {
this.paintCell(
tile.cell(),
this.theme.borderColor(owner.info()),
255
)
if (tile.defenseBonuses().filter(db => db.unit.owner() == owner).length > 0) {
this.paintCell(
tile.cell(),
colord({ r: 0, g: 0, b: 0 }),
255
)
} else {
this.paintCell(
tile.cell(),
this.theme.borderColor(owner.info()),
255
)
}
} else {
this.paintCell(
tile.cell(),
@@ -112,7 +118,17 @@ export class TerritoryLayer implements Layer {
this.imageData.data[offset + 3] = 0; // Set alpha to 0 (fully transparent)
}
unitEvent(event: UnitEvent) {
if (event.unit.type() == UnitType.DefensePost) {
bfs(event.unit.tile(), dist(event.unit.tile(), this.game.config().defensePostRange())).forEach(t => this.enqueue(t))
}
}
tileUpdate(event: TileEvent) {
this.tileToRenderQueue.push({ tileEvent: event, lastUpdate: this.game.ticks() + this.random.nextFloat(0, .5) })
this.enqueue(event.tile)
}
enqueue(tile: Tile) {
this.tileToRenderQueue.push({ tile: tile, lastUpdate: this.game.ticks() + this.random.nextFloat(0, .5) })
}
}
+5 -5
View File
@@ -28,7 +28,7 @@
</svg>
</a>
<h1 class="text-7xl sm:text-5xl md:text-6xl lg:text-7xl mb-2">OpenFront.io</h1>
<h2 class="text-3xl sm:text-4xl md:text-5xl lg:text-6xl mb-4">(v0.10.0)</h2>
<h2 class="text-3xl sm:text-4xl md:text-5xl lg:text-6xl mb-4">(v0.10.1)</h2>
<div class="flex justify-center items-start">
<div class="w-full max-w-3xl p-4 space-y-4">
<br />
@@ -42,11 +42,11 @@
</div>
<br />
<!-- Button layout -->
<div class="flex space-x-4 max-w-xs mx-auto">
<div class="flex space-x-4 max-w-xs mx-auto/>
<!-- Single Player button -->
<button id="single-player"
class="flex-1 h-31 px-6 py-8 text-xl font-bold text-white bg-blue-600 rounded-lg hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 transition duration-300 ease-in-out">
Single Player
<button id=" single-player"
class="flex-1 h-31 px-6 py-8 text-xl font-bold text-white bg-blue-600 rounded-lg hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 transition duration-300 ease-in-out">
Single Player
</button>
<!-- Create and Join Lobby buttons stacked -->
+2
View File
@@ -60,6 +60,8 @@ export interface Config {
unitInfo(type: UnitType): UnitInfo
tradeShipGold(src: Unit, dst: Unit): Gold
tradeShipSpawnRate(): number
defensePostRange(): number
defensePostDefenseBonus(): number
}
export interface Theme {
+8 -1
View File
@@ -7,6 +7,12 @@ import { pastelTheme } from "./PastelTheme";
export class DefaultConfig implements Config {
defensePostRange(): number {
return 20
}
defensePostDefenseBonus(): number {
return 3
}
spawnNPCs(): boolean {
return true
}
@@ -140,7 +146,8 @@ export class DefaultConfig implements Config {
speed = 30
break
}
// speed = mag
mag *= tileToConquer.defenseBonus(attacker)
speed *= tileToConquer.defenseBonus(attacker)
if (attacker.isPlayer() && defender.isPlayer()) {
if (attacker.type() == PlayerType.Human && defender.type() == PlayerType.Bot) {
+4 -4
View File
@@ -13,14 +13,14 @@ export const devConfig = new class extends DefaultConfig {
return 95
}
numSpawnPhaseTurns(): number {
return 40
// return 100
// return 40
return 100
}
gameCreationRate(): number {
return 20 * 1000
return 10 * 1000
}
lobbyLifetime(): number {
return 20 * 1000
return 10 * 1000
}
turnIntervalMs(): number {
return 100
+14 -1
View File
@@ -1,4 +1,5 @@
import { Cell, Execution, MutableGame, MutablePlayer, MutableUnit, PlayerID, Tile, UnitType } from "../game/Game";
import { Cell, DefenseBonus, Execution, MutableGame, MutablePlayer, MutableUnit, PlayerID, Tile, UnitType } from "../game/Game";
import { bfs, dist } from "../Util";
export class DefensePostExecution implements Execution {
@@ -8,6 +9,8 @@ export class DefensePostExecution implements Execution {
private tile: Tile
private active: boolean = true
private defenseBonuses: DefenseBonus[] = []
constructor(private ownerId: PlayerID, private cell: Cell) { }
init(mg: MutableGame, ticks: number): void {
@@ -25,6 +28,16 @@ export class DefensePostExecution implements Execution {
return
}
this.post = this.player.buildUnit(UnitType.DefensePost, 0, spawnTile)
bfs(spawnTile, dist(spawnTile, this.mg.config().defensePostRange())).forEach(t => {
if (t.isLand()) {
this.defenseBonuses.push(this.mg.addTileDefenseBonus(t, this.post, this.mg.config().defensePostDefenseBonus()))
}
})
}
if (!this.post.isActive()) {
this.defenseBonuses.forEach(df => this.mg.removeTileDefenseBonus(df))
this.active = false
return
}
}
+13
View File
@@ -144,6 +144,13 @@ export interface TerrainTile extends SearchNode {
terrainType(): TerrainType
}
export interface DefenseBonus {
// Unit providing the defense bonus
unit: Unit
amount: number
tile: Tile
}
export interface Tile extends SearchNode {
isLand(): boolean
isShore(): boolean
@@ -163,6 +170,10 @@ export interface Tile extends SearchNode {
neighbors(): Tile[]
neighborsWrapped(): Tile[]
onShore(): boolean
defenseBonuses(): DefenseBonus[]
// defense bonus against this player
defenseBonus(player: Player): number
}
export interface Unit {
@@ -301,6 +312,8 @@ export interface MutableGame extends Game {
addPlayer(playerInfo: PlayerInfo, manpower: number): MutablePlayer
executions(): Execution[]
units(...types: UnitType[]): MutableUnit[]
addTileDefenseBonus(tile: Tile, unit: Unit, amount: number): DefenseBonus
removeTileDefenseBonus(bonus: DefenseBonus): void
}
export class TileEvent implements GameEvent {
+15 -1
View File
@@ -1,7 +1,7 @@
import { info } from "console";
import { Config } from "../configuration/Config";
import { EventBus } from "../EventBus";
import { Cell, Execution, MutableGame, Game, MutablePlayer, PlayerEvent, PlayerID, PlayerInfo, Player, TerraNullius, Tile, TileEvent, Unit, UnitEvent as UnitEvent, PlayerType, MutableAllianceRequest, AllianceRequestReplyEvent, AllianceRequestEvent, BrokeAllianceEvent, MutableAlliance, Alliance, AllianceExpiredEvent, Nation, UnitType, UnitInfo, TerrainMap } from "./Game";
import { Cell, Execution, MutableGame, Game, MutablePlayer, PlayerEvent, PlayerID, PlayerInfo, Player, TerraNullius, Tile, TileEvent, Unit, UnitEvent as UnitEvent, PlayerType, MutableAllianceRequest, AllianceRequestReplyEvent, AllianceRequestEvent, BrokeAllianceEvent, MutableAlliance, Alliance, AllianceExpiredEvent, Nation, UnitType, UnitInfo, TerrainMap, DefenseBonus } from "./Game";
import { createMiniMap, TerrainMapImpl } from "./TerrainMapLoader";
import { PlayerImpl } from "./PlayerImpl";
import { TerraNulliusImpl } from "./TerraNulliusImpl";
@@ -64,6 +64,20 @@ export class GameImpl implements MutableGame {
this._terrainMiniMap = m
})
}
addTileDefenseBonus(tile: Tile, unit: Unit, amount: number): DefenseBonus {
const df = { unit: unit, tile: tile, amount: amount };
(tile as TileImpl)._defenseBonuses.push(df)
this.eventBus.emit(new TileEvent(tile))
return df
}
removeTileDefenseBonus(bonus: DefenseBonus): void {
const t = bonus.tile as TileImpl
t._defenseBonuses = t._defenseBonuses.filter(db => db != bonus)
this.eventBus.emit(new TileEvent(bonus.tile))
}
units(...types: UnitType[]): UnitImpl[] {
return Array.from(this._players.values()).flatMap(p => p.units(...types))
}
+20 -1
View File
@@ -1,4 +1,4 @@
import { Tile, Cell, TerrainType, Player, TerraNullius, MutablePlayer, TerrainTile } from "./Game";
import { Tile, Cell, TerrainType, Player, TerraNullius, MutablePlayer, TerrainTile, DefenseBonus } from "./Game";
import { SearchNode } from "../pathfinding/AStar";
import { TerrainTileImpl } from "./TerrainMapLoader";
import { GameImpl } from "./GameImpl";
@@ -11,6 +11,8 @@ export class TileImpl implements Tile {
public _isBorder = false;
private _neighbors: Tile[] = null;
public _defenseBonuses: DefenseBonus[] = []
constructor(
private readonly gs: GameImpl,
public _owner: PlayerImpl | TerraNulliusImpl,
@@ -18,6 +20,23 @@ export class TileImpl implements Tile {
private readonly _terrain: TerrainTileImpl
) { }
defenseBonus(player: Player): number {
if (this.owner() == player) {
throw Error(`cannot get defense bonus of tile already owned by player`)
}
let bonusAmount = 0
for (const bonus of this._defenseBonuses) {
if (bonus.unit.owner() != player) {
bonusAmount += bonus.amount
}
}
return Math.max(bonusAmount, 1)
}
defenseBonuses(): DefenseBonus[] {
return this._defenseBonuses
}
neighborsWrapped(): Tile[] {
const x = this._cell.x;
const y = this._cell.y;