From 511547f72d8fce67e2971c5117096b02077ca081 Mon Sep 17 00:00:00 2001 From: Restart2008 Date: Thu, 19 Feb 2026 17:15:15 -0800 Subject: [PATCH] improved lag issues and added terrain effects --- src/client/graphics/layers/TerritoryLayer.ts | 64 ++++++++++++++------ src/core/game/GameView.ts | 42 +++++++++++-- 2 files changed, 84 insertions(+), 22 deletions(-) diff --git a/src/client/graphics/layers/TerritoryLayer.ts b/src/client/graphics/layers/TerritoryLayer.ts index a1e7e36b8..0f25ddb97 100644 --- a/src/client/graphics/layers/TerritoryLayer.ts +++ b/src/client/graphics/layers/TerritoryLayer.ts @@ -37,6 +37,7 @@ export class TerritoryLayer implements Layer { private tileToRenderQueue: PriorityQueue<{ tile: TileRef; lastUpdate: number; + redrawNeighbors: boolean; }> = new PriorityQueue((a, b) => { return a.lastUpdate - b.lastUpdate; }); @@ -534,8 +535,10 @@ export class TerritoryLayer implements Layer { const tile = entry.tile; this.paintTerritory(tile); - for (const neighbor of this.game.neighbors(tile)) { - this.paintTerritory(neighbor, true); + if (entry.redrawNeighbors) { + for (const neighbor of this.game.neighbors(tile)) { + this.paintTerritory(neighbor, true); + } } } } @@ -596,7 +599,9 @@ export class TerritoryLayer implements Layer { } const urbanization = this.game.getUrbanization(tile); - let color = owner.territoryColor(tile); + let r = owner.baseR; + let g = owner.baseG; + let b = owner.baseB; let alpha = 150; if (urbanization.density > 0.05) { @@ -604,15 +609,22 @@ export class TerritoryLayer implements Layer { alpha = 160 + Math.min(85, urbanization.density * 70); // Lightness Gradient: Rural (Low density) is lighter, Urban (High density) is darker/richer. - // We interpolate between a "rural" shade and an "urban" shade of the player's color. - const ruralShade = color.lighten(0.15).desaturate(0.05); - const urbanShade = color.darken(0.15).saturate(0.2); - + // Fast RGB interpolation using pre-calculated shades const mixRatio = Math.min(1, urbanization.density); - color = ruralShade.mix(urbanShade, mixRatio); + const invRatio = 1 - mixRatio; + + r = (owner.ruralR * invRatio + owner.urbanR * mixRatio) | 0; + g = (owner.ruralG * invRatio + owner.urbanG * mixRatio) | 0; + b = (owner.ruralB * invRatio + owner.urbanB * mixRatio) | 0; + } else { + // Apply pattern if not in urban area + const color = owner.territoryColor(tile); + r = color.rgba.r; + g = color.rgba.g; + b = color.rgba.b; } - this.paintTile(this.imageData, tile, color, alpha); + this.paintTileRaw(this.imageData, tile, r, g, b, alpha); } } @@ -639,13 +651,30 @@ export class TerritoryLayer implements Layer { } paintTile(imageData: ImageData, tile: TileRef, color: Colord, alpha: number) { - const offset = tile * 4; - imageData.data[offset] = color.rgba.r; - imageData.data[offset + 1] = color.rgba.g; - imageData.data[offset + 2] = color.rgba.b; - imageData.data[offset + 3] = alpha; + this.paintTileRaw( + imageData, + tile, + color.rgba.r, + color.rgba.g, + color.rgba.b, + alpha, + ); } + paintTileRaw( + imageData: ImageData, + tile: TileRef, + r: number, + g: number, + b: number, + alpha: number, + ) { + const offset = tile * 4; + imageData.data[offset] = r; + imageData.data[offset + 1] = g; + imageData.data[offset + 2] = b; + imageData.data[offset + 3] = alpha; + } clearTile(tile: TileRef) { const offset = tile * 4; this.imageData.data[offset + 3] = 0; // Set alpha to 0 (fully transparent) @@ -657,15 +686,16 @@ export class TerritoryLayer implements Layer { this.alternativeImageData.data[offset + 3] = 0; // Set alpha to 0 (fully transparent) } - enqueueTile(tile: TileRef) { + enqueueTile(tile: TileRef, redrawNeighbors: boolean = true) { this.tileToRenderQueue.push({ tile: tile, lastUpdate: this.game.ticks() + this.random.nextFloat(0, 0.5), + redrawNeighbors: redrawNeighbors, }); } enqueueCityArea(city: UnitView) { - const radius = Math.ceil(city.areaRadius() * 1.4 + 2); + const radius = Math.ceil(city.areaRadius() * 1.6 + 2); const tile = city.tile(); const cx = this.game.x(tile); const cy = this.game.y(tile); @@ -673,7 +703,7 @@ export class TerritoryLayer implements Layer { for (let x = cx - radius; x <= cx + radius; x++) { for (let y = cy - radius; y <= cy + radius; y++) { if (this.game.isValidCoord(x, y)) { - this.enqueueTile(this.game.ref(x, y)); + this.enqueueTile(this.game.ref(x, y), false); } } } diff --git a/src/core/game/GameView.ts b/src/core/game/GameView.ts index d706f70ce..b2db29268 100644 --- a/src/core/game/GameView.ts +++ b/src/core/game/GameView.ts @@ -227,6 +227,17 @@ export class PlayerView { private _urbanizationBuildingColor: Colord; private _urbanizationCoreColor: Colord; + // Optimized RGB values for urbanization shading + public readonly ruralR: number; + public readonly ruralG: number; + public readonly ruralB: number; + public readonly urbanR: number; + public readonly urbanG: number; + public readonly urbanB: number; + public readonly baseR: number; + public readonly baseG: number; + public readonly baseB: number; + constructor( private game: GameView, public data: PlayerUpdate, @@ -272,6 +283,21 @@ export class PlayerView { this._territoryColor = defaultTerritoryColor; } + const rural = this._territoryColor.lighten(0.15).desaturate(0.05).rgba; + this.ruralR = rural.r; + this.ruralG = rural.g; + this.ruralB = rural.b; + + const urban = this._territoryColor.darken(0.15).saturate(0.2).rgba; + this.urbanR = urban.r; + this.urbanG = urban.g; + this.urbanB = urban.b; + + const base = this._territoryColor.rgba; + this.baseR = base.r; + this.baseG = base.g; + this.baseB = base.b; + this._structureColors = this.game .config() .theme() @@ -1013,7 +1039,7 @@ export class GameView implements GameMap { const cached = this.urbanizationCache.get(tile); if (cached) return cached; - const nearby = this.nearbyUnits(tile, 40, UnitType.City); + const nearby = this.nearbyUnits(tile, 60, UnitType.City); if (nearby.length === 0) { const result = { density: 0 }; this.urbanizationCache.set(tile, result); @@ -1026,6 +1052,7 @@ export class GameView implements GameMap { const tileOwnerID = this.ownerID(tile); const tx = this.x(tile); const ty = this.y(tile); + const terrainMag = this.magnitude(tile); for (let i = 0; i < nearby.length; i++) { const { unit, distSquared } = nearby[i]; @@ -1042,15 +1069,20 @@ export class GameView implements GameMap { const dx = tx - ux; const dy = ty - uy; - // Faster angle approximation or just use atan2 (it's called once per nearby city) const angle = Math.atan2(dy, dx); const uid = unit.id(); - // Simplified noise with fewer sin calls + // More complex noise to ensure irregular shapes even at high city levels const noise = - 1 + 0.18 * Math.sin(angle * 3 + uid) + 0.12 * Math.sin(angle * 5 - uid); + 1 + + 0.22 * Math.sin(angle * 3 + uid) + + 0.15 * Math.sin(angle * 7 - uid * 1.3) + + 0.08 * Math.sin(angle * 13 + uid * 0.7); - const irregularRadius = radius * noise; + // Terrain expansion logic: cities expand easily in plains, poorly in mountains. + // Magnitude 0-30 scale. We penalize distance based on terrain difficulty. + const terrainFactor = Math.max(0.2, 1.0 - Math.min(terrainMag, 25) / 35); + const irregularRadius = radius * noise * terrainFactor; if (dist <= irregularRadius) { // Linear fade is faster than Math.pow