mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 09:30:45 +00:00
tiles now have lake or ocean method
This commit is contained in:
@@ -40,15 +40,15 @@
|
||||
* only put imageDataOnce, draw territories on top DONE 8/23/2024
|
||||
* have boats not get close to shore DONE 8/23/2024
|
||||
* improve terrain colors DONE 8/23/2024
|
||||
* BUG: boat doesn't work if on lake if other player not on same lake
|
||||
* try vintage theme
|
||||
* BUG: boat doesn't work if on lake if other player not on same lake
|
||||
* BUG: boat doesn't work if on lake if other player not on same lake DONE 8/23/2024
|
||||
* Allow boats to attack TerraNullius
|
||||
* improve menu
|
||||
* try vintage theme
|
||||
* add shader to dim border
|
||||
* remove player.info()
|
||||
* improve menu
|
||||
* give time to (re) spawn at start of game
|
||||
* BUG: ocean is considered TerraNullius ?
|
||||
* BUG: fix hotreload (priority queue breaks it)
|
||||
* PERF: use hierarchical a* search for boats
|
||||
* Add terrain elevation to map
|
||||
* Add terrain elevation to map
|
||||
* Boats can go diagonally
|
||||
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 217 KiB After Width: | Height: | Size: 221 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 217 KiB |
@@ -7,6 +7,7 @@ import {GameRenderer} from "./graphics/GameRenderer";
|
||||
import {InputHandler, MouseUpEvent, ZoomEvent, DragEvent, MouseDownEvent} from "./InputHandler"
|
||||
import {ClientID, ClientIntentMessageSchema, ClientJoinMessageSchema, ClientLeaveMessageSchema, ClientMessageSchema, GameID, Intent, ServerMessage, ServerMessageSchema, ServerSyncMessage, Turn} from "../core/Schemas";
|
||||
import {TerrainMap} from "../core/TerrainMapLoader";
|
||||
import {bfs, manhattanDist} from "../core/Util";
|
||||
|
||||
|
||||
|
||||
@@ -189,10 +190,17 @@ export class ClientGame {
|
||||
const owner = tile.owner()
|
||||
const targetID = owner.isPlayer() ? owner.id() : null
|
||||
if (tile.owner() != this.myPlayer && tile.isLand()) {
|
||||
// const ocean = Array.from(bfs(tile, 4))
|
||||
// .filter(t => t.isOcean)
|
||||
// .sort((a, b) => manhattanDist(tile.cell(), a.cell()) - manhattanDist(tile.cell(), b.cell()))
|
||||
// if (ocean.length > 0) {
|
||||
// this.sendBoatAttackIntent(targetID, cell, this.config.player().boatAttackAmount(this.myPlayer, owner))
|
||||
// return
|
||||
// }
|
||||
|
||||
if (this.myPlayer.sharesBorderWith(tile.owner())) {
|
||||
this.sendAttackIntent(targetID, cell, this.config.player().attackAmount(this.myPlayer, owner))
|
||||
} else if (owner.isPlayer()) {
|
||||
// TODO verify on ocean
|
||||
console.log('going to send boat')
|
||||
this.sendBoatAttackIntent(targetID, cell, this.config.player().boatAttackAmount(this.myPlayer, owner))
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import {Cell, Game, PlayerEvent, Tile, TileEvent, Player, Execution, BoatEvent}
|
||||
import {Theme} from "../../core/configuration/Config";
|
||||
import {DragEvent, ZoomEvent} from "../InputHandler";
|
||||
import {NameRenderer} from "./NameRenderer";
|
||||
import {manhattanDist} from "../../core/Util";
|
||||
import {bfs, manhattanDist} from "../../core/Util";
|
||||
import {PseudoRandom} from "../../core/PseudoRandom";
|
||||
|
||||
|
||||
@@ -146,30 +146,14 @@ export class GameRenderer {
|
||||
}
|
||||
|
||||
boatEvent(event: BoatEvent) {
|
||||
this.bfs(event.oldTile, 2).forEach(t => this.paintTerritory(t))
|
||||
bfs(event.oldTile, 2).forEach(t => this.paintTerritory(t))
|
||||
if (event.boat.isActive()) {
|
||||
this.bfs(event.boat.tile(), 2).forEach(t => this.paintCell(t.cell(), this.theme.borderColor(event.boat.owner().id())))
|
||||
this.bfs(event.boat.tile(), 1).forEach(t => this.paintCell(t.cell(), this.theme.territoryColor(event.boat.owner().id())))
|
||||
bfs(event.boat.tile(), 2).forEach(t => this.paintCell(t.cell(), this.theme.borderColor(event.boat.owner().id())))
|
||||
bfs(event.boat.tile(), 1).forEach(t => this.paintCell(t.cell(), this.theme.territoryColor(event.boat.owner().id())))
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private bfs(tile: Tile, dist: number): Set<Tile> {
|
||||
const seen = new Set<Tile>
|
||||
const q: Tile[] = []
|
||||
q.push(tile)
|
||||
while (q.length > 0) {
|
||||
const curr = q.pop()
|
||||
seen.add(curr)
|
||||
for (const n of curr.neighbors()) {
|
||||
if (!seen.has(n) && manhattanDist(tile.cell(), n.cell()) <= dist) {
|
||||
q.push(n)
|
||||
}
|
||||
}
|
||||
}
|
||||
return seen
|
||||
}
|
||||
|
||||
resize(width: number, height: number): void {
|
||||
this.canvas.width = Math.ceil(width / window.devicePixelRatio);
|
||||
this.canvas.height = Math.ceil(height / window.devicePixelRatio);
|
||||
|
||||
@@ -74,7 +74,7 @@ export function createGrid(game: Game, player: Player, boundingBox: {min: Point;
|
||||
const cell = new Cell(x * scalingFactor, y * scalingFactor);
|
||||
if (game.isOnMap(cell)) {
|
||||
const tile = game.tile(cell);
|
||||
grid[x - scaledBoundingBox.min.x][y - scaledBoundingBox.min.y] = tile.owner() === player; // TODO: okay if lake
|
||||
grid[x - scaledBoundingBox.min.x][y - scaledBoundingBox.min.y] = tile.isLake() || tile.owner() === player; // TODO: okay if lake
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
-2
@@ -23,8 +23,8 @@ export interface ExecutionView {
|
||||
}
|
||||
|
||||
export interface Execution extends ExecutionView {
|
||||
init(mg: MutableGame, ticks: number)
|
||||
tick(ticks: number)
|
||||
init(mg: MutableGame, ticks: number): void
|
||||
tick(ticks: number): void
|
||||
owner(): MutablePlayer
|
||||
}
|
||||
|
||||
@@ -42,6 +42,8 @@ export interface Tile {
|
||||
isShore(): boolean
|
||||
isWater(): boolean
|
||||
isShorelineWater(): boolean
|
||||
isOcean(): boolean
|
||||
isLake(): boolean
|
||||
magnitude(): number
|
||||
owner(): Player | TerraNullius
|
||||
hasOwner(): boolean
|
||||
|
||||
@@ -19,6 +19,12 @@ class TileImpl implements Tile {
|
||||
private readonly _cell: Cell,
|
||||
private readonly _terrain: Terrain
|
||||
) { }
|
||||
isLake(): boolean {
|
||||
return !this.isLand() && !this.isOcean()
|
||||
}
|
||||
isOcean(): boolean {
|
||||
return this._terrain.ocean
|
||||
}
|
||||
magnitude(): number {
|
||||
return this._terrain.magnitude
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ export enum TerrainType {
|
||||
|
||||
export class Terrain {
|
||||
public shoreline: boolean = false
|
||||
public ocean: boolean = false
|
||||
public magnitude: number = 0
|
||||
constructor(public type: TerrainType) { }
|
||||
}
|
||||
@@ -55,10 +56,12 @@ export function loadTerrainMap(): TerrainMap {
|
||||
const packedByte = fileData.charCodeAt(4 + y * width + x); // +4 to skip dimension bytes
|
||||
const type = (packedByte & 0b10000000) ? TerrainType.Land : TerrainType.Water;
|
||||
const shoreline = !!(packedByte & 0b01000000);
|
||||
const magnitude = packedByte & 0b00111111;
|
||||
const ocean = !!(packedByte & 0b00100000);
|
||||
const magnitude = packedByte & 0b00011111;
|
||||
|
||||
terrain[x][y] = new Terrain(type);
|
||||
terrain[x][y].shoreline = shoreline;
|
||||
terrain[x][y].ocean = ocean;
|
||||
terrain[x][y].magnitude = magnitude;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
import {EventBus, GameEvent} from "./EventBus";
|
||||
import {Config} from "./configuration/Config";
|
||||
|
||||
export class TickEvent implements GameEvent {
|
||||
constructor(public readonly tickCount: number) { }
|
||||
}
|
||||
|
||||
export class Ticker {
|
||||
private ticker: NodeJS.Timeout;
|
||||
private tickCount: number;
|
||||
|
||||
constructor(private tickInterval: number, private eventBus: EventBus) {
|
||||
|
||||
}
|
||||
|
||||
start() {
|
||||
this.tickCount = 0;
|
||||
this.ticker = setInterval(() => this.tick(), this.tickInterval);
|
||||
}
|
||||
|
||||
stop() {
|
||||
clearInterval(this.ticker);
|
||||
}
|
||||
|
||||
private tick() {
|
||||
this.eventBus.emit(new TickEvent(this.tickCount))
|
||||
this.tickCount++;
|
||||
}
|
||||
|
||||
getTickCount(): number {
|
||||
return this.tickCount;
|
||||
}
|
||||
}
|
||||
+17
-1
@@ -1,4 +1,4 @@
|
||||
import {Cell} from "./Game";
|
||||
import {Cell, Tile} from "./Game";
|
||||
|
||||
export function manhattanDist(c1: Cell, c2: Cell): number {
|
||||
return Math.abs(c1.x - c2.x) + Math.abs(c1.y - c2.y);
|
||||
@@ -6,4 +6,20 @@ export function manhattanDist(c1: Cell, c2: Cell): number {
|
||||
|
||||
export function within(value: number, min: number, max: number): number {
|
||||
return Math.min(Math.max(value, min), max);
|
||||
}
|
||||
|
||||
export function bfs(tile: Tile, dist: number): Set<Tile> {
|
||||
const seen = new Set<Tile>
|
||||
const q: Tile[] = []
|
||||
q.push(tile)
|
||||
while (q.length > 0) {
|
||||
const curr = q.pop()
|
||||
seen.add(curr)
|
||||
for (const n of curr.neighbors()) {
|
||||
if (!seen.has(n) && manhattanDist(tile.cell(), n.cell()) <= dist) {
|
||||
q.push(n)
|
||||
}
|
||||
}
|
||||
}
|
||||
return seen
|
||||
}
|
||||
@@ -117,7 +117,7 @@ export class BoatAttackExecution implements Execution {
|
||||
}
|
||||
|
||||
private closestShoreTileToTarget(player: Player, target: Cell): Tile | null {
|
||||
const shoreTiles = Array.from(player.borderTiles()).filter(t => t.onShore())
|
||||
const shoreTiles = Array.from(player.borderTiles()).filter(t => t.onShore() && t.neighbors().filter(n => n.isOcean()).length > 0)
|
||||
if (shoreTiles.length == 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ export enum TerrainType {
|
||||
export class Terrain {
|
||||
public shoreline: boolean = false
|
||||
public magnitude: number = 0
|
||||
public ocean: boolean
|
||||
constructor(public type: TerrainType) { }
|
||||
}
|
||||
|
||||
@@ -57,12 +58,12 @@ export async function loadTerrainMap(): Promise<void> {
|
||||
for (let x = 0; x < img.width; x++) {
|
||||
for (let y = 0; y < img.height; y++) {
|
||||
const color = img.getPixelRGBA(x, y);
|
||||
const red = (color >> 24) & 0xff;
|
||||
const alpha = color & 0xff;
|
||||
|
||||
if (red > 100) {
|
||||
terrain[x][y] = new Terrain(TerrainType.Land)
|
||||
} else {
|
||||
if (alpha < 20) { // transparent
|
||||
terrain[x][y] = new Terrain(TerrainType.Water);
|
||||
} else {
|
||||
terrain[x][y] = new Terrain(TerrainType.Land)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,6 +71,7 @@ export async function loadTerrainMap(): Promise<void> {
|
||||
|
||||
const shorelineWaters = processShore(terrain)
|
||||
processDistToLand(shorelineWaters, terrain)
|
||||
processOcean(terrain)
|
||||
const packed = packTerrain(terrain)
|
||||
const outputPath = path.join(__dirname, '..', '..', 'resources', 'World.bin');
|
||||
fs.writeFile(outputPath, packed);
|
||||
@@ -163,7 +165,10 @@ function packTerrain(map: Terrain[][]): Uint8Array {
|
||||
if (terrain.shoreline) {
|
||||
packedByte |= 0b01000000;
|
||||
}
|
||||
packedByte |= Math.min(Math.ceil(terrain.magnitude / 2), 63);
|
||||
if (terrain.ocean) {
|
||||
packedByte |= 0b00100000;
|
||||
}
|
||||
packedByte |= Math.min(Math.ceil(terrain.magnitude / 2), 31);
|
||||
|
||||
packedData[4 + y * width + x] = packedByte;
|
||||
}
|
||||
@@ -172,6 +177,34 @@ function packTerrain(map: Terrain[][]): Uint8Array {
|
||||
return packedData;
|
||||
}
|
||||
|
||||
function processOcean(map: Terrain[][]) {
|
||||
const queue: Coord[] = [{x: 0, y: 0}];
|
||||
const visited = new Set<string>();
|
||||
|
||||
while (queue.length > 0) {
|
||||
const coord = queue.shift()!;
|
||||
const key = `${coord.x},${coord.y}`;
|
||||
|
||||
if (visited.has(key)) continue;
|
||||
visited.add(key);
|
||||
|
||||
const terrain = map[coord.x][coord.y];
|
||||
if (terrain.type === TerrainType.Water) {
|
||||
terrain.ocean = true;
|
||||
|
||||
// Check neighbors
|
||||
for (const [dx, dy] of [[-1, 0], [1, 0], [0, -1], [0, 1]]) {
|
||||
const newX = coord.x + dx;
|
||||
const newY = coord.y + dy;
|
||||
|
||||
if (newX >= 0 && newX < map.length && newY >= 0 && newY < map[0].length) {
|
||||
queue.push({x: newX, y: newY});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function logBinaryAsBits(data: Uint8Array, length: number = 8) {
|
||||
const bits = Array.from(data.slice(0, length))
|
||||
.map(b => b.toString(2).padStart(8, '0'))
|
||||
|
||||
Reference in New Issue
Block a user