mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 15:00:43 +00:00
Implement tile transition effects in TerritoryLayer
- Introduced a new `TileTransition` type to manage tile transitions. - Added methods to handle the beginning and updating of tile transitions. - Enhanced `TerritoryRenderer` interfaces to support transition progress. - Updated `TerritoryWebGLRenderer` to manage transition textures and rendering. - Modified `GameView` to track owner changes for tiles, enabling transition effects during gameplay.
This commit is contained in:
@@ -29,6 +29,13 @@ import {
|
||||
} from "./TerritoryRenderers";
|
||||
import { TerritoryWebGLRenderer } from "./TerritoryWebGLRenderer";
|
||||
|
||||
type TileTransition = {
|
||||
startTime: number;
|
||||
durationMs: number;
|
||||
highlight: boolean;
|
||||
lastProgressByte: number;
|
||||
};
|
||||
|
||||
export class TerritoryLayer implements Layer {
|
||||
profileName(): string {
|
||||
return "TerritoryLayer:renderLayer";
|
||||
@@ -66,6 +73,12 @@ export class TerritoryLayer implements Layer {
|
||||
private lastFocusedPlayer: PlayerView | null = null;
|
||||
private lastMyPlayerSmallId: number | null = null;
|
||||
private lastPaletteSignature: string | null = null;
|
||||
private tileTransitions: Map<TileRef, TileTransition> = new Map();
|
||||
private transitionHighlightTiles: TileRef[] = [];
|
||||
private transitionHighlightAlphas: number[] = [];
|
||||
private lastGameTick = 0;
|
||||
private lastTickTime = 0;
|
||||
private lastTickDurationMs = 100;
|
||||
|
||||
constructor(
|
||||
private game: GameView,
|
||||
@@ -77,6 +90,7 @@ export class TerritoryLayer implements Layer {
|
||||
this.theme = game.config().theme();
|
||||
this.cachedTerritoryPatternsEnabled = undefined;
|
||||
this.lastMyPlayerSmallId = game.myPlayer()?.smallID() ?? null;
|
||||
this.lastTickTime = Date.now();
|
||||
}
|
||||
|
||||
shouldTransform(): boolean {
|
||||
@@ -92,6 +106,8 @@ export class TerritoryLayer implements Layer {
|
||||
|
||||
tick() {
|
||||
const tickProfile = FrameProfiler.start();
|
||||
const now = Date.now();
|
||||
this.updateTickTiming(now);
|
||||
if (this.game.inSpawnPhase()) {
|
||||
this.spawnHighlight();
|
||||
}
|
||||
@@ -104,6 +120,7 @@ export class TerritoryLayer implements Layer {
|
||||
this.refreshPaletteIfNeeded();
|
||||
|
||||
this.game.recentlyUpdatedTiles().forEach((t) => this.enqueueTile(t));
|
||||
this.beginTileTransitions(this.game.recentlyUpdatedOwnerTiles(), now);
|
||||
const updates = this.game.updatesSinceLastTick();
|
||||
const unitUpdates = updates !== null ? updates[GameUpdateType.Unit] : [];
|
||||
unitUpdates.forEach((update) => {
|
||||
@@ -431,6 +448,9 @@ export class TerritoryLayer implements Layer {
|
||||
this.lastMyPlayerSmallId = this.game.myPlayer()?.smallID() ?? null;
|
||||
this.cachedTerritoryPatternsEnabled = this.userSettings.territoryPatterns();
|
||||
this.configureRenderers();
|
||||
this.tileTransitions.clear();
|
||||
this.transitionHighlightTiles.length = 0;
|
||||
this.transitionHighlightAlphas.length = 0;
|
||||
this.territoryRenderer?.redraw();
|
||||
|
||||
// Add a second canvas for highlights
|
||||
@@ -521,6 +541,8 @@ export class TerritoryLayer implements Layer {
|
||||
FrameProfiler.end("TerritoryLayer:renderTerritory", renderTerritoryStart);
|
||||
}
|
||||
|
||||
this.updateTransitionProgress(now);
|
||||
|
||||
const [topLeft, bottomRight] = this.transformHandler.screenBoundingRect();
|
||||
const vx0 = Math.max(0, topLeft.x);
|
||||
const vy0 = Math.max(0, topLeft.y);
|
||||
@@ -542,6 +564,8 @@ export class TerritoryLayer implements Layer {
|
||||
);
|
||||
}
|
||||
|
||||
this.drawTransitionHighlights(context, now);
|
||||
|
||||
if (this.game.inSpawnPhase()) {
|
||||
const highlightDrawStart = FrameProfiler.start();
|
||||
context.drawImage(
|
||||
@@ -562,13 +586,9 @@ export class TerritoryLayer implements Layer {
|
||||
if (!this.territoryRenderer) {
|
||||
return;
|
||||
}
|
||||
let numToRender = Math.floor(this.tileToRenderQueue.size() / 10);
|
||||
if (
|
||||
numToRender === 0 ||
|
||||
this.game.inSpawnPhase() ||
|
||||
this.territoryRenderer.isWebGL()
|
||||
) {
|
||||
numToRender = this.tileToRenderQueue.size();
|
||||
let numToRender = this.tileToRenderQueue.size();
|
||||
if (numToRender === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const useNeighborPaint = !(this.territoryRenderer?.isWebGL() ?? false);
|
||||
@@ -680,6 +700,111 @@ export class TerritoryLayer implements Layer {
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
private updateTickTiming(now: number) {
|
||||
const currentTick = this.game.ticks();
|
||||
if (currentTick === this.lastGameTick) {
|
||||
return;
|
||||
}
|
||||
if (this.lastGameTick !== 0) {
|
||||
const tickDelta = Math.max(1, currentTick - this.lastGameTick);
|
||||
const elapsed = now - this.lastTickTime;
|
||||
const estimate = elapsed / tickDelta;
|
||||
this.lastTickDurationMs = Math.max(50, Math.min(200, estimate));
|
||||
}
|
||||
this.lastGameTick = currentTick;
|
||||
this.lastTickTime = now;
|
||||
}
|
||||
|
||||
private beginTileTransitions(
|
||||
changes: Array<{ tile: TileRef; previousOwner: number; newOwner: number }>,
|
||||
now: number,
|
||||
) {
|
||||
if (changes.length === 0) {
|
||||
return;
|
||||
}
|
||||
const durationMs = this.lastTickDurationMs;
|
||||
for (const change of changes) {
|
||||
if (change.newOwner === change.previousOwner) {
|
||||
continue;
|
||||
}
|
||||
if (change.newOwner === 0) {
|
||||
this.tileTransitions.delete(change.tile);
|
||||
this.territoryRenderer?.setTransitionProgress(change.tile, 1);
|
||||
continue;
|
||||
}
|
||||
this.tileTransitions.set(change.tile, {
|
||||
startTime: now,
|
||||
durationMs,
|
||||
highlight: change.newOwner !== 0,
|
||||
lastProgressByte: -1,
|
||||
});
|
||||
this.territoryRenderer?.setTransitionProgress(change.tile, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private updateTransitionProgress(now: number) {
|
||||
this.transitionHighlightTiles.length = 0;
|
||||
this.transitionHighlightAlphas.length = 0;
|
||||
if (!this.territoryRenderer || this.tileTransitions.size === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const toDelete: TileRef[] = [];
|
||||
for (const [tile, transition] of this.tileTransitions) {
|
||||
const elapsed = now - transition.startTime;
|
||||
const duration = transition.durationMs > 0 ? transition.durationMs : 1;
|
||||
const progress = Math.max(0, Math.min(1, elapsed / duration));
|
||||
const progressByte = Math.round(progress * 255);
|
||||
if (progressByte !== transition.lastProgressByte) {
|
||||
transition.lastProgressByte = progressByte;
|
||||
this.territoryRenderer.setTransitionProgress(tile, progress);
|
||||
}
|
||||
if (transition.highlight && progress < 1) {
|
||||
const alpha = (1 - progress) * 0.35;
|
||||
if (alpha > 0.01) {
|
||||
this.transitionHighlightTiles.push(tile);
|
||||
this.transitionHighlightAlphas.push(alpha);
|
||||
}
|
||||
}
|
||||
if (progress >= 1) {
|
||||
toDelete.push(tile);
|
||||
}
|
||||
}
|
||||
for (const tile of toDelete) {
|
||||
this.tileTransitions.delete(tile);
|
||||
}
|
||||
}
|
||||
|
||||
private drawTransitionHighlights(
|
||||
context: CanvasRenderingContext2D,
|
||||
now: number,
|
||||
) {
|
||||
if (this.transitionHighlightTiles.length === 0) {
|
||||
return;
|
||||
}
|
||||
const pulse = 0.75 + 0.25 * Math.sin((now - this.lastTickTime) * 0.015);
|
||||
const highlight = this.theme.spawnHighlightColor();
|
||||
const offsetX = -this.game.width() / 2;
|
||||
const offsetY = -this.game.height() / 2;
|
||||
context.save();
|
||||
context.fillStyle = highlight.toRgbString();
|
||||
for (let i = 0; i < this.transitionHighlightTiles.length; i++) {
|
||||
const alpha = this.transitionHighlightAlphas[i] * pulse;
|
||||
if (alpha <= 0) {
|
||||
continue;
|
||||
}
|
||||
const tile = this.transitionHighlightTiles[i];
|
||||
context.globalAlpha = alpha;
|
||||
context.fillRect(
|
||||
this.game.x(tile) + offsetX,
|
||||
this.game.y(tile) + offsetY,
|
||||
1,
|
||||
1,
|
||||
);
|
||||
}
|
||||
context.restore();
|
||||
}
|
||||
|
||||
private computePaletteSignature(): string {
|
||||
let maxSmallId = 0;
|
||||
for (const player of this.game.playerViews()) {
|
||||
|
||||
@@ -13,6 +13,7 @@ export interface TerritoryRendererStrategy {
|
||||
redraw(): void;
|
||||
markAllDirty(): void;
|
||||
paintTile(tile: TileRef): void;
|
||||
setTransitionProgress(tile: TileRef, progress: number): void;
|
||||
render(
|
||||
context: CanvasRenderingContext2D,
|
||||
viewport: { x: number; y: number; width: number; height: number },
|
||||
@@ -31,6 +32,7 @@ export class CanvasTerritoryRenderer implements TerritoryRendererStrategy {
|
||||
private imageData: ImageData;
|
||||
private alternativeImageData: ImageData;
|
||||
private alternativeView = false;
|
||||
private transitionProgress: Map<TileRef, number> = new Map();
|
||||
|
||||
constructor(
|
||||
private readonly game: GameView,
|
||||
@@ -85,19 +87,20 @@ export class CanvasTerritoryRenderer implements TerritoryRendererStrategy {
|
||||
const isDefended =
|
||||
owner && isBorderTile ? this.game.isDefended(tile) : false;
|
||||
|
||||
const transitionFactor = this.transitionProgress.get(tile) ?? 1;
|
||||
if (!owner) {
|
||||
if (hasFallout) {
|
||||
this.paintTileColor(
|
||||
this.imageData,
|
||||
tile,
|
||||
this.theme.falloutColor(),
|
||||
150,
|
||||
Math.round(150 * transitionFactor),
|
||||
);
|
||||
this.paintTileColor(
|
||||
this.alternativeImageData,
|
||||
tile,
|
||||
this.theme.falloutColor(),
|
||||
150,
|
||||
Math.round(150 * transitionFactor),
|
||||
);
|
||||
} else {
|
||||
this.clearTile(tile);
|
||||
@@ -115,14 +118,14 @@ export class CanvasTerritoryRenderer implements TerritoryRendererStrategy {
|
||||
this.alternativeImageData,
|
||||
tile,
|
||||
alternativeColor,
|
||||
255,
|
||||
Math.round(255 * transitionFactor),
|
||||
);
|
||||
}
|
||||
this.paintTileColor(
|
||||
this.imageData,
|
||||
tile,
|
||||
owner.borderColor(tile, isDefended),
|
||||
255,
|
||||
Math.round(255 * transitionFactor),
|
||||
);
|
||||
} else {
|
||||
// Alternative view only shows borders.
|
||||
@@ -131,7 +134,7 @@ export class CanvasTerritoryRenderer implements TerritoryRendererStrategy {
|
||||
this.imageData,
|
||||
tile,
|
||||
owner.territoryColor(tile),
|
||||
150,
|
||||
Math.round(150 * transitionFactor),
|
||||
);
|
||||
}
|
||||
FrameProfiler.end("CanvasTerritoryRenderer:paintTile", cpuStart);
|
||||
@@ -175,6 +178,22 @@ export class CanvasTerritoryRenderer implements TerritoryRendererStrategy {
|
||||
this.alternativeView = enabled;
|
||||
}
|
||||
|
||||
setTransitionProgress(tile: TileRef, progress: number): void {
|
||||
const clamped = Math.max(0, Math.min(1, progress));
|
||||
if (clamped >= 1) {
|
||||
if (this.transitionProgress.delete(tile)) {
|
||||
this.paintTile(tile);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const previous = this.transitionProgress.get(tile);
|
||||
if (previous !== undefined && Math.abs(previous - clamped) < 1 / 255) {
|
||||
return;
|
||||
}
|
||||
this.transitionProgress.set(tile, clamped);
|
||||
this.paintTile(tile);
|
||||
}
|
||||
|
||||
setHover(): void {
|
||||
// Canvas path relies on CPU highlight redraw in TerritoryLayer.
|
||||
}
|
||||
@@ -259,6 +278,10 @@ export class WebglTerritoryRenderer implements TerritoryRendererStrategy {
|
||||
this.renderer.markTile(tile);
|
||||
}
|
||||
|
||||
setTransitionProgress(tile: TileRef, progress: number): void {
|
||||
this.renderer.setTransitionProgress(tile, progress);
|
||||
}
|
||||
|
||||
render(
|
||||
context: CanvasRenderingContext2D,
|
||||
_viewport: { x: number; y: number; width: number; height: number },
|
||||
|
||||
@@ -35,12 +35,14 @@ export class TerritoryWebGLRenderer {
|
||||
private readonly paletteTexture: WebGLTexture | null;
|
||||
private readonly relationTexture: WebGLTexture | null;
|
||||
private readonly patternTexture: WebGLTexture | null;
|
||||
private readonly transitionTexture: WebGLTexture | null;
|
||||
private readonly uniforms: {
|
||||
resolution: WebGLUniformLocation | null;
|
||||
state: WebGLUniformLocation | null;
|
||||
palette: WebGLUniformLocation | null;
|
||||
relations: WebGLUniformLocation | null;
|
||||
patterns: WebGLUniformLocation | null;
|
||||
transitions: WebGLUniformLocation | null;
|
||||
patternStride: WebGLUniformLocation | null;
|
||||
patternRows: WebGLUniformLocation | null;
|
||||
fallout: WebGLUniformLocation | null;
|
||||
@@ -60,8 +62,11 @@ export class TerritoryWebGLRenderer {
|
||||
};
|
||||
|
||||
private readonly state: Uint16Array;
|
||||
private readonly transitionState: Uint8Array;
|
||||
private readonly dirtyRows: Map<number, DirtySpan> = new Map();
|
||||
private readonly transitionDirtyRows: Map<number, DirtySpan> = new Map();
|
||||
private needsFullUpload = true;
|
||||
private needsTransitionFullUpload = true;
|
||||
private alternativeView = false;
|
||||
private paletteWidth = 0;
|
||||
private hoverHighlightStrength = 0.7;
|
||||
@@ -83,6 +88,8 @@ export class TerritoryWebGLRenderer {
|
||||
this.canvas.height = game.height();
|
||||
|
||||
this.state = state;
|
||||
this.transitionState = new Uint8Array(state.length);
|
||||
this.transitionState.fill(255);
|
||||
|
||||
this.gl = this.canvas.getContext("webgl2", {
|
||||
premultipliedAlpha: true,
|
||||
@@ -98,12 +105,14 @@ export class TerritoryWebGLRenderer {
|
||||
this.paletteTexture = null;
|
||||
this.relationTexture = null;
|
||||
this.patternTexture = null;
|
||||
this.transitionTexture = null;
|
||||
this.uniforms = {
|
||||
resolution: null,
|
||||
state: null,
|
||||
palette: null,
|
||||
relations: null,
|
||||
patterns: null,
|
||||
transitions: null,
|
||||
patternStride: null,
|
||||
patternRows: null,
|
||||
fallout: null,
|
||||
@@ -133,12 +142,14 @@ export class TerritoryWebGLRenderer {
|
||||
this.paletteTexture = null;
|
||||
this.relationTexture = null;
|
||||
this.patternTexture = null;
|
||||
this.transitionTexture = null;
|
||||
this.uniforms = {
|
||||
resolution: null,
|
||||
state: null,
|
||||
palette: null,
|
||||
relations: null,
|
||||
patterns: null,
|
||||
transitions: null,
|
||||
patternStride: null,
|
||||
patternRows: null,
|
||||
fallout: null,
|
||||
@@ -165,6 +176,7 @@ export class TerritoryWebGLRenderer {
|
||||
palette: gl.getUniformLocation(this.program, "u_palette"),
|
||||
relations: gl.getUniformLocation(this.program, "u_relations"),
|
||||
patterns: gl.getUniformLocation(this.program, "u_patterns"),
|
||||
transitions: gl.getUniformLocation(this.program, "u_transitions"),
|
||||
patternStride: gl.getUniformLocation(this.program, "u_patternStride"),
|
||||
patternRows: gl.getUniformLocation(this.program, "u_patternRows"),
|
||||
fallout: gl.getUniformLocation(this.program, "u_fallout"),
|
||||
@@ -223,6 +235,7 @@ export class TerritoryWebGLRenderer {
|
||||
this.paletteTexture = gl.createTexture();
|
||||
this.relationTexture = gl.createTexture();
|
||||
this.patternTexture = gl.createTexture();
|
||||
this.transitionTexture = gl.createTexture();
|
||||
|
||||
gl.activeTexture(gl.TEXTURE0);
|
||||
gl.bindTexture(gl.TEXTURE_2D, this.stateTexture);
|
||||
@@ -245,11 +258,31 @@ export class TerritoryWebGLRenderer {
|
||||
|
||||
this.uploadPalette();
|
||||
|
||||
gl.activeTexture(gl.TEXTURE4);
|
||||
gl.bindTexture(gl.TEXTURE_2D, this.transitionTexture);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
||||
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
|
||||
gl.texImage2D(
|
||||
gl.TEXTURE_2D,
|
||||
0,
|
||||
gl.R8UI,
|
||||
this.canvas.width,
|
||||
this.canvas.height,
|
||||
0,
|
||||
gl.RED_INTEGER,
|
||||
gl.UNSIGNED_BYTE,
|
||||
this.transitionState,
|
||||
);
|
||||
|
||||
gl.useProgram(this.program);
|
||||
gl.uniform1i(this.uniforms.state, 0);
|
||||
gl.uniform1i(this.uniforms.palette, 1);
|
||||
gl.uniform1i(this.uniforms.relations, 2);
|
||||
gl.uniform1i(this.uniforms.patterns, 3);
|
||||
gl.uniform1i(this.uniforms.transitions, 4);
|
||||
|
||||
if (this.uniforms.resolution) {
|
||||
gl.uniform2f(
|
||||
@@ -411,6 +444,27 @@ export class TerritoryWebGLRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
setTransitionProgress(tile: TileRef, progress: number) {
|
||||
const clamped = Math.max(0, Math.min(1, progress));
|
||||
const value = Math.round(clamped * 255);
|
||||
if (this.transitionState[tile] === value) {
|
||||
return;
|
||||
}
|
||||
this.transitionState[tile] = value;
|
||||
if (this.needsTransitionFullUpload) {
|
||||
return;
|
||||
}
|
||||
const x = tile % this.canvas.width;
|
||||
const y = Math.floor(tile / this.canvas.width);
|
||||
const span = this.transitionDirtyRows.get(y);
|
||||
if (span === undefined) {
|
||||
this.transitionDirtyRows.set(y, { minX: x, maxX: x });
|
||||
} else {
|
||||
span.minX = Math.min(span.minX, x);
|
||||
span.maxX = Math.max(span.maxX, x);
|
||||
}
|
||||
}
|
||||
|
||||
markAllDirty() {
|
||||
this.needsFullUpload = true;
|
||||
this.dirtyRows.clear();
|
||||
@@ -433,6 +487,13 @@ export class TerritoryWebGLRenderer {
|
||||
this.uploadStateTexture();
|
||||
FrameProfiler.end("TerritoryWebGLRenderer:uploadState", uploadStateSpan);
|
||||
|
||||
const uploadTransitionSpan = FrameProfiler.start();
|
||||
this.uploadTransitionTexture();
|
||||
FrameProfiler.end(
|
||||
"TerritoryWebGLRenderer:uploadTransitions",
|
||||
uploadTransitionSpan,
|
||||
);
|
||||
|
||||
const renderSpan = FrameProfiler.start();
|
||||
gl.viewport(0, 0, this.canvas.width, this.canvas.height);
|
||||
gl.useProgram(this.program);
|
||||
@@ -530,6 +591,61 @@ export class TerritoryWebGLRenderer {
|
||||
return { rows: rowsUploaded, bytes: bytesUploaded };
|
||||
}
|
||||
|
||||
private uploadTransitionTexture(): { rows: number; bytes: number } {
|
||||
if (!this.gl || !this.transitionTexture) return { rows: 0, bytes: 0 };
|
||||
const gl = this.gl;
|
||||
gl.activeTexture(gl.TEXTURE4);
|
||||
gl.bindTexture(gl.TEXTURE_2D, this.transitionTexture);
|
||||
|
||||
const bytesPerPixel = Uint8Array.BYTES_PER_ELEMENT;
|
||||
let rowsUploaded = 0;
|
||||
let bytesUploaded = 0;
|
||||
|
||||
if (this.needsTransitionFullUpload) {
|
||||
gl.texImage2D(
|
||||
gl.TEXTURE_2D,
|
||||
0,
|
||||
gl.R8UI,
|
||||
this.canvas.width,
|
||||
this.canvas.height,
|
||||
0,
|
||||
gl.RED_INTEGER,
|
||||
gl.UNSIGNED_BYTE,
|
||||
this.transitionState,
|
||||
);
|
||||
this.needsTransitionFullUpload = false;
|
||||
this.transitionDirtyRows.clear();
|
||||
rowsUploaded = this.canvas.height;
|
||||
bytesUploaded = this.canvas.width * this.canvas.height * bytesPerPixel;
|
||||
return { rows: rowsUploaded, bytes: bytesUploaded };
|
||||
}
|
||||
|
||||
if (this.transitionDirtyRows.size === 0) {
|
||||
return { rows: 0, bytes: 0 };
|
||||
}
|
||||
|
||||
for (const [y, span] of this.transitionDirtyRows) {
|
||||
const width = span.maxX - span.minX + 1;
|
||||
const offset = y * this.canvas.width + span.minX;
|
||||
const rowSlice = this.transitionState.subarray(offset, offset + width);
|
||||
gl.texSubImage2D(
|
||||
gl.TEXTURE_2D,
|
||||
0,
|
||||
span.minX,
|
||||
y,
|
||||
width,
|
||||
1,
|
||||
gl.RED_INTEGER,
|
||||
gl.UNSIGNED_BYTE,
|
||||
rowSlice,
|
||||
);
|
||||
rowsUploaded++;
|
||||
bytesUploaded += width * bytesPerPixel;
|
||||
}
|
||||
this.transitionDirtyRows.clear();
|
||||
return { rows: rowsUploaded, bytes: bytesUploaded };
|
||||
}
|
||||
|
||||
private uploadPalette() {
|
||||
if (
|
||||
!this.gl ||
|
||||
@@ -717,6 +833,7 @@ export class TerritoryWebGLRenderer {
|
||||
uniform sampler2D u_palette;
|
||||
uniform usampler2D u_relations;
|
||||
uniform usampler2D u_patterns;
|
||||
uniform usampler2D u_transitions;
|
||||
uniform int u_patternStride;
|
||||
uniform int u_patternRows;
|
||||
uniform int u_viewerId;
|
||||
@@ -801,6 +918,7 @@ export class TerritoryWebGLRenderer {
|
||||
ivec2 texCoord = ivec2(fragCoord.x, int(u_resolution.y) - 1 - fragCoord.y);
|
||||
|
||||
uint state = texelFetch(u_state, texCoord, 0).r;
|
||||
float transition = float(texelFetch(u_transitions, texCoord, 0).r) / 255.0;
|
||||
uint owner = state & 0xFFFu;
|
||||
bool hasFallout = (state & 0x2000u) != 0u;
|
||||
bool isDefended = (state & 0x1000u) != 0u;
|
||||
@@ -808,7 +926,7 @@ export class TerritoryWebGLRenderer {
|
||||
if (owner == 0u) {
|
||||
if (hasFallout) {
|
||||
vec3 color = u_fallout.rgb;
|
||||
float a = u_alpha;
|
||||
float a = u_alpha * transition;
|
||||
outColor = vec4(color * a, a);
|
||||
} else {
|
||||
outColor = vec4(0.0);
|
||||
@@ -858,7 +976,7 @@ export class TerritoryWebGLRenderer {
|
||||
} else if (isEmbargo(relationAlt)) {
|
||||
altColor = u_altEnemy;
|
||||
}
|
||||
float a = isBorder ? 1.0 : 0.0;
|
||||
float a = (isBorder ? 1.0 : 0.0) * transition;
|
||||
vec3 color = altColor.rgb;
|
||||
if (u_hoveredPlayerId >= 0.0 && abs(float(owner) - u_hoveredPlayerId) < 0.5) {
|
||||
float pulse = u_hoverPulseStrength > 0.0
|
||||
@@ -915,6 +1033,7 @@ export class TerritoryWebGLRenderer {
|
||||
color = mix(color, u_hoverHighlightColor, u_hoverHighlightStrength * pulse);
|
||||
}
|
||||
|
||||
a *= transition;
|
||||
outColor = vec4(color * a, a);
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -587,6 +587,11 @@ export class GameView implements GameMap {
|
||||
private _players = new Map<PlayerID, PlayerView>();
|
||||
private _units = new Map<number, UnitView>();
|
||||
private updatedTiles: TileRef[] = [];
|
||||
private updatedOwnerChanges: Array<{
|
||||
tile: TileRef;
|
||||
previousOwner: number;
|
||||
newOwner: number;
|
||||
}> = [];
|
||||
|
||||
private _myPlayer: PlayerView | null = null;
|
||||
|
||||
@@ -635,8 +640,19 @@ export class GameView implements GameMap {
|
||||
this.lastUpdate = gu;
|
||||
|
||||
this.updatedTiles = [];
|
||||
this.updatedOwnerChanges = [];
|
||||
this.lastUpdate.packedTileUpdates.forEach((tu) => {
|
||||
const tileRef = Number(tu >> 16n);
|
||||
const previousOwner = this._map.ownerID(tileRef);
|
||||
this.updatedTiles.push(this.updateTile(tu));
|
||||
const newOwner = this._map.ownerID(tileRef);
|
||||
if (previousOwner !== newOwner) {
|
||||
this.updatedOwnerChanges.push({
|
||||
tile: tileRef,
|
||||
previousOwner,
|
||||
newOwner,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (gu.updates === null) {
|
||||
@@ -695,6 +711,14 @@ export class GameView implements GameMap {
|
||||
return this.updatedTiles;
|
||||
}
|
||||
|
||||
recentlyUpdatedOwnerTiles(): Array<{
|
||||
tile: TileRef;
|
||||
previousOwner: number;
|
||||
newOwner: number;
|
||||
}> {
|
||||
return this.updatedOwnerChanges;
|
||||
}
|
||||
|
||||
nearbyUnits(
|
||||
tile: TileRef,
|
||||
searchRange: number,
|
||||
|
||||
Reference in New Issue
Block a user