@rpgm-tools/neo-angband-mod-sdk 0.18.0 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/neo-angband-mod-build.mjs +4 -3
- package/bin/neo-angband-mod-check.mjs +152 -0
- package/dist/authoring.d.ts +239 -0
- package/dist/authoring.d.ts.map +1 -0
- package/dist/authoring.js +738 -0
- package/dist/authoring.js.map +1 -0
- package/dist/blueprints.d.ts +53 -0
- package/dist/blueprints.d.ts.map +1 -0
- package/dist/blueprints.js +4596 -0
- package/dist/blueprints.js.map +1 -0
- package/dist/capabilities.d.ts +34 -3
- package/dist/capabilities.d.ts.map +1 -1
- package/dist/capabilities.js +35 -4
- package/dist/capabilities.js.map +1 -1
- package/dist/compose.d.ts +5 -1
- package/dist/compose.d.ts.map +1 -1
- package/dist/compose.js +118 -27
- package/dist/compose.js.map +1 -1
- package/dist/contested.d.ts +1 -1
- package/dist/contested.d.ts.map +1 -1
- package/dist/contested.js.map +1 -1
- package/dist/fields.d.ts +123 -0
- package/dist/fields.d.ts.map +1 -0
- package/dist/fields.js +217 -0
- package/dist/fields.js.map +1 -0
- package/dist/frontend.d.ts +61 -0
- package/dist/frontend.d.ts.map +1 -0
- package/dist/frontend.js +8 -0
- package/dist/frontend.js.map +1 -0
- package/dist/index.d.ts +29 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -2
- package/dist/index.js.map +1 -1
- package/dist/loader.d.ts +40 -6
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +167 -36
- package/dist/loader.js.map +1 -1
- package/dist/manifest.d.ts +62 -0
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +107 -0
- package/dist/manifest.js.map +1 -1
- package/dist/project.d.ts +126 -0
- package/dist/project.d.ts.map +1 -0
- package/dist/project.js +248 -0
- package/dist/project.js.map +1 -0
- package/dist/provenance.d.ts +86 -0
- package/dist/provenance.d.ts.map +1 -0
- package/dist/provenance.js +98 -0
- package/dist/provenance.js.map +1 -0
- package/dist/record-key.d.ts +95 -4
- package/dist/record-key.d.ts.map +1 -1
- package/dist/record-key.js +187 -15
- package/dist/record-key.js.map +1 -1
- package/dist/references.d.ts +111 -0
- package/dist/references.d.ts.map +1 -0
- package/dist/references.js +207 -0
- package/dist/references.js.map +1 -0
- package/dist/resources.d.ts +225 -0
- package/dist/resources.d.ts.map +1 -0
- package/dist/resources.js +359 -0
- package/dist/resources.js.map +1 -0
- package/dist/standards.d.ts +137 -0
- package/dist/standards.d.ts.map +1 -0
- package/dist/standards.js +462 -0
- package/dist/standards.js.map +1 -0
- package/dist/validate.d.ts +108 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +175 -0
- package/dist/validate.js.map +1 -0
- package/package.json +3 -2
- package/src/authoring.ts +880 -0
- package/src/blueprints.ts +4630 -0
- package/src/capabilities.ts +235 -203
- package/src/compose.ts +132 -30
- package/src/contested.ts +2 -1
- package/src/fields.ts +286 -0
- package/src/frontend.ts +66 -0
- package/src/index.ts +102 -1
- package/src/loader.ts +205 -32
- package/src/manifest.ts +181 -0
- package/src/project.ts +311 -0
- package/src/provenance.ts +113 -0
- package/src/record-key.ts +219 -16
- package/src/references.ts +259 -0
- package/src/resources.ts +483 -0
- package/src/standards.ts +507 -0
- package/src/validate.ts +220 -0
|
@@ -329,11 +329,12 @@ function pluginProblem(plugin) {
|
|
|
329
329
|
if (
|
|
330
330
|
plugin.hooks === undefined &&
|
|
331
331
|
plugin.register === undefined &&
|
|
332
|
-
plugin.controller === undefined
|
|
332
|
+
plugin.controller === undefined &&
|
|
333
|
+
plugin.frontend === undefined
|
|
333
334
|
) {
|
|
334
|
-
return "plugin.js declares no hooks, register or
|
|
335
|
+
return "plugin.js declares no hooks, register, controller or frontend, so it would do nothing";
|
|
335
336
|
}
|
|
336
|
-
for (const name of ["hooks", "register", "controller", "uninstall"]) {
|
|
337
|
+
for (const name of ["hooks", "register", "controller", "frontend", "uninstall"]) {
|
|
337
338
|
if (plugin[name] !== undefined && typeof plugin[name] !== "function") {
|
|
338
339
|
return `plugin.js: ${name} is not a function`;
|
|
339
340
|
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Check a mod folder against the requirements the game enforces.
|
|
4
|
+
*
|
|
5
|
+
* neo-angband-mod-check [path] check the folder (default: .)
|
|
6
|
+
* neo-angband-mod-check --list print every rule and why it exists
|
|
7
|
+
* neo-angband-mod-check --write-docs regenerate docs/modding/REQUIREMENTS.md
|
|
8
|
+
*
|
|
9
|
+
* WHY A CLI AND NOT A DOCUMENT. An author following a document finds out whether they
|
|
10
|
+
* got it right when somebody else's install fails. This runs the SAME rules the game
|
|
11
|
+
* runs at install time - literally the same functions, imported from the SDK - so a
|
|
12
|
+
* green run here means the game will accept the mod, and a red one names the field.
|
|
13
|
+
*
|
|
14
|
+
* ARCHIVES. If the folder holds committed .zip files, they are read as the payload
|
|
15
|
+
* the way the installer reads them, so the "does this mod ship plugin.js" questions
|
|
16
|
+
* are asked of the UNPACKED contents. Checking the repository's file list instead
|
|
17
|
+
* would have answered "no plugin.js" for every mod that ships a pack, which is the
|
|
18
|
+
* shape of wrong answer that makes a checker worse than nothing.
|
|
19
|
+
*
|
|
20
|
+
* Exit 0 when nothing REQUIRED failed. Advice never changes the exit code: a check
|
|
21
|
+
* that fails a build over a missing description is a check authors route around.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
25
|
+
import { join, relative, resolve, sep } from "node:path";
|
|
26
|
+
|
|
27
|
+
import { MOD_REQUIREMENTS, checkMod, requirementsMarkdown } from "../dist/index.js";
|
|
28
|
+
|
|
29
|
+
const argv = process.argv.slice(2);
|
|
30
|
+
const flag = (name) => argv.includes(name);
|
|
31
|
+
|
|
32
|
+
/** Every file under `dir`, relative, with forward slashes. */
|
|
33
|
+
function walk(dir, base = dir) {
|
|
34
|
+
const out = [];
|
|
35
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
36
|
+
/* Skip what is never part of a mod, so the report is about the mod rather than
|
|
37
|
+
* about node_modules. Mirrors mod-source.ts's NOT_PAYLOAD in spirit; the game's
|
|
38
|
+
* own list is the authority for what actually installs. */
|
|
39
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
40
|
+
const full = join(dir, entry.name);
|
|
41
|
+
if (entry.isDirectory()) out.push(...walk(full, base));
|
|
42
|
+
else out.push(relative(base, full).split(sep).join("/"));
|
|
43
|
+
}
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The installed file list, unpacking any committed archive the manifest declares.
|
|
49
|
+
*
|
|
50
|
+
* Uses the same reasoning as the installer: a declared archive contributes its
|
|
51
|
+
* CONTENTS, and its own path does not survive the install.
|
|
52
|
+
*/
|
|
53
|
+
async function installedFiles(root, repoFiles, declared) {
|
|
54
|
+
const archives = declared?.archives ?? [];
|
|
55
|
+
if (archives.length === 0) return repoFiles;
|
|
56
|
+
let unzipSync;
|
|
57
|
+
try {
|
|
58
|
+
({ unzipSync } = await import("fflate"));
|
|
59
|
+
} catch {
|
|
60
|
+
/* fflate is the game's unzip, not the SDK's dependency. Without it the archive
|
|
61
|
+
* contents cannot be seen - so say so rather than reporting a mod as missing the
|
|
62
|
+
* files that are inside a zip this could not open. */
|
|
63
|
+
console.error(
|
|
64
|
+
"note: fflate is not installed, so declared archives were not opened.\n" +
|
|
65
|
+
" Rules about the mod's files may be reported wrongly.\n" +
|
|
66
|
+
" Install it (npm i -D fflate) for an accurate check.",
|
|
67
|
+
);
|
|
68
|
+
return repoFiles;
|
|
69
|
+
}
|
|
70
|
+
const files = repoFiles.filter((f) => !archives.includes(f));
|
|
71
|
+
for (const archive of archives) {
|
|
72
|
+
let bytes;
|
|
73
|
+
try {
|
|
74
|
+
bytes = readFileSync(resolve(root, archive));
|
|
75
|
+
} catch {
|
|
76
|
+
console.error(`note: payload.archives names ${archive}, which is not there`);
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
for (const name of Object.keys(unzipSync(bytes))) {
|
|
81
|
+
if (!name.endsWith("/")) files.push(name);
|
|
82
|
+
}
|
|
83
|
+
} catch (e) {
|
|
84
|
+
console.error(`note: ${archive} is not a readable zip (${e.message})`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return files;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (flag("--list")) {
|
|
91
|
+
for (const r of MOD_REQUIREMENTS) {
|
|
92
|
+
console.log(`${r.level === "required" ? "MUST " : "SHOULD"} ${r.title}`);
|
|
93
|
+
console.log(` ${r.id}`);
|
|
94
|
+
console.log(` ${r.why}\n`);
|
|
95
|
+
}
|
|
96
|
+
process.exit(0);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (flag("--write-docs")) {
|
|
100
|
+
/* Written relative to this script, so it works from any directory - and it is the
|
|
101
|
+
* ONLY writer of that file, which is what lets a test assert the two agree. */
|
|
102
|
+
const out = resolve(import.meta.dirname, "..", "..", "..", "docs", "modding", "REQUIREMENTS.md");
|
|
103
|
+
writeFileSync(out, `${requirementsMarkdown()}\n`, "utf8");
|
|
104
|
+
console.log(`wrote ${out}`);
|
|
105
|
+
process.exit(0);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const root = resolve(argv.find((a) => !a.startsWith("-")) ?? ".");
|
|
109
|
+
try {
|
|
110
|
+
if (!statSync(root).isDirectory()) throw new Error("not a directory");
|
|
111
|
+
} catch {
|
|
112
|
+
console.error(`${root} is not a folder this can check.`);
|
|
113
|
+
process.exit(2);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const repoFiles = walk(root);
|
|
117
|
+
let manifestText = null;
|
|
118
|
+
try {
|
|
119
|
+
manifestText = readFileSync(join(root, "manifest.json"), "utf8");
|
|
120
|
+
} catch {
|
|
121
|
+
/* manifest-present reports this; reading it here must not be fatal. */
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
let declared;
|
|
125
|
+
try {
|
|
126
|
+
declared = manifestText === null ? undefined : JSON.parse(manifestText).payload;
|
|
127
|
+
} catch {
|
|
128
|
+
/* manifest-json reports this. */
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const files = await installedFiles(root, repoFiles, declared);
|
|
132
|
+
const report = checkMod({ files, manifestText, repoFiles, declaredPayload: declared });
|
|
133
|
+
|
|
134
|
+
const show = (findings, label) => {
|
|
135
|
+
if (findings.length === 0) return;
|
|
136
|
+
console.log(`\n${label}\n`);
|
|
137
|
+
for (const f of findings) {
|
|
138
|
+
console.log(` ${f.title}`);
|
|
139
|
+
console.log(` ${f.problem}`);
|
|
140
|
+
console.log(` (${f.id})`);
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
console.log(`checked ${root}`);
|
|
145
|
+
console.log(` ${String(files.length)} installed file(s), ${String(MOD_REQUIREMENTS.length)} rules`);
|
|
146
|
+
show(report.errors, "MUST FIX - the game will refuse to install this:");
|
|
147
|
+
show(report.advice, "SHOULD FIX - players will notice:");
|
|
148
|
+
|
|
149
|
+
if (report.ok && report.advice.length === 0) console.log("\nAll clear.");
|
|
150
|
+
else if (report.ok) console.log("\nInstallable. The advice above is optional.");
|
|
151
|
+
|
|
152
|
+
process.exit(report.ok ? 0 : 1);
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shortcuts: what a new record needs, what values are typical, and what an
|
|
3
|
+
* author is about to forget.
|
|
4
|
+
*
|
|
5
|
+
* THE PROBLEM. Adding a record to a pack has never been hard - it is JSON, and
|
|
6
|
+
* composition takes it. Adding a record that WORKS is hard, and it is hard in a
|
|
7
|
+
* way no error message reaches: an object with no `alloc` is legal, loads
|
|
8
|
+
* cleanly, and never appears in the dungeon. A monster whose `base` is
|
|
9
|
+
* misspelled is legal, loads cleanly, and binds to nothing. A forty-first
|
|
10
|
+
* potion is legal, loads cleanly, and consumes the last unused flavour so that
|
|
11
|
+
* some other potion becomes unidentifiable. Nothing in the pipeline can say any
|
|
12
|
+
* of that, because nothing in the pipeline knows what a working record LOOKS
|
|
13
|
+
* like.
|
|
14
|
+
*
|
|
15
|
+
* Core's own 3,279 records know. This file is the three ways of asking them:
|
|
16
|
+
*
|
|
17
|
+
* - `templateRecord(file)` - the fields core always writes, filled with core's
|
|
18
|
+
* typical values. The starting point, so nobody has to reverse-engineer a
|
|
19
|
+
* record shape out of a 6,000-line JSON file.
|
|
20
|
+
* - `suggestFields(file, draft, records)` - what core's own comparable records
|
|
21
|
+
* do. A price for a level-20 sword is the median price of core's level-20
|
|
22
|
+
* swords, which is a derivation rather than a guess and is what the author
|
|
23
|
+
* actually wanted when they asked "what should this cost".
|
|
24
|
+
* - `checkRecords(subject, all)` - every way the draft will silently do
|
|
25
|
+
* nothing, named.
|
|
26
|
+
*
|
|
27
|
+
* WHY THIS IS NOT A SCHEMA. A schema says what is allowed; core's data cannot,
|
|
28
|
+
* because the whole point of the mod system is that a mod may do things core
|
|
29
|
+
* never does. So every check here is graded - `error` only where the record
|
|
30
|
+
* cannot work at all, `warn` where it will not do what the author meant, `hint`
|
|
31
|
+
* where it is worth a look - and NOTHING here refuses anything. The refusals
|
|
32
|
+
* live in manifest.ts and fields.ts, where the rules are the engine's own.
|
|
33
|
+
*/
|
|
34
|
+
import type { FieldShape, RecordBlueprint } from "./blueprints.js";
|
|
35
|
+
import type { JsonRecord, JsonValue } from "./compose.js";
|
|
36
|
+
/**
|
|
37
|
+
* How much a finding costs.
|
|
38
|
+
*
|
|
39
|
+
* - `error`: the record cannot work. A required field is absent.
|
|
40
|
+
* - `warn`: the record loads and will not do what it looks like it does. A
|
|
41
|
+
* dangling reference, a missing `alloc`.
|
|
42
|
+
* - `hint`: worth a look. An unfamiliar value, a companion step, a field core
|
|
43
|
+
* always writes and this record does not.
|
|
44
|
+
*/
|
|
45
|
+
export type FindingLevel = "error" | "warn" | "hint";
|
|
46
|
+
/** One thing worth saying about one record. */
|
|
47
|
+
export interface AuthoringFinding {
|
|
48
|
+
readonly level: FindingLevel;
|
|
49
|
+
/** Pack file stem. */
|
|
50
|
+
readonly file: string;
|
|
51
|
+
/** The record's name, for the message. */
|
|
52
|
+
readonly record: string;
|
|
53
|
+
/** The field path, when the finding is about one. */
|
|
54
|
+
readonly field?: string;
|
|
55
|
+
/** A full sentence naming the record, the field and what to do. */
|
|
56
|
+
readonly message: string;
|
|
57
|
+
/** Stable id of the rule that produced it, so a host can filter. */
|
|
58
|
+
readonly rule: string;
|
|
59
|
+
}
|
|
60
|
+
/** The blueprint for a record file, or undefined if core ships no such file. */
|
|
61
|
+
export declare function blueprintFor(file: string): RecordBlueprint | undefined;
|
|
62
|
+
/** Every record file core ships, sorted. */
|
|
63
|
+
export declare const BLUEPRINT_FILES: readonly string[];
|
|
64
|
+
/**
|
|
65
|
+
* The fields EVERY one of core's records in this file carries.
|
|
66
|
+
*
|
|
67
|
+
* This is the definition of "required" used throughout, and it is a measurement
|
|
68
|
+
* rather than a schema: if all 624 monsters have `base`, a monster without one
|
|
69
|
+
* is missing something core has never once omitted.
|
|
70
|
+
*/
|
|
71
|
+
export declare function requiredFields(file: string): readonly string[];
|
|
72
|
+
/** One field of a record file, with how much of core's data uses it. */
|
|
73
|
+
export interface FieldUsage {
|
|
74
|
+
readonly name: string;
|
|
75
|
+
readonly shape: FieldShape;
|
|
76
|
+
/** Fraction of the file's records that carry it, 0 to 1. */
|
|
77
|
+
readonly share: number;
|
|
78
|
+
}
|
|
79
|
+
/** Every field of a record file, most-used first. */
|
|
80
|
+
export declare function fieldUsage(file: string): readonly FieldUsage[];
|
|
81
|
+
/** How much of a file's data a template includes. */
|
|
82
|
+
export type TemplateScope = "required" | "common" | "all";
|
|
83
|
+
/**
|
|
84
|
+
* A starting record for `file`, with core's typical value in every field.
|
|
85
|
+
*
|
|
86
|
+
* TYPICAL VALUES RATHER THAN EMPTY ONES, deliberately. An author handed
|
|
87
|
+
* `{"name":"","level":0,"weight":0}` has been handed the shape and none of the
|
|
88
|
+
* knowledge; handed `{"name":"","level":25,"weight":150,"cost":150}` they can
|
|
89
|
+
* see at a glance what scale the game works on and change what they mean to
|
|
90
|
+
* change. `name` is the one field left empty, because it is the one field
|
|
91
|
+
* nobody wants a default for.
|
|
92
|
+
*
|
|
93
|
+
* Scope `common` (the default) adds the fields at least half of core's records
|
|
94
|
+
* carry - which for an object is the difference between a shape and a working
|
|
95
|
+
* item, since `alloc` is on 352 of 375 and is exactly what a new object is
|
|
96
|
+
* likeliest to be missing.
|
|
97
|
+
*/
|
|
98
|
+
export declare function templateRecord(file: string, scope?: TemplateScope): JsonRecord;
|
|
99
|
+
/** The comparable records a suggestion or a model is drawn from. */
|
|
100
|
+
export interface PeerSet {
|
|
101
|
+
/** Core's records comparable to the draft, nearest first when depth is known. */
|
|
102
|
+
readonly peers: readonly JsonRecord[];
|
|
103
|
+
/** Which records these are, in a sentence, for the evidence line. */
|
|
104
|
+
readonly because: string;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Core's own records that are comparable to `draft`, narrowed twice: to the
|
|
108
|
+
* same peer field (all swords), then to the nearest in depth (level-20 swords).
|
|
109
|
+
*
|
|
110
|
+
* Shared by suggestFields and draftRecord so a suggested number and the record
|
|
111
|
+
* it lands in are measured against the SAME set. Two different notions of
|
|
112
|
+
* "comparable" in one drafted record would produce a coherent-looking item
|
|
113
|
+
* whose price came from somewhere else.
|
|
114
|
+
*/
|
|
115
|
+
export declare function peersFor(file: string, draft: JsonRecord, records?: Readonly<Record<string, readonly JsonRecord[]>>): PeerSet;
|
|
116
|
+
/** One suggested value, with the evidence behind it. */
|
|
117
|
+
export interface Suggestion {
|
|
118
|
+
readonly field: string;
|
|
119
|
+
readonly value: JsonValue;
|
|
120
|
+
/** Where the number came from, so an author can disagree with it knowingly. */
|
|
121
|
+
readonly because: string;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* What core's comparable records would put in the fields this draft leaves out.
|
|
125
|
+
*
|
|
126
|
+
* THIS IS THE ANSWER TO "WHAT SHOULD IT COST". A price is not derivable from
|
|
127
|
+
* first principles - Angband's costs are hand-set - but it is derivable from
|
|
128
|
+
* precedent, and precedent is what core's 375 objects are. A draft that says
|
|
129
|
+
* `{type: "sword", level: 20}` gets the median cost, weight and to-hit of the
|
|
130
|
+
* seven core swords nearest level 20, and every suggestion carries the sentence
|
|
131
|
+
* explaining which records it came from.
|
|
132
|
+
*
|
|
133
|
+
* `records` is the composed data - core plus any packs already loaded - so a
|
|
134
|
+
* mod that has added its own tval gets suggestions from its OWN records once it
|
|
135
|
+
* has more than none. With no records supplied the numbers fall back to the
|
|
136
|
+
* blueprint's file-wide medians, which is weaker and says so.
|
|
137
|
+
*
|
|
138
|
+
* Only NUMERIC fields are suggested. A name, a description or a set of flags is
|
|
139
|
+
* a design decision, and filling those in from a neighbour would produce a
|
|
140
|
+
* record that reads like core's and is not what the author meant.
|
|
141
|
+
*/
|
|
142
|
+
export declare function suggestFields(file: string, draft: JsonRecord, records?: Readonly<Record<string, readonly JsonRecord[]>>): Suggestion[];
|
|
143
|
+
/**
|
|
144
|
+
* A companion step: something the record itself is fine without and the AUTHOR
|
|
145
|
+
* is not.
|
|
146
|
+
*
|
|
147
|
+
* Expressed as data rather than as code so the whole list can be read at once,
|
|
148
|
+
* printed by a scaffolder, and extended without touching the checker. A rule
|
|
149
|
+
* fires when the record carries every path in `present` and none of the paths
|
|
150
|
+
* in `absent`.
|
|
151
|
+
*/
|
|
152
|
+
export interface CompanionRule {
|
|
153
|
+
readonly id: string;
|
|
154
|
+
readonly file: string;
|
|
155
|
+
/** Fires only if the record carries a value at every one of these. */
|
|
156
|
+
readonly present?: readonly string[];
|
|
157
|
+
/** Fires only if the record carries a value at NONE of these. */
|
|
158
|
+
readonly absent?: readonly string[];
|
|
159
|
+
readonly level: FindingLevel;
|
|
160
|
+
/** What is missing and what to do about it. */
|
|
161
|
+
readonly message: string;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Every "you will not find out until you play it" rule, written down.
|
|
165
|
+
*
|
|
166
|
+
* These are Angband's rules, not the port's, and each one is a real way a
|
|
167
|
+
* well-formed record does nothing. They are all `warn` or `hint` because every
|
|
168
|
+
* one of them is legal: an object with no `alloc` is exactly how core defines
|
|
169
|
+
* an item that only ever comes from a store or an artifact.
|
|
170
|
+
*/
|
|
171
|
+
export declare const COMPANION_RULES: readonly CompanionRule[];
|
|
172
|
+
/** Options for checkRecords. */
|
|
173
|
+
export interface CheckOptions {
|
|
174
|
+
/** Findings at or above this level only. Default: everything. */
|
|
175
|
+
readonly minLevel?: FindingLevel;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Every way the records in `subject` will silently not work, named.
|
|
179
|
+
*
|
|
180
|
+
* TWO ARGUMENTS, AND THE SPLIT IS THE WHOLE DESIGN. `subject` is what is
|
|
181
|
+
* reported on - the mod's own records. `all` is what they are allowed to resolve
|
|
182
|
+
* against - core plus every loaded pack, as composition produced it. Checking a
|
|
183
|
+
* mod against itself would report every reference to core as broken; reporting
|
|
184
|
+
* on everything would drown the author in upstream's own warts, of which there
|
|
185
|
+
* are real ones (see references.ts). Pass the same object twice to audit
|
|
186
|
+
* everything, which is what the tests do.
|
|
187
|
+
*/
|
|
188
|
+
export declare function checkRecords(subject: Readonly<Record<string, readonly JsonRecord[]>>, all: Readonly<Record<string, readonly JsonRecord[]>>, options?: CheckOptions): AuthoringFinding[];
|
|
189
|
+
/** A drafted record, with everything that was decided for the author. */
|
|
190
|
+
export interface DraftedRecord {
|
|
191
|
+
/** The record itself: the model, the suggestions, then the author's values. */
|
|
192
|
+
readonly record: JsonRecord;
|
|
193
|
+
/** Every number that was chosen for them, and on what evidence. */
|
|
194
|
+
readonly suggestions: readonly Suggestion[];
|
|
195
|
+
/** Everything still worth saying about it. */
|
|
196
|
+
readonly findings: readonly AuthoringFinding[];
|
|
197
|
+
/**
|
|
198
|
+
* The core record the shape was taken from, when there was one.
|
|
199
|
+
*
|
|
200
|
+
* Named so the author can go and read it. "Modelled on the Main Gauche" is a
|
|
201
|
+
* far more useful thing to know than any list of defaults.
|
|
202
|
+
*/
|
|
203
|
+
readonly modelledOn?: string;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* The whole workflow for one new record, in one call.
|
|
207
|
+
*
|
|
208
|
+
* THIS IS THE SHORTCUT. An author who knows what they want ("a sludge-brand
|
|
209
|
+
* dagger for around dungeon level 20") should not have to learn what an
|
|
210
|
+
* Angband object record contains before they can have one:
|
|
211
|
+
*
|
|
212
|
+
* draftRecord("object", { name: "& Sludge Dagger~", type: "sword", level: 20 }, core)
|
|
213
|
+
*
|
|
214
|
+
* comes back with a complete record - cost, weight, to-hit, alloc and graphics
|
|
215
|
+
* all taken from core's own level-20 swords - a list of which numbers were
|
|
216
|
+
* chosen and why, and a list of what is still wrong with it. Every step is
|
|
217
|
+
* separately callable (templateRecord / suggestFields / checkRecords); this is
|
|
218
|
+
* the order they go in.
|
|
219
|
+
*
|
|
220
|
+
* THE AUTHOR'S VALUES ALWAYS WIN, and a value they supplied is never
|
|
221
|
+
* "suggested" over. The suggestions fill the gaps that remain, which is the
|
|
222
|
+
* only place a derived number is better than no number.
|
|
223
|
+
*
|
|
224
|
+
* MODELLED ON A REAL RECORD, NOT ASSEMBLED FROM AVERAGES. The first version of
|
|
225
|
+
* this built the shape from the blueprint's file-wide statistics, and produced
|
|
226
|
+
* a sword carrying `armor` - because 59% of core's objects have `armor`, even
|
|
227
|
+
* though no sword does. Field frequency across a whole file is not a statement
|
|
228
|
+
* about any record in it. So the shape comes from core's nearest comparable
|
|
229
|
+
* record, minus everything that would confer behaviour (MODEL_EXCLUDE), and
|
|
230
|
+
* only the numbers are averaged. When there is no comparable record at all it
|
|
231
|
+
* falls back to `templateRecord`, which is weaker and says so in the findings.
|
|
232
|
+
*/
|
|
233
|
+
export declare function draftRecord(file: string, values?: JsonRecord, records?: Readonly<Record<string, readonly JsonRecord[]>>, scope?: TemplateScope): DraftedRecord;
|
|
234
|
+
/**
|
|
235
|
+
* A short human summary of what a record file wants, for a scaffolder or a
|
|
236
|
+
* `--help`.
|
|
237
|
+
*/
|
|
238
|
+
export declare function describeFile(file: string): string;
|
|
239
|
+
//# sourceMappingURL=authoring.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authoring.d.ts","sourceRoot":"","sources":["../src/authoring.ts"],"names":[],"mappings":"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"}
|