#!/usr/bin/env node // Local patch: custom game values and elements from custom/game.js. // // The loader (docker/custom_game.js) itself needs no source changes, but the // game data is built inside eval() scope, so three small hooks are inserted at // image build time: // // 1. main.js and node/server.js call the loader after the seasonal flags are // applied, so custom values win over the shipped design and over events. // 2. node/logic/encouragement.js reads the Lone Wolf rates from the condition // definition instead of hardcoded 3s, so custom/game.js can tune them. // 3. languages/index.js merges custom phrase overrides into the catalogs, so // tooltips such as the Lone Wolf explanation match tuned numbers. // // Every edit is idempotent and fails loudly if the upstream anchor is gone, so // an upstream update cannot silently drop the hooks. const fs = require("node:fs"); const path = require("node:path"); const app = path.resolve(process.argv[2] || "/app"); function fail(message) { console.error("patch-custom-game: " + message); process.exit(1); } function edit(file, marker, from, to, count) { const full = path.join(app, file); const source = fs.readFileSync(full, "utf8"); if (source.includes(marker)) { console.log("patch-custom-game: " + file + " already patched"); return; } const occurrences = source.split(from).length - 1; if (occurrences !== count) { fail(file + ": expected " + count + " occurrence(s) of the anchor, found " + occurrences); } fs.writeFileSync(full, source.split(from).join(to)); console.log("patch-custom-game: patched " + file); } // 1a. Backend: design globals are module-scoped in main.js. edit( "main.js", 'require("./docker/custom_game.js").apply', `apply_event_flags({ maps: maps, items: items, monsters: monsters, drops: drops }, active_event_flags); // docs`, `apply_event_flags({ maps: maps, items: items, monsters: monsters, drops: drops }, active_event_flags); // Local: values and new elements from the mounted custom/game.js. require("./docker/custom_game.js").apply({ items: items, monsters: monsters, npcs: npcs, maps: maps, sets: sets, craft: craft, skills: skills, conditions: conditions, classes: classes, levels: levels, upgrades: upgrades, compounds: compounds, multipliers: multipliers, titles: titles, tokens: tokens, dismantle: dismantle, drops: drops, dimensions: dimensions, positions: positions, }); // docs`, 1, ); // 1b. Game server: the same globals are local to init_game() and reload_server(). edit( "node/server.js", 'require("../docker/custom_game.js").apply', `\t\tvar season_data = { maps: maps, items: items, monsters: monsters, drops: drops }; \t\tapply_event_flags(season_data, active_event_flags); \t\tseason_controls = { restore: season_data.event_restore || {}, end: end_event_flag };`, `\t\tvar season_data = { maps: maps, items: items, monsters: monsters, drops: drops }; \t\tapply_event_flags(season_data, active_event_flags); \t\t// Local: values and new elements from the mounted custom/game.js. \t\trequire("../docker/custom_game.js").apply({ \t\t\titems: items, monsters: monsters, npcs: npcs, maps: maps, sets: sets, craft: craft, \t\t\tskills: skills, conditions: conditions, classes: classes, levels: levels, upgrades: upgrades, \t\t\tcompounds: compounds, multipliers: multipliers, titles: titles, tokens: tokens, dismantle: dismantle, \t\t\tdrops: drops, dimensions: dimensions, positions: positions, \t\t}); \t\tseason_controls = { restore: season_data.event_restore || {}, end: end_event_flag };`, 2, ); // 2. Lone Wolf rates become data-driven; the defaults keep upstream behaviour. edit( "node/logic/encouragement.js", "lonewolf_definition", `\t\t} else if (!blocked && name === "encouragement_lonewolf" && lonewolf) { \t\t\tcondition = { gold_multiplier: 3, xp_multiplier: 3, luck_multiplier: 3 };`, `\t\t} else if (!blocked && name === "encouragement_lonewolf" && lonewolf) { \t\t\t// Local: read the rates from design/conditions.js so custom/game.js can tune them. \t\t\tvar lonewolf_definition = (G.conditions && G.conditions[name]) || {}; \t\t\tcondition = { \t\t\t\tgold_multiplier: lonewolf_definition.gold_multiplier === undefined ? 3 : lonewolf_definition.gold_multiplier, \t\t\t\txp_multiplier: lonewolf_definition.xp_multiplier === undefined ? 3 : lonewolf_definition.xp_multiplier, \t\t\t\tluck_multiplier: lonewolf_definition.luck_multiplier === undefined ? 3 : lonewolf_definition.luck_multiplier, \t\t\t};`, 1, ); // 3a. Phrase lookup helper, next to the catalog loader. edit( "languages/index.js", "function custom_phrases(language)", `function create_catalog_loader(directory, domains) {`, `// Local: phrase overrides from the mounted custom/game.js. function custom_phrases(language) { try { return require("../docker/custom_game.js").phrases(language); } catch (e) { if (e && e.code === "MODULE_NOT_FOUND") return {}; throw e; } } function create_catalog_loader(directory, domains) {`, 1, ); // 3b. Apply the overrides after each language's files have been read. edit( "languages/index.js", "var custom = custom_phrases(language);", `\t\t} \t\treturn result; \t} \treturn function catalog(language) {`, `\t\t} \t\t// Local: custom names and explanations win over the shipped text. \t\tvar custom = custom_phrases(language); \t\tfor (var custom_id of Object.keys(custom)) result[custom_id] = custom[custom_id]; \t\treturn result; \t} \treturn function catalog(language) {`, 1, );