From 683ba4c6c115bd6d9d4ef261513e019bbf1c109d Mon Sep 17 00:00:00 2001 From: FloPinguin <25036848+FloPinguin@users.noreply.github.com> Date: Fri, 30 Jan 2026 22:45:00 +0100 Subject: [PATCH 1/7] =?UTF-8?q?Make=20easy=20difficulty=20easier=20?= =?UTF-8?q?=F0=9F=93=8A=20(#3072)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description: https://www.reddit.com/r/Openfront/comments/1qquvdg/difficulty_scaling_in_single_player/ People seem to feel that easy is not easy enough, even after the various nerfs in multiple places. So let's touch DefaultConfig. ## Please complete the following: - [X] I have added screenshots for all UI updates - [X] I process any text displayed to the user through translateText() and I've added it to the en.json file - [X] I have added relevant tests to the test directory - [X] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: FloPinguin --- src/core/configuration/DefaultConfig.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core/configuration/DefaultConfig.ts b/src/core/configuration/DefaultConfig.ts index 36057bdad..6ad7efb27 100644 --- a/src/core/configuration/DefaultConfig.ts +++ b/src/core/configuration/DefaultConfig.ts @@ -725,7 +725,7 @@ export class DefaultConfig implements Config { if (playerInfo.playerType === PlayerType.Nation) { switch (this._gameConfig.difficulty) { case Difficulty.Easy: - return 18_750; + return 12_500; case Difficulty.Medium: return 25_000; // Like humans case Difficulty.Hard: @@ -760,7 +760,7 @@ export class DefaultConfig implements Config { switch (this._gameConfig.difficulty) { case Difficulty.Easy: - return maxTroops * 0.75; + return maxTroops * 0.5; case Difficulty.Medium: return maxTroops * 1; // Like humans case Difficulty.Hard: @@ -787,7 +787,7 @@ export class DefaultConfig implements Config { if (player.type() === PlayerType.Nation) { switch (this._gameConfig.difficulty) { case Difficulty.Easy: - toAdd *= 0.95; + toAdd *= 0.9; break; case Difficulty.Medium: toAdd *= 1; // Like humans From 02dc5fc153c7ee6c711140156fbb71675e11415b Mon Sep 17 00:00:00 2001 From: Evan Date: Fri, 30 Jan 2026 14:24:55 -0800 Subject: [PATCH 2/7] Add glowing red dot on store when skins change (#3066) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description: Hash the pattern + is for sale and store it in local storage. then check if the new cosmetics hash is different from the one we last saw. If it's different, add a glowing red dot on the store. After user clicks it, then update the hash and remove the dot. Screenshot 2026-01-29 at 3 54 46 PM ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: evan --- src/client/Cosmetics.ts | 22 +++++++++++++++ src/client/components/DesktopNavBar.ts | 39 +++++++++++++++++++++++--- 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/src/client/Cosmetics.ts b/src/client/Cosmetics.ts index d1f00e88d..0f368175c 100644 --- a/src/client/Cosmetics.ts +++ b/src/client/Cosmetics.ts @@ -30,6 +30,18 @@ export async function handlePurchase( } let __cosmetics: Promise | null = null; +let __cosmeticsHash: string | null = null; + +function simpleHash(str: string): string { + let hash = 0; + for (let i = 0; i < str.length; i++) { + const char = str.charCodeAt(i); + hash = (hash << 5) - hash + char; + hash = hash & hash; + } + return hash.toString(36); +} + export async function fetchCosmetics(): Promise { if (__cosmetics !== null) { return __cosmetics; @@ -46,6 +58,11 @@ export async function fetchCosmetics(): Promise { console.error(`Invalid cosmetics: ${result.error.message}`); return null; } + const patternKeys = Object.keys(result.data.patterns).sort(); + const hashInput = patternKeys + .map((k) => k + (result.data.patterns[k].product ? "sale" : "")) + .join(","); + __cosmeticsHash = simpleHash(hashInput); return result.data; } catch (error) { console.error("Error getting cosmetics:", error); @@ -55,6 +72,11 @@ export async function fetchCosmetics(): Promise { return __cosmetics; } +export async function getCosmeticsHash(): Promise { + await fetchCosmetics(); + return __cosmeticsHash; +} + export function patternRelationship( pattern: Pattern, colorPalette: { name: string; isArchived?: boolean } | null, diff --git a/src/client/components/DesktopNavBar.ts b/src/client/components/DesktopNavBar.ts index 1e13f87c1..6fbe8d398 100644 --- a/src/client/components/DesktopNavBar.ts +++ b/src/client/components/DesktopNavBar.ts @@ -1,12 +1,15 @@ import { LitElement, html } from "lit"; import { customElement, state } from "lit/decorators.js"; +import { getCosmeticsHash } from "../Cosmetics"; import { getGamesPlayed } from "../Utils"; const HELP_SEEN_KEY = "helpSeen"; +const STORE_SEEN_HASH_KEY = "storeSeenHash"; @customElement("desktop-nav-bar") export class DesktopNavBar extends LitElement { @state() private _helpSeen = localStorage.getItem(HELP_SEEN_KEY) === "true"; + @state() private _hasNewCosmetics = false; createRenderRoot() { return this; @@ -23,6 +26,12 @@ export class DesktopNavBar extends LitElement { this._updateActiveState(current); }); } + + // Check if cosmetics have changed + getCosmeticsHash().then((hash: string | null) => { + const seenHash = localStorage.getItem(STORE_SEEN_HASH_KEY); + this._hasNewCosmetics = hash !== null && hash !== seenHash; + }); } disconnectedCallback() { @@ -46,14 +55,29 @@ export class DesktopNavBar extends LitElement { } private showHelpDot(): boolean { + // Only show one dot at a time to prevent + // overwhelming users. return getGamesPlayed() < 10 && !this._helpSeen; } + private showStoreDot(): boolean { + return this._hasNewCosmetics && !this.showHelpDot(); + } + private onHelpClick = () => { localStorage.setItem(HELP_SEEN_KEY, "true"); this._helpSeen = true; }; + private onStoreClick = () => { + this._hasNewCosmetics = false; + getCosmeticsHash().then((hash: string | null) => { + if (hash !== null) { + localStorage.setItem(STORE_SEEN_HASH_KEY, hash); + } + }); + }; + render() { return html`