Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
319 commits
Select commit Hold shift + click to select a range
1e5545f
update to team box
ryanbarlow97 Jan 8, 2026
e9adb6b
adds transparency when selected
ryanbarlow97 Jan 8, 2026
47d0135
fix input handler
ryanbarlow97 Jan 8, 2026
577d805
fix slider
ryanbarlow97 Jan 8, 2026
15799c8
update to fix, update test
ryanbarlow97 Jan 8, 2026
b53e476
added slider test
ryanbarlow97 Jan 8, 2026
f2c2f40
removed Unused @query decorator
ryanbarlow97 Jan 8, 2026
aa03a86
removed declaration duplicated
ryanbarlow97 Jan 8, 2026
4af690e
update class names to match existing definitions
ryanbarlow97 Jan 8, 2026
896e648
fix: check now uses correct variable
ryanbarlow97 Jan 8, 2026
3e631f6
added type assertion for window.showPage and fixed Duplicate default …
ryanbarlow97 Jan 8, 2026
9d276fd
removed unused import
ryanbarlow97 Jan 8, 2026
83caf85
Remove duplicate .setting-input.slider definition.
ryanbarlow97 Jan 8, 2026
4850971
Remove unnecessary export
ryanbarlow97 Jan 8, 2026
40046a7
Remove unused kingSkull variable
ryanbarlow97 Jan 8, 2026
9ae6bd9
Simplify the value extraction logic and improve type safety
ryanbarlow97 Jan 8, 2026
10f3762
removed console log
ryanbarlow97 Jan 8, 2026
1cd3295
Remove unused event handler methods
ryanbarlow97 Jan 8, 2026
7432be8
Debug logging removed
ryanbarlow97 Jan 8, 2026
a22eaab
Empty handler removed, Remove debug console.log, Simplify getKeyValue…
ryanbarlow97 Jan 8, 2026
56a619b
empty lifecycle hooks connectedCallback and disconnectedCallback have…
ryanbarlow97 Jan 8, 2026
b6d969a
fix locations of items
ryanbarlow97 Jan 8, 2026
9d66d99
ensure consistency between the local value and the event payload
ryanbarlow97 Jan 8, 2026
c3e283e
add reddit icon
ryanbarlow97 Jan 8, 2026
889462d
fix icon issue
ryanbarlow97 Jan 8, 2026
764f1ce
fix spacing on help modal
ryanbarlow97 Jan 8, 2026
b4944d5
fix text colour on slider
ryanbarlow97 Jan 8, 2026
42cb942
move the player list inside the modal
ryanbarlow97 Jan 8, 2026
0354e98
flipped image/description
ryanbarlow97 Jan 8, 2026
b39b58c
fix for ui
ryanbarlow97 Jan 8, 2026
8e6033e
username fix
ryanbarlow97 Jan 8, 2026
353b3b4
tighter grouping
ryanbarlow97 Jan 8, 2026
7cbcbd1
fix username spacing
ryanbarlow97 Jan 8, 2026
a611e5d
add flag selector to options
ryanbarlow97 Jan 8, 2026
5f34527
update flag logic based on location
ryanbarlow97 Jan 8, 2026
ce9a763
fix hamburger menu
ryanbarlow97 Jan 8, 2026
28ad955
add blur
ryanbarlow97 Jan 8, 2026
d3fee1f
remove shadows
ryanbarlow97 Jan 8, 2026
cdcbe4f
more fluid ui
ryanbarlow97 Jan 8, 2026
710e4c2
fixed spilling text
ryanbarlow97 Jan 8, 2026
2d84695
ui refinements for shadows + scrollbars
ryanbarlow97 Jan 8, 2026
49f0a9e
fix scrollbar
ryanbarlow97 Jan 8, 2026
dd29e95
(ios fix)?
ryanbarlow97 Jan 8, 2026
fe0e0fd
test for connection sake
ryanbarlow97 Jan 8, 2026
00aebed
remove race
ryanbarlow97 Jan 8, 2026
b451b6c
add error message
ryanbarlow97 Jan 8, 2026
1e0ac82
add some debugs
ryanbarlow97 Jan 8, 2026
d141f9a
fix a lobby kick
ryanbarlow97 Jan 8, 2026
830a530
remove quickplay tag
ryanbarlow97 Jan 8, 2026
01a0807
revert log
ryanbarlow97 Jan 8, 2026
824651f
Merge remote-tracking branch 'origin/main' into menu
ryanbarlow97 Jan 8, 2026
1f4f865
add "special"
ryanbarlow97 Jan 8, 2026
4f8c3dd
added an accessible label
ryanbarlow97 Jan 8, 2026
a81df1a
add a safe type guard for the showPage call
ryanbarlow97 Jan 8, 2026
590f55b
Remove empty lifecycle methods
ryanbarlow97 Jan 8, 2026
200bf76
Fix incorrect return value in getKeyValue
ryanbarlow97 Jan 8, 2026
30dd375
both interactive card containers keyboard-accessible:
ryanbarlow97 Jan 8, 2026
21481e1
Extract display key formatting to avoid duplication
ryanbarlow97 Jan 8, 2026
dd25909
Error" string should use translateText().
ryanbarlow97 Jan 8, 2026
8914d66
Replace inline onclick with module-based event handler
ryanbarlow97 Jan 8, 2026
d87c985
improve the type safety
ryanbarlow97 Jan 8, 2026
9c7154b
Type the window.showPage API instead of using as any
ryanbarlow97 Jan 8, 2026
640937a
Replaced hardcoded "Started"
ryanbarlow97 Jan 8, 2026
2b20af5
updated the z-index values in the JavaScript toggle logic to match th…
ryanbarlow97 Jan 8, 2026
afdf224
Updated the __toggleSidebar function
ryanbarlow97 Jan 8, 2026
4141867
fix localisations
ryanbarlow97 Jan 8, 2026
879e64b
some unification work into basemodal
ryanbarlow97 Jan 8, 2026
524d49e
fix build
ryanbarlow97 Jan 8, 2026
7cb818e
removed # from clan stats
ryanbarlow97 Jan 8, 2026
9f71a6d
unify some more and fix clantag
ryanbarlow97 Jan 8, 2026
d44bdd3
Merge main into menu
ryanbarlow97 Jan 8, 2026
0f7335e
Merge remote-tracking branch 'origin/main' into menu
ryanbarlow97 Jan 8, 2026
4385d15
ha ha centre a div
ryanbarlow97 Jan 8, 2026
ccfdca6
fill map icon
ryanbarlow97 Jan 8, 2026
a2b32bc
change how create/join buttons are on mobile/pc
ryanbarlow97 Jan 8, 2026
f9036cf
changed to just private
ryanbarlow97 Jan 8, 2026
be72234
added not logged in bit
ryanbarlow97 Jan 8, 2026
1ec3d6d
move skin button
ryanbarlow97 Jan 8, 2026
a2e413e
one or the other!
ryanbarlow97 Jan 8, 2026
10747ec
test repeating
ryanbarlow97 Jan 8, 2026
7d279fb
added back padding
ryanbarlow97 Jan 8, 2026
f057e15
flip flop logic for gold donate
ryanbarlow97 Jan 8, 2026
94dded3
add extra cards
ryanbarlow97 Jan 8, 2026
23b80ee
remove accidently debug
ryanbarlow97 Jan 8, 2026
13fb456
added accessibility attributes for toggle button
ryanbarlow97 Jan 8, 2026
e275de2
add localisation
ryanbarlow97 Jan 8, 2026
7b4a1e8
make showPage non-optional (removed the ?)
ryanbarlow97 Jan 8, 2026
b65df4e
add localisation
ryanbarlow97 Jan 8, 2026
1ef572e
Use showErrorModal() instead of alert()
ryanbarlow97 Jan 8, 2026
1a11d6d
Removed the transition-all duration-300
ryanbarlow97 Jan 8, 2026
f1a869e
Updated the alt text in Maps
ryanbarlow97 Jan 8, 2026
a35634c
removed dupe code
ryanbarlow97 Jan 8, 2026
16952f0
The medal tooltip on Maps now displays translated difficulty names
ryanbarlow97 Jan 8, 2026
01b841b
tag consistency
ryanbarlow97 Jan 8, 2026
f07d848
remove new items
ryanbarlow97 Jan 8, 2026
556e2d0
The product access in PatternButton now uses defensive checks
ryanbarlow97 Jan 8, 2026
bc19454
add localisation
ryanbarlow97 Jan 8, 2026
c592102
update comment
ryanbarlow97 Jan 8, 2026
8f81e28
added stats localisation
ryanbarlow97 Jan 8, 2026
d888111
added settings localisation
ryanbarlow97 Jan 8, 2026
2a92a5c
Removed the duplicate returnTo handling from firstUpdated().
ryanbarlow97 Jan 8, 2026
35f3ea9
Fixed the edge case. When prevValue is undefined (first time binding)…
ryanbarlow97 Jan 8, 2026
15bd418
add singleplayer localisation
ryanbarlow97 Jan 8, 2026
a223b08
added localisation
ryanbarlow97 Jan 8, 2026
65fb99a
add shop localisations
ryanbarlow97 Jan 8, 2026
4ff8f4a
Username validation
ryanbarlow97 Jan 8, 2026
84acbf6
Validate max timer value before enabling
ryanbarlow97 Jan 8, 2026
dc6c897
added a guard to the leaveLobby() method
ryanbarlow97 Jan 8, 2026
efb6c51
Fix UI/config mismatch
ryanbarlow97 Jan 8, 2026
7d0e725
adds localisaTION
ryanbarlow97 Jan 8, 2026
6df64e0
numeric enum reverse mapping directly instead of scanning through all…
ryanbarlow97 Jan 8, 2026
6c2d5b4
fixed setting keybinds as escape
ryanbarlow97 Jan 8, 2026
4a116da
img alt attribute now uses ${this.translation || this.mapName}
ryanbarlow97 Jan 8, 2026
84af95d
fix loclaisation
ryanbarlow97 Jan 8, 2026
4c5ed0a
add localisation
ryanbarlow97 Jan 8, 2026
4b00b3b
updated the Client initialisation
ryanbarlow97 Jan 8, 2026
e62082f
cleaner and more idiomatic TypeScript
ryanbarlow97 Jan 8, 2026
3cee095
The null check now uses options !== null && typeof options === "objec…
ryanbarlow97 Jan 8, 2026
a4a6f29
Localise the Back button
ryanbarlow97 Jan 8, 2026
3e1ef45
fiux map names
ryanbarlow97 Jan 8, 2026
3e60ecc
removed dead code
ryanbarlow97 Jan 8, 2026
32cc9e7
single method helper
ryanbarlow97 Jan 8, 2026
1faeb42
fix defaults
ryanbarlow97 Jan 8, 2026
112711c
streamer mode stuff
ryanbarlow97 Jan 8, 2026
9cd439b
fixed text
ryanbarlow97 Jan 8, 2026
026e4eb
ordering
ryanbarlow97 Jan 9, 2026
212e182
localised
ryanbarlow97 Jan 9, 2026
de4ff82
remvoed redundant class
ryanbarlow97 Jan 9, 2026
739a079
added guards using optional chaining (?.) for all the unguarded modal…
ryanbarlow97 Jan 9, 2026
a66b8a2
localisations
ryanbarlow97 Jan 9, 2026
aa175fe
added window.dispatchEvent(new CustomEvent("showPage", { detail: page…
ryanbarlow97 Jan 9, 2026
99ecf06
Fix shadowRoot usage
ryanbarlow97 Jan 9, 2026
4e81be7
ranked matchmaking
ryanbarlow97 Jan 9, 2026
ae2122a
matchmaking
ryanbarlow97 Jan 9, 2026
5b440a1
localisation
ryanbarlow97 Jan 9, 2026
b8904fc
change hamburger menu button
ryanbarlow97 Jan 9, 2026
39c00f9
accessibility attributes for keyboard and screen reader users.
ryanbarlow97 Jan 9, 2026
b690dd6
Remove unused translationKey attribute from <o-modal>.
ryanbarlow97 Jan 9, 2026
6f646a1
remove thod
ryanbarlow97 Jan 9, 2026
048d0bd
Added a preventHashUpdate flag
ryanbarlow97 Jan 9, 2026
17bb3d2
remove unneeded BR
ryanbarlow97 Jan 9, 2026
c32163b
Fixed the state synchronization issues
ryanbarlow97 Jan 9, 2026
5f098d9
removed shadow lit sutff
ryanbarlow97 Jan 9, 2026
6c87ca2
cleaned up hamburger menu
ryanbarlow97 Jan 9, 2026
748afa1
Simplified to a single cohesive code block
ryanbarlow97 Jan 9, 2026
41bc99c
Merge branch 'main' into menu
ryanbarlow97 Jan 9, 2026
e911316
fix language for lobbies
ryanbarlow97 Jan 9, 2026
7a185eb
fix input modal for username/clan tag
ryanbarlow97 Jan 9, 2026
c31ab39
fix spanning
ryanbarlow97 Jan 9, 2026
f33c9fc
Merge branch 'menu' of https://github.com/openfrontio/OpenFrontIO int…
ryanbarlow97 Jan 9, 2026
b0bb015
fix: Critical accessibility violation: zoom prevention blocks users w…
ryanbarlow97 Jan 9, 2026
947b3f5
fix Critical: FOUC prevention creates accessibility risk.
ryanbarlow97 Jan 9, 2026
4759b7c
hom page fixes
ryanbarlow97 Jan 9, 2026
1bf87b5
updated getEndTimerInput to use this.querySelector("#end-timer-value"…
ryanbarlow97 Jan 9, 2026
49bab45
ios fix attempt 1
ryanbarlow97 Jan 9, 2026
74697cf
Changed .onClose=${this.close} to .onClose=${this.close.bind(this)} s…
ryanbarlow97 Jan 9, 2026
cd6a66f
localisation
ryanbarlow97 Jan 9, 2026
8bba1c2
Added strict object check
ryanbarlow97 Jan 9, 2026
f1d76db
fix name
ryanbarlow97 Jan 9, 2026
9048847
changed to FlagInputModal
ryanbarlow97 Jan 9, 2026
5ae946d
onclose consistency (reset ui)
ryanbarlow97 Jan 9, 2026
9f4bf64
reset ui consistency
ryanbarlow97 Jan 9, 2026
7c6be62
fix lang
ryanbarlow97 Jan 9, 2026
8b6ade7
Merge main into menu, resolving conflicts in Matchmaking and en.json
ryanbarlow97 Jan 9, 2026
3489a04
tailwind v4 oops
ryanbarlow97 Jan 9, 2026
97eb45e
added keyboard accessibility to the Flag Selector
ryanbarlow97 Jan 9, 2026
5fa66b7
prettier
ryanbarlow97 Jan 9, 2026
e1a1f0e
timer fix
ryanbarlow97 Jan 9, 2026
083b07c
improved the type safety of the languageList
ryanbarlow97 Jan 9, 2026
5d6c47d
localisations
ryanbarlow97 Jan 9, 2026
f7e19d9
interactive hamburger button correctly references the sidebar menu
ryanbarlow97 Jan 9, 2026
68e4dd9
add aria
ryanbarlow97 Jan 9, 2026
2d9baa4
Fix type declaration to match validation logic
ryanbarlow97 Jan 9, 2026
c23a4fa
removed the non-null assertion
ryanbarlow97 Jan 9, 2026
7eaec29
change public lobby ring to blue
ryanbarlow97 Jan 9, 2026
c97aaef
change names to white
ryanbarlow97 Jan 9, 2026
1c15d84
modernise join lobby modal
ryanbarlow97 Jan 9, 2026
63c8c98
hasanystats check
ryanbarlow97 Jan 9, 2026
835d7b8
prettier fix
ryanbarlow97 Jan 9, 2026
9c1e439
remove keyCode
ryanbarlow97 Jan 9, 2026
3642f1d
Add an early-exit check
ryanbarlow97 Jan 9, 2026
589290f
text colour fix
ryanbarlow97 Jan 9, 2026
5e632f2
prettier
ryanbarlow97 Jan 9, 2026
e0a059f
fix
ryanbarlow97 Jan 9, 2026
a9cdff6
prettier
ryanbarlow97 Jan 9, 2026
dcf5b85
prettier fix
ryanbarlow97 Jan 9, 2026
e683344
fix the ESLint error
ryanbarlow97 Jan 9, 2026
e944f22
removed borked account button
ryanbarlow97 Jan 9, 2026
88b8116
click outside modal to go back
ryanbarlow97 Jan 9, 2026
9166301
update keybind display for localisations
ryanbarlow97 Jan 9, 2026
4260442
Merge branch 'main' into menu
ryanbarlow97 Jan 9, 2026
d94d618
reset logic fix
ryanbarlow97 Jan 9, 2026
12bef88
default page to page-play
ryanbarlow97 Jan 9, 2026
30cb228
Consolidated the initialixation logic.
ryanbarlow97 Jan 9, 2026
73b6d00
cosmetics
ryanbarlow97 Jan 9, 2026
756fc02
Tighten the type guard logic
ryanbarlow97 Jan 9, 2026
71dd0ef
cleanup for the window event listener
ryanbarlow97 Jan 9, 2026
151b90d
change skin modal to not be white
ryanbarlow97 Jan 9, 2026
ad51dbf
Merge branch 'main' into menu
ryanbarlow97 Jan 9, 2026
b6a7261
test skin
ryanbarlow97 Jan 10, 2026
5194b99
remove weird fadein issue
ryanbarlow97 Jan 10, 2026
8fc73d9
use en.json
ryanbarlow97 Jan 10, 2026
3f8920c
removed uuid
ryanbarlow97 Jan 10, 2026
0e33a9a
localisations
ryanbarlow97 Jan 10, 2026
9c9555d
Merge main into testaskin
ryanbarlow97 Jan 10, 2026
5da49b0
remove logs
ryanbarlow97 Jan 10, 2026
d4b9cde
tests
ryanbarlow97 Jan 10, 2026
1a4ba86
fix timer
ryanbarlow97 Jan 10, 2026
c1cd919
added accessibility attributes
ryanbarlow97 Jan 10, 2026
63d3069
reset rated back to null
ryanbarlow97 Jan 10, 2026
23fb8e8
cleanup for the skin-test “initial attack” retry timer so it can’t fi…
ryanbarlow97 Jan 10, 2026
f5b83b9
Removed the unused skinTestTimeout field
ryanbarlow97 Jan 10, 2026
e6ec868
Merge branch 'main' into testaskin
ryanbarlow97 Jan 10, 2026
5f72df2
remove console
ryanbarlow97 Jan 10, 2026
1cef169
Self-clearing timeout is a no-op.
ryanbarlow97 Jan 10, 2026
d3b90c3
remove unused eventmus
ryanbarlow97 Jan 10, 2026
ed345e2
Merge branch 'main' into testaskin
ryanbarlow97 Jan 10, 2026
f921e83
fix build
ryanbarlow97 Jan 10, 2026
b6ebad6
Merge branch 'main' into testaskin
ryanbarlow97 Jan 11, 2026
0423b38
Merge branch 'main' into testaskin
evanpelle Jan 12, 2026
5ea429f
Merge branch 'main' into testaskin
ryanbarlow97 Jan 12, 2026
b7ea630
move logic into modal
ryanbarlow97 Jan 12, 2026
c84fc4a
new TestSkinExecution.ts that starts an attack
ryanbarlow97 Jan 12, 2026
c65bed8
need to carry over the userid to show winmodal
ryanbarlow97 Jan 12, 2026
181324d
Stop skin-test resources on show; add stopSkinTest helper and use it …
ryanbarlow97 Jan 12, 2026
9c1abd7
moved stuff out of clientgamerunner
ryanbarlow97 Jan 12, 2026
4ce8fa2
test
ryanbarlow97 Jan 12, 2026
d411c78
Merge branch 'main' into testaskin
ryanbarlow97 Jan 13, 2026
aa4f43d
Merge branch 'main' into testaskin
ryanbarlow97 Jan 13, 2026
197960e
conflict res
ryanbarlow97 Jan 14, 2026
e981ecc
Merge branch 'main' into testaskin
ryanbarlow97 Jan 14, 2026
f0d6e30
Merge branch 'main' into testaskin
ryanbarlow97 Jan 14, 2026
4878737
Merge branch 'main' into testaskin
ryanbarlow97 Jan 17, 2026
bda3961
Merge branch 'main' into testaskin
ryanbarlow97 Jan 17, 2026
f81e990
change to "preview skin"
ryanbarlow97 Jan 17, 2026
b3b405c
update wording
ryanbarlow97 Jan 17, 2026
6c65d25
Merge branch 'main' into testaskin
ryanbarlow97 Jan 18, 2026
e5ad3cc
Merge branch 'main' into testaskin
ryanbarlow97 Jan 19, 2026
73159c7
fixed missing }
ryanbarlow97 Jan 19, 2026
074b2c0
Merge branch 'main' into testaskin
ryanbarlow97 Jan 19, 2026
af2fe78
call stop instead
ryanbarlow97 Jan 19, 2026
9d19396
Merge branch 'main' into testaskin
ryanbarlow97 Jan 24, 2026
3101b73
Merge branch 'main' into testaskin
ryanbarlow97 Feb 6, 2026
f3d0fab
comment
ryanbarlow97 Feb 6, 2026
e549f38
updates
ryanbarlow97 Feb 6, 2026
dffed45
rabbit
ryanbarlow97 Feb 6, 2026
480fd7b
Merge branch 'main' into testaskin
ryanbarlow97 Feb 10, 2026
470eb52
updated for new build
ryanbarlow97 Feb 10, 2026
571ad9c
Merge branch 'main' into testaskin
ryanbarlow97 Feb 13, 2026
b0a6992
Merge branch 'main' into testaskin
ryanbarlow97 Feb 15, 2026
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
1 change: 1 addition & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@
<emoji-table></emoji-table>
<build-menu></build-menu>
<win-modal></win-modal>
<skin-test-win-modal></skin-test-win-modal>
<game-starting-modal></game-starting-modal>
<unit-display></unit-display>
<div
Expand Down
7 changes: 7 additions & 0 deletions resources/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -699,6 +699,13 @@
"join_server": "Join Server",
"youtube_tutorial": "Need some help?"
},
"skin_test_modal": {
"title": "Preview Complete",
"rate_skin": "Rate this Skin",
"rate_up": "Rate up",
"rate_down": "Rate down",
"preview_skin": "Preview Skin"
},
"leaderboard": {
"hide": "Hide",
"player": "Player",
Expand Down
54 changes: 53 additions & 1 deletion src/client/ClientGameRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
import { createPartialGameRecord, replacer } from "../core/Util";
import { ServerConfig } from "../core/configuration/Config";
import { getConfig } from "../core/configuration/ConfigLoader";
import { TestSkinExecution } from "../core/execution/TestSkinExecution";
import { PlayerActions, UnitType } from "../core/game/Game";
import { TileRef } from "../core/game/GameMap";
import { GameMapLoader } from "../core/game/GameMapLoader";
Expand All @@ -35,6 +36,7 @@ import {
InputHandler,
MouseMoveEvent,
MouseUpEvent,
ReplaySpeedChangeEvent,
TickMetricsEvent,
} from "./InputHandler";
import { endGame, startGame, startTime } from "./LocalPersistantStats";
Expand All @@ -50,7 +52,9 @@ import {
import { createCanvas } from "./Utils";
import { createRenderer, GameRenderer } from "./graphics/GameRenderer";
import { GoToPlayerEvent } from "./graphics/layers/Leaderboard";
import { ShowSkinTestModalEvent } from "./graphics/layers/SkinTestWinModal";
import SoundManager from "./sound/SoundManager";
import { ReplaySpeedMultiplier } from "./utilities/ReplaySpeedMultiplier";

export interface LobbyConfig {
serverConfig: ServerConfig;
Expand All @@ -62,6 +66,7 @@ export interface LobbyConfig {
gameStartInfo?: GameStartInfo;
// GameRecord exists when replaying an archived game.
gameRecord?: GameRecord;
isSkinTest?: boolean;
}

export function joinLobby(
Expand Down Expand Up @@ -230,6 +235,7 @@ async function createClientGame(
lobbyConfig.playerName,
lobbyConfig.gameStartInfo.gameID,
lobbyConfig.gameStartInfo.players,
lobbyConfig.isSkinTest,
);

const canvas = createCanvas();
Expand Down Expand Up @@ -261,6 +267,7 @@ export class ClientGameRunner {
private lastMessageTime: number = 0;
private connectionCheckInterval: NodeJS.Timeout | null = null;
private goToPlayerTimeout: NodeJS.Timeout | null = null;
private testSkinExecution: TestSkinExecution | null = null;

private lastTickReceiveTime: number = 0;
private currentTickDelay: number | undefined = undefined;
Expand All @@ -278,6 +285,15 @@ export class ClientGameRunner {
this.lastMessageTime = Date.now();
}

private stopSkinTest() {
if (this.testSkinExecution !== null) {
try {
this.testSkinExecution.stop();
} finally {
this.testSkinExecution = null;
}
}
}
/**
* Determines whether window closing should be prevented.
*
Expand Down Expand Up @@ -336,6 +352,35 @@ export class ClientGameRunner {
);
}, 20000);

if (this.lobby.isSkinTest) {
// Set game speed to maximum
this.eventBus.emit(
new ReplaySpeedChangeEvent(ReplaySpeedMultiplier.fastest),
);

// Clean up any prior skin test resources, then set a new timeout and start a fresh execution
this.stopSkinTest();

// Start a fresh TestSkinExecution which manages its own modal timeout
this.testSkinExecution = new TestSkinExecution(
this.gameView,
this.clientID,
() => this.isActive,
() => {
// Called when execution requests the modal be shown — stop the game and
// clean up resources first.
this.stop();
},
(targetID, troops) =>
this.eventBus.emit(new SendAttackIntentEvent(targetID, troops)),
(patternName, colorPalette) =>
this.eventBus.emit(
new ShowSkinTestModalEvent(patternName, colorPalette),
),
);
this.testSkinExecution.start();
}

this.eventBus.on(MouseUpEvent, this.inputEvent.bind(this));
this.eventBus.on(MouseMoveEvent, this.onMouseMove.bind(this));
this.eventBus.on(AutoUpgradeEvent, this.autoUpgradeEvent.bind(this));
Expand Down Expand Up @@ -381,7 +426,12 @@ export class ClientGameRunner {
this.currentTickDelay = undefined;

if (gu.updates[GameUpdateType.Win].length > 0) {
this.saveGame(gu.updates[GameUpdateType.Win][0]);
if (this.lobby.isSkinTest) {
// For skin tests, show the modal immediately on win instead of waiting
this.testSkinExecution?.showModal();
} else {
this.saveGame(gu.updates[GameUpdateType.Win][0]);
}
}
});

Expand Down Expand Up @@ -515,6 +565,8 @@ export class ClientGameRunner {
if (!this.isActive) return;

this.isActive = false;
// Clean up skin test resources
this.stopSkinTest();
this.worker.cleanup();
this.transport.leaveGame();
if (this.connectionCheckInterval) {
Expand Down
2 changes: 2 additions & 0 deletions src/client/Main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ export interface JoinLobbyEvent {
gameStartInfo?: GameStartInfo;
// GameRecord exists when replaying an archived game.
gameRecord?: GameRecord;
isSkinTest?: boolean;
source?: "public" | "private" | "host" | "matchmaking" | "singleplayer";
}

Expand Down Expand Up @@ -815,6 +816,7 @@ class Client {
this.usernameInput?.getCurrentUsername() ?? genAnonUsername(),
gameStartInfo: lobby.gameStartInfo ?? lobby.gameRecord?.info,
gameRecord: lobby.gameRecord,
isSkinTest: lobby.isSkinTest,
},
() => {
console.log("Closing modals");
Expand Down
100 changes: 100 additions & 0 deletions src/client/TerritoryPatternsModal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@
import { customElement, state } from "lit/decorators.js";
import { UserMeResponse } from "../core/ApiSchemas";
import { ColorPalette, Cosmetics, Pattern } from "../core/CosmeticSchemas";
import {
Difficulty,
GameMapSize,
GameMapType,
GameMode,
GameType,
UnitType,
} from "../core/game/Game";
import { UserSettings } from "../core/game/UserSettings";
import { PlayerPattern } from "../core/Schemas";
import { hasLinkedAccount } from "./Api";
Expand Down Expand Up @@ -65,7 +73,7 @@
}

private async updateFromSettings() {
const cosmetics = await getPlayerCosmetics();

Check failure on line 76 in src/client/TerritoryPatternsModal.ts

View workflow job for this annotation

GitHub Actions / 🔬 Test

tests/client/TerritoryPatternsModal.test.ts > TerritoryPatternsModal skin button simulation > clicking 'Preview Skin' dispatches a join-lobby event with isSkinTest=true

Error: [vitest] No "getPlayerCosmetics" export is defined on the "../../src/client/Cosmetics" mock. Did you forget to return it from "vi.mock"? If you need to partially mock a module, you can use "importOriginal" helper inside: vi.mock(import("../../src/client/Cosmetics"), async (importOriginal) => { const actual = await importOriginal() return { ...actual, // your mocked methods } }) ❯ TerritoryPatternsModal.updateFromSettings src/client/TerritoryPatternsModal.ts:76:29 ❯ TerritoryPatternsModal.onUserMe src/client/TerritoryPatternsModal.ts:84:16 ❯ tests/client/TerritoryPatternsModal.test.ts:174:5

Check failure on line 76 in src/client/TerritoryPatternsModal.ts

View workflow job for this annotation

GitHub Actions / 🔬 Test

tests/client/TerritoryPatternsModal.test.ts > TerritoryPatternsModal skin button simulation > toggles the 'My Skins' (show only owned) button

Error: [vitest] No "getPlayerCosmetics" export is defined on the "../../src/client/Cosmetics" mock. Did you forget to return it from "vi.mock"? If you need to partially mock a module, you can use "importOriginal" helper inside: vi.mock(import("../../src/client/Cosmetics"), async (importOriginal) => { const actual = await importOriginal() return { ...actual, // your mocked methods } }) ❯ TerritoryPatternsModal.updateFromSettings src/client/TerritoryPatternsModal.ts:76:29 ❯ TerritoryPatternsModal.onUserMe src/client/TerritoryPatternsModal.ts:84:16 ❯ tests/client/TerritoryPatternsModal.test.ts:174:5
this.selectedPattern = cosmetics.pattern ?? null;
this.selectedColor = cosmetics.color?.color ?? null;
}
Expand Down Expand Up @@ -176,6 +184,10 @@
.onSelect=${(p: PlayerPattern | null) => this.selectPattern(p)}
.onPurchase=${(p: Pattern, colorPalette: ColorPalette | null) =>
handlePurchase(p, colorPalette)}
.onTest=${hasLinkedAccount(this.userMeResponse)
? (p: Pattern, colorPalette: ColorPalette | null) =>
this.startTestGame(p, colorPalette)
: undefined}
></pattern-button>
`);
}
Expand Down Expand Up @@ -205,6 +217,94 @@
`;
}

private startTestGame(pattern: Pattern, colorPalette: ColorPalette | null) {
if (!this.userMeResponse) {
window.dispatchEvent(
new CustomEvent("show-message", {
detail: {
message: translateText("territory_patterns.not_logged_in"),
duration: 3000,
},
}),
);
return;
}
const clientID = this.userMeResponse.player.publicId;
const gameID = pattern.name;

const selectedPattern = {
name: pattern.name,
patternData: pattern.pattern,
colorPalette: colorPalette ?? undefined,
};

// Use translation if available, otherwise format the name
const translation = translateText(
`territory_patterns.pattern.${pattern.name}`,
);
const displayName = translation.startsWith("territory_patterns.pattern.")
? pattern.name
.split("_")
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
.join(" ")
: translation;

this.dispatchEvent(
new CustomEvent("join-lobby", {
detail: {
clientID: clientID,
gameID: gameID,
isSkinTest: true,
source: "singleplayer",
gameStartInfo: {
gameID: gameID,
players: [
{
clientID,
username: displayName,
cosmetics: {
pattern: selectedPattern,
},
},
],
config: {
gameMap: GameMapType.Iceland,
gameMapSize: GameMapSize.Compact,
gameType: GameType.Singleplayer,
gameMode: GameMode.FFA,
playerTeams: 1,
bots: 0,
difficulty: Difficulty.Easy,
donateGold: false,
donateTroops: false,
instantBuild: false,
randomSpawn: true,
disableNations: true,
infiniteGold: true,
infiniteTroops: true,
percentageTilesOwnedToWin: 99,
disabledUnits: [
UnitType.City,
UnitType.Factory,
UnitType.Port,
UnitType.MissileSilo,
UnitType.DefensePost,
UnitType.SAMLauncher,
UnitType.AtomBomb,
UnitType.HydrogenBomb,
UnitType.MIRV,
UnitType.Warship,
],
},
lobbyCreatedAt: Date.now(),
},
},
bubbles: true,
composed: true,
}),
);
}

private renderMySkinsButton(): TemplateResult {
return html`<button
class="px-4 py-2 text-xs font-bold transition-all duration-200 rounded-lg uppercase tracking-wider border mb-4 ${this
Expand Down
20 changes: 20 additions & 0 deletions src/client/components/PatternButton.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ export class PatternButton extends LitElement {
@property({ type: Function })
onPurchase?: (pattern: Pattern, colorPalette: ColorPalette | null) => void;

@property({ type: Function })
onTest?: (pattern: Pattern, colorPalette: ColorPalette | null) => void;
private _countdownInterval: ReturnType<typeof setInterval> | null = null;

@state()
Expand Down Expand Up @@ -216,6 +218,13 @@ export class PatternButton extends LitElement {
}
}

private handleTest(e: Event) {
e.stopPropagation();
if (this.pattern) {
this.onTest?.(this.pattern, this.colorPalette ?? null);
}
}

render() {
const isDefaultPattern = this.pattern === null;

Expand Down Expand Up @@ -342,6 +351,17 @@ export class PatternButton extends LitElement {
>(${this.pattern.product.price})</span
>
</button>
${this.onTest
? html`
<button
class="w-full mt-2 px-4 py-2 bg-blue-500/20 text-blue-400 border border-blue-500/30 rounded-lg text-xs font-bold uppercase tracking-wider cursor-pointer transition-all duration-200
hover:bg-blue-500/30 hover:shadow-[0_0_15px_rgba(59,130,246,0.2)]"
@click=${this.handleTest}
>
${translateText("skin_test_modal.preview_skin")}
</button>
`
: null}
</div>
`
: null}
Expand Down
9 changes: 9 additions & 0 deletions src/client/graphics/GameRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { RailroadLayer } from "./layers/RailroadLayer";
import { ReplayPanel } from "./layers/ReplayPanel";
import { SAMRadiusLayer } from "./layers/SAMRadiusLayer";
import { SettingsModal } from "./layers/SettingsModal";
import { SkinTestWinModal } from "./layers/SkinTestWinModal";
import { SpawnTimer } from "./layers/SpawnTimer";
import { SpawnVideoAd } from "./layers/SpawnVideoReward";
import { StructureIconsLayer } from "./layers/StructureIconsLayer";
Expand Down Expand Up @@ -159,6 +160,13 @@ export function createRenderer(
winModal.eventBus = eventBus;
winModal.game = game;

const skinTestWinModal = document.querySelector(
"skin-test-win-modal",
) as SkinTestWinModal;
if (skinTestWinModal instanceof SkinTestWinModal) {
skinTestWinModal.eventBus = eventBus;
}

const replayPanel = document.querySelector("replay-panel") as ReplayPanel;
if (!(replayPanel instanceof ReplayPanel)) {
console.error("replay panel not found");
Expand Down Expand Up @@ -311,6 +319,7 @@ export function createRenderer(
controlPanel,
playerInfo,
winModal,
skinTestWinModal,
replayPanel,
settingsModal,
teamStats,
Expand Down
Loading
Loading