137 lines
5.4 KiB
JavaScript
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,
|
|
);
|