Skip to content

Commit

Permalink
Added support for sections to flags
Browse files Browse the repository at this point in the history
Added support for plugic-specific flags
  • Loading branch information
LennardF1989 committed Apr 17, 2023
1 parent 606b84b commit b5e6a66
Show file tree
Hide file tree
Showing 3 changed files with 228 additions and 154 deletions.
355 changes: 208 additions & 147 deletions components/flags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,118 +17,152 @@
*/

import { existsSync, readFileSync, unlinkSync, 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 { parse as parseJson } from "json5"
import type { IIniObject } from "js-ini/lib/interfaces/ini-object"

let tempFlags: IIniObject = {}
let flags: IIniObject = {}

const defaultFlags: Flags = {
discordRp: {
desc: "Toggle Discord rich presence on or off.",
default: false,
},
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,
},
liveSplit: {
desc: "Toggle LiveSplit support on or off",
default: false,
},
autoSplitterCampaign: {
desc: "Which (main) campaign to use for the AutoSplitter. Can be set to 1, 2, 3, or 'trilogy'.",
default: "trilogy",
},
autoSplitterRacetimegg: {
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: {
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,
},
jokes: {
desc: "The Peacock server window will tell you a joke on startup if this is set to true.",
default: false,
},
leaderboardsHost: {
desc: "Please do not modify - intended for development only",
default: "https://backend.rdil.rocks",
},
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: {
desc: "Allow Peacock to check for updates on startup.",
default: true,
},
loadoutSaving: {
desc: "Default loadout mode - either PROFILES (loadout profiles) or LEGACY for per-user saving",
default: "PROFILES",
},
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,
},
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",
default: "SAVEASREQUESTED",
},
overrideFrameworkChecks: {
desc: "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,
},
developmentAllowRuntimeRestart: {
desc: "[Development] When set to true, it will be possible to restart Peacock while the game is running and connected.",
default: false,
},
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,
},
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,
},
gameplayUnlockAllShortcuts: {
desc: "When set to true, all shortcuts will always be unlocked.",
default: false,
},
gameplayUnlockAllFreelancerMasteries: {
desc: "When set to true, all Freelancer unlocks will always be available.",
default: false,
},
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,
},
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.',
default: "KEEP",
},
enableMasteryProgression: {
desc: "When set to false, mastery progression will be disabled and all unlockables will be awarded at the beginning",
default: true,
},
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: "Test",
flags: {
discordRp: {
title: "Discord rich presence",
desc: "Toggle Discord rich presence on or off.",
default: false,
},
discordRpAppTime: {
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,
},
liveSplit: {
title: "LiveSplit",
desc: "Toggle LiveSplit support on or off",
default: false,
},
autoSplitterCampaign: {
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: {
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: {
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,
},
jokes: {
title: "jokes",
desc: "The Peacock server window will tell you a joke on startup if this is set to true.",
default: false,
},
leaderboardsHost: {
title: "leaderboardsHost",
desc: "Please do not modify - intended for development only",
default: "https://backend.rdil.rocks",
},
leaderboards: {
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: {
title: "updateChecking",
desc: "Allow Peacock to check for updates on startup.",
default: true,
},
loadoutSaving: {
title: "loadoutSaving",
desc: "Default loadout mode - either PROFILES (loadout profiles) or LEGACY for per-user saving",
possibleValues: ["PROFILES", "LEGACY"],
default: "PROFILES",
},
elusivesAreShown: {
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,
},
imageLoading: {
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",
},
overrideFrameworkChecks: {
title: "overrideFrameworkChecks",
desc: "Forcibly disable installed mod checks",
default: false,
},
experimentalHMR: {
title: "experimentalHMR",
desc: "[Experimental] Toggle hot reloading of contracts",
default: false,
},
developmentPluginDevHost: {
title: "developmentPluginDevHost",
desc: "[Development - Workspace required] Toggle loading of plugins with a .ts/.cts extension inside the /plugins folder",
default: false,
},
developmentAllowRuntimeRestart: {
title: "developmentAllowRuntimeRestart",
desc: "[Development] When set to true, it will be possible to restart Peacock while the game is running and connected.",
default: false,
},
developmentLogRequests: {
title: "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,
},
legacyContractDownloader: {
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,
},
gameplayUnlockAllShortcuts: {
title: "gameplayUnlockAllShortcuts",
desc: "When set to true, all shortcuts will always be unlocked.",
default: false,
},
gameplayUnlockAllFreelancerMasteries: {
title: "gameplayUnlockAllFreelancerMasteries",
desc: "When set to true, all Freelancer unlocks will always be available.",
default: false,
},
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,
},
mapDiscoveryState: {
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: {
title: "enableMasteryProgression",
desc: "When set to false, mastery progression will be disabled and all unlockables will be awarded at the beginning",
default: true,
},
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,
},
},
},
}

const OLD_FLAGS_FILE = "flags.json5"
const NEW_FLAGS_FILE = "options.ini"
const FLAGS_FILE = "options.ini"

/**
* Get a flag from the flag file.
Expand All @@ -137,70 +171,97 @@ const NEW_FLAGS_FILE = "options.ini"
* @returns The flag's value.
*/
export function getFlag(flagId: string): string | boolean | number {
const { section, flag } = convertFlagId(flagId)

return (
(flags[flagId] as string | boolean | number) ??
defaultFlags[flagId].default
(flags[section][flag] as string | boolean | number) ??
defaultFlags[section][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} = ${_flags[flagId]}`
export function setFlag(
flagId: string,
value: string | boolean | number,
): void {
const { section, flag } = convertFlagId(flagId)

flags[section][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]

lines.push(`; ${defaultSection.title} - ${defaultSection.desc}`)
lines.push(`[${sectionKey}]`)

Object.keys(defaultSection.flags).forEach((flagKey) => {
const defaultFlag = defaultSection.flags[flagKey]
const flag = section[flagKey]

lines.push(`; ${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 {
// somebody please, clean this method up, I hate it
if (existsSync(OLD_FLAGS_FILE)) {
log(
LogLevel.WARN,
"The flags file (flags.json5) has been revamped in the latest Peacock version, and we had to remove your settings.",
)
log(
LogLevel.INFO,
"You can take a look at the new options.ini file, which includes descriptions and more!",
)

unlinkSync(OLD_FLAGS_FILE)
if (!existsSync(FLAGS_FILE)) {
writeFileSync(FLAGS_FILE, "")
}

if (!existsSync(NEW_FLAGS_FILE)) {
const allTheFlags = {}
//Load the current INI-file
tempFlags = parse(readFileSync(FLAGS_FILE).toString())

Object.keys(defaultFlags).forEach((f) => {
allTheFlags[f] = defaultFlags[f].default
})
//Create a new INI-file
flags = {}

const ini = makeFlagsIni(allTheFlags)
//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)

writeFileSync(NEW_FLAGS_FILE, ini)
}
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][flag]
: undefined

flags[sectionKey][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)
}

/**
Expand Down
Loading

0 comments on commit b5e6a66

Please sign in to comment.