-
Notifications
You must be signed in to change notification settings - Fork 706
Night Mode #2364
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Night Mode #2364
Changes from 17 commits
9eee198
1159b47
8a0d5c7
927a76f
fb0bdd6
dfb8d17
b48a247
ee8bb56
d39731f
02d27cf
45437d2
b081f3b
d688bb0
195ad4d
c0b6d30
f2d3bac
3e635d1
3ce8b77
0ca18d1
865a64a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| reviews: | ||
| profile: assertive |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,180 @@ | ||
| import { UnitType } from "../../../core/game/Game"; | ||
| import { GameView } from "../../../core/game/GameView"; | ||
| import { UserSettings } from "../../../core/game/UserSettings"; | ||
| import { TransformHandler } from "../TransformHandler"; | ||
| import { Layer } from "./Layer"; | ||
|
|
||
| export class NightModeLayer implements Layer { | ||
| setGame(game: GameView) { | ||
| this.game = game; | ||
| } | ||
|
|
||
| private darkenColor: [number, number, number] = [0, 0, 0]; | ||
| private darkenAlpha: number = 0.8; | ||
| private flashlightRadius: number = 50; | ||
| private userSettingsInstance = new UserSettings(); | ||
| private mouseX: number = 0; | ||
| private mouseY: number = 0; | ||
|
|
||
| // Add game reference | ||
| private game: GameView | null = null; | ||
|
|
||
| private handleMouseMove(event: MouseEvent) { | ||
| const rect = this.transformHandler.boundingRect(); | ||
| this.mouseX = event.clientX - rect.left; | ||
| this.mouseY = event.clientY - rect.top; | ||
| } | ||
|
|
||
| init(): void {} | ||
| tick(): void {} | ||
| redraw(): void {} | ||
|
|
||
| constructor( | ||
| private transformHandler: TransformHandler, | ||
| game?: GameView, // Add game parameter | ||
| ) { | ||
| this.game = game ?? null; | ||
| if (this.userSettingsInstance.nightMode()) { | ||
| document.documentElement.classList.add("night"); | ||
| } else { | ||
| document.documentElement.classList.remove("night"); | ||
| } | ||
coderabbitai[bot] marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| document.addEventListener("mousemove", (e) => this.handleMouseMove(e)); | ||
coderabbitai[bot] marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| // New method to set game reference after construction | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| renderLayer(context: CanvasRenderingContext2D): void { | ||
| if (!this.userSettingsInstance.nightMode()) return; | ||
|
|
||
| const width = this.transformHandler.width(); | ||
| const height = this.transformHandler.boundingRect().height; | ||
| const cellSize = this.transformHandler.scale; | ||
|
|
||
| // Fill the entire screen with dark | ||
| context.fillStyle = `rgba(${this.darkenColor[0]}, ${this.darkenColor[1]}, ${this.darkenColor[2]}, ${this.darkenAlpha})`; | ||
| context.fillRect(0, 0, width, height); | ||
|
|
||
| // ===== NEW: Render city lights ===== | ||
| if (this.game) { | ||
| this.renderCityLights(context, cellSize); | ||
| } | ||
|
|
||
| // Render flashlight effect around mouse | ||
| this.renderFlashlight(context, width, height, cellSize); | ||
| } | ||
|
|
||
| /** | ||
| * Renders illumination for all cities on the map. | ||
| * Creates a glow effect similar to satellite images of Earth at night. | ||
| */ | ||
| private renderCityLights( | ||
| context: CanvasRenderingContext2D, | ||
| cellSize: number, | ||
| ): void { | ||
| // Get all cities in the game | ||
| const cities = this.game!.units(UnitType.City); | ||
|
|
||
| for (const city of cities) { | ||
| // Get city position | ||
| const tileRef = city.tile(); | ||
| const cityX = this.game!.x(tileRef); | ||
| const cityY = this.game!.y(tileRef); | ||
|
|
||
| // Convert tile coordinates to screen coordinates | ||
| const screenX = cityX * cellSize; | ||
| const screenY = cityY * cellSize; | ||
coderabbitai[bot] marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| // Get city level for scaling the light effect | ||
| const cityLevel = city.level(); | ||
|
|
||
| // Render city glow - you can customize this pattern | ||
| this.renderCityGlow(context, screenX, screenY, cellSize, cityLevel); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Renders a glow effect for a single city. | ||
| * Customize this method to achieve your desired lighting pattern. | ||
| */ | ||
| private renderCityGlow( | ||
| context: CanvasRenderingContext2D, | ||
| x: number, | ||
| y: number, | ||
| cellSize: number, | ||
| level: number, | ||
| ): void { | ||
| // Example 1: Simple bright square (like a satellite image) | ||
| const lightRadius = 5 + level * 2; // Larger cities have bigger glow | ||
|
|
||
| for (let dy = -lightRadius; dy <= lightRadius; dy++) { | ||
| for (let dx = -lightRadius; dx <= lightRadius; dx++) { | ||
| const distance = Math.sqrt(dx * dx + dy * dy); | ||
| if (distance <= lightRadius) { | ||
| // Brightness decreases with distance | ||
| const brightness = 1 - distance / lightRadius; | ||
| const alpha = this.darkenAlpha * 0.8 * brightness; | ||
|
|
||
| context.fillStyle = `rgba(255, 220, 150, ${alpha})`; | ||
| context.fillRect( | ||
| x + dx * cellSize, | ||
| y + dy * cellSize, | ||
| cellSize, | ||
| cellSize, | ||
| ); | ||
| } | ||
| } | ||
| } | ||
coderabbitai[bot] marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| // Example 2: Add a brighter core | ||
| context.fillStyle = `rgba(255, 255, 200, ${this.darkenAlpha * 0.9})`; | ||
| context.fillRect(x, y, cellSize, cellSize); | ||
| } | ||
|
|
||
| /** | ||
| * Renders the flashlight effect around the mouse cursor. | ||
| * Extracted from original renderLayer for better organization. | ||
| */ | ||
| private renderFlashlight( | ||
| context: CanvasRenderingContext2D, | ||
| width: number, | ||
| height: number, | ||
| cellSize: number, | ||
| ): void { | ||
| const startX = | ||
| Math.floor( | ||
| Math.max(this.mouseX - this.flashlightRadius * cellSize, 0) / cellSize, | ||
| ) * cellSize; | ||
| const endX = | ||
| Math.ceil( | ||
| Math.min(this.mouseX + this.flashlightRadius * cellSize, width) / | ||
| cellSize, | ||
| ) * cellSize; | ||
|
|
||
| const startY = | ||
| Math.floor( | ||
| Math.max(this.mouseY - this.flashlightRadius * cellSize, 0) / cellSize, | ||
| ) * cellSize; | ||
| const endY = | ||
| Math.ceil( | ||
| Math.min(this.mouseY + this.flashlightRadius * cellSize, height) / | ||
| cellSize, | ||
| ) * cellSize; | ||
|
|
||
| for (let y = startY; y < endY; y += cellSize) { | ||
| for (let x = startX; x < endX; x += cellSize) { | ||
| const dist = Math.hypot( | ||
| (this.mouseX - (x + cellSize / 2)) / cellSize, | ||
| (this.mouseY - (y + cellSize / 2)) / cellSize, | ||
| ); | ||
|
|
||
| const brightness = Math.max(0, 1 - dist / this.flashlightRadius); | ||
|
|
||
| if (brightness > 0) { | ||
| context.fillStyle = `rgba(200,200,130,${(this.darkenAlpha / 2) * brightness})`; | ||
| context.fillRect(x, y, cellSize, cellSize); | ||
| } | ||
| } | ||
| } | ||
|
Comment on lines
+191
to
+205
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Performance consideration: flashlight rendering complexity. Similar to city glow, the flashlight effect uses nested loops that could iterate many times per frame depending on Consider using a radial gradient for better performance: private renderFlashlight(
context: CanvasRenderingContext2D,
width: number,
height: number,
cellSize: number,
): void {
const radius = this.flashlightRadius * cellSize;
const gradient = context.createRadialGradient(
this.mouseX, this.mouseY, 0,
this.mouseX, this.mouseY, radius
);
gradient.addColorStop(0, `rgba(200, 200, 130, ${this.darkenAlpha / 2})`);
gradient.addColorStop(1, 'rgba(200, 200, 130, 0)');
context.fillStyle = gradient;
context.fillRect(
Math.max(0, this.mouseX - radius),
Math.max(0, this.mouseY - radius),
Math.min(width, radius * 2),
Math.min(height, radius * 2)
);
}This eliminates the nested loops while achieving a similar visual effect with much better performance. 🤖 Prompt for AI Agents |
||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.