From f4cb00cd77916aa63ca168e6edd106c154698201 Mon Sep 17 00:00:00 2001 From: Lennard Fonteijn Date: Sun, 11 Aug 2024 17:08:59 +0200 Subject: [PATCH 01/10] Added new flags format for the in-game menu system Added plugin for the in-game menu system Added example plugin with using custom flags --- components/flags.ts | 389 +++++++++++++-------- components/index.ts | 5 +- components/profileHandler.ts | 84 ++++- components/types/types.ts | 22 +- plugins/elusive-destinations.plugin.ts | 74 ++++ plugins/peacock-menu.plugin.ts | 311 ++++++++++++++++ plugins/peacock-menu/flags/category.json | 69 ++++ plugins/peacock-menu/flags/flag.json | 128 +++++++ plugins/peacock-menu/flags/index.json | 46 +++ plugins/peacock-menu/hub.json | 101 ++++++ plugins/peacock-menu/index.json | 13 + plugins/peacock-menu/test/description.json | 22 ++ plugins/peacock-menu/test/index.json | 221 ++++++++++++ static/HubPageData.json | 5 + 14 files changed, 1345 insertions(+), 145 deletions(-) create mode 100644 plugins/elusive-destinations.plugin.ts create mode 100644 plugins/peacock-menu.plugin.ts create mode 100644 plugins/peacock-menu/flags/category.json create mode 100644 plugins/peacock-menu/flags/flag.json create mode 100644 plugins/peacock-menu/flags/index.json create mode 100644 plugins/peacock-menu/hub.json create mode 100644 plugins/peacock-menu/index.json create mode 100644 plugins/peacock-menu/test/description.json create mode 100644 plugins/peacock-menu/test/index.json diff --git a/components/flags.ts b/components/flags.ts index 8bd9c974d..ef44be0ce 100644 --- a/components/flags.ts +++ b/components/flags.ts @@ -17,117 +17,180 @@ */ import { existsSync, readFileSync, writeFileSync } from "fs" -import type { Flags } from "./types/types" +import type { FlagSection, Flags } from "./types/types" import { log, LogLevel } from "./loggingInterop" -import { parse } from "js-ini" +import { IIniObjectSection, parse } from "js-ini" import type { IIniObject } from "js-ini/lib/interfaces/ini-object" +let tempFlags: IIniObject = {} let flags: IIniObject = {} -const defaultFlags: Flags = { - gameplayUnlockAllShortcuts: { - desc: "[Gameplay] When set to true, all shortcuts will always be unlocked.", - default: false, - }, - gameplayUnlockAllFreelancerMasteries: { - desc: "[Gameplay] When set to true, all Freelancer unlocks will always be available.", - default: false, - }, - mapDiscoveryState: { - desc: '[Gameplay] Decides what to do with the discovery state of the maps. REVEALED will reset all map locations to discovered, CLOUDED will reset all maps to undiscovered, and KEEP will keep your current discovery state. Note that these actions will take effect every time you connect to Peacock. Your progress of the "Discover [Location]" challenges will not be affected by this option.', - default: "KEEP", - }, - enableMasteryProgression: { - desc: "[Gameplay] When set to false, mastery progression will be disabled and all unlockables will be awarded at the beginning", - default: true, - }, - elusivesAreShown: { - desc: "[Gameplay] Show elusive targets in instinct like normal targets would appear on normal missions. (for speedrunners who are submitting to speedrun.com, just as a reminder, this tool is for practice only!)", - default: false, - }, - legacyNoticedKillScoring: { - desc: '[Gameplay] In the HITMAN 2016 engine, if noticed kills should behave in the official way ("vanilla"), or how they were previously handled by Peacock ("sane")', - default: "vanilla", - }, - jokes: { - desc: "[Services] The Peacock server window will tell you a joke on startup if this is set to true.", - default: false, - }, - leaderboards: { - desc: "[Services] Allow your times to be submitted to the ingame leaderboards. If you do not want your times on the leaderboards, change this to false.", - default: true, - }, - updateChecking: { - desc: "[Services] Allow Peacock to check for updates on startup.", - default: true, - }, - loadoutSaving: { - desc: "[Services] Default loadout mode - either PROFILES (loadout profiles) or LEGACY for per-user saving", - default: "PROFILES", - }, - legacyContractDownloader: { - desc: "[Services] When set to true, the official servers will be used for contract downloading in H3, which only works for the platform you are playing on. When false, the HITMAPS servers will be used instead. Note that this option only pertains to H3. Official servers will be used for H1 and H2 regardless of the value of this option.", - default: false, - }, - imageLoading: { - desc: "[Services] How images are loaded. SAVEASREQUESTED will fetch images from online when needed (and save them in the images folder), ONLINE will fetch them without saving, and OFFLINE will load them from the image folder", - default: "SAVEASREQUESTED", - }, - liveSplit: { - desc: "[Splitter] Toggle LiveSplit support on or off", - default: false, - }, - autoSplitterCampaign: { - desc: "[Splitter] Which (main) campaign to use for the AutoSplitter. Can be set to 1, 2, 3, or 'trilogy'.", - default: "trilogy", - }, - autoSplitterRacetimegg: { - desc: "[Splitter] When set to true, autosplitter is set in a special mode for use with livesplit integration for racetime.gg realtime races.", - default: false, - }, - autoSplitterForceSilentAssassin: { - desc: "[Splitter] When set to true, the autosplitter will only accept missions completed with silent assassin to be valid completions. When false, any completion will split.", - default: true, - }, - discordRp: { - desc: "[Discord] Toggle Discord rich presence on or off.", - default: false, - }, - discordRpAppTime: { - desc: "[Discord] For Discord Rich Presence, if set to false, the time playing the current level will be shown, and if set to true, the total time using Peacock will be shown.", - default: false, - }, - overrideFrameworkChecks: { - desc: "[Modding] Forcibly disable installed mod checks", - default: false, - }, - experimentalHMR: { - desc: "[Experimental] Toggle hot reloading of contracts", - default: false, - }, - developmentPluginDevHost: { - desc: "[Development - Workspace required] Toggle loading of plugins with a .ts/.cts extension inside the /plugins folder", - default: false, - }, - leaderboardsHost: { - desc: "[Development] Please do not modify - intended for development only", - default: "https://backend.rdil.rocks", - }, - developmentLogRequests: { - desc: "[Development] When set to true, will log the body of all requests the game makes. This can cause huge log files!", - default: false, - }, - legacyElusivesEnableSaving: { - desc: '[Gameplay] When set to true, playing elusive target missions in Hitman 2016 will share the same restarting/replanning/saving rules with normal missions, but the "Elusive Target [Location]" challenges will not be completable. These challenges will only be completable when this option is set to false.', - default: false, - }, - getDefaultSuits: { - desc: `[Gameplay] Set this to true to add all the default starting suits to your inventory. Note: If you set both this and "enableMasteryProgression" to "true" at the same time, a starting suit that is also the unlock for a challenge/mastery will be locked behind its challenge/mastery.`, - default: false, +export const defaultFlags: Flags = { + peacock: { + title: "Peacock", + desc: "All options for Peacock.", + flags: { + gameplayUnlockAllShortcuts: { + category: "Gameplay", + title: "gameplayUnlockAllShortcuts", + desc: "When set to true, all shortcuts will always be unlocked.", + default: false, + }, + gameplayUnlockAllFreelancerMasteries: { + category: "Gameplay", + title: "gameplayUnlockAllFreelancerMasteries", + desc: "When set to true, all Freelancer unlocks will always be available.", + default: false, + }, + mapDiscoveryState: { + category: "Gameplay", + title: "mapDiscoveryState", + desc: "Decides what to do with the discovery state of the maps. REVEALED will reset all map locations to discovered, CLOUDED will reset all maps to undiscovered, and KEEP will keep your current discovery state. Note that these actions will take effect every time you connect to Peacock. Your progress of the \"Discover [Location]\" challenges will not be affected by this option.", + possibleValues: ["REVEALED", "CLOUDED", "KEEP"], + default: "KEEP", + }, + enableMasteryProgression: { + category: "Gameplay", + title: "enableMasteryProgression", + desc: "When set to false, mastery progression will be disabled and all unlockables will be awarded at the beginning", + default: true, + }, + elusivesAreShown: { + category: "Gameplay", + title: "elusivesAreShown", + desc: "Show elusive targets in instinct like normal targets would appear on normal missions. (for speedrunners who are submitting to speedrun.com, just as a reminder, this tool is for practice only!)", + default: false, + }, + legacyNoticedKillScoring: { + category: "Gameplay", + title: "legacyNoticedKillScoring", + desc: "In the HITMAN 2016 engine, if noticed kills should behave in the official way (\"vanilla\"), or how they were previously handled by Peacock (\"sane\")", + possibleValues: ["vanilla", "sane"], + default: "vanilla", + }, + legacyElusivesEnableSaving: { + category: "Services", + title: "legacyElusivesEnableSaving", + desc: "When set to true, playing elusive target missions in Hitman 2016 will share the same restarting/replanning/saving rules with normal missions, but the \"Elusive Target [Location]\" challenges will not be completable. These challenges will only be completable when this option is set to false.", + default: false, + }, + getDefaultSuits: { + category: "Services", + title: "getDefaultSuits", + desc: "Set this to true to add all the default starting suits to your inventory. Note: If you set both this and \"enableMasteryProgression\" to \"true\" at the same time, a starting suit that is also the unlock for a challenge/mastery will be locked behind its challenge/mastery.", + default: false, + }, + jokes: { + category: "Services", + title: "jokes", + desc: "The Peacock server window will tell you a joke on startup if this is set to true.", + default: false, + }, + leaderboards: { + category: "Services", + title: "leaderboards", + desc: "Allow your times to be submitted to the ingame leaderboards. If you do not want your times on the leaderboards, change this to false.", + default: true, + }, + updateChecking: { + category: "Services", + title: "updateChecking", + desc: "Allow Peacock to check for updates on startup.", + default: true, + }, + loadoutSaving: { + category: "Services", + title: "loadoutSaving", + desc: "Default loadout mode - either PROFILES (loadout profiles) or LEGACY for per-user saving", + possibleValues: ["PROFILES", "LEGACY"], + default: "PROFILES", + }, + legacyContractDownloader: { + category: "Services", + title: "legacyContractDownloader", + desc: "When set to true, the official servers will be used for contract downloading in H3, which only works for the platform you are playing on. When false, the HITMAPS servers will be used instead. Note that this option only pertains to H3. Official servers will be used for H1 and H2 regardless of the value of this option.", + default: false, + }, + imageLoading: { + category: "Services", + title: "imageLoading", + desc: "How images are loaded. SAVEASREQUESTED will fetch images from online when needed (and save them in the images folder), ONLINE will fetch them without saving, and OFFLINE will load them from the image folder", + possibleValues: ["SAVEASREQUESTED", "ONLINE", "OFFLINE"], + default: "SAVEASREQUESTED", + }, + liveSplit: { + category: "Splitter", + title: "LiveSplit", + desc: "Toggle LiveSplit support on or off", + default: false, + }, + autoSplitterCampaign: { + category: "Splitter", + title: "Campaign for AutoSplitter", + desc: "Which (main) campaign to use for the AutoSplitter. Can be set to 1, 2, 3, or 'trilogy'.", + possibleValues: ["1", "2", "3", "trilogy"], + default: "trilogy", + }, + autoSplitterRacetimegg: { + category: "Splitter", + title: "AutoSplitter with racetime.gg", + desc: "When set to true, autosplitter is set in a special mode for use with livesplit integration for racetime.gg realtime races.", + default: false, + }, + autoSplitterForceSilentAssassin: { + category: "Splitter", + title: "Only split when Silent Assassin", + desc: "When set to true, the autosplitter will only accept missions completed with silent assassin to be valid completions. When false, any completion will split.", + default: true, + }, + discordRp: { + category: "Discord", + title: "Discord rich presence", + desc: "Toggle Discord rich presence on or off.", + default: false, + }, + discordRpAppTime: { + category: "Discord", + title: "discordRpAppTime", + desc: "For Discord Rich Presence, if set to false, the time playing the current level will be shown, and if set to true, the total time using Peacock will be shown.", + default: false, + }, + overrideFrameworkChecks: { + category: "Modding", + title: "overrideFrameworkChecks", + desc: "Forcibly disable installed mod checks", + default: false, + }, + experimentalHMR: { + category: "Experimental", + title: "experimentalHMR", + desc: "Toggle hot reloading of contracts", + default: false, + }, + developmentPluginDevHost: { + category: "Development", + title: "developmentPluginDevHost", + desc: "[Workspace required] Toggle loading of plugins with a .ts/.cts extension inside the /plugins folder", + default: false + }, + leaderboardsHost: { + category: "Development", + title: "leaderboardsHost", + desc: "Please do not modify - intended for development only", + default: "https://backend.rdil.rocks", + showIngame: false, + }, + developmentLogRequests: { + category: "Development", + title: "developmentLogRequests", + desc: "When set to true, will log the body of all requests the game makes. This can cause huge log files!", + default: false, + }, + }, }, } -const NEW_FLAGS_FILE = "options.ini" +const FLAGS_FILE = "options-new.ini" /** * Get a flag from the flag file. @@ -136,60 +199,108 @@ const NEW_FLAGS_FILE = "options.ini" * @returns The flag's value. */ export function getFlag(flagId: string): string | boolean | number { + const { section, flag } = convertFlagId(flagId) + + const tempSection = flags[section] as IIniObjectSection + + if(!tempSection) { + return defaultFlags[section].flags[flag].default + } + return ( - (flags[flagId] as string | boolean | number) ?? - defaultFlags[flagId].default + tempSection[flag] as string | boolean | number ?? + defaultFlags[section].flags[flag].default ) } -/** - * At this point, you may be asking "what on Earth does this do?" - I completely understand. - * - * It should do something along the lines of generating a string that is the flags - * file with the appropriate comments (js-ini's stringify doesn't support them), - * and all the flags will either be the default value, or what they are set to already. - */ -const makeFlagsIni = ( - _flags: IIniObject | { desc: string; default: string }[], -): string => - Object.keys(defaultFlags) - .map((flagId) => { - return `; ${defaultFlags[flagId].desc} -${flagId} = ${ - // @ts-expect-error You know what, I don't care - _flags[flagId] - }` +export function setFlag( + flagId: string, + value: string | boolean | number, +): void { + const { section, flag } = convertFlagId(flagId) + + const tempSection = flags[section] as IIniObjectSection + tempSection[flag] = value +} + +function convertFlagId(flagId: string) { + const splittedFlagId = flagId.split(".") + const sectionKey = + splittedFlagId.length === 1 ? "peacock" : splittedFlagId[0] + const flagKey = + splittedFlagId.length === 1 ? splittedFlagId[0] : splittedFlagId[1] + + return { + section: sectionKey, + flag: flagKey, + } +} + +export function saveFlags() { + const lines: string[] = [] + + Object.keys(defaultFlags).forEach((sectionKey) => { + const defaultSection = defaultFlags[sectionKey] + const section = flags[sectionKey] as IIniObjectSection + + lines.push(`; ${defaultSection.title} - ${defaultSection.desc}`) + lines.push(`[${sectionKey}]`) + + Object.keys(defaultSection.flags).forEach((flagKey) => { + const defaultFlag = defaultSection.flags[flagKey] + const flag = section[flagKey] + + const category = defaultFlag.category ? `[${defaultFlag.category}] ` : ""; + + lines.push(`; ${category}${defaultFlag.title || flag} - ${defaultFlag.desc}`) + lines.push(`${flagKey}=${flag}`) + lines.push("") }) - .join("\n\n") + }) + + writeFileSync(FLAGS_FILE, lines.join("\n")) +} /** * Loads all flags. */ export function loadFlags(): void { - if (!existsSync(NEW_FLAGS_FILE)) { - const allTheFlags = {} + if (!existsSync(FLAGS_FILE)) { + writeFileSync(FLAGS_FILE, "") + } - Object.keys(defaultFlags).forEach((f) => { - // @ts-expect-error You know what, I don't care - allTheFlags[f] = defaultFlags[f].default - }) + // Load the current INI-file + tempFlags = parse(readFileSync(FLAGS_FILE).toString()) - const ini = makeFlagsIni(allTheFlags) + // Create a new INI-file + flags = {} - writeFileSync(NEW_FLAGS_FILE, ini) - } + // Re-create the default flags in the new INI-file, but keep the existing values from the current INI-file. + // NOTE: This will intentionally drop any non-existing sections/flags! + Object.keys(defaultFlags).forEach(loadFlagSection) + + log(LogLevel.DEBUG, "Loaded all default flags.") +} - flags = parse(readFileSync(NEW_FLAGS_FILE).toString()) +function loadFlagSection(sectionKey: string) { + flags[sectionKey] = {} - Object.keys(defaultFlags).forEach((key) => { - if (!Object.prototype.hasOwnProperty.call(flags, key)) { - flags[key] = defaultFlags[key].default - } + const defaultFlagKeys = Object.keys(defaultFlags[sectionKey].flags) + + defaultFlagKeys.forEach((flag) => { + const currentFlagValue = tempFlags[sectionKey] + ? (tempFlags[sectionKey] as IIniObjectSection)[flag] + : undefined + + const tempSection = flags[sectionKey] as IIniObjectSection + tempSection[flag] = currentFlagValue ?? defaultFlags[sectionKey].flags[flag].default }) +} - writeFileSync(NEW_FLAGS_FILE, makeFlagsIni(flags)) +export function registerFlagSection(sectionKey: string, section: FlagSection) { + defaultFlags[sectionKey] = section - log(LogLevel.DEBUG, "Loaded flags.") + loadFlagSection(sectionKey) } /** diff --git a/components/index.ts b/components/index.ts index ee03cf0a6..acd81b801 100644 --- a/components/index.ts +++ b/components/index.ts @@ -75,7 +75,7 @@ import { multiplayerRouter } from "./multiplayer/multiplayerService" import { multiplayerMenuDataRouter } from "./multiplayer/multiplayerMenuData" import { liveSplitManager } from "./livesplit/liveSplitManager" import { cheapLoadUserData, setupFileStructure } from "./databaseHandler" -import { getFlag } from "./flags" +import { getFlag, saveFlags } from "./flags" const host = process.env.HOST || "0.0.0.0" const port = process.env.PORT || 80 @@ -511,6 +511,9 @@ export async function startServer(options: { await loadouts.init() await controller.boot(options.pluginDevHost) + // all plugins had a chance to provide their flags now + saveFlags() + const httpServer = http.createServer(app) // @ts-expect-error Non-matching method sig diff --git a/components/profileHandler.ts b/components/profileHandler.ts index 39e252e34..9416e9545 100644 --- a/components/profileHandler.ts +++ b/components/profileHandler.ts @@ -18,6 +18,7 @@ import { Router } from "express" import path from "path" +import querystring from "querystring" import { castUserProfile, getMaxProfileLevel, @@ -39,7 +40,7 @@ import { UpdateUserSaveFileTableBody, UserProfile, } from "./types/types" -import { log, LogLevel } from "./loggingInterop" +import { log, logDebug, LogLevel } from "./loggingInterop" import { deleteContractSession, getContractSession, @@ -64,6 +65,7 @@ import { ResolveGamerTagsBody, } from "./types/gameSchemas" import assert from "assert" +import { SyncBailHook } from "./hooksImpl" const profileRouter = Router() @@ -937,4 +939,84 @@ export async function loadSession( ) } +export const handleCommand: SyncBailHook< + [/** lastResponse */ unknown, /** command */ string, /** args */ unknown], + unknown +> = new SyncBailHook() + +const peacockCommandPrefix = "peacock:" +const commandResponseCache: { + [key: string]: unknown +} = {} + +profileRouter.post( + "/ProfileService/GetSemLinkStatus", + jsonMiddleware(), + (_, res) => { + res.json({ + CacheBuster: Date.now().toString(), + Commands: commandResponseCache, + IsConfirmed: true, + LinkedEmail: "mail@example.com", + IOIAccountId: nilUuid, + IOIAccountBaseUrl: "https://account.ioi.dk", + }) + }, +) + +profileRouter.post( + "/ProfileService/SubmitSemEmail", + jsonMiddleware(), + // @ts-expect-error Has jwt props. + (req: RequestWithJwt, res) => { + if (req.body.email.startsWith(peacockCommandPrefix)) { + try { + const commands = req.body.email + .substring(peacockCommandPrefix.length) + .split("|") + + commands.forEach((c) => { + const commandIndex = c.indexOf("?") + + let command = undefined + let args = undefined + + if (commandIndex < 0) { + command = c + args = {} + } else { + command = c.substring(0, commandIndex) + args = querystring.parse(c.substring(commandIndex + 1)) + } + + if (command === "clear") { + (args.commands as string)?.split(",").forEach((c2) => { + commandResponseCache[c2] = undefined + }) + } else { + commandResponseCache[command] = handleCommand.call( + commandResponseCache[command], + command, + args, + ) + } + + logDebug(command, args, commandResponseCache) + }) + } catch (e) { + log(LogLevel.ERROR, `Failed to handle Peacock command: ${e}`) + } + } + + res.json({ + CacheBuster: Date.now().toString(), + Commands: commandResponseCache, + IsConfirmed: true, + LinkedEmail: "mail@example.com", + IOIAccountId: nilUuid, + IOIAccountBaseUrl: "https://account.ioi.dk", + }) + }, +) + export { profileRouter } diff --git a/components/types/types.ts b/components/types/types.ts index 302594542..0e5fb9cc9 100644 --- a/components/types/types.ts +++ b/components/types/types.ts @@ -1310,10 +1310,24 @@ export interface CompiledChallengeRuntimeData { export type LoadoutSavingMechanism = "PROFILES" | "LEGACY" export type ImageLoadingStrategy = "SAVEASREQUESTED" | "ONLINE" | "OFFLINE" -export type Flags = Record< - string, - { desc: string; default: boolean | string | number } -> +export type Flag = { + category?: string + title: string + desc: string + possibleValues?: string[] + default: boolean | string | number + showIngame?: boolean + requiresGameRestart?: boolean + requiresPeacockRestart?: boolean +} + +export type FlagSection = { + title: string + desc: string + flags: Record +} + +export type Flags = Record /** * A "hit" object. diff --git a/plugins/elusive-destinations.plugin.ts b/plugins/elusive-destinations.plugin.ts new file mode 100644 index 000000000..024361ff7 --- /dev/null +++ b/plugins/elusive-destinations.plugin.ts @@ -0,0 +1,74 @@ +import { Controller } from "@peacockproject/core/controller" +import { LogLevel, log } from "@peacockproject/core/loggingInterop" +import { orderedETs } from "@peacockproject/core/contracts/elusiveTargets" +import { FlagSection } from "@peacockproject/core/types/types" +import { getFlag, registerFlagSection } from "@peacockproject/core/flags" + +const pluginFlagSectionKey = "elusiveDestinations" +const pluginFlagSection: FlagSection = { + title: "Plugin - Elusive Destinations", + desc: "Add elusive targets to Destinations", + flags: { + allDifficulties: { + title: "All difficulties", + desc: "When turned on, elusive targets can be played on any difficulty.", + default: true, + requiresPeacockRestart: true + }, + allEntrances: { + title: "All entrances", + desc: "When turned on, any starting location can be chosen for elusive targets, regardless of unlock status.", + default: true, + requiresPeacockRestart: true + }, + smallTiles: { + title: "Small tiles", + desc: "When turned on, show elusive targets as small tiles under Destinations.", + default: true, + requiresPeacockRestart: true + }, + }, +} + +function initPlugin(controller: Controller): void { + log(LogLevel.INFO, "[Plugin] Elusive destinations", "elusive-destinations") + + registerFlagSection(pluginFlagSectionKey, pluginFlagSection) + + for (const contractId of orderedETs) { + const contract = controller.resolveContract(contractId) + + if(!contract) { + continue + } + + // @ts-expect-error Indexer + const baseContractId = controller.missionsInLocations[contract.Metadata.Location][0] + const baseContract = controller.resolveContract(baseContractId) + + if(!baseContract) { + continue + } + + if (getFlag(`${pluginFlagSectionKey}.allDifficulties`)) { + contract.Data.GameDifficulties = baseContract.Data.GameDifficulties + } + + if (getFlag(`${pluginFlagSectionKey}.allEntrances`)) { + contract.Data.Entrances = baseContract.Data.Entrances + } + + if (getFlag(`${pluginFlagSectionKey}.smallTiles`)) { + contract.Metadata.Subtype = "specialassignment" + } + + controller.addMission(contract) + + // @ts-expect-error Indexer + controller.missionsInLocations[contract.Metadata.Location].push( + contract.Metadata.Id, + ) + } +} + +module.exports = initPlugin \ No newline at end of file diff --git a/plugins/peacock-menu.plugin.ts b/plugins/peacock-menu.plugin.ts new file mode 100644 index 000000000..3a17321d3 --- /dev/null +++ b/plugins/peacock-menu.plugin.ts @@ -0,0 +1,311 @@ +import { Controller } from "@peacockproject/core/controller" +import { menuSystemDatabase } from "@peacockproject/core/menus/menuSystem" +import { Flag, FlagSection, GameVersion } from "@peacockproject/core/types/types" +import { LogLevel, log, logDebug } from "@peacockproject/core/loggingInterop" +import { handleCommand as handleCommandHook } from "@peacockproject/core/profileHandler" +import { + defaultFlags, + getAllFlags, + saveFlags, + setFlag, +} from "@peacockproject/core/flags" +import { existsSync, readFileSync } from "fs" +import path from "path" +import { IIniObjectSection, IniValue } from "js-ini" + +interface modalTestResponse { + Count: number +} + +interface buttonTestResponse { + Count: number +} + +interface forEachTestResponse { + Title: string + Body: string +} + +interface getAllFlagsResponse { + key: string + title: string + description: string + // defaultValue?: string | number | boolean + possibleValues?: string[] + valueType: "category" | "boolean" | "string" | "number" | "enum" + value: getAllFlagsResponse[] | boolean | string | number +} + +interface setFlagArgs { + key: string + value: string +} + +type commandFunction = ( + lastResponse: unknown | undefined, + args: unknown, +) => unknown + +const commandMap = new Map([ + ["modalTest", commandModalTest as commandFunction], + ["buttonTest", commandButtonTest as commandFunction], + ["forEachTest", commandForEachTest as commandFunction], + ["getAllFlags", commandGetAllFlags as commandFunction], + ["setFlagBoolean", commandSetFlagBoolean as commandFunction], + ["setFlagEnum", commandSetFlagEnum as commandFunction], +]) + +const pluginPrefix = "/plugins/peacock-menu/" +const jsonExtension = ".json" + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getDatabaseDiff(_configs: string[], _gameVersion: GameVersion) { + return +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getConfig(name: string, _gameVersion: GameVersion) { + if (!name.startsWith(pluginPrefix)) { + return + } + + const fileName = name.substring(pluginPrefix.length) + const cacheBusterIndex = fileName.indexOf(jsonExtension) + const fileNameWithCacheBuster = + cacheBusterIndex < 0 + ? fileName + : fileName.substring(0, cacheBusterIndex + jsonExtension.length) + + const filePath = path.join( + process.cwd(), + "plugins", + "peacock-menu", + fileNameWithCacheBuster, + ) + + if (existsSync(filePath)) { + return JSON.parse(readFileSync(filePath).toString()) + } + + return undefined +} + +function commandModalTest(lastResponse: modalTestResponse): modalTestResponse { + let count = (lastResponse || { Count: 0 }).Count + + return { + Count: ++count, + } +} + +function commandButtonTest( + lastResponse: buttonTestResponse, +): buttonTestResponse { + let count = (lastResponse || { Count: 0 }).Count + + return { + Count: ++count, + } +} + +function commandForEachTest(): forEachTestResponse[] { + return [ + { + Title: "Title #1", + Body: "Body #1", + }, + { + Title: "Title #2", + Body: "Body #2", + }, + { + Title: "Title #3", + Body: "Body #3", + }, + ] +} + +function getFlagType( + defaultFlag: Flag, + value: IniValue, +): { + valueType: "boolean" | "string" | "number" | "enum" + value: boolean | string | number +} { + if (defaultFlag.possibleValues) { + return { + valueType: "enum", + value: value, + } + } + + switch (typeof defaultFlag.default) { + case "string": + return { + valueType: "string", + value: value, + } + + case "number": + return { + valueType: "number", + value: value, + } + + case "boolean": + return { + valueType: "boolean", + value: value, + } + } +} + +function commandGetAllFlags(): getAllFlagsResponse[] { + const allFlags = getAllFlags() + + const flagsArray: getAllFlagsResponse[] = [] + + for (const sectionKey of Object.keys(allFlags)) { + const defaultSection = defaultFlags[sectionKey] + + flagsArray.push({ + key: `${sectionKey}`, + title: defaultSection.title, + description: defaultSection.desc, + valueType: "category", + value: commandGetAllFlagsForSection(sectionKey, defaultSection, allFlags[sectionKey] as IIniObjectSection), + }) + } + + return flagsArray +} + +function commandGetAllFlagsForSection(sectionKey: string, section: FlagSection, sectionValues: IIniObjectSection): getAllFlagsResponse[] { + const flagsArray: getAllFlagsResponse[] = [] + + const categoryMap = new Map() + + for(const flagKey of Object.keys(section.flags)) { + const flag = section.flags[flagKey] + + if(flag.showIngame === false) { + continue; + } + + const flagsType = getFlagType( + flag, + sectionValues[flagKey], + ) + + if (!flagsType) { + continue + } + + const noteLines = []; + + if(flag.requiresGameRestart) { + noteLines.push("Game has to be restarted before changes take effect!"); + } + + if(flag.requiresPeacockRestart) { + noteLines.push("Peacock has to be restarted before changes take effect!"); + } + + const flagResult = { + key: `${sectionKey}.${flagKey}`, + title: flag.title, + description: `${flag.desc}\n\nDefault value: ${flag.default}${noteLines.length > 0 ? "\n\nNotes:\n- " + noteLines.join("\n- ") : ""}`, + // defaultValue: flag.default, + possibleValues: flag.possibleValues, + ...flagsType, + } + + if(flag.category) { + let categoryFlag = categoryMap.get(flag.category) + + if(!categoryFlag) { + categoryFlag = { + key: `${sectionKey}.${flag.category}`, + title: flag.category, + description: "", + valueType: "category", + value: [], + } + + categoryMap.set(flag.category, categoryFlag) + } + + (categoryFlag.value as getAllFlagsResponse[]).push(flagResult) + } + else { + flagsArray.push(flagResult) + } + } + + const categoryFlags = [...categoryMap.values()] + + return [ + ...categoryFlags, + ...flagsArray + ] +} + +function commandSetFlagBoolean(_lastResponse: unknown, args: setFlagArgs): void { + const keys = args.key.split(".") + const section = keys[0] + const key = keys[1] + + if (!defaultFlags[section]?.flags[key]) { + return + } + + setFlag(args.key, args.value === "true") + + saveFlags() +} + +function commandSetFlagEnum(_lastResponse: unknown, args: setFlagArgs): void { + const keys = args.key.split(".") + const section = keys[0] + const key = keys[1] + + if (!defaultFlags[section]?.flags[key]) { + return + } + + setFlag(args.key, args.value) + + saveFlags() +} + +function handleCommand( + lastResponse: unknown, + command: string, + args: unknown, +): unknown { + if (commandMap.has(command)) { + return commandMap.get(command)!(lastResponse, args) + } + + // TODO: Prevent refresh of UI after setFlag and/or remember context (maybe set-value?) + // TODO: Add to pause menu + // TODO: Add support for plugins to add custom menus + // TODO: Reload plugin settings hook + // TODO: Restart peacock option? + // TODO: Add plugin for all weapons unlock + // TODO: Add command to swap profile + // TODO: Add command to reset profile + + return undefined +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function initPlugin(_controller: Controller): void { + log(LogLevel.INFO, "[Plugin] Peacock Menu", "peacock-menu") + + menuSystemDatabase.hooks.getDatabaseDiff.tap("PeacockMenu", getDatabaseDiff) + menuSystemDatabase.hooks.getConfig.tap("PeacockMenu", getConfig) + handleCommandHook.tap("PeacockMenu", handleCommand) +} + +module.exports = initPlugin diff --git a/plugins/peacock-menu/flags/category.json b/plugins/peacock-menu/flags/category.json new file mode 100644 index 000000000..1523a6bd6 --- /dev/null +++ b/plugins/peacock-menu/flags/category.json @@ -0,0 +1,69 @@ +{ + "$datacontext": { + "in": "$.", + "datavalues": { + "Title": "$.title", + "Description": "$.description", + "Flags": "$.value" + }, + "do": { + "view": "menu3.basic.OptionsListElement", + "controller": "contextitem", + "data": { + "title": "$.Title" + }, + "children": [ + { + "view": "menu3.containers.ScrollingListContainer", + "controller": "context", + "direction": "vertical", + "data": { + "direction": "vertical", + "overflowscrolling": "1.0" + }, + "col": 0, + "nrows": 3.75, + "ncols": 4, + "children": { + "$each $.Flags": { + "$if $eq($.valueType,category)": { + "$then": { + "$include": { + "$path": "$formatstring menusystem/plugins/peacock-menu/flags/category.json@{$.@global.CacheBuster}" + } + }, + "$else": { + "$include": { + "$path": "$formatstring menusystem/plugins/peacock-menu/flags/flag.json@{$.@global.CacheBuster}" + } + } + } + } + } + } + ], + "actions": { + "select": { + "replace-children": { + "target": "options_info_container", + "children": [ + { + "view": "menu3.basic.OptionsInfo", + "data": { + "title": "$.Title", + "paragraph": "$.Description" + } + } + ] + } + }, + "deselect": { + "replace-children": { + "target": "options_info_container", + "children": [] + } + } + } + } + } +} diff --git a/plugins/peacock-menu/flags/flag.json b/plugins/peacock-menu/flags/flag.json new file mode 100644 index 000000000..14633e7c7 --- /dev/null +++ b/plugins/peacock-menu/flags/flag.json @@ -0,0 +1,128 @@ +{ + "$datacontext": { + "in": "$.", + "datavalues": { + "Title": "$.title", + "Description": "$.description", + "PossibleValues": { + "$arraysort": { + "source": "$.possibleValues", + "property": "" + } + } + }, + "do": { + "view": "menu3.basic.OptionsListElement", + "controller": "tabsitem", + "data": { + "$mergeobjects": [ + { + "title": "$.Title", + "value": "$.value" + }, + { + "$switch $.valueType": [ + { + "case": "enum", + "return": { + "displayValue": "$formatstring [{$.value}]" + } + }, + { + "case": "boolean", + "return": { + "toggle": true, + "displayValue": { + "$if $.value": { + "$then": "$formatstring [{$loc UI_AID_VALUE_ON}]", + "$else": "$formatstring [{$loc UI_AID_VALUE_OFF}]" + } + } + } + } + ] + } + ] + }, + "children": { + "$mergearrays": [ + { + "$switch $.valueType": [ + { + "case": "enum", + "return": { + "controller": "list", + "view": "menu3.containers.ScrollingListContainer", + "col": 4.25, + "nrows": 3.75, + "ncols": 2, + "data": { + "overflowscrolling": "1.0" + }, + "children": { + "$each $.PossibleValues": { + "view": "menu3.basic.OptionsListElementSmall", + "data": { + "title": "{$.}" + }, + "actions": { + "accept": [ + { + "trigger-input": { + "action": "cancel" + } + }, + { + "ioiaccount": { + "mode": "submit-email", + "email": "$formatstring peacock:setFlagEnum?key={$.@parent.key}&value={$.}|getAllFlags" + } + } + ] + } + } + } + } + } + ] + } + ] + }, + "actions": { + "accept": { + "$switch $.valueType": [ + { + "case": "boolean", + "return": { + "ioiaccount": { + "mode": "submit-email", + "email": "$formatstring peacock:setFlagBoolean?key={$.key}&value={$not $.value}|getAllFlags" + } + } + } + ] + }, + "select": { + "replace-children": { + "target": "options_info_container", + "children": [ + { + "view": "menu3.basic.OptionsInfo", + "data": { + "title": "$.Title", + "paragraph": "$.Description" + } + } + ] + } + }, + "deselect": { + "replace-children": { + "target": "options_info_container", + "children": [] + } + } + } + } + } +} diff --git a/plugins/peacock-menu/flags/index.json b/plugins/peacock-menu/flags/index.json new file mode 100644 index 000000000..81c4175d8 --- /dev/null +++ b/plugins/peacock-menu/flags/index.json @@ -0,0 +1,46 @@ +{ + "$datacontext": { + "in": "$.", + "datavalues": { + "Flags": "{$ioiaccountstatus}.Commands.getAllFlags" + }, + "do": { + "view": "menu3.basic.ListElementSmall", + "controller": "tabsitem", + "data": { + "title": "Options", + "icon": "settings" + }, + "children": [ + { + "view": "menu3.containers.ScrollingListContainer", + "controller": "context", + "direction": "vertical", + "data": { + "direction": "vertical", + "overflowscrolling": "1.0" + }, + "col": 2.25, + "nrows": 3.75, + "ncols": 4, + "children": { + "$each $.Flags": { + "$if $eq($.valueType,category)": { + "$then": { + "$include": { + "$path": "$formatstring menusystem/plugins/peacock-menu/flags/category.json@{$.@global.CacheBuster}" + } + }, + "$else": { + "$include": { + "$path": "$formatstring menusystem/plugins/peacock-menu/flags/flag.json@{$.@global.CacheBuster}" + } + } + } + } + } + } + ] + } + } +} diff --git a/plugins/peacock-menu/hub.json b/plugins/peacock-menu/hub.json new file mode 100644 index 000000000..5661c3a3a --- /dev/null +++ b/plugins/peacock-menu/hub.json @@ -0,0 +1,101 @@ +{ + "id": "UI_MENU_PAGE_HUB_PEACOCK", + "view": "menu3.basic.CategoryElement", + "controller": "categoryitem", + "data": { + "title": "Peacock", + "icon": "ioiaccount" + }, + "actions": { + "activated": [ + { + "set-value": { + "target": "$.@global.CurrentBackgroundImage", + "value": "images/backgrounds/menu_bg.jpg" + }, + "ioiaccount": { + "_comment": "Do not trigger an ioiaccount-event after another ioiaccount-event, it will cause the game to crash!", + "mode": "submit-email", + "email": "peacock:getAllFlags" + } + } + ] + }, + "children": [ + { + "controller": "list", + "row": 1, + "col": 6.5, + "nrows": 4.1, + "ncols": 4, + "children": [ + { + "$include": { + "$path": "menusystem/elements/settings/generic/optionsinfo_container.json" + } + } + ] + }, + { + "controller": "list", + "row": 1, + "col": 6.5, + "children": [ + { + "$include": { + "$path": "menusystem/elements/settings/generic/optionsgroupinfo_container.json" + } + } + ] + }, + { + "view": "menu3.containers.ListContainer", + "controller": "tabs", + "row": 1, + "col": 0, + "nrows": 3, + "ncols": 2, + "children": [ + { + "$include": { + "$path": "$formatstring menusystem/plugins/peacock-menu/flags/index.json@{$.@global.CacheBuster}" + } + }, + { + "$include": { + "$path": "$formatstring menusystem/plugins/peacock-menu/test/index.json@{$.@global.CacheBuster}" + } + }, + { + "view": "menu3.basic.ListElementSmall", + "controller": "tabsitem", + "data": { + "title": "Debug - Refresh UI", + "icon": "reset" + }, + "actions": { + "accept": { + "set-value": { + "target": "$.@global.CacheBuster", + "value": "{$ioiaccountstatus}.CacheBuster" + }, + "_ioiaccount": { + "mode": "refresh" + } + } + } + }, + { + "view": "menu3.TextboxElement", + "col": 0, + "data": { + "size": 24, + "width": 500, + "text": "$formatstring CacheBuster Version\n- Next:\t{{$ioiaccountstatus}.CacheBuster}\n- Active:\t{$.@global.CacheBuster}", + "color": "ffffff" + } + } + ] + } + ] +} diff --git a/plugins/peacock-menu/index.json b/plugins/peacock-menu/index.json new file mode 100644 index 000000000..6bc779615 --- /dev/null +++ b/plugins/peacock-menu/index.json @@ -0,0 +1,13 @@ +{ + "$datacontext": { + "in": "$.", + "datavalues": { + "@global.CacheBuster": "0" + }, + "do": { + "$include": { + "$path": "$formatstring menusystem/plugins/peacock-menu/hub.json@{$.@global.CacheBuster}" + } + } + } +} diff --git a/plugins/peacock-menu/test/description.json b/plugins/peacock-menu/test/description.json new file mode 100644 index 000000000..72401b56a --- /dev/null +++ b/plugins/peacock-menu/test/description.json @@ -0,0 +1,22 @@ +{ + "select": { + "replace-children": { + "target": "options_info_container", + "children": [ + { + "view": "menu3.basic.OptionsInfo", + "data": { + "title": "Test Title", + "paragraph": "$formatstring {$.Command1}\n{$.Command2}" + } + } + ] + } + }, + "deselect": { + "replace-children": { + "target": "options_info_container", + "children": [] + } + } +} diff --git a/plugins/peacock-menu/test/index.json b/plugins/peacock-menu/test/index.json new file mode 100644 index 000000000..22b76919d --- /dev/null +++ b/plugins/peacock-menu/test/index.json @@ -0,0 +1,221 @@ +{ + "$datacontext": { + "in": "$.", + "datavalues": { + "Command1": { + "$if $isnull {$ioiaccountstatus}.Commands.buttonTest": { + "$then": "Push the button!", + "$else": "$formatstring You pushed the button {{$ioiaccountstatus}.Commands.buttonTest.Count, .f} times!" + } + }, + "Command2": { + "$if $isnull {$ioiaccountstatus}.Commands.modalTest": { + "$then": "Push the button (on the modal)!", + "$else": "$formatstring You pushed the button (on the modal) {{$ioiaccountstatus}.Commands.modalTest.Count, .f} times!" + } + }, + "Command3": { + "$if $isnull {$ioiaccountstatus}.Commands.forEachTest": { + "$then": "[]", + "$else": "{{$ioiaccountstatus}.Commands}.forEachTest" + } + } + }, + "do": { + "view": "menu3.basic.ListElementSmall", + "controller": "tabsitem", + "data": { + "title": "Test", + "icon": "game" + }, + "children": [ + { + "view": "menu3.containers.ScrollingListContainer", + "controller": "tabs", + "direction": "vertical", + "data": { + "direction": "vertical", + "overflowscrolling": "1.0" + }, + "col": 2.25, + "nrows": 3.75, + "ncols": 4, + "children": { + "$mergearrays": [ + { + "view": "menu3.basic.OptionsListElement", + "controller": "tabsitem", + "data": { + "title": "Button" + }, + "actions": { + "$mergeobjects": [ + { + "$include": { + "$path": "$formatstring menusystem/plugins/peacock-menu/test/description.json@{$.@global.CacheBuster}" + } + }, + { + "accept": { + "ioiaccount": { + "mode": "submit-email", + "email": "peacock:buttonTest?test=test" + } + } + } + ] + } + }, + { + "view": "menu3.basic.OptionsListElement", + "controller": "tabsitem", + "data": { + "title": "Button with Modal" + }, + "actions": { + "$mergeobjects": [ + { + "$include": { + "$path": "$formatstring menusystem/plugins/peacock-menu/test/description.json@{$.@global.CacheBuster}" + } + }, + { + "accept": { + "show-modal": { + "config": { + "view": "menu3.modal.ModalDialogGeneric", + "buttons": [ + { + "label": "Yes", + "type": "ok" + }, + { + "label": "No", + "type": "cancel" + } + ], + "data": { + "title": "Modal Title", + "description": "Modal Body", + "typeoverride": "online" + } + }, + "onbutton": [ + { + "ioiaccount": { + "mode": "submit-email", + "email": "peacock:modalTest?test=test" + } + } + ] + } + } + } + ] + } + }, + { + "view": "menu3.basic.OptionsListElement", + "controller": "tabsitem", + "data": { + "title": "Batch" + }, + "actions": { + "$mergeobjects": [ + { + "$include": { + "$path": "$formatstring menusystem/plugins/peacock-menu/test/description.json@{$.@global.CacheBuster}" + } + }, + { + "accept": { + "ioiaccount": { + "mode": "submit-email", + "email": "peacock:buttonTest?test=test|modalTest?test=test" + } + } + } + ] + } + }, + { + "view": "menu3.basic.OptionsListElement", + "controller": "tabsitem", + "data": { + "title": "Clear" + }, + "actions": { + "$mergeobjects": [ + { + "$include": { + "$path": "$formatstring menusystem/plugins/peacock-menu/test/description.json@{$.@global.CacheBuster}" + } + }, + { + "accept": { + "ioiaccount": { + "mode": "submit-email", + "email": "peacock:clear?commands=buttonTest,modalTest" + } + } + } + ] + } + }, + { + "$mergeobjects": [ + { + "view": "menu3.basic.OptionsListElement", + "controller": "tabsitem" + }, + { + "$if $eq($arraysize $.Command3, 0)": { + "$then": { + "data": { + "title": "Load Dynamic Cards" + }, + "actions": { + "accept": { + "ioiaccount": { + "mode": "submit-email", + "email": "peacock:forEachTest" + } + } + } + }, + "$else": { + "data": { + "title": "Clear Dynamic Cards" + }, + "actions": { + "accept": { + "ioiaccount": { + "mode": "submit-email", + "email": "peacock:clear?commands=forEachTest" + } + } + } + } + } + } + ] + }, + { + "$each $.Command3": { + "view": "menu3.basic.MenuTileSmall", + "controller": "tabsitem", + "data": { + "header": "$formatstring {$.Title}", + "icon": "weapon", + "title": "$formatstring {$.Body}", + "image": "$res images/opportunities/wet/wet_opp_thecontroller.jpg" + } + } + } + ] + } + } + ] + } + } +} diff --git a/static/HubPageData.json b/static/HubPageData.json index 3c33fd6e8..65315f904 100644 --- a/static/HubPageData.json +++ b/static/HubPageData.json @@ -291,6 +291,11 @@ } } }, + { + "$include": { + "$path": "menusystem/plugins/peacock-menu/index.json" + } + }, { "$if $.IsFullMenuAvailable": { "$then": { From 2aeccdeebc6312d5e1774f355f43841cb5dc29b1 Mon Sep 17 00:00:00 2001 From: AnthonyFuller <24512050+AnthonyFuller@users.noreply.github.com> Date: Tue, 20 Aug 2024 04:58:18 +0100 Subject: [PATCH 02/10] feat: change back to options.ini, port older versions I think this should work. --- components/flags.ts | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/components/flags.ts b/components/flags.ts index ef44be0ce..34441a38e 100644 --- a/components/flags.ts +++ b/components/flags.ts @@ -45,7 +45,7 @@ export const defaultFlags: Flags = { mapDiscoveryState: { category: "Gameplay", title: "mapDiscoveryState", - desc: "Decides what to do with the discovery state of the maps. REVEALED will reset all map locations to discovered, CLOUDED will reset all maps to undiscovered, and KEEP will keep your current discovery state. Note that these actions will take effect every time you connect to Peacock. Your progress of the \"Discover [Location]\" challenges will not be affected by this option.", + desc: 'Decides what to do with the discovery state of the maps. REVEALED will reset all map locations to discovered, CLOUDED will reset all maps to undiscovered, and KEEP will keep your current discovery state. Note that these actions will take effect every time you connect to Peacock. Your progress of the "Discover [Location]" challenges will not be affected by this option.', possibleValues: ["REVEALED", "CLOUDED", "KEEP"], default: "KEEP", }, @@ -64,20 +64,20 @@ export const defaultFlags: Flags = { legacyNoticedKillScoring: { category: "Gameplay", title: "legacyNoticedKillScoring", - desc: "In the HITMAN 2016 engine, if noticed kills should behave in the official way (\"vanilla\"), or how they were previously handled by Peacock (\"sane\")", + desc: 'In the HITMAN 2016 engine, if noticed kills should behave in the official way ("vanilla"), or how they were previously handled by Peacock ("sane")', possibleValues: ["vanilla", "sane"], default: "vanilla", }, legacyElusivesEnableSaving: { category: "Services", title: "legacyElusivesEnableSaving", - desc: "When set to true, playing elusive target missions in Hitman 2016 will share the same restarting/replanning/saving rules with normal missions, but the \"Elusive Target [Location]\" challenges will not be completable. These challenges will only be completable when this option is set to false.", + desc: 'When set to true, playing elusive target missions in Hitman 2016 will share the same restarting/replanning/saving rules with normal missions, but the "Elusive Target [Location]" challenges will not be completable. These challenges will only be completable when this option is set to false.', default: false, }, getDefaultSuits: { category: "Services", title: "getDefaultSuits", - desc: "Set this to true to add all the default starting suits to your inventory. Note: If you set both this and \"enableMasteryProgression\" to \"true\" at the same time, a starting suit that is also the unlock for a challenge/mastery will be locked behind its challenge/mastery.", + desc: 'Set this to true to add all the default starting suits to your inventory. Note: If you set both this and "enableMasteryProgression" to "true" at the same time, a starting suit that is also the unlock for a challenge/mastery will be locked behind its challenge/mastery.', default: false, }, jokes: { @@ -171,8 +171,8 @@ export const defaultFlags: Flags = { category: "Development", title: "developmentPluginDevHost", desc: "[Workspace required] Toggle loading of plugins with a .ts/.cts extension inside the /plugins folder", - default: false - }, + default: false, + }, leaderboardsHost: { category: "Development", title: "leaderboardsHost", @@ -190,7 +190,7 @@ export const defaultFlags: Flags = { }, } -const FLAGS_FILE = "options-new.ini" +const FLAGS_FILE = "options.ini" /** * Get a flag from the flag file. @@ -203,12 +203,12 @@ export function getFlag(flagId: string): string | boolean | number { const tempSection = flags[section] as IIniObjectSection - if(!tempSection) { + if (!tempSection) { return defaultFlags[section].flags[flag].default } return ( - tempSection[flag] as string | boolean | number ?? + (tempSection[flag] as string | boolean | number) ?? defaultFlags[section].flags[flag].default ) } @@ -250,9 +250,13 @@ export function saveFlags() { const defaultFlag = defaultSection.flags[flagKey] const flag = section[flagKey] - const category = defaultFlag.category ? `[${defaultFlag.category}] ` : ""; - - lines.push(`; ${category}${defaultFlag.title || flag} - ${defaultFlag.desc}`) + const category = defaultFlag.category + ? `[${defaultFlag.category}] ` + : "" + + lines.push( + `; ${category}${defaultFlag.title || flag} - ${defaultFlag.desc}`, + ) lines.push(`${flagKey}=${flag}`) lines.push("") }) @@ -272,6 +276,13 @@ export function loadFlags(): void { // Load the current INI-file tempFlags = parse(readFileSync(FLAGS_FILE).toString()) + if (!tempFlags["peacock"]) { + // This is an options file from before the rewrite. + tempFlags = { + peacock: tempFlags, + } + } + // Create a new INI-file flags = {} @@ -293,7 +304,8 @@ function loadFlagSection(sectionKey: string) { : undefined const tempSection = flags[sectionKey] as IIniObjectSection - tempSection[flag] = currentFlagValue ?? defaultFlags[sectionKey].flags[flag].default + tempSection[flag] = + currentFlagValue ?? defaultFlags[sectionKey].flags[flag].default }) } From 0dfe79c22686f3b5cfc5c5c2a2bd08273bc0afa0 Mon Sep 17 00:00:00 2001 From: AnthonyFuller <24512050+AnthonyFuller@users.noreply.github.com> Date: Tue, 20 Aug 2024 05:02:48 +0100 Subject: [PATCH 03/10] chore: prettier run --- components/profileHandler.ts | 2 +- plugins/elusive-destinations.plugin.ts | 19 ++++---- plugins/peacock-menu.plugin.ts | 64 +++++++++++++++----------- 3 files changed, 49 insertions(+), 36 deletions(-) diff --git a/components/profileHandler.ts b/components/profileHandler.ts index 9416e9545..b8905a757 100644 --- a/components/profileHandler.ts +++ b/components/profileHandler.ts @@ -990,7 +990,7 @@ profileRouter.post( } if (command === "clear") { - (args.commands as string)?.split(",").forEach((c2) => { + ;(args.commands as string)?.split(",").forEach((c2) => { commandResponseCache[c2] = undefined }) } else { diff --git a/plugins/elusive-destinations.plugin.ts b/plugins/elusive-destinations.plugin.ts index 024361ff7..05d29f8cd 100644 --- a/plugins/elusive-destinations.plugin.ts +++ b/plugins/elusive-destinations.plugin.ts @@ -13,19 +13,19 @@ const pluginFlagSection: FlagSection = { title: "All difficulties", desc: "When turned on, elusive targets can be played on any difficulty.", default: true, - requiresPeacockRestart: true + requiresPeacockRestart: true, }, allEntrances: { title: "All entrances", desc: "When turned on, any starting location can be chosen for elusive targets, regardless of unlock status.", default: true, - requiresPeacockRestart: true + requiresPeacockRestart: true, }, smallTiles: { title: "Small tiles", desc: "When turned on, show elusive targets as small tiles under Destinations.", default: true, - requiresPeacockRestart: true + requiresPeacockRestart: true, }, }, } @@ -38,15 +38,16 @@ function initPlugin(controller: Controller): void { for (const contractId of orderedETs) { const contract = controller.resolveContract(contractId) - if(!contract) { + if (!contract) { continue } - - // @ts-expect-error Indexer - const baseContractId = controller.missionsInLocations[contract.Metadata.Location][0] + + const baseContractId = + // @ts-expect-error Indexer + controller.missionsInLocations[contract.Metadata.Location][0] const baseContract = controller.resolveContract(baseContractId) - if(!baseContract) { + if (!baseContract) { continue } @@ -71,4 +72,4 @@ function initPlugin(controller: Controller): void { } } -module.exports = initPlugin \ No newline at end of file +module.exports = initPlugin diff --git a/plugins/peacock-menu.plugin.ts b/plugins/peacock-menu.plugin.ts index 3a17321d3..a49734092 100644 --- a/plugins/peacock-menu.plugin.ts +++ b/plugins/peacock-menu.plugin.ts @@ -1,7 +1,11 @@ import { Controller } from "@peacockproject/core/controller" import { menuSystemDatabase } from "@peacockproject/core/menus/menuSystem" -import { Flag, FlagSection, GameVersion } from "@peacockproject/core/types/types" -import { LogLevel, log, logDebug } from "@peacockproject/core/loggingInterop" +import { + Flag, + FlagSection, + GameVersion, +} from "@peacockproject/core/types/types" +import { LogLevel, log } from "@peacockproject/core/loggingInterop" import { handleCommand as handleCommandHook } from "@peacockproject/core/profileHandler" import { defaultFlags, @@ -173,42 +177,51 @@ function commandGetAllFlags(): getAllFlagsResponse[] { title: defaultSection.title, description: defaultSection.desc, valueType: "category", - value: commandGetAllFlagsForSection(sectionKey, defaultSection, allFlags[sectionKey] as IIniObjectSection), + value: commandGetAllFlagsForSection( + sectionKey, + defaultSection, + allFlags[sectionKey] as IIniObjectSection, + ), }) } return flagsArray } -function commandGetAllFlagsForSection(sectionKey: string, section: FlagSection, sectionValues: IIniObjectSection): getAllFlagsResponse[] { +function commandGetAllFlagsForSection( + sectionKey: string, + section: FlagSection, + sectionValues: IIniObjectSection, +): getAllFlagsResponse[] { const flagsArray: getAllFlagsResponse[] = [] const categoryMap = new Map() - for(const flagKey of Object.keys(section.flags)) { + for (const flagKey of Object.keys(section.flags)) { const flag = section.flags[flagKey] - if(flag.showIngame === false) { - continue; + if (flag.showIngame === false) { + continue } - const flagsType = getFlagType( - flag, - sectionValues[flagKey], - ) + const flagsType = getFlagType(flag, sectionValues[flagKey]) if (!flagsType) { continue } - const noteLines = []; + const noteLines = [] - if(flag.requiresGameRestart) { - noteLines.push("Game has to be restarted before changes take effect!"); + if (flag.requiresGameRestart) { + noteLines.push( + "Game has to be restarted before changes take effect!", + ) } - if(flag.requiresPeacockRestart) { - noteLines.push("Peacock has to be restarted before changes take effect!"); + if (flag.requiresPeacockRestart) { + noteLines.push( + "Peacock has to be restarted before changes take effect!", + ) } const flagResult = { @@ -220,10 +233,10 @@ function commandGetAllFlagsForSection(sectionKey: string, section: FlagSection, ...flagsType, } - if(flag.category) { + if (flag.category) { let categoryFlag = categoryMap.get(flag.category) - if(!categoryFlag) { + if (!categoryFlag) { categoryFlag = { key: `${sectionKey}.${flag.category}`, title: flag.category, @@ -235,22 +248,21 @@ function commandGetAllFlagsForSection(sectionKey: string, section: FlagSection, categoryMap.set(flag.category, categoryFlag) } - (categoryFlag.value as getAllFlagsResponse[]).push(flagResult) - } - else { + ;(categoryFlag.value as getAllFlagsResponse[]).push(flagResult) + } else { flagsArray.push(flagResult) } } const categoryFlags = [...categoryMap.values()] - return [ - ...categoryFlags, - ...flagsArray - ] + return [...categoryFlags, ...flagsArray] } -function commandSetFlagBoolean(_lastResponse: unknown, args: setFlagArgs): void { +function commandSetFlagBoolean( + _lastResponse: unknown, + args: setFlagArgs, +): void { const keys = args.key.split(".") const section = keys[0] const key = keys[1] From 8ebe704ddbf3b21130341af953d9028fc8ac35d8 Mon Sep 17 00:00:00 2001 From: AnthonyFuller <24512050+AnthonyFuller@users.noreply.github.com> Date: Wed, 21 Aug 2024 01:22:31 +0100 Subject: [PATCH 04/10] feat: move peacock menu to options, remove old testing --- plugins/peacock-menu.plugin.ts | 13 +- plugins/peacock-menu/flags/index.json | 50 ++--- plugins/peacock-menu/hub.json | 101 ---------- plugins/peacock-menu/index.json | 2 +- plugins/peacock-menu/test/description.json | 22 -- plugins/peacock-menu/test/index.json | 221 --------------------- static/HubPageData.json | 5 - 7 files changed, 31 insertions(+), 383 deletions(-) delete mode 100644 plugins/peacock-menu/hub.json delete mode 100644 plugins/peacock-menu/test/description.json delete mode 100644 plugins/peacock-menu/test/index.json diff --git a/plugins/peacock-menu.plugin.ts b/plugins/peacock-menu.plugin.ts index a49734092..cb8abbf03 100644 --- a/plugins/peacock-menu.plugin.ts +++ b/plugins/peacock-menu.plugin.ts @@ -62,13 +62,20 @@ const commandMap = new Map([ const pluginPrefix = "/plugins/peacock-menu/" const jsonExtension = ".json" -// eslint-disable-next-line @typescript-eslint/no-unused-vars -function getDatabaseDiff(_configs: string[], _gameVersion: GameVersion) { - return +function getDatabaseDiff(configs: string[], gameVersion: GameVersion) { + if (gameVersion === "h3") { + configs.push( + "menusystem/elements/settings/ioiaccount/ioiaccount_maintab.json", + ) + } } // eslint-disable-next-line @typescript-eslint/no-unused-vars function getConfig(name: string, _gameVersion: GameVersion) { + if (name.endsWith("ioiaccount/ioiaccount_maintab.json")) { + name = "/plugins/peacock-menu/index.json" + } + if (!name.startsWith(pluginPrefix)) { return } diff --git a/plugins/peacock-menu/flags/index.json b/plugins/peacock-menu/flags/index.json index 81c4175d8..4a5ebcaa6 100644 --- a/plugins/peacock-menu/flags/index.json +++ b/plugins/peacock-menu/flags/index.json @@ -5,42 +5,32 @@ "Flags": "{$ioiaccountstatus}.Commands.getAllFlags" }, "do": { - "view": "menu3.basic.ListElementSmall", - "controller": "tabsitem", + "view": "menu3.containers.ScrollingListContainer", + "controller": "context", + "direction": "vertical", "data": { - "title": "Options", - "icon": "settings" + "direction": "vertical", + "overflowscrolling": "1.0" }, - "children": [ - { - "view": "menu3.containers.ScrollingListContainer", - "controller": "context", - "direction": "vertical", - "data": { - "direction": "vertical", - "overflowscrolling": "1.0" - }, - "col": 2.25, - "nrows": 3.75, - "ncols": 4, - "children": { - "$each $.Flags": { - "$if $eq($.valueType,category)": { - "$then": { - "$include": { - "$path": "$formatstring menusystem/plugins/peacock-menu/flags/category.json@{$.@global.CacheBuster}" - } - }, - "$else": { - "$include": { - "$path": "$formatstring menusystem/plugins/peacock-menu/flags/flag.json@{$.@global.CacheBuster}" - } - } + "col": 2.25, + "nrows": 3.75, + "ncols": 4, + "children": { + "$each $.Flags": { + "$if $eq($.valueType,category)": { + "$then": { + "$include": { + "$path": "$formatstring menusystem/plugins/peacock-menu/flags/category.json@{$.@global.CacheBuster}" + } + }, + "$else": { + "$include": { + "$path": "$formatstring menusystem/plugins/peacock-menu/flags/flag.json@{$.@global.CacheBuster}" } } } } - ] + } } } } diff --git a/plugins/peacock-menu/hub.json b/plugins/peacock-menu/hub.json deleted file mode 100644 index 5661c3a3a..000000000 --- a/plugins/peacock-menu/hub.json +++ /dev/null @@ -1,101 +0,0 @@ -{ - "id": "UI_MENU_PAGE_HUB_PEACOCK", - "view": "menu3.basic.CategoryElement", - "controller": "categoryitem", - "data": { - "title": "Peacock", - "icon": "ioiaccount" - }, - "actions": { - "activated": [ - { - "set-value": { - "target": "$.@global.CurrentBackgroundImage", - "value": "images/backgrounds/menu_bg.jpg" - }, - "ioiaccount": { - "_comment": "Do not trigger an ioiaccount-event after another ioiaccount-event, it will cause the game to crash!", - "mode": "submit-email", - "email": "peacock:getAllFlags" - } - } - ] - }, - "children": [ - { - "controller": "list", - "row": 1, - "col": 6.5, - "nrows": 4.1, - "ncols": 4, - "children": [ - { - "$include": { - "$path": "menusystem/elements/settings/generic/optionsinfo_container.json" - } - } - ] - }, - { - "controller": "list", - "row": 1, - "col": 6.5, - "children": [ - { - "$include": { - "$path": "menusystem/elements/settings/generic/optionsgroupinfo_container.json" - } - } - ] - }, - { - "view": "menu3.containers.ListContainer", - "controller": "tabs", - "row": 1, - "col": 0, - "nrows": 3, - "ncols": 2, - "children": [ - { - "$include": { - "$path": "$formatstring menusystem/plugins/peacock-menu/flags/index.json@{$.@global.CacheBuster}" - } - }, - { - "$include": { - "$path": "$formatstring menusystem/plugins/peacock-menu/test/index.json@{$.@global.CacheBuster}" - } - }, - { - "view": "menu3.basic.ListElementSmall", - "controller": "tabsitem", - "data": { - "title": "Debug - Refresh UI", - "icon": "reset" - }, - "actions": { - "accept": { - "set-value": { - "target": "$.@global.CacheBuster", - "value": "{$ioiaccountstatus}.CacheBuster" - }, - "_ioiaccount": { - "mode": "refresh" - } - } - } - }, - { - "view": "menu3.TextboxElement", - "col": 0, - "data": { - "size": 24, - "width": 500, - "text": "$formatstring CacheBuster Version\n- Next:\t{{$ioiaccountstatus}.CacheBuster}\n- Active:\t{$.@global.CacheBuster}", - "color": "ffffff" - } - } - ] - } - ] -} diff --git a/plugins/peacock-menu/index.json b/plugins/peacock-menu/index.json index 6bc779615..3ac471b55 100644 --- a/plugins/peacock-menu/index.json +++ b/plugins/peacock-menu/index.json @@ -6,7 +6,7 @@ }, "do": { "$include": { - "$path": "$formatstring menusystem/plugins/peacock-menu/hub.json@{$.@global.CacheBuster}" + "$path": "$formatstring menusystem/plugins/peacock-menu/options.json@{$.@global.CacheBuster}" } } } diff --git a/plugins/peacock-menu/test/description.json b/plugins/peacock-menu/test/description.json deleted file mode 100644 index 72401b56a..000000000 --- a/plugins/peacock-menu/test/description.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "select": { - "replace-children": { - "target": "options_info_container", - "children": [ - { - "view": "menu3.basic.OptionsInfo", - "data": { - "title": "Test Title", - "paragraph": "$formatstring {$.Command1}\n{$.Command2}" - } - } - ] - } - }, - "deselect": { - "replace-children": { - "target": "options_info_container", - "children": [] - } - } -} diff --git a/plugins/peacock-menu/test/index.json b/plugins/peacock-menu/test/index.json deleted file mode 100644 index 22b76919d..000000000 --- a/plugins/peacock-menu/test/index.json +++ /dev/null @@ -1,221 +0,0 @@ -{ - "$datacontext": { - "in": "$.", - "datavalues": { - "Command1": { - "$if $isnull {$ioiaccountstatus}.Commands.buttonTest": { - "$then": "Push the button!", - "$else": "$formatstring You pushed the button {{$ioiaccountstatus}.Commands.buttonTest.Count, .f} times!" - } - }, - "Command2": { - "$if $isnull {$ioiaccountstatus}.Commands.modalTest": { - "$then": "Push the button (on the modal)!", - "$else": "$formatstring You pushed the button (on the modal) {{$ioiaccountstatus}.Commands.modalTest.Count, .f} times!" - } - }, - "Command3": { - "$if $isnull {$ioiaccountstatus}.Commands.forEachTest": { - "$then": "[]", - "$else": "{{$ioiaccountstatus}.Commands}.forEachTest" - } - } - }, - "do": { - "view": "menu3.basic.ListElementSmall", - "controller": "tabsitem", - "data": { - "title": "Test", - "icon": "game" - }, - "children": [ - { - "view": "menu3.containers.ScrollingListContainer", - "controller": "tabs", - "direction": "vertical", - "data": { - "direction": "vertical", - "overflowscrolling": "1.0" - }, - "col": 2.25, - "nrows": 3.75, - "ncols": 4, - "children": { - "$mergearrays": [ - { - "view": "menu3.basic.OptionsListElement", - "controller": "tabsitem", - "data": { - "title": "Button" - }, - "actions": { - "$mergeobjects": [ - { - "$include": { - "$path": "$formatstring menusystem/plugins/peacock-menu/test/description.json@{$.@global.CacheBuster}" - } - }, - { - "accept": { - "ioiaccount": { - "mode": "submit-email", - "email": "peacock:buttonTest?test=test" - } - } - } - ] - } - }, - { - "view": "menu3.basic.OptionsListElement", - "controller": "tabsitem", - "data": { - "title": "Button with Modal" - }, - "actions": { - "$mergeobjects": [ - { - "$include": { - "$path": "$formatstring menusystem/plugins/peacock-menu/test/description.json@{$.@global.CacheBuster}" - } - }, - { - "accept": { - "show-modal": { - "config": { - "view": "menu3.modal.ModalDialogGeneric", - "buttons": [ - { - "label": "Yes", - "type": "ok" - }, - { - "label": "No", - "type": "cancel" - } - ], - "data": { - "title": "Modal Title", - "description": "Modal Body", - "typeoverride": "online" - } - }, - "onbutton": [ - { - "ioiaccount": { - "mode": "submit-email", - "email": "peacock:modalTest?test=test" - } - } - ] - } - } - } - ] - } - }, - { - "view": "menu3.basic.OptionsListElement", - "controller": "tabsitem", - "data": { - "title": "Batch" - }, - "actions": { - "$mergeobjects": [ - { - "$include": { - "$path": "$formatstring menusystem/plugins/peacock-menu/test/description.json@{$.@global.CacheBuster}" - } - }, - { - "accept": { - "ioiaccount": { - "mode": "submit-email", - "email": "peacock:buttonTest?test=test|modalTest?test=test" - } - } - } - ] - } - }, - { - "view": "menu3.basic.OptionsListElement", - "controller": "tabsitem", - "data": { - "title": "Clear" - }, - "actions": { - "$mergeobjects": [ - { - "$include": { - "$path": "$formatstring menusystem/plugins/peacock-menu/test/description.json@{$.@global.CacheBuster}" - } - }, - { - "accept": { - "ioiaccount": { - "mode": "submit-email", - "email": "peacock:clear?commands=buttonTest,modalTest" - } - } - } - ] - } - }, - { - "$mergeobjects": [ - { - "view": "menu3.basic.OptionsListElement", - "controller": "tabsitem" - }, - { - "$if $eq($arraysize $.Command3, 0)": { - "$then": { - "data": { - "title": "Load Dynamic Cards" - }, - "actions": { - "accept": { - "ioiaccount": { - "mode": "submit-email", - "email": "peacock:forEachTest" - } - } - } - }, - "$else": { - "data": { - "title": "Clear Dynamic Cards" - }, - "actions": { - "accept": { - "ioiaccount": { - "mode": "submit-email", - "email": "peacock:clear?commands=forEachTest" - } - } - } - } - } - } - ] - }, - { - "$each $.Command3": { - "view": "menu3.basic.MenuTileSmall", - "controller": "tabsitem", - "data": { - "header": "$formatstring {$.Title}", - "icon": "weapon", - "title": "$formatstring {$.Body}", - "image": "$res images/opportunities/wet/wet_opp_thecontroller.jpg" - } - } - } - ] - } - } - ] - } - } -} diff --git a/static/HubPageData.json b/static/HubPageData.json index 65315f904..3c33fd6e8 100644 --- a/static/HubPageData.json +++ b/static/HubPageData.json @@ -291,11 +291,6 @@ } } }, - { - "$include": { - "$path": "menusystem/plugins/peacock-menu/index.json" - } - }, { "$if $.IsFullMenuAvailable": { "$then": { From 5b2ce828b259bbde2b9710aa27035aee2008f7c2 Mon Sep 17 00:00:00 2001 From: Lennard Fonteijn Date: Wed, 21 Aug 2024 23:03:24 +0200 Subject: [PATCH 05/10] Fixed enums --- plugins/peacock-menu/flags/flag.json | 35 +++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/plugins/peacock-menu/flags/flag.json b/plugins/peacock-menu/flags/flag.json index 14633e7c7..8e9ad43cf 100644 --- a/plugins/peacock-menu/flags/flag.json +++ b/plugins/peacock-menu/flags/flag.json @@ -13,7 +13,18 @@ }, "do": { "view": "menu3.basic.OptionsListElement", - "controller": "tabsitem", + "controller": { + "$switch $.valueType": [ + { + "case": "enum", + "return": "contextitem" + }, + { + "case": "boolean", + "return": "tabsitem" + } + ] + }, "data": { "$mergeobjects": [ { @@ -51,21 +62,33 @@ { "case": "enum", "return": { - "controller": "list", + "controller": "tabs", "view": "menu3.containers.ScrollingListContainer", - "col": 4.25, - "nrows": 3.75, - "ncols": 2, "data": { "overflowscrolling": "1.0" }, "children": { "$each $.PossibleValues": { - "view": "menu3.basic.OptionsListElementSmall", + "view": "menu3.basic.OptionsListElement", + "controller": "tabsitem", "data": { "title": "{$.}" }, "actions": { + "select": { + "replace-children": { + "target": "options_info_container", + "children": [ + { + "view": "menu3.basic.OptionsInfo", + "data": { + "title": "$.@parent.Title", + "paragraph": "$.@parent.Description" + } + } + ] + } + }, "accept": [ { "trigger-input": { From a8f061ef50c3998133e98ad19198e9700db7f60b Mon Sep 17 00:00:00 2001 From: AnthonyFuller <24512050+AnthonyFuller@users.noreply.github.com> Date: Wed, 21 Aug 2024 22:49:25 +0100 Subject: [PATCH 06/10] chore: add options.json --- plugins/peacock-menu/options.json | 53 +++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 plugins/peacock-menu/options.json diff --git a/plugins/peacock-menu/options.json b/plugins/peacock-menu/options.json new file mode 100644 index 000000000..48536e3f6 --- /dev/null +++ b/plugins/peacock-menu/options.json @@ -0,0 +1,53 @@ +{ + "id": "UI_MENU_PEACOCK_FLAGS", + "view": "menu3.basic.ListElementSmall", + "controller": "tabsitem", + "data": { + "title": "$loc UI_PEACOCK_BRANDING_SHORT", + "icon": "fixed" + }, + "actions": { + "select": [ + { + "ioiaccount": { + "_comment": "Do not trigger an ioiaccount-event after another ioiaccount-event, it will cause the game to crash!", + "mode": "submit-email", + "email": "peacock:getAllFlags" + } + } + ] + }, + "children": [ + { + "controller": "list", + "row": 1, + "col": 6.5, + "nrows": 4.1, + "ncols": 4, + "children": [ + { + "$include": { + "$path": "menusystem/elements/settings/generic/optionsinfo_container.json" + } + } + ] + }, + { + "controller": "list", + "row": 1, + "col": 6.5, + "children": [ + { + "$include": { + "$path": "menusystem/elements/settings/generic/optionsgroupinfo_container.json" + } + } + ] + }, + { + "$include": { + "$path": "$formatstring menusystem/plugins/peacock-menu/flags/index.json@{$.@global.CacheBuster}" + } + } + ] +} From 1b778f3c86764b38d3fab535cb6b0c8f9d16bd73 Mon Sep 17 00:00:00 2001 From: AnthonyFuller <24512050+AnthonyFuller@users.noreply.github.com> Date: Thu, 22 Aug 2024 01:09:58 +0100 Subject: [PATCH 07/10] feat: add cachebuster version and refresh ui in dev --- components/menus/menuSystem.ts | 7 +++ plugins/peacock-menu/flags/index.json | 63 ++++++++++++++++++++++----- 2 files changed, 59 insertions(+), 11 deletions(-) diff --git a/components/menus/menuSystem.ts b/components/menus/menuSystem.ts index ba5042d38..eed920103 100644 --- a/components/menus/menuSystem.ts +++ b/components/menus/menuSystem.ts @@ -271,6 +271,13 @@ export class MenuSystemDatabase { $else: true, }, } + case "/data/ispeacockdev.json": + return { + "$if $eq (0,0)": { + $then: PEACOCK_DEV, + $else: PEACOCK_DEV, + }, + } // This will only get hit by H2 case "/pages/hub/career.json": return getConfig("H2CareerTemplate", false) diff --git a/plugins/peacock-menu/flags/index.json b/plugins/peacock-menu/flags/index.json index 4a5ebcaa6..1509a4682 100644 --- a/plugins/peacock-menu/flags/index.json +++ b/plugins/peacock-menu/flags/index.json @@ -16,20 +16,61 @@ "nrows": 3.75, "ncols": 4, "children": { - "$each $.Flags": { - "$if $eq($.valueType,category)": { - "$then": { - "$include": { - "$path": "$formatstring menusystem/plugins/peacock-menu/flags/category.json@{$.@global.CacheBuster}" - } - }, - "$else": { - "$include": { - "$path": "$formatstring menusystem/plugins/peacock-menu/flags/flag.json@{$.@global.CacheBuster}" + "$mergearrays": [ + { + "$each $.Flags": { + "$if $eq($.valueType,category)": { + "$then": { + "$include": { + "$path": "$formatstring menusystem/plugins/peacock-menu/flags/category.json@{$.@global.CacheBuster}" + } + }, + "$else": { + "$include": { + "$path": "$formatstring menusystem/plugins/peacock-menu/flags/flag.json@{$.@global.CacheBuster}" + } + } } } + }, + { + "$if": { + "$condition": { + "$include": "menusystem/data/ispeacockdev.json" + }, + "$then": [ + { + "view": "menu3.basic.OptionsListElement", + "controller": "tabsitem", + "data": { + "title": "Debug - Refresh UI" + }, + "actions": { + "accept": { + "set-value": { + "target": "$.@global.CacheBuster", + "value": "{$ioiaccountstatus}.CacheBuster" + }, + "_ioiaccount": { + "mode": "refresh" + } + } + } + }, + { + "view": "menu3.TextboxElement", + "col": 0, + "data": { + "size": 24, + "width": 500, + "text": "$formatstring CacheBuster Version\n- Next:\t{{$ioiaccountstatus}.CacheBuster}\n- Active:\t{$.@global.CacheBuster}", + "color": "ffffff" + } + } + ] + } } - } + ] } } } From 7bd7d633954392d529b32ebeb21e52dae96cab70 Mon Sep 17 00:00:00 2001 From: Lennard Fonteijn Date: Tue, 22 Oct 2024 00:19:47 +0200 Subject: [PATCH 08/10] Moved logic from plugin to core --- components/commandService.ts | 129 ++++++++ components/flags.ts | 6 +- components/generatedPeacockRequireTable.ts | 4 + components/index.ts | 3 + components/menus/settings.ts | 246 +++++++++++++++ components/profileHandler.ts | 75 +---- patcher/.gitignore | 8 +- plugins/elusive-destinations.plugin.ts | 27 +- plugins/peacock-menu.plugin.ts | 297 ++---------------- .../peacock-menu/flags/category.json | 4 +- .../peacock-menu/flags/flag.json | 0 .../peacock-menu/flags/index.json | 4 +- {plugins => static}/peacock-menu/index.json | 2 +- {plugins => static}/peacock-menu/options.json | 2 +- 14 files changed, 447 insertions(+), 360 deletions(-) create mode 100644 components/commandService.ts create mode 100644 components/menus/settings.ts rename {plugins => static}/peacock-menu/flags/category.json (94%) rename {plugins => static}/peacock-menu/flags/flag.json (100%) rename {plugins => static}/peacock-menu/flags/index.json (95%) rename {plugins => static}/peacock-menu/index.json (63%) rename {plugins => static}/peacock-menu/options.json (92%) diff --git a/components/commandService.ts b/components/commandService.ts new file mode 100644 index 000000000..55e447b23 --- /dev/null +++ b/components/commandService.ts @@ -0,0 +1,129 @@ +import { SyncBailHook } from "./hooksImpl" +import { log, logDebug, LogLevel } from "./loggingInterop" +import { nilUuid } from "./utils" + +const peacockCommandPrefix = "peacock:" + +// LennardF1989: Do we want to consider string[] because of the use of URLSearchParams? +type CommandArgs = { + [key: string]: string /* | string[] */ +} + +type CommandResponseCache = { + [key: string]: unknown +} + +type CommandStatus = { + CacheBuster: string + Commands: CommandResponseCache + IsConfirmed: boolean + LinkedEmail: string + IOIAccountId: string + IOIAccountBaseUrl: string +} + +export type CommandFunction = ( + lastResponse: unknown | undefined, + args: unknown, +) => unknown + +/* export */ class CommandService { + public handleCommand: SyncBailHook< + [ + /** lastResponse */ unknown, + /** command */ string, + /** args */ unknown, + ], + unknown + > + + private commandResponseCache: CommandResponseCache + + constructor() { + this.handleCommand = new SyncBailHook() + this.commandResponseCache = {} + } + + public getCommandStatus(): CommandStatus { + return { + CacheBuster: Date.now().toString(), + Commands: this.commandResponseCache, + IsConfirmed: true, + LinkedEmail: "mail@example.com", + IOIAccountId: nilUuid, + IOIAccountBaseUrl: "https://account.ioi.dk", + } + } + + public submitCommands(email: string): CommandStatus { + if (!email.startsWith(peacockCommandPrefix)) { + return this.getCommandStatus() + } + + try { + const commands = email + .substring(peacockCommandPrefix.length) + .split("|") + + commands.forEach((c) => { + const commandIndex = c.indexOf("?") + + let command = undefined + let args: CommandArgs + + if (commandIndex < 0) { + command = c + args = {} + } else { + command = c.substring(0, commandIndex) + args = Object.fromEntries( + new URLSearchParams(c.substring(commandIndex + 1)), + ) + } + + if (command === "clear") { + const commands = (args.commands as string)?.split(",") + + for (const c of commands) { + this.commandResponseCache[c] = undefined + } + } else { + this.commandResponseCache[command] = + this.handleCommand.call( + this.commandResponseCache[command], + command, + args, + ) + } + + // logDebug(command, args, this.commandResponseCache) + }) + } catch (e) { + log(LogLevel.ERROR, `Failed to handle Peacock command: ${e}`) + } + + return this.getCommandStatus() + } + + public handleCommandMap( + name: string, + commandMap: Map, + ) { + this.handleCommand.tap( + name, + ( + lastResponse: unknown, + command: string, + args: unknown, + ): unknown => { + if (commandMap.has(command)) { + return commandMap.get(command)!(lastResponse, args) + } + + return undefined + }, + ) + } +} + +export const commandService = new CommandService() diff --git a/components/flags.ts b/components/flags.ts index 34441a38e..5e1e043c9 100644 --- a/components/flags.ts +++ b/components/flags.ts @@ -246,7 +246,9 @@ export function saveFlags() { lines.push(`; ${defaultSection.title} - ${defaultSection.desc}`) lines.push(`[${sectionKey}]`) - Object.keys(defaultSection.flags).forEach((flagKey) => { + const flagsKeys = Object.keys(defaultSection.flags) + + for (const flagKey of flagsKeys) { const defaultFlag = defaultSection.flags[flagKey] const flag = section[flagKey] @@ -259,7 +261,7 @@ export function saveFlags() { ) lines.push(`${flagKey}=${flag}`) lines.push("") - }) + } }) writeFileSync(FLAGS_FILE, lines.join("\n")) diff --git a/components/generatedPeacockRequireTable.ts b/components/generatedPeacockRequireTable.ts index 51f055b68..56d2ee9b9 100644 --- a/components/generatedPeacockRequireTable.ts +++ b/components/generatedPeacockRequireTable.ts @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +import * as commandService from "./commandService" import * as configSwizzleManager from "./configSwizzleManager" import * as controller from "./controller" import * as databaseHandler from "./databaseHandler" @@ -69,6 +70,7 @@ import * as menuSystem from "./menus/menuSystem" import * as planning from "./menus/planning" import * as playerProfile from "./menus/playerProfile" import * as playnext from "./menus/playnext" +import * as settings from "./menus/settings" import * as sniper from "./menus/sniper" import * as stashpoints from "./menus/stashpoints" import * as multiplayerMenuData from "./multiplayer/multiplayerMenuData" @@ -79,6 +81,7 @@ import * as contractCreation from "./statemachines/contractCreation" import * as escalationService from "./contracts/escalations/escalationService" export default { + "@peacockproject/core/commandService": commandService, "@peacockproject/core/configSwizzleManager": configSwizzleManager, "@peacockproject/core/controller": controller, "@peacockproject/core/databaseHandler": databaseHandler, @@ -132,6 +135,7 @@ export default { "@peacockproject/core/menus/planning": planning, "@peacockproject/core/menus/playerProfile": playerProfile, "@peacockproject/core/menus/playnext": playnext, + "@peacockproject/core/menus/settings": settings, "@peacockproject/core/menus/sniper": sniper, "@peacockproject/core/menus/stashpoints": stashpoints, "@peacockproject/core/multiplayer/multiplayerMenuData": multiplayerMenuData, diff --git a/components/index.ts b/components/index.ts index 559b83c8f..c7aef0ada 100644 --- a/components/index.ts +++ b/components/index.ts @@ -76,6 +76,7 @@ import { multiplayerMenuDataRouter } from "./multiplayer/multiplayerMenuData" import { liveSplitManager } from "./livesplit/liveSplitManager" import { cheapLoadUserData, setupFileStructure } from "./databaseHandler" import { getFlag, saveFlags } from "./flags" +import { initializePeacockMenu } from "./menus/settings" const host = process.env.HOST || "0.0.0.0" const port = process.env.PORT || 80 @@ -527,6 +528,8 @@ export async function startServer(options: { // initialize livesplit await liveSplitManager.init() + initializePeacockMenu() + return } catch (e) { log(LogLevel.ERROR, "Critical error during bootstrap!") diff --git a/components/menus/settings.ts b/components/menus/settings.ts new file mode 100644 index 000000000..399650083 --- /dev/null +++ b/components/menus/settings.ts @@ -0,0 +1,246 @@ +import { existsSync, readFileSync } from "fs" +import path from "path" +import { IIniObjectSection, IniValue } from "js-ini" +import { menuSystemDatabase } from "./menuSystem" +import { Flag, FlagSection, GameVersion } from "../types/types" +import { defaultFlags, getAllFlags, saveFlags, setFlag } from "../flags" +import { CommandFunction, commandService } from "../commandService" + +interface GetAllFlagsResponse { + key: string + title: string + description: string + // defaultValue?: string | number | boolean + possibleValues?: string[] + valueType: "category" | "boolean" | "string" | "number" | "enum" + value: GetAllFlagsResponse[] | boolean | string | number +} + +interface SetFlagArgs { + key: string + value: string +} + +const commandMap = new Map([ + ["getAllFlags", commandGetAllFlags as CommandFunction], + ["setFlagBoolean", commandSetFlagBoolean as CommandFunction], + ["setFlagEnum", commandSetFlagEnum as CommandFunction], +]) + +const pluginPrefix = "/pages/peacock-menu/" +const jsonExtension = ".json" + +function getDatabaseDiff(configs: string[], gameVersion: GameVersion) { + if (gameVersion === "h3") { + configs.push( + "menusystem/elements/settings/ioiaccount/ioiaccount_maintab.json", + ) + } +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getConfig(name: string, _gameVersion: GameVersion) { + if (name.endsWith("ioiaccount/ioiaccount_maintab.json")) { + name = "/pages/peacock-menu/index.json" + } + + if (!name.startsWith(pluginPrefix)) { + return + } + + const fileName = name.substring(pluginPrefix.length) + const cacheBusterIndex = fileName.indexOf(jsonExtension) + const fileNameWithCacheBuster = + cacheBusterIndex < 0 + ? fileName + : fileName.substring(0, cacheBusterIndex + jsonExtension.length) + + const filePath = path.join( + process.cwd(), + "static", + "peacock-menu", + fileNameWithCacheBuster, + ) + + if (existsSync(filePath)) { + return JSON.parse(readFileSync(filePath).toString()) + } + + return undefined +} + +function getFlagType( + defaultFlag: Flag, + value: IniValue, +): { + valueType: "boolean" | "string" | "number" | "enum" + value: boolean | string | number +} { + if (defaultFlag.possibleValues) { + return { + valueType: "enum", + value: value, + } + } + + switch (typeof defaultFlag.default) { + case "string": + return { + valueType: "string", + value: value, + } + + case "number": + return { + valueType: "number", + value: value, + } + + case "boolean": + return { + valueType: "boolean", + value: value, + } + } +} + +function commandGetAllFlags(): GetAllFlagsResponse[] { + const allFlags = getAllFlags() + + const flagsArray: GetAllFlagsResponse[] = [] + + for (const sectionKey of Object.keys(allFlags)) { + const defaultSection = defaultFlags[sectionKey] + + flagsArray.push({ + key: `${sectionKey}`, + title: defaultSection.title, + description: defaultSection.desc, + valueType: "category", + value: commandGetAllFlagsForSection( + sectionKey, + defaultSection, + allFlags[sectionKey] as IIniObjectSection, + ), + }) + } + + return flagsArray +} + +function commandGetAllFlagsForSection( + sectionKey: string, + section: FlagSection, + sectionValues: IIniObjectSection, +): GetAllFlagsResponse[] { + const flagsArray: GetAllFlagsResponse[] = [] + + const categoryMap = new Map() + + for (const flagKey of Object.keys(section.flags)) { + const flag = section.flags[flagKey] + + if (flag.showIngame === false) { + continue + } + + const flagsType = getFlagType(flag, sectionValues[flagKey]) + + if (!flagsType) { + continue + } + + const noteLines = [] + + if (flag.requiresGameRestart) { + noteLines.push( + "Game has to be restarted before changes take effect!", + ) + } + + if (flag.requiresPeacockRestart) { + noteLines.push( + "Peacock has to be restarted before changes take effect!", + ) + } + + const flagResult = { + key: `${sectionKey}.${flagKey}`, + title: flag.title, + description: `${flag.desc}\n\nDefault value: ${flag.default}${noteLines.length > 0 ? "\n\nNotes:\n- " + noteLines.join("\n- ") : ""}`, + // defaultValue: flag.default, + possibleValues: flag.possibleValues, + ...flagsType, + } + + if (flag.category) { + let categoryFlag = categoryMap.get(flag.category) + + if (!categoryFlag) { + categoryFlag = { + key: `${sectionKey}.${flag.category}`, + title: flag.category, + description: "", + valueType: "category", + value: [], + } + + categoryMap.set(flag.category, categoryFlag) + } + + ;(categoryFlag.value as GetAllFlagsResponse[]).push(flagResult) + } else { + flagsArray.push(flagResult) + } + } + + const categoryFlags = [...categoryMap.values()] + + return [...categoryFlags, ...flagsArray] +} + +function commandSetFlagBoolean( + _lastResponse: unknown, + args: SetFlagArgs, +): void { + const keys = args.key.split(".") + const section = keys[0] + const key = keys[1] + + if (!defaultFlags[section]?.flags[key]) { + return + } + + setFlag(args.key, args.value === "true") + + saveFlags() +} + +function commandSetFlagEnum(_lastResponse: unknown, args: SetFlagArgs): void { + const keys = args.key.split(".") + const section = keys[0] + const key = keys[1] + + if (!defaultFlags[section]?.flags[key]) { + return + } + + setFlag(args.key, args.value) + + saveFlags() +} + +// TODO: Prevent refresh of UI after setFlag and/or remember context (maybe set-value?) +// TODO: Add to pause menu +// TODO: Add support for plugins to add custom menus +// TODO: Reload plugin settings hook +// TODO: Restart peacock option? +// TODO: Add command to swap profile +// TODO: Add command to reset profile + +export function initializePeacockMenu(): void { + menuSystemDatabase.hooks.getDatabaseDiff.tap("PeacockMenu", getDatabaseDiff) + menuSystemDatabase.hooks.getConfig.tap("PeacockMenu", getConfig) + + commandService.handleCommandMap("PeacockMenu", commandMap) +} diff --git a/components/profileHandler.ts b/components/profileHandler.ts index 317e80e39..64974787d 100644 --- a/components/profileHandler.ts +++ b/components/profileHandler.ts @@ -18,7 +18,6 @@ import { Router } from "express" import path from "path" -import querystring from "querystring" import { castUserProfile, getMaxProfileLevel, @@ -40,7 +39,7 @@ import { UpdateUserSaveFileTableBody, UserProfile, } from "./types/types" -import { log, logDebug, LogLevel } from "./loggingInterop" +import { log, LogLevel } from "./loggingInterop" import { deleteContractSession, getContractSession, @@ -65,8 +64,9 @@ import { ResolveGamerTagsBody, } from "./types/gameSchemas" import assert from "assert" -import { SyncBailHook } from "./hooksImpl" import { generateCompletionData } from "./contracts/dataGen" +import { commandService } from "./commandService" +import { initializePeacockMenu } from "./menus/settings" const profileRouter = Router() @@ -542,7 +542,7 @@ profileRouter.post( // prettier-ignore val.Challenge.Definition!["States"]["Start"][ "CrowdNPC_Died" - ]["Transition"] = "Success" + ]["Transition"] = "Success" } }) } @@ -961,28 +961,11 @@ export async function loadSession( ) } -export const handleCommand: SyncBailHook< - [/** lastResponse */ unknown, /** command */ string, /** args */ unknown], - unknown -> = new SyncBailHook() - -const peacockCommandPrefix = "peacock:" -const commandResponseCache: { - [key: string]: unknown -} = {} - profileRouter.post( "/ProfileService/GetSemLinkStatus", jsonMiddleware(), (_, res) => { - res.json({ - CacheBuster: Date.now().toString(), - Commands: commandResponseCache, - IsConfirmed: true, - LinkedEmail: "mail@example.com", - IOIAccountId: nilUuid, - IOIAccountBaseUrl: "https://account.ioi.dk", - }) + res.json(commandService.getCommandStatus()) }, ) @@ -991,53 +974,7 @@ profileRouter.post( jsonMiddleware(), // @ts-expect-error Has jwt props. (req: RequestWithJwt, res) => { - if (req.body.email.startsWith(peacockCommandPrefix)) { - try { - const commands = req.body.email - .substring(peacockCommandPrefix.length) - .split("|") - - commands.forEach((c) => { - const commandIndex = c.indexOf("?") - - let command = undefined - let args = undefined - - if (commandIndex < 0) { - command = c - args = {} - } else { - command = c.substring(0, commandIndex) - args = querystring.parse(c.substring(commandIndex + 1)) - } - - if (command === "clear") { - ;(args.commands as string)?.split(",").forEach((c2) => { - commandResponseCache[c2] = undefined - }) - } else { - commandResponseCache[command] = handleCommand.call( - commandResponseCache[command], - command, - args, - ) - } - - logDebug(command, args, commandResponseCache) - }) - } catch (e) { - log(LogLevel.ERROR, `Failed to handle Peacock command: ${e}`) - } - } - - res.json({ - CacheBuster: Date.now().toString(), - Commands: commandResponseCache, - IsConfirmed: true, - LinkedEmail: "mail@example.com", - IOIAccountId: nilUuid, - IOIAccountBaseUrl: "https://account.ioi.dk", - }) + res.json(commandService.submitCommands(req.body.email)) }, ) diff --git a/patcher/.gitignore b/patcher/.gitignore index 1747fa209..d79f2c75b 100644 --- a/patcher/.gitignore +++ b/patcher/.gitignore @@ -1,6 +1,8 @@ -.vs/* -bin/* -obj/* +.vs +bin +obj /HitmanPatcher.sln.DotSettings.user /.idea/.idea.HitmanPatcher/.idea/riderMarkupCache.xml *.suo +*.user +/Publish \ No newline at end of file diff --git a/plugins/elusive-destinations.plugin.ts b/plugins/elusive-destinations.plugin.ts index 05d29f8cd..d082c1b35 100644 --- a/plugins/elusive-destinations.plugin.ts +++ b/plugins/elusive-destinations.plugin.ts @@ -27,6 +27,26 @@ const pluginFlagSection: FlagSection = { default: true, requiresPeacockRestart: true, }, + smallTiles1: { + title: "Small tiles", + desc: "When turned on, show elusive targets as small tiles under Destinations.", + category: "Test category", + default: true, + requiresPeacockRestart: true, + }, + test1: { + title: "Test 1", + desc: "This is a test", + default: "1", + possibleValues: ["1", "2", "3"], + }, + test2: { + title: "Test 2", + category: "Test category", + desc: "This is a test", + default: "1", + possibleValues: ["1", "2", "3"], + }, }, } @@ -36,7 +56,7 @@ function initPlugin(controller: Controller): void { registerFlagSection(pluginFlagSectionKey, pluginFlagSection) for (const contractId of orderedETs) { - const contract = controller.resolveContract(contractId) + const contract = controller.resolveContract(contractId, undefined!) if (!contract) { continue @@ -45,7 +65,10 @@ function initPlugin(controller: Controller): void { const baseContractId = // @ts-expect-error Indexer controller.missionsInLocations[contract.Metadata.Location][0] - const baseContract = controller.resolveContract(baseContractId) + const baseContract = controller.resolveContract( + baseContractId, + undefined!, + ) if (!baseContract) { continue diff --git a/plugins/peacock-menu.plugin.ts b/plugins/peacock-menu.plugin.ts index cb8abbf03..78c794c0b 100644 --- a/plugins/peacock-menu.plugin.ts +++ b/plugins/peacock-menu.plugin.ts @@ -1,107 +1,30 @@ -import { Controller } from "@peacockproject/core/controller" -import { menuSystemDatabase } from "@peacockproject/core/menus/menuSystem" -import { - Flag, - FlagSection, - GameVersion, -} from "@peacockproject/core/types/types" -import { LogLevel, log } from "@peacockproject/core/loggingInterop" -import { handleCommand as handleCommandHook } from "@peacockproject/core/profileHandler" import { - defaultFlags, - getAllFlags, - saveFlags, - setFlag, -} from "@peacockproject/core/flags" -import { existsSync, readFileSync } from "fs" -import path from "path" -import { IIniObjectSection, IniValue } from "js-ini" + CommandFunction, + commandService, +} from "@peacockproject/core/commandService" +import { Controller } from "@peacockproject/core/controller" +import { log, LogLevel } from "@peacockproject/core/loggingInterop" -interface modalTestResponse { +type ModalTestResponse = { Count: number } -interface buttonTestResponse { +type ButtonTestResponse = { Count: number } -interface forEachTestResponse { +type ForEachTestResponse = { Title: string Body: string } -interface getAllFlagsResponse { - key: string - title: string - description: string - // defaultValue?: string | number | boolean - possibleValues?: string[] - valueType: "category" | "boolean" | "string" | "number" | "enum" - value: getAllFlagsResponse[] | boolean | string | number -} - -interface setFlagArgs { - key: string - value: string -} - -type commandFunction = ( - lastResponse: unknown | undefined, - args: unknown, -) => unknown - -const commandMap = new Map([ - ["modalTest", commandModalTest as commandFunction], - ["buttonTest", commandButtonTest as commandFunction], - ["forEachTest", commandForEachTest as commandFunction], - ["getAllFlags", commandGetAllFlags as commandFunction], - ["setFlagBoolean", commandSetFlagBoolean as commandFunction], - ["setFlagEnum", commandSetFlagEnum as commandFunction], +const commandMap = new Map([ + ["modalTest", commandModalTest as CommandFunction], + ["buttonTest", commandButtonTest as CommandFunction], + ["forEachTest", commandForEachTest as CommandFunction], ]) -const pluginPrefix = "/plugins/peacock-menu/" -const jsonExtension = ".json" - -function getDatabaseDiff(configs: string[], gameVersion: GameVersion) { - if (gameVersion === "h3") { - configs.push( - "menusystem/elements/settings/ioiaccount/ioiaccount_maintab.json", - ) - } -} - -// eslint-disable-next-line @typescript-eslint/no-unused-vars -function getConfig(name: string, _gameVersion: GameVersion) { - if (name.endsWith("ioiaccount/ioiaccount_maintab.json")) { - name = "/plugins/peacock-menu/index.json" - } - - if (!name.startsWith(pluginPrefix)) { - return - } - - const fileName = name.substring(pluginPrefix.length) - const cacheBusterIndex = fileName.indexOf(jsonExtension) - const fileNameWithCacheBuster = - cacheBusterIndex < 0 - ? fileName - : fileName.substring(0, cacheBusterIndex + jsonExtension.length) - - const filePath = path.join( - process.cwd(), - "plugins", - "peacock-menu", - fileNameWithCacheBuster, - ) - - if (existsSync(filePath)) { - return JSON.parse(readFileSync(filePath).toString()) - } - - return undefined -} - -function commandModalTest(lastResponse: modalTestResponse): modalTestResponse { +function commandModalTest(lastResponse: ModalTestResponse): ModalTestResponse { let count = (lastResponse || { Count: 0 }).Count return { @@ -110,8 +33,8 @@ function commandModalTest(lastResponse: modalTestResponse): modalTestResponse { } function commandButtonTest( - lastResponse: buttonTestResponse, -): buttonTestResponse { + lastResponse: ButtonTestResponse, +): ButtonTestResponse { let count = (lastResponse || { Count: 0 }).Count return { @@ -119,7 +42,7 @@ function commandButtonTest( } } -function commandForEachTest(): forEachTestResponse[] { +function commandForEachTest(): ForEachTestResponse[] { return [ { Title: "Title #1", @@ -136,195 +59,13 @@ function commandForEachTest(): forEachTestResponse[] { ] } -function getFlagType( - defaultFlag: Flag, - value: IniValue, -): { - valueType: "boolean" | "string" | "number" | "enum" - value: boolean | string | number -} { - if (defaultFlag.possibleValues) { - return { - valueType: "enum", - value: value, - } - } - - switch (typeof defaultFlag.default) { - case "string": - return { - valueType: "string", - value: value, - } - - case "number": - return { - valueType: "number", - value: value, - } - - case "boolean": - return { - valueType: "boolean", - value: value, - } - } -} - -function commandGetAllFlags(): getAllFlagsResponse[] { - const allFlags = getAllFlags() - - const flagsArray: getAllFlagsResponse[] = [] - - for (const sectionKey of Object.keys(allFlags)) { - const defaultSection = defaultFlags[sectionKey] - - flagsArray.push({ - key: `${sectionKey}`, - title: defaultSection.title, - description: defaultSection.desc, - valueType: "category", - value: commandGetAllFlagsForSection( - sectionKey, - defaultSection, - allFlags[sectionKey] as IIniObjectSection, - ), - }) - } - - return flagsArray -} - -function commandGetAllFlagsForSection( - sectionKey: string, - section: FlagSection, - sectionValues: IIniObjectSection, -): getAllFlagsResponse[] { - const flagsArray: getAllFlagsResponse[] = [] - - const categoryMap = new Map() - - for (const flagKey of Object.keys(section.flags)) { - const flag = section.flags[flagKey] - - if (flag.showIngame === false) { - continue - } - - const flagsType = getFlagType(flag, sectionValues[flagKey]) - - if (!flagsType) { - continue - } - - const noteLines = [] - - if (flag.requiresGameRestart) { - noteLines.push( - "Game has to be restarted before changes take effect!", - ) - } - - if (flag.requiresPeacockRestart) { - noteLines.push( - "Peacock has to be restarted before changes take effect!", - ) - } - - const flagResult = { - key: `${sectionKey}.${flagKey}`, - title: flag.title, - description: `${flag.desc}\n\nDefault value: ${flag.default}${noteLines.length > 0 ? "\n\nNotes:\n- " + noteLines.join("\n- ") : ""}`, - // defaultValue: flag.default, - possibleValues: flag.possibleValues, - ...flagsType, - } - - if (flag.category) { - let categoryFlag = categoryMap.get(flag.category) - - if (!categoryFlag) { - categoryFlag = { - key: `${sectionKey}.${flag.category}`, - title: flag.category, - description: "", - valueType: "category", - value: [], - } - - categoryMap.set(flag.category, categoryFlag) - } - - ;(categoryFlag.value as getAllFlagsResponse[]).push(flagResult) - } else { - flagsArray.push(flagResult) - } - } - - const categoryFlags = [...categoryMap.values()] - - return [...categoryFlags, ...flagsArray] -} - -function commandSetFlagBoolean( - _lastResponse: unknown, - args: setFlagArgs, -): void { - const keys = args.key.split(".") - const section = keys[0] - const key = keys[1] - - if (!defaultFlags[section]?.flags[key]) { - return - } - - setFlag(args.key, args.value === "true") - - saveFlags() -} - -function commandSetFlagEnum(_lastResponse: unknown, args: setFlagArgs): void { - const keys = args.key.split(".") - const section = keys[0] - const key = keys[1] - - if (!defaultFlags[section]?.flags[key]) { - return - } - - setFlag(args.key, args.value) - - saveFlags() -} - -function handleCommand( - lastResponse: unknown, - command: string, - args: unknown, -): unknown { - if (commandMap.has(command)) { - return commandMap.get(command)!(lastResponse, args) - } - - // TODO: Prevent refresh of UI after setFlag and/or remember context (maybe set-value?) - // TODO: Add to pause menu - // TODO: Add support for plugins to add custom menus - // TODO: Reload plugin settings hook - // TODO: Restart peacock option? - // TODO: Add plugin for all weapons unlock - // TODO: Add command to swap profile - // TODO: Add command to reset profile - - return undefined -} - // eslint-disable-next-line @typescript-eslint/no-unused-vars function initPlugin(_controller: Controller): void { log(LogLevel.INFO, "[Plugin] Peacock Menu", "peacock-menu") - menuSystemDatabase.hooks.getDatabaseDiff.tap("PeacockMenu", getDatabaseDiff) - menuSystemDatabase.hooks.getConfig.tap("PeacockMenu", getConfig) - handleCommandHook.tap("PeacockMenu", handleCommand) + commandService.handleCommandMap("PeacockMenuPlugin", commandMap) } +// TODO: Add plugin for all weapons unlock + module.exports = initPlugin diff --git a/plugins/peacock-menu/flags/category.json b/static/peacock-menu/flags/category.json similarity index 94% rename from plugins/peacock-menu/flags/category.json rename to static/peacock-menu/flags/category.json index 1523a6bd6..bb02791f2 100644 --- a/plugins/peacock-menu/flags/category.json +++ b/static/peacock-menu/flags/category.json @@ -29,12 +29,12 @@ "$if $eq($.valueType,category)": { "$then": { "$include": { - "$path": "$formatstring menusystem/plugins/peacock-menu/flags/category.json@{$.@global.CacheBuster}" + "$path": "$formatstring menusystem/pages/peacock-menu/flags/category.json@{$.@global.CacheBuster}" } }, "$else": { "$include": { - "$path": "$formatstring menusystem/plugins/peacock-menu/flags/flag.json@{$.@global.CacheBuster}" + "$path": "$formatstring menusystem/pages/peacock-menu/flags/flag.json@{$.@global.CacheBuster}" } } } diff --git a/plugins/peacock-menu/flags/flag.json b/static/peacock-menu/flags/flag.json similarity index 100% rename from plugins/peacock-menu/flags/flag.json rename to static/peacock-menu/flags/flag.json diff --git a/plugins/peacock-menu/flags/index.json b/static/peacock-menu/flags/index.json similarity index 95% rename from plugins/peacock-menu/flags/index.json rename to static/peacock-menu/flags/index.json index 1509a4682..3381c3d81 100644 --- a/plugins/peacock-menu/flags/index.json +++ b/static/peacock-menu/flags/index.json @@ -22,12 +22,12 @@ "$if $eq($.valueType,category)": { "$then": { "$include": { - "$path": "$formatstring menusystem/plugins/peacock-menu/flags/category.json@{$.@global.CacheBuster}" + "$path": "$formatstring menusystem/pages/peacock-menu/flags/category.json@{$.@global.CacheBuster}" } }, "$else": { "$include": { - "$path": "$formatstring menusystem/plugins/peacock-menu/flags/flag.json@{$.@global.CacheBuster}" + "$path": "$formatstring menusystem/pages/peacock-menu/flags/flag.json@{$.@global.CacheBuster}" } } } diff --git a/plugins/peacock-menu/index.json b/static/peacock-menu/index.json similarity index 63% rename from plugins/peacock-menu/index.json rename to static/peacock-menu/index.json index 3ac471b55..1453e27b6 100644 --- a/plugins/peacock-menu/index.json +++ b/static/peacock-menu/index.json @@ -6,7 +6,7 @@ }, "do": { "$include": { - "$path": "$formatstring menusystem/plugins/peacock-menu/options.json@{$.@global.CacheBuster}" + "$path": "$formatstring menusystem/pages/peacock-menu/options.json@{$.@global.CacheBuster}" } } } diff --git a/plugins/peacock-menu/options.json b/static/peacock-menu/options.json similarity index 92% rename from plugins/peacock-menu/options.json rename to static/peacock-menu/options.json index 48536e3f6..ddb367240 100644 --- a/plugins/peacock-menu/options.json +++ b/static/peacock-menu/options.json @@ -46,7 +46,7 @@ }, { "$include": { - "$path": "$formatstring menusystem/plugins/peacock-menu/flags/index.json@{$.@global.CacheBuster}" + "$path": "$formatstring menusystem/pages/peacock-menu/flags/index.json@{$.@global.CacheBuster}" } } ] From 0231f123c11b8d96ac50d91838e7ee42f3416a30 Mon Sep 17 00:00:00 2001 From: Lennard Fonteijn Date: Tue, 22 Oct 2024 00:29:23 +0200 Subject: [PATCH 09/10] Forgot to lint... --- components/commandService.ts | 2 +- components/profileHandler.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/components/commandService.ts b/components/commandService.ts index 55e447b23..f9c9dcde5 100644 --- a/components/commandService.ts +++ b/components/commandService.ts @@ -1,5 +1,5 @@ import { SyncBailHook } from "./hooksImpl" -import { log, logDebug, LogLevel } from "./loggingInterop" +import { log, LogLevel } from "./loggingInterop" import { nilUuid } from "./utils" const peacockCommandPrefix = "peacock:" diff --git a/components/profileHandler.ts b/components/profileHandler.ts index 64974787d..459fc6dca 100644 --- a/components/profileHandler.ts +++ b/components/profileHandler.ts @@ -66,7 +66,6 @@ import { import assert from "assert" import { generateCompletionData } from "./contracts/dataGen" import { commandService } from "./commandService" -import { initializePeacockMenu } from "./menus/settings" const profileRouter = Router() From 61f85d556950ec24b45508238b4fb7eed017ba78 Mon Sep 17 00:00:00 2001 From: AnthonyFuller Date: Tue, 29 Oct 2024 23:20:03 +0000 Subject: [PATCH 10/10] chore: bump version to 8.0.0-alpha.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a1e340761..13552a515 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@peacockproject/monorepo", - "version": "8.0.0-alpha.1", + "version": "8.0.0-alpha.2", "private": true, "license": "AGPL-3.0", "scripts": {