Skip to content
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .coderabbit.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
reviews:
profile: assertive
8 changes: 8 additions & 0 deletions resources/images/NightModeIconWhite.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions resources/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,8 @@
"tab_keybinds": "Keybinds",
"dark_mode_label": "Dark Mode",
"dark_mode_desc": "Toggle the site’s appearance between light and dark themes",
"night_mode_label": "Night Mode",
"night_mode_desc": "Puts the map into a night-time mode. Purely aesthetic.",
"emojis_label": "Emojis",
"emojis_desc": "Toggle whether emojis are shown in game",
"alert_frame_label": "Alert Frame",
Expand Down
6 changes: 6 additions & 0 deletions src/client/Main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,12 @@ class Client {
document.documentElement.classList.remove("dark");
}

if (this.userSettings.nightMode()) {
document.documentElement.classList.add("night");
} else {
document.documentElement.classList.remove("night");
}

// Attempt to join lobby
this.handleHash();

Expand Down
34 changes: 34 additions & 0 deletions src/client/UserSettingModal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,30 @@ export class UserSettingModal extends LitElement {
console.log("🌙 Dark Mode:", enabled ? "ON" : "OFF");
}

toggleNightMode(e: CustomEvent<{ checked: boolean }>) {
const enabled = e.detail?.checked;

if (typeof enabled !== "boolean") {
console.warn("Unexpected toggle event payload", e);
return;
}
this.userSettings.set("settings.nightMode", enabled);

if (enabled) {
document.documentElement.classList.add("night");
} else {
document.documentElement.classList.remove("night");
}

this.dispatchEvent(
new CustomEvent("night-mode-changed", {
detail: { nightMode: enabled },
bubbles: true,
composed: true,
}),
);
}

private toggleEmojis(e: CustomEvent<{ checked: boolean }>) {
const enabled = e.detail?.checked;
if (typeof enabled !== "boolean") return;
Expand Down Expand Up @@ -283,6 +307,16 @@ export class UserSettingModal extends LitElement {
this.toggleDarkMode(e)}
></setting-toggle>

<!-- 🌙 Night Mode -->
<setting-toggle
label="${translateText("user_setting.night_mode_label")}"
description="${translateText("user_setting.night_mode_desc")}"
id="night-mode-toggle"
.checked=${this.userSettings.nightMode()}
@change=${(e: CustomEvent<{ checked: boolean }>) =>
this.toggleNightMode(e)}
></setting-toggle>

<!-- 😊 Emojis -->
<setting-toggle
label="${translateText("user_setting.emojis_label")}"
Expand Down
2 changes: 2 additions & 0 deletions src/client/graphics/GameRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { Leaderboard } from "./layers/Leaderboard";
import { MainRadialMenu } from "./layers/MainRadialMenu";
import { MultiTabModal } from "./layers/MultiTabModal";
import { NameLayer } from "./layers/NameLayer";
import { NightModeLayer } from "./layers/NightModeLayer";
import { PlayerInfoOverlay } from "./layers/PlayerInfoOverlay";
import { PlayerPanel } from "./layers/PlayerPanel";
import { RailroadLayer } from "./layers/RailroadLayer";
Expand Down Expand Up @@ -242,6 +243,7 @@ export function createRenderer(
new FxLayer(game),
new UILayer(game, eventBus, transformHandler),
new StructureIconsLayer(game, eventBus, uiState, transformHandler),
new NightModeLayer(transformHandler),
new NameLayer(game, transformHandler, eventBus),
eventsDisplay,
chatDisplay,
Expand Down
84 changes: 84 additions & 0 deletions src/client/graphics/layers/NightModeLayer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
//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 {
private darkenColor: [number, number, number] = [0, 0, 0];
private darkenAlpha: number = 0.8; // separated from darkenColor for more readable code

private flashlightRadius: number = 50; // in-game tiles

private userSettingsInstance = new UserSettings();

private mouseX: number = 0;
private mouseY: number = 0;
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) {
if (this.userSettingsInstance.nightMode()) {
document.documentElement.classList.add("night");
} else {
document.documentElement.classList.remove("night");
}
document.addEventListener("mousemove", (e) => this.handleMouseMove(e));
}

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);

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) {
// distance from mouse in tile units
const dist = Math.hypot(
(this.mouseX - (x + cellSize / 2)) / cellSize,
(this.mouseY - (y + cellSize / 2)) / cellSize,
);

// Determine brightness factor (adjust 3 for flashlight size)
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);
}
}
}
}
}
32 changes: 32 additions & 0 deletions src/client/graphics/layers/SettingsModal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import emojiIcon from "../../../../resources/images/EmojiIconWhite.svg";
import exitIcon from "../../../../resources/images/ExitIconWhite.svg";
import explosionIcon from "../../../../resources/images/ExplosionIconWhite.svg";
import mouseIcon from "../../../../resources/images/MouseIconWhite.svg";
import nightModeIcon from "../../../../resources/images/NightModeIconWhite.svg";
import ninjaIcon from "../../../../resources/images/NinjaIconWhite.svg";
import settingsIcon from "../../../../resources/images/SettingIconWhite.svg";
import treeIcon from "../../../../resources/images/TreeIconWhite.svg";
Expand Down Expand Up @@ -136,6 +137,12 @@ export class SettingsModal extends LitElement implements Layer {
this.requestUpdate();
}

private onToggleNightModeButtonClick() {
this.userSettings.toggleNightMode();
this.eventBus.emit(new RefreshGraphicsEvent());
this.requestUpdate();
}

private onToggleRandomNameModeButtonClick() {
this.userSettings.toggleRandomName();
this.requestUpdate();
Expand Down Expand Up @@ -321,6 +328,31 @@ export class SettingsModal extends LitElement implements Layer {
</div>
</button>

<button
class="flex gap-3 items-center w-full text-left p-3 hover:bg-slate-700 rounded text-white transition-colors"
@click="${this.onToggleNightModeButtonClick}"
>
<img
src=${nightModeIcon}
alt="nightModeIcon"
width="20"
height="20"
/>
<div class="flex-1">
<div class="font-medium">
${translateText("user_setting.night_mode_label")}
</div>
<div class="text-sm text-slate-400">
${translateText("user_setting.night_mode_desc")}
</div>
</div>
<div class="text-sm text-slate-400">
${this.userSettings.nightMode()
? translateText("user_setting.on")
: translateText("user_setting.off")}
</div>
</button>

<button
class="flex gap-3 items-center w-full text-left p-3 hover:bg-slate-700 rounded text-white transition-colors"
@click="${this.onToggleSpecialEffectsButtonClick}"
Expand Down
13 changes: 13 additions & 0 deletions src/core/game/UserSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ export class UserSettings {
return this.get("settings.darkMode", false);
}

nightMode() {
return this.get("settings.nightMode", false);
}

leftClickOpensMenu() {
return this.get("settings.leftClickOpensMenu", false);
}
Expand Down Expand Up @@ -128,6 +132,15 @@ export class UserSettings {
}
}

toggleNightMode() {
this.set("settings.nightMode", !this.nightMode());
if (this.nightMode()) {
document.documentElement.classList.add("night");
} else {
document.documentElement.classList.remove("night");
}
}

// For development only. Used for testing patterns, set in the console manually.
getDevOnlyPattern(): PlayerPattern | undefined {
const data = localStorage.getItem("dev-pattern") ?? undefined;
Expand Down
Loading
Loading