Files
adventureland/docker/patch-custom-game.js
anima 1ccf4a760b
build container / build (push) Failing after 5m13s
add custom game data overrides
2026-11-01 16:18:38 +01:00

137 lines
5.4 KiB
JavaScript

#!/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,
);