Skip to content

Commit

Permalink
[Feature] Seasonal splash messages logic + scaffolding (#4318)
Browse files Browse the repository at this point in the history
* add: seasonsl splash messages logic + scaffolding

* refactor: settin up and displaying splash messages.

They are now stored with their i18next keys and only get translated as soon as they are displayed. This also allows for better display of the `battlesWon` parameter which now supports better number formatting and the count is an interpolation

* fix: updateTitleStats not checking the namespace of battlesWon

* add tests for splash_messages

* test: always use UTC time

* fix: time-pattern to MM-DD

* fix splash_messages test

* add: const to control usage of seasonal splash messages

* fix tests (splashj)

* Update src/locales/ja/splash-messages.json

Co-authored-by: Chapybara-jp <charlie.beer@hotmail.com>

* Update src/locales/es/splash-messages.json

Add missing `number` format for battlesWon message

---------

Co-authored-by: Chapybara-jp <charlie.beer@hotmail.com>
  • Loading branch information
flx-sta and Chapybara-jp authored Sep 19, 2024
1 parent 7490699 commit 48430c8
Show file tree
Hide file tree
Showing 15 changed files with 234 additions and 57 deletions.
6 changes: 5 additions & 1 deletion src/constants.ts
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
export const PLAYER_PARTY_MAX_SIZE = 6;
/** The maximum size of the player's party */
export const PLAYER_PARTY_MAX_SIZE: number = 6;

/** Whether to use seasonal splash messages in general */
export const USE_SEASONAL_SPLASH_MESSAGES: boolean = false;
172 changes: 131 additions & 41 deletions src/data/splash-messages.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,136 @@
import i18next from "i18next";
import { USE_SEASONAL_SPLASH_MESSAGES } from "#app/constants";

export function getBattleCountSplashMessage(): string {
return `{COUNT} ${i18next.t("splashMessages:battlesWon")}`;
//#region Interfaces/Types

type Month = "01" | "02" | "03" | "04" | "05" | "06" | "07" | "08" | "09" | "10" | "11" | "12";
type Day =
| Month
| "13"
| "14"
| "15"
| "16"
| "17"
| "18"
| "19"
| "20"
| "21"
| "22"
| "23"
| "24"
| "25"
| "26"
| "27"
| "28"
| "29"
| "30"
| "31";

/**
* Represents a season with its {@linkcode name},
* {@linkcode start} day+month, {@linkcode end} day+month
* and {@linkcode messages}.
*/
interface Season {
/** The name of the season (internal use only) */
name: string;
/** The start day and month of the season. Format `MM-DD` */
start: `${Month}-${Day}`;
/** The end day and month of the season. Format `MM-DD` */
end: `${Month}-${Day}`;
/** Collection of the messages to display (without the `i18next.t()` call!) */
messages: string[];
}

//#region Constants

/** The weight multiplier for the battles-won splash message */
const BATTLES_WON_WEIGHT_MULTIPLIER = 10;
/** The weight multiplier for the seasonal splash messages */
const SEASONAL_WEIGHT_MULTIPLIER = 10;

//#region Common Messages

const commonSplashMessages = [
...Array(BATTLES_WON_WEIGHT_MULTIPLIER).fill("battlesWon"),
"joinTheDiscord",
"infiniteLevels",
"everythingStacks",
"optionalSaveScumming",
"biomes",
"openSource",
"playWithSpeed",
"liveBugTesting",
"heavyInfluence",
"pokemonRiskAndPokemonRain",
"nowWithMoreSalt",
"infiniteFusionAtHome",
"brokenEggMoves",
"magnificent",
"mubstitute",
"thatsCrazy",
"oranceJuice",
"questionableBalancing",
"coolShaders",
"aiFree",
"suddenDifficultySpikes",
"basedOnAnUnfinishedFlashGame",
"moreAddictiveThanIntended",
"mostlyConsistentSeeds",
"achievementPointsDontDoAnything",
"youDoNotStartAtLevel",
"dontTalkAboutTheManaphyEggIncident",
"alsoTryPokengine",
"alsoTryEmeraldRogue",
"alsoTryRadicalRed",
"eeveeExpo",
"ynoproject",
"breedersInSpace",
];

//#region Seasonal Messages

const seasonalSplashMessages: Season[] = [
{
name: "Halloween",
start: "09-15",
end: "10-31",
messages: ["halloween.pumpkaboosAbout", "halloween.mayContainSpiders", "halloween.spookyScaryDuskulls"],
},
{
name: "XMAS",
start: "12-01",
end: "12-26",
messages: ["xmas.happyHolidays", "xmas.delibirdSeason"],
},
{
name: "New Year's",
start: "01-01",
end: "01-31",
messages: ["newYears.happyNewYear"],
},
];

//#endregion

export function getSplashMessages(): string[] {
const splashMessages = Array(10).fill(getBattleCountSplashMessage());
splashMessages.push(
i18next.t("splashMessages:joinTheDiscord"),
i18next.t("splashMessages:infiniteLevels"),
i18next.t("splashMessages:everythingStacks"),
i18next.t("splashMessages:optionalSaveScumming"),
i18next.t("splashMessages:biomes"),
i18next.t("splashMessages:openSource"),
i18next.t("splashMessages:playWithSpeed"),
i18next.t("splashMessages:liveBugTesting"),
i18next.t("splashMessages:heavyInfluence"),
i18next.t("splashMessages:pokemonRiskAndPokemonRain"),
i18next.t("splashMessages:nowWithMoreSalt"),
i18next.t("splashMessages:infiniteFusionAtHome"),
i18next.t("splashMessages:brokenEggMoves"),
i18next.t("splashMessages:magnificent"),
i18next.t("splashMessages:mubstitute"),
i18next.t("splashMessages:thatsCrazy"),
i18next.t("splashMessages:oranceJuice"),
i18next.t("splashMessages:questionableBalancing"),
i18next.t("splashMessages:coolShaders"),
i18next.t("splashMessages:aiFree"),
i18next.t("splashMessages:suddenDifficultySpikes"),
i18next.t("splashMessages:basedOnAnUnfinishedFlashGame"),
i18next.t("splashMessages:moreAddictiveThanIntended"),
i18next.t("splashMessages:mostlyConsistentSeeds"),
i18next.t("splashMessages:achievementPointsDontDoAnything"),
i18next.t("splashMessages:youDoNotStartAtLevel"),
i18next.t("splashMessages:dontTalkAboutTheManaphyEggIncident"),
i18next.t("splashMessages:alsoTryPokengine"),
i18next.t("splashMessages:alsoTryEmeraldRogue"),
i18next.t("splashMessages:alsoTryRadicalRed"),
i18next.t("splashMessages:eeveeExpo"),
i18next.t("splashMessages:ynoproject"),
i18next.t("splashMessages:breedersInSpace"),
);

return splashMessages;
const splashMessages: string[] = [...commonSplashMessages];
console.log("use seasonal splash messages", USE_SEASONAL_SPLASH_MESSAGES);
if (USE_SEASONAL_SPLASH_MESSAGES) {
// add seasonal splash messages if the season is active
for (const { name, start, end, messages } of seasonalSplashMessages) {
const now = new Date();
const startDate = new Date(`${start}-${now.getFullYear()}`);
const endDate = new Date(`${end}-${now.getFullYear()}`);

if (now >= startDate && now <= endDate) {
console.log(`Adding ${messages.length} ${name} splash messages (weight: x${SEASONAL_WEIGHT_MULTIPLIER})`);
messages.forEach((message) => {
const weightedMessage = Array(SEASONAL_WEIGHT_MULTIPLIER).fill(message);
splashMessages.push(...weightedMessage);
});
}
}
}

return splashMessages.map((message) => `splashMessages:${message}`);
}
2 changes: 1 addition & 1 deletion src/locales/de/splash-messages.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"battlesWon": "Kämpfe gewonnen!",
"battlesWon": "{{count, number}} Kämpfe gewonnen!",
"joinTheDiscord": "Tritt dem Discord bei!",
"infiniteLevels": "Unendliche Level!",
"everythingStacks": "Alles stapelt sich!",
Expand Down
16 changes: 14 additions & 2 deletions src/locales/en/splash-messages.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"battlesWon": "Battles Won!",
"battlesWon": "{{count, number}} Battles Won!",
"joinTheDiscord": "Join the Discord!",
"infiniteLevels": "Infinite Levels!",
"everythingStacks": "Everything Stacks!",
Expand Down Expand Up @@ -32,5 +32,17 @@
"alsoTryRadicalRed": "Also Try Radical Red!",
"eeveeExpo": "Eevee Expo!",
"ynoproject": "YNOproject!",
"breedersInSpace": "Breeders in space!"
"breedersInSpace": "Breeders in space!",
"halloween": {
"pumpkaboosAbout": "Pumpkaboos about!",
"mayContainSpiders": "May contain spiders!",
"spookyScaryDuskulls": "Spooky, Scary Duskulls!"
},
"xmas": {
"happyHolidays": "Happy Holidays!",
"delibirdSeason": "Delibird Season!"
},
"newYears": {
"happyNewYear": "Happy New Year!"
}
}
2 changes: 1 addition & 1 deletion src/locales/es/splash-messages.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"battlesWon": "¡Batallas ganadas!",
"battlesWon": "¡{{count, number}} Batallas ganadas!",
"joinTheDiscord": "¡Únete al Discord!",
"infiniteLevels": "¡Niveles infinitos!",
"everythingStacks": "¡Todo se acumula!",
Expand Down
2 changes: 1 addition & 1 deletion src/locales/fr/splash-messages.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"battlesWon": "combats gagnés !",
"battlesWon": "{{count, number}} combats gagnés !",
"joinTheDiscord": "Rejoins le Discord !",
"infiniteLevels": "Niveaux infinis !",
"everythingStacks": "Tout se cumule !",
Expand Down
2 changes: 1 addition & 1 deletion src/locales/it/splash-messages.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"battlesWon": "Battaglie Vinte!",
"battlesWon": "{{count, number}} Battaglie Vinte!",
"joinTheDiscord": "Entra nel Discord!",
"infiniteLevels": "Livelli Infiniti!",
"everythingStacks": "Tutto si impila!",
Expand Down
2 changes: 1 addition & 1 deletion src/locales/ja/splash-messages.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"battlesWon": "Battles Won!",
"battlesWon": "勝ったバトル:{{count, number}}回!",
"joinTheDiscord": "Join the Discord!",
"infiniteLevels": "Infinite Levels!",
"everythingStacks": "Everything Stacks!",
Expand Down
2 changes: 1 addition & 1 deletion src/locales/ko/splash-messages.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"battlesWon": "전투에서 승리하세요!",
"battlesWon": "{{count, number}} 전투에서 승리하세요!",
"joinTheDiscord": "디스코드에 가입하세요!",
"infiniteLevels": "무한한 레벨!",
"everythingStacks": "모든 것이 누적됩니다!",
Expand Down
2 changes: 1 addition & 1 deletion src/locales/pt_BR/splash-messages.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"battlesWon": "Batalhas Ganhas!",
"battlesWon": "{{count, number}} Batalhas Ganhas!",
"joinTheDiscord": "Junte-se ao Discord!",
"infiniteLevels": "Níveis Infinitos!",
"everythingStacks": "Tudo Acumula!",
Expand Down
2 changes: 1 addition & 1 deletion src/locales/zh_CN/splash-messages.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"battlesWon": "场胜利!",
"battlesWon": "{{count, number}} 场胜利!",
"joinTheDiscord": "加入Discord!",
"infiniteLevels": "等级无限!",
"everythingStacks": "道具全部叠加!",
Expand Down
2 changes: 1 addition & 1 deletion src/locales/zh_TW/splash-messages.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"battlesWon": "勝利場數!",
"battlesWon": "{{count, number}} 勝利場數!",
"joinTheDiscord": "加入Discord!",
"infiniteLevels": "無限等級!",
"everythingStacks": "所有效果都能疊加!",
Expand Down
66 changes: 66 additions & 0 deletions src/test/data/splash_messages.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { getSplashMessages } from "#app/data/splash-messages";
import { describe, expect, it, vi, afterEach, beforeEach } from "vitest";
import * as Constants from "#app/constants";

describe("Data - Splash Messages", () => {
it("should contain at least 15 splash messages", () => {
expect(getSplashMessages().length).toBeGreaterThanOrEqual(15);
});

// make sure to adjust this test if the weight it changed!
it("should add contain 10 `battlesWon` splash messages", () => {
const battlesWonMessages = getSplashMessages().filter((message) => message === "splashMessages:battlesWon");
expect(battlesWonMessages).toHaveLength(10);
});

describe("Seasonal", () => {
beforeEach(() => {
vi.spyOn(Constants, "USE_SEASONAL_SPLASH_MESSAGES", "get").mockReturnValue(true);
});

afterEach(() => {
vi.useRealTimers(); // reset system time
});

it("should contain halloween messages from Sep 15 to Oct 31", () => {
testSeason(new Date("2024-09-15"), new Date("2024-10-31"), "halloween");
});

it("should contain xmas messages from Dec 1 to Dec 26", () => {
testSeason(new Date("2024-12-01"), new Date("2024-12-26"), "xmas");
});

it("should contain new years messages frm Jan 1 to Jan 31", () => {
testSeason(new Date("2024-01-01"), new Date("2024-01-31"), "newYears");
});
});
});

/**
* Helpoer method to test seasonal messages
* @param startDate The seasons start date
* @param endDate The seasons end date
* @param prefix the splash message prefix (e.g. `newYears` or `xmas`)
*/
function testSeason(startDate: Date, endDate: Date, prefix: string) {
const filterFn = (message: string) => message.startsWith(`splashMessages:${prefix}.`);

const beforeDate = new Date(startDate);
beforeDate.setDate(startDate.getDate() - 1);

const afterDate = new Date(endDate);
afterDate.setDate(endDate.getDate() + 1);

const dates: Date[] = [beforeDate, startDate, endDate, afterDate];
const [before, start, end, after] = dates.map((date) => {
vi.setSystemTime(date);
console.log("System time set to", date);
const count = getSplashMessages().filter(filterFn).length;
return count;
});

expect(before).toBe(0);
expect(start).toBeGreaterThanOrEqual(10); // make sure to adjust if weight is changed!
expect(end).toBeGreaterThanOrEqual(10); // make sure to adjust if weight is changed!
expect(after).toBe(0);
}
2 changes: 2 additions & 0 deletions src/test/vitest.setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import { initStatsKeys } from "#app/ui/game-stats-ui-handler";
import { initMysteryEncounters } from "#app/data/mystery-encounters/mystery-encounters";
import { beforeAll, vi } from "vitest";

process.env.TZ = "UTC";

/** Mock the override import to always return default values, ignoring any custom overrides. */
vi.mock("#app/overrides", async (importOriginal) => {
const { defaultOverrides } = await importOriginal<typeof import("#app/overrides")>();
Expand Down
11 changes: 7 additions & 4 deletions src/ui/title-ui-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@ import OptionSelectUiHandler from "./settings/option-select-ui-handler";
import { Mode } from "./ui";
import * as Utils from "../utils";
import { TextStyle, addTextObject, getTextStyleOptions } from "./text";
import { getBattleCountSplashMessage, getSplashMessages } from "../data/splash-messages";
import { getSplashMessages } from "../data/splash-messages";
import i18next from "i18next";
import { TimedEventDisplay } from "#app/timed-event-manager";

export default class TitleUiHandler extends OptionSelectUiHandler {
/** If the stats can not be retrieved, use this fallback value */
private static readonly BATTLES_WON_FALLBACK: number = -99999999;

private titleContainer: Phaser.GameObjects.Container;
private playerCountLabel: Phaser.GameObjects.Text;
private splashMessage: string;
Expand Down Expand Up @@ -72,8 +75,8 @@ export default class TitleUiHandler extends OptionSelectUiHandler {
.then(request => request.json())
.then(stats => {
this.playerCountLabel.setText(`${stats.playerCount} ${i18next.t("menu:playersOnline")}`);
if (this.splashMessage === getBattleCountSplashMessage()) {
this.splashMessageText.setText(getBattleCountSplashMessage().replace("{COUNT}", stats.battleCount.toLocaleString("en-US")));
if (this.splashMessage === "splashMessages:battlesWon") {
this.splashMessageText.setText(i18next.t(this.splashMessage, { count: stats.battlesWon }));
}
})
.catch(err => {
Expand All @@ -86,7 +89,7 @@ export default class TitleUiHandler extends OptionSelectUiHandler {

if (ret) {
this.splashMessage = Utils.randItem(getSplashMessages());
this.splashMessageText.setText(this.splashMessage.replace("{COUNT}", "?"));
this.splashMessageText.setText(i18next.t(this.splashMessage, { count: TitleUiHandler.BATTLES_WON_FALLBACK }));

const ui = this.getUi();

Expand Down

0 comments on commit 48430c8

Please sign in to comment.