@rpgm-tools/neo-angband-mod-sdk 1.2.1 → 1.8.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.
@@ -10,7 +10,7 @@ folder is and how to load one.
10
10
 
11
11
  ---
12
12
 
13
- ## The whole mod
13
+ ## Mod files
14
14
 
15
15
  ```
16
16
  my-item-mod/
@@ -71,21 +71,21 @@ invented, which means the base game's own data is your reference manual. If you
71
71
  want to know what a field does, find an item that already does it and copy how
72
72
  it says so.
73
73
 
74
- - **`name`**: the trailing `~` is where the plural goes. "Padded Jerkin~"
74
+ - `name`: the trailing `~` is where the plural goes. "Padded Jerkin~"
75
75
  displays as *a Padded Jerkin* and *2 Padded Jerkins*. An item whose name starts
76
76
  with `&` (`"& Dagger~"`) takes an article. These marks are the original game's
77
77
  convention, and they are why you should copy an existing name's punctuation
78
78
  rather than invent it.
79
- - **`type`**: which kind of item this is. `soft armor` puts it in the body
79
+ - `type`: which kind of item this is. `soft armor` puts it in the body
80
80
  armour slot and in the armoury's stock. Use one the game already has;
81
81
  [Tutorial 4](04-change-a-spell.md)'s follow-on reading covers inventing a
82
82
  wholly new item class.
83
- - **`level`**: the depth at which it starts appearing.
84
- - **`alloc`**: how often, and between which depths. `common: 20` is roughly the
83
+ - `level`: the depth at which it starts appearing.
84
+ - `alloc`: how often, and between which depths. `common: 20` is roughly the
85
85
  frequency of ordinary early gear; `"1 to 40"` is the depth window.
86
- - **`armor.ac`**: the armour it gives. Soft Leather Armour has 8 and costs 20,
86
+ - `armor.ac`: the armour it gives. Soft Leather Armour has 8 and costs 20,
87
87
  so 5 for 12 gold is a deliberately worse, cheaper option.
88
- - **`desc`**: an array of lines, shown when the player inspects it.
88
+ - `desc`: an array of lines, shown when the player inspects it.
89
89
 
90
90
  **Where to get the numbers.** Open `packages/content/pack/object.json` in this
91
91
  repository and find an item like the one you want. That file is the base game's
@@ -94,7 +94,7 @@ always a working template. There is also a helper that does this for you:
94
94
  `draftRecord` fills in a new record from the game's comparable records, including
95
95
  a sensible price. See [AUTHORING.md](../AUTHORING.md).
96
96
 
97
- ## What you should see
97
+ ## Check the result
98
98
 
99
99
  Your item exists in the game as soon as the mod is on: it generates in the
100
100
  dungeon inside its own depth window, monsters can be carrying one, and wearing it
@@ -112,13 +112,13 @@ made tutorial 1's dagger `core:sword--dagger`. The `my-item-mod:` half is your
112
112
  own namespace, so your item can never collide with the base game's or with
113
113
  another mod's, even if you both add a Padded Jerkin.
114
114
 
115
- ## Try changing this
115
+ ## Variations to try
116
116
 
117
- - Make it **cursed-cheap and heavy**: `weight` 200, `cost` 2.
118
- - Make it **rare and deep**: `level` 30, `alloc` `{ "common": 3, "minmax": "30 to 100" }`.
119
- - Add a **second item** in the same `records` array. A mod can add as many as it
117
+ - Make it cursed-cheap and heavy: `weight` 200, `cost` 2.
118
+ - Make it rare and deep: `level` 30, `alloc` `{ "common": 3, "minmax": "30 to 100" }`.
119
+ - Add a second item in the same `records` array. A mod can add as many as it
120
120
  likes; the array is a list.
121
- - Give it an **ego** possibility, or a flag. Find an item in the base game's
121
+ - Give it an ego possibility, or a flag. Find an item in the base game's
122
122
  `object.json` that has the property you want and copy the field across.
123
123
 
124
124
  ## What your item joins automatically
@@ -126,15 +126,15 @@ another mod's, even if you both add a Padded Jerkin.
126
126
  This is the part worth knowing, because it is the part you do not have to build.
127
127
  Your Padded Jerkin is a soft armour, and the game treats it as one everywhere:
128
128
 
129
- - **Egos, runes and brands apply to it.** An ego declares the *kinds* of item it
129
+ - Egos, runes and brands apply to it. An ego declares the *kinds* of item it
130
130
  can land on, not a list of named ones: "of Resist Acid" says `soft armor`, so
131
131
  it says your jerkin too. A **Padded Jerkin of Resist Fire** is a thing a player
132
132
  can find, and nobody had to add it to a list.
133
- - **Quality enchantment applies.** It can turn up as `[5,+6]`, and its price is
133
+ - Quality enchantment applies. It can turn up as `[5,+6]`, and its price is
134
134
  recomputed from what it ended up being rather than from the `cost` you wrote.
135
- - **It is generated in the dungeon** at the depths your `alloc` names, and it is
135
+ - It is generated in the dungeon at the depths your `alloc` names, and it is
136
136
  in the drop tables from the moment the mod is on.
137
- - **Shops will buy it,** because a shop's buy list is by item kind too.
137
+ - Shops will buy it, because a shop's buy list is by item kind too.
138
138
 
139
139
  ## One thing that does NOT happen: a picture
140
140
 
@@ -191,18 +191,18 @@ offer it at any depth; `alloc` governs where it generates in the dungeon.
191
191
 
192
192
  Three things worth knowing about that patch:
193
193
 
194
- - **`core:store-armor`** is the store's record id: its code, `STORE_ARMOR`,
194
+ - `core:store-armor` is the store's record id: its code, `STORE_ARMOR`,
195
195
  lowercased with `_` turned into `-`. That rule is the STORE file's, not a
196
196
  universal one - each data file declares what identifies its records, and the
197
197
  table of them is `packages/mod-sdk/src/record-key.ts`. Most files use `name`;
198
198
  `object` uses `type` plus `name`; `brand`, `slay` and `projection` use `code`;
199
199
  `constants` and `visuals` have one record each and are named by the file.
200
- - **`normal` is the "may stock" table; `always` is the staples.** Appending to
200
+ - `normal` is the "may stock" table; `always` is the staples. Appending to
201
201
  `always` means a shop keeps one on the shelf at all times, which for most
202
202
  items is not what you want - and note the Armoury ships no `always` list at
203
203
  all, so appending to one there has nothing to append to and is reported rather
204
204
  than working quietly.
205
- - **`sval` is the item's name without the `~`.** If it does not match an item
205
+ - `sval` is the item's name without the `~`. If it does not match an item
206
206
  that exists, the game drops that one line from the shop's table and reports it
207
207
  against your mod in the mod manager, so a typo costs you a line and tells you
208
208
  which one, rather than being silent or taking the game down with it.
@@ -223,7 +223,7 @@ manager says so on your mod's row, and everything else in the Armoury is exactly
223
223
  as it was. Declare the other mod in your `dependencies` so the player is told
224
224
  before they get there.
225
225
 
226
- ## The finished version
226
+ ## Sample mod
227
227
 
228
228
  `samples/tutorials/tutorial-02-add-an-item/`, which is loaded and composed against the
229
229
  real game data on every test run.
@@ -9,7 +9,7 @@ soldier ant, living on dungeon level 2.
9
9
 
10
10
  ---
11
11
 
12
- ## The whole mod
12
+ ## Mod files
13
13
 
14
14
  ```
15
15
  my-monster-mod/
@@ -49,8 +49,8 @@ file is `monster.json`:
49
49
 
50
50
  Every monster inherits from a *monster base*, the template that decides its
51
51
  symbol on the map, what it is made of, which attacks it can have, and a pile of
52
- other defaults. `ant` is one the base game ships. So are `canine`, `orc`,
53
- `dragon`, and fifty-odd others - the file is
52
+ other defaults. `ant` is one that the base game ships. So are `canine`, `orc`,
53
+ `dragon`, and fifty-odd others. The file is
54
54
  `packages/content/pack/monster_base.json` and it holds 56 records in total, so it
55
55
  is short enough to read.
56
56
 
@@ -70,36 +70,36 @@ tutorial's own test asserts the base exists rather than trusting it.
70
70
  Same principle as items: find a monster close to what you want in
71
71
  `packages/content/pack/monster.json` and copy its shape.
72
72
 
73
- - **`speed`**: 110 is normal walking pace, the same as an unhasted player. 120
73
+ - `speed`: 110 is normal walking pace, the same as an unhasted player. 120
74
74
  is fast enough to be genuinely dangerous.
75
- - **`depth`** and **`rarity`**: where it lives and how often it shows up there.
76
- - **`experience`**: not a flat award. What the player actually gets is
75
+ - `depth` and `rarity`: where it lives and how often it shows up there.
76
+ - `experience`: not a flat award. What the player actually gets is
77
77
  `experience * the monster's level / the player's level`, so the same monster is
78
78
  worth steadily less as the character grows.
79
- - **`sleepiness`**: how likely it is to be asleep when you arrive. 0 means it is
79
+ - `sleepiness`: how likely it is to be asleep when you arrive. 0 means it is
80
80
  always awake and coming for you.
81
- - **`blow`**: a list. Each entry is a `method` (how it attacks), an `effect`
81
+ - `blow`: a list. Each entry is a `method` (how it attacks), an `effect`
82
82
  (what that does to you), and `damage` dice. Three blows means three attacks per
83
83
  turn. The available methods and effects are in `blow_methods.json` and
84
84
  `blow_effects.json` beside the monster file.
85
- - **`color`**: a single letter, and the case matters. `u` is Umber (brown), `y`
85
+ - `color`: a single letter, and the case matters. `u` is Umber (brown), `y`
86
86
  is Yellow, `w` is White - `W` is Light Slate, which is what the soldier ant
87
87
  actually is. `packages/core/src/color.ts` is the chart.
88
88
 
89
- ## What you should see
89
+ ## Check the result
90
90
 
91
91
  Start a character, descend to level 2, and look for a brown `a`. Look it up with
92
92
  `/` or recall it with `l` and the game will describe it using the text you wrote.
93
93
 
94
- ## Try changing this
94
+ ## Variations to try
95
95
 
96
- - Make it a **unique**: add `"flags": ["UNIQUE"]`, give it a capitalised proper
96
+ - Make it a unique: add `"flags": ["UNIQUE"]`, give it a capitalised proper
97
97
  name and a lot more hit points.
98
- - Give it a **second blow** by adding another entry to the `blow` array.
99
- - Make it **fast and fragile**: `speed` 130, `hit-points` 4.
100
- - Add a whole **family**: several records in one array, sharing a base.
98
+ - Give it a second blow by adding another entry to the `blow` array.
99
+ - Make it fast and fragile: `speed` 130, `hit-points` 4.
100
+ - Add a whole family: several records in one array, sharing a base.
101
101
 
102
- ## Try breaking it
102
+ ## Test a bad base reference
103
103
 
104
104
  Change `"base": "ant"` to `"base": "aunt"` and reload. Notice what does *and does
105
105
  not* happen. That is the failure mode described above, and it is much easier to
@@ -109,9 +109,9 @@ recognise later if you have seen it once on purpose.
109
109
 
110
110
  Your ant already has an appearance, and you only wrote half of it:
111
111
 
112
- - **`"color": "u"`** is yours: umber, so it draws as a brown `a` and reads as a
112
+ - `"color": "u"` is yours: umber, so it draws as a brown `a` and reads as a
113
113
  different creature from the white `a` beside it.
114
- - **The letter `a` is not yours.** It comes from `"base": "ant"`, along with
114
+ - The letter `a` is not yours. It comes from `"base": "ant"`, along with
115
115
  everything else the template carries. Change the base and the letter changes.
116
116
 
117
117
  **In a tile set, your ant has no picture, and the game will not invent one.** A
@@ -152,13 +152,13 @@ tile mod that fills blanks, the family it borrows from.
152
152
  If you want a *specific* picture, a mod can say so. Both routes are past what this
153
153
  tutorial covers, and they differ more than they look:
154
154
 
155
- - **Point at a picture that already exists.** Ship a `.prf` as a `prefs`
155
+ - Point at a picture that already exists. Ship a `.prf` as a `prefs`
156
156
  resource, and its `monster:carpenter ant:<attr>:<char>` line layers over the
157
157
  player's tile set and wins over anything a tile mod would have filled in. One
158
158
  line, no art, but the numbers are *atlas coordinates*, so they are correct for
159
159
  one pack and wrong for every other. Reach for this when your mod ships or
160
160
  requires a particular set.
161
- - **Ship a whole tile set.** A mod with the `tiles` facet contributes a graphics
161
+ - Ship a whole tile set. A mod with the `tiles` facet contributes a graphics
162
162
  mode of its own (`tilePacks`), which is how the Linoleum sets are delivered.
163
163
  That is a set the player chooses from the Graphics menu, not one picture added
164
164
  to somebody else's set, which nothing supports today.
@@ -185,11 +185,11 @@ mirror of [Tutorial 1](01-tweak-a-value.md) is worth seeing on a monster:
185
185
  Giant black ants now have a little more health and hunt in groups. Two things
186
186
  that section is teaching beyond the ops themselves:
187
187
 
188
- - **`add` is not `set`.** `{"op": "add", "path": "hit-points", "value": 3}` means
188
+ - `add` is not `set`. `{"op": "add", "path": "hit-points", "value": 3}` means
189
189
  "three more than whatever it is", so it still does the right thing if the base
190
190
  game retunes the monster, and it still does the right thing if another mod
191
191
  changed it first. `set` would silently undo both.
192
- - **`addFlag` composes.** Two mods adding different flags to the same monster both
192
+ - `addFlag` composes. Two mods adding different flags to the same monster both
193
193
  get their flag; neither is a conflict. That is true of `addFlag`, `removeFlag`
194
194
  and `append`, and not true of `set`, `merge`, `add` or `mul`. For those, two
195
195
  mods on the same field is a reported conflict and the one that loads last wins.
@@ -197,7 +197,7 @@ that section is teaching beyond the ops themselves:
197
197
  A patched monster keeps its picture, because the tile set already knows it by
198
198
  name. Only the ant you *added* has nothing drawn for it.
199
199
 
200
- ## The finished version
200
+ ## Sample mod
201
201
 
202
202
  `samples/tutorials/tutorial-03-add-a-monster/` in this repository is exactly
203
203
  this mod. It is not a copy of the tutorial. It is a mod that gets loaded and
@@ -9,7 +9,7 @@ and almost never fails.
9
9
 
10
10
  ---
11
11
 
12
- ## The whole mod
12
+ ## Mod files
13
13
 
14
14
  ```
15
15
  my-spell-mod/
@@ -48,7 +48,7 @@ To find the spell you want, open `packages/content/pack/class.json`, find your
48
48
  class, and count. Books and spells are in the order they appear in the game's own
49
49
  spell menu, so you can count them on screen instead if that is easier.
50
50
 
51
- ## The catch worth knowing now
51
+ ## Positional paths can target the wrong spell
52
52
 
53
53
  **A number in that path is a position, not a name.**
54
54
 
@@ -101,12 +101,12 @@ The `effect` block is the game's own effect vocabulary: `BOLT_STATUS`,
101
101
  game ships, but inventing a new one is a code job rather than a data job; that
102
102
  is [Tutorial 5](05-hook-behaviour.md) and the pages beyond it.
103
103
 
104
- ## What you should see
104
+ ## Check the result
105
105
 
106
106
  Roll a Priest, learn Minor Healing, and open the spell menu. Its mana cost reads
107
107
  1 and its failure rate is far lower than an unmodded Priest's at the same level.
108
108
 
109
- ## Try changing this
109
+ ## Variations to try
110
110
 
111
111
  - Retune a **different class**: `core:mage`, `core:ranger`, `core:paladin`.
112
112
  - Move a spell **earlier**: set its `level` to 1 and get it at character
@@ -114,7 +114,7 @@ Roll a Priest, learn Minor Healing, and open the spell menu. Its mana cost reads
114
114
  - Make a class **worse** at something, which is often what makes a variant
115
115
  interesting rather than a cheat.
116
116
 
117
- ## The finished version
117
+ ## Sample mod
118
118
 
119
119
  `samples/tutorials/tutorial-04-change-a-spell/`.
120
120
 
@@ -11,7 +11,7 @@ dependencies, just one `.js` file beside the manifest.
11
11
 
12
12
  ---
13
13
 
14
- ## The whole mod
14
+ ## Mod files
15
15
 
16
16
  ```
17
17
  my-code-mod/
@@ -109,24 +109,24 @@ Exactly as before: the `mods/` folder on the desktop build, or **Choose a mods
109
109
  folder...** in a browser. A code mod is loaded from a folder the same way a data
110
110
  mod is.
111
111
 
112
- ## What you should see
112
+ ## Check the result
113
113
 
114
114
  Gain a level. The message reads *"Congratulations! Welcome to level 2."*
115
115
 
116
116
  Turn the mod off, reload, and it reads *"Welcome to level 2."* again.
117
117
 
118
- ## Try changing this
118
+ ## Variations to try
119
119
 
120
- - Congratulate **loudly**: `` `*** ${raw.toUpperCase()} ***` ``.
120
+ - Congratulate loudly: `` `*** ${raw.toUpperCase()} ***` ``.
121
121
  - React to a **different** message. Find one you like in the game and match on
122
122
  it.
123
- - Add a **second** hook. `saveNoiseScent` and `objectListTiebreak` are two of the
124
- other seven; see [MOD_SEAMS.md](../MOD_SEAMS.md) for what each one is asked and
125
- when.
123
+ - Add a second hook. `saveNoiseScent` and `objectListTiebreak` are two of the
124
+ other seven; see [MOD_SEAMS.md](../MOD_SEAMS.md) for what each one is asked to do
125
+ and when.
126
126
  - Print something to the console from inside the hook and watch how often it is
127
127
  called. It is a good way to get a feel for the game's message volume.
128
128
 
129
- ## The finished version
129
+ ## Sample mod
130
130
 
131
131
  `samples/tutorials/tutorial-05-hook-behaviour/`, which really is imported and really is
132
132
  folded through the game's own hook composition on every test run. The test would
@@ -9,7 +9,7 @@ and off, from the game, without editing anything.
9
9
 
10
10
  ---
11
11
 
12
- ## The whole mod
12
+ ## Mod files
13
13
 
14
14
  Same two files. `manifest.json` gains a `rules` block:
15
15
 
@@ -55,7 +55,7 @@ export default {
55
55
  That is the entire feature. There is no settings API to learn, no storage to
56
56
  manage, and no screen to build.
57
57
 
58
- ## How it works
58
+ ## The manifest flag controls hook registration
59
59
 
60
60
  You **declare** the switch in the manifest. The game builds the screen, shows
61
61
  your `title` and `description`, remembers what the player chose, and puts the
@@ -79,9 +79,9 @@ hook at all**, not inside `messageText`, returning `raw` unchanged.
79
79
 
80
80
  Both look identical to the player. They are not the same thing:
81
81
 
82
- - **Checking inside the hook** means the game calls your function for every
82
+ - Checking inside the hook means the game calls your function for every
83
83
  message it ever prints, forever, and your function decides to do nothing.
84
- - **Checking around the hook** means that when the option is off, you supplied no
84
+ - Checking around the hook means that when the option is off, you supplied no
85
85
  hook, so the game runs its own untouched path and your mod is not in it at all.
86
86
 
87
87
  The second is the shape to reach for. A disabled option should cost nothing and
@@ -102,7 +102,7 @@ let them choose.
102
102
  The first-party `feature-restoration` mod works exactly this way: enabling it
103
103
  changes nothing at all until you pick a feature.
104
104
 
105
- ## What you should see
105
+ ## Check the result
106
106
 
107
107
  Enable the mod and reload. Nothing changes yet, and that is correct.
108
108
 
@@ -110,7 +110,7 @@ Press `Escape`, choose **Mods**, choose your mod, and you will find *Congratulat
110
110
  me on gaining a level* on its own screen, off. Turn it on, choose **Apply changes
111
111
  and reload**, and gain a level.
112
112
 
113
- ## Try changing this
113
+ ## Variations to try
114
114
 
115
115
  - Add a **second** option, controlling something else, and see both appear.
116
116
  - Make one **default on** and notice how differently the mod feels to install.
@@ -119,7 +119,7 @@ and reload**, and gain a level.
119
119
  `renamedSectionFlags` in [AUTHORING.md](../AUTHORING.md), since there is a
120
120
  supported way to do either without losing everyone's saved choice.
121
121
 
122
- ## The finished version
122
+ ## Sample mod
123
123
 
124
124
  `samples/tutorials/tutorial-06-add-an-option/`. Its test asserts the interesting half:
125
125
  that with the option off, the mod supplies **no hook at all**.
@@ -26,10 +26,10 @@ describing the difference, not the thing.
26
26
  Two consequences follow from that, and they are what makes artifacts feel
27
27
  different from tutorial 2:
28
28
 
29
- - **It is unique.** The game generates each artifact at most once per character.
29
+ - It is unique. The game generates each artifact at most once per character.
30
30
  There is no `alloc` fight with a hundred other shields; there is one of these,
31
31
  or there is not one yet.
32
- - **It inherits some things and not others.** The base object supplies the
32
+ - It inherits some things and not others. The base object supplies the
33
33
  kind's flags, and its activation when your artifact declares none. It does NOT
34
34
  supply the numbers: `weight`, `ac`, `to-a`, `to-h`, `to-d` and the damage dice
35
35
  all come from the artifact record, and one you leave out binds to zero rather
@@ -37,7 +37,7 @@ different from tutorial 2:
37
37
  leather shield's weight class, its material, how it reacts to acid: all of that
38
38
  arrives for free because you named the base.
39
39
 
40
- ## The whole mod
40
+ ## Mod files
41
41
 
42
42
  ```
43
43
  my-artifact-mod/
@@ -123,11 +123,11 @@ Leather Shield of the Watchful Eye~*.
123
123
 
124
124
  Both halves of it have to name something real:
125
125
 
126
- - **`tval`** is the item type, and it comes from `object_base.json`. `shield`,
126
+ - `tval` is the item type, and it comes from `object_base.json`. `shield`,
127
127
  `sword`, `hard armor`, `light`, `ring`. Note the American spelling on the
128
128
  armour ones, which catches people, and note that these are the base game's
129
129
  own strings rather than anything you get to choose.
130
- - **`sval`** is the base object's name inside that type, from `object.json`, with
130
+ - `sval` is the base object's name inside that type, from `object.json`, with
131
131
  the `&` and `~` decoration stripped off. The entry reads
132
132
  `"& Leather Shield~"`; you write `"Leather Shield"`.
133
133
 
@@ -174,21 +174,21 @@ dropped, so `'Angrist'` is `core:angrist`. If a ref does not resolve, the loader
174
174
  tells you so by name; that is the friendly failure, and it is what
175
175
  `npx neo-angband-mod-check` is for.
176
176
 
177
- ## What to fiddle with
177
+ ## Artifact fields
178
178
 
179
179
  Everything below `base-object` is the fun part.
180
180
 
181
- - **`level`** is how deep the game considers the artifact to be, which feeds
181
+ - `level` is how deep the game considers the artifact to be, which feeds
182
182
  into how it is priced and how good it is allowed to be.
183
- - **`alloc`** is `common`, the weighting against everything else eligible, and
183
+ - `alloc` is `common`, the weighting against everything else eligible, and
184
184
  `minmax`, the depth band it can appear in. `"12 to 70"` means it will not be
185
185
  generated shallower than 12 or deeper than 70.
186
- - **`armor.ac`** replaces the base object's armour class, and **`armor.to-a`** is
186
+ - `armor.ac` replaces the base object's armour class, and `armor.to-a` is
187
187
  the bonus on top. On a weapon you would be reaching for `attack.hd`, `to-h` and
188
188
  `to-d` instead.
189
- - **`flags`** are the yes-or-no properties: `SEE_INVIS`, `PROT_FEAR`,
189
+ - `flags` are the yes-or-no properties: `SEE_INVIS`, `PROT_FEAR`,
190
190
  `FREE_ACT`, `REGEN`, and a long list more.
191
- - **`values`** are the ones that carry a number, written with the number in
191
+ - `values` are the ones that carry a number, written with the number in
192
192
  square brackets: `INFRA[2]`, `RES_DARK[1]`, `STR[2]`, `SPEED[5]`.
193
193
 
194
194
  Both lists are in `packages/content/pack/object_property.json`. One catch when
@@ -204,7 +204,7 @@ If you want an artifact that does something when you activate it, that is `act`
204
204
  plus `time`, and the names come from `activation.json`. Copy a base-game artifact
205
205
  that already activates and change the numbers before writing one from scratch.
206
206
 
207
- ## What you learned
207
+ ## Artifact rules to remember
208
208
 
209
209
  - An artifact is a **layer over an existing item**, not a new item, which is why
210
210
  `base-object` exists and why the rest is mostly numbers.
@@ -214,7 +214,7 @@ that already activates and change the numbers before writing one from scratch.
214
214
  because the base game needs that behaviour for the Phial. Copy the sval.
215
215
  - `add` beats `set` when you are adjusting a number the base game owns.
216
216
 
217
- ## The finished version
217
+ ## Sample mod
218
218
 
219
219
  `samples/tutorials/tutorial-07-add-an-artifact/` in this repository is exactly
220
220
  this mod. It is not a copy of the tutorial. It is a mod that gets loaded and
@@ -1,6 +1,6 @@
1
1
  # Make a mod
2
2
 
3
- **Never made a mod before? That is fine.** These seven tutorials assume you can
3
+ **Never made a mod before? That is fine.** These eight tutorials assume you can
4
4
  edit a text file and nothing else. The first one takes about five minutes and
5
5
  changes the game.
6
6
 
@@ -10,7 +10,7 @@ in it.
10
10
 
11
11
  ---
12
12
 
13
- ## The seven tutorials
13
+ ## The eight tutorials
14
14
 
15
15
  Each one teaches exactly one idea and ends with something you can see on screen.
16
16
 
@@ -23,19 +23,22 @@ Each one teaches exactly one idea and ends with something you can see on screen.
23
23
  | 5 | [Hook behaviour](05-hook-behaviour.md) | Your mod running code, in ten lines | 2 |
24
24
  | 6 | [Add an option](06-add-an-option.md) | Letting the player switch your change on and off | 2 |
25
25
  | 7 | [Add an artifact](07-add-an-artifact.md) | Building on top of an item the game already has | 2 |
26
+ | 8 | [Add a store](https://github.com/neostryder/neo-angband/blob/master/docs/modding/tutorials/08-add-a-store.md) | Turning a town storefront into your own shop, and adding one shop rule | 4 |
26
27
 
27
28
  Do them in order if you are new. They build on each other, and each one is short
28
29
  enough to read in full before you type anything. Tutorial 7 is the one exception:
29
30
  it needs tutorial 2 and nothing else, so you can jump to it if an artifact is
30
- what brought you here.
31
+ what brought you here. Tutorial 8 needs tutorials 2 and 5, and is the one to
32
+ jump to if a shop is what brought you here.
31
33
 
32
- **These are not snippets.** Every tutorial's finished mod is a real folder in
34
+ **These are not snippets.** Tutorials 1 through 7 each have a finished mod in
33
35
  this repository under `samples/tutorials/`, loaded and checked against the actual
34
36
  game data on every test run, including a check that each one would be accepted
35
- by the same folder reader the game itself uses. If a tutorial ever stops working,
36
- the build fails before you find out the hard way.
37
+ by the same folder reader the game itself uses. Tutorial 8's four complete files
38
+ are on its page. If a sampled tutorial ever stops working, the build fails before
39
+ you find out the hard way.
37
40
 
38
- **You can run them without typing anything.** Copy any of the seven folders out of
41
+ **You can run them without typing anything.** Copy any of the first seven folders out of
39
42
  `samples/tutorials/` into the `mods/` folder beside the desktop build (or point a
40
43
  browser at `samples/tutorials/` with **Choose a mods folder...**), and they turn
41
44
  on from the Mods screen like any other mod. They are named `tutorial-01-...` to
@@ -54,17 +57,17 @@ it.
54
57
  Neo Angband is for when you would rather your change be a **portable thing** than
55
58
  an edit:
56
59
 
57
- - **The base game stays untouched.** Your change is a layer over it, not a
60
+ - The base game stays untouched. Your change is a layer over it, not a
58
61
  modification of it.
59
- - **It has an off switch**, and turning it off gives back the unmodified game
62
+ - It has an off switch, and turning it off gives back the unmodified game
60
63
  exactly.
61
- - **You share a folder or a repository link**, not a fork and not a patch file.
62
- - **Several mods combine**, from several people, without any of them having to
64
+ - You share a folder or a repository link, not a fork and not a patch file.
65
+ - Several mods combine, from several people, without any of them having to
63
66
  know about each other.
64
- - **You can expose settings** so the people using it can tune it without editing
67
+ - You can expose settings so the people using it can tune it without editing
65
68
  it.
66
- - **The game can update underneath it** without you re-applying anything.
67
- - **It scales all the way up.** The same system that changes one number can carry
69
+ - The game can update underneath it without you re-applying anything.
70
+ - It scales all the way up. The same system that changes one number can carry
68
71
  an entire variant.
69
72
 
70
73
  That last point is the real one. The spectrum runs from:
@@ -89,12 +92,12 @@ personal versions of the game and no shared ground.
89
92
 
90
93
  That is not what this is for, and the design says so:
91
94
 
92
- - **The base game ships as the shared vanilla target**, the latest official
95
+ - The base game ships as the shared vanilla target, the latest official
93
96
  Angband release, faithfully, with no mods enabled. That is what an untouched
94
97
  install gives you and what the project measures itself against.
95
- - **This project bundles no mods at all.** Not even the author's. Every one of
98
+ - This project bundles no mods at all. Not even the author's. Every one of
96
99
  them installs the same way yours would.
97
- - **Every mod is a layer you can name, list, and remove.** A game's enabled mods
100
+ - Every mod is a layer you can name, list, and remove. A game's enabled mods
98
101
  and versions are recorded in its diagnostics and written into the character
99
102
  dump, so any two people can tell whether they were playing the same thing.
100
103
 
@@ -103,7 +106,7 @@ having one.
103
106
 
104
107
  ---
105
108
 
106
- ## What would I even mod?
109
+ ## Ideas for a first mod
107
110
 
108
111
  If you want a starting point rather than a blank folder:
109
112
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rpgm-tools/neo-angband-mod-sdk",
3
- "version": "1.2.1",
3
+ "version": "1.8.0",
4
4
  "description": "Pack schemas, validation, and tooling for Neo Angband mods (content packs, tile packs, scripted plugins)",
5
5
  "license": "GPL-2.0-only",
6
6
  "author": "neostryder (RPGM Tools)",
@@ -181,6 +181,10 @@
181
181
  * the character on disk untouched. Neither is a
182
182
  * superset of the other and `grantCovers` compares the
183
183
  * action, so one consent cannot buy both.
184
+ * - "display:filter" - apply a post-processing filter to the game's rendered
185
+ * terminal canvas. It is deliberately separate from
186
+ * display:replace: changing the appearance of the
187
+ * faithful renderer is not taking ownership of it.
184
188
  * - "ui:panel.mount" - draw with real HTML instead of the character grid: a
185
189
  * panel of the mod's own, mounted on the page above
186
190
  * the game. A THIRD "ui:" action, and the reason it is
@@ -226,11 +230,17 @@ export type ParsedCapability =
226
230
  | { kind: "state"; domain: string; access: "read" }
227
231
  | { kind: "network"; host: string }
228
232
  | { kind: "registry"; domain: string }
229
- | { kind: "display"; action: "replace" }
233
+ | { kind: "display"; action: "replace" | "filter" }
230
234
  | { kind: "ui"; region: string; action: "replace" | "create" | "mount" }
231
235
  | { kind: "backup"; action: "folder" }
232
236
  | { kind: "mod"; action: "install" | "session" }
233
- | { kind: "debug"; action: "spawn" | "wizard" };
237
+ | { kind: "debug"; action: "spawn" | "wizard" }
238
+ | { kind: "keymap"; action: "write" }
239
+ | { kind: "query"; action: "snapshot" }
240
+ | { kind: "policy"; action: "install" }
241
+ | { kind: "hook"; action: "respond" }
242
+ | { kind: "registry-declaration"; action: "declare" }
243
+ | { kind: "ui-region"; action: "write" };
234
244
 
235
245
  const EVENT_RE = /^event:([a-z][a-z0-9-]*)$/;
236
246
  /**
@@ -319,8 +329,8 @@ export function parseCapability(cap: string): ParsedCapability {
319
329
  * sees of the dungeon is drawn by this mod." It is the display OWNER, so it
320
330
  * has no domain to name and no wildcard to sit under - `registry:*` must not
321
331
  * carry it, which is exactly what a separate kind buys. */
322
- if (cap === "display:replace") {
323
- return { kind: "display", action: "replace" };
332
+ if (cap === "display:replace" || cap === "display:filter") {
333
+ return { kind: "display", action: cap === "display:replace" ? "replace" : "filter" };
324
334
  }
325
335
  /* "backup:folder": lets the mod write files into a folder the player picks;
326
336
  * the mod never learns the folder's real path (the browser will not say),
@@ -368,6 +378,14 @@ export function parseCapability(cap: string): ParsedCapability {
368
378
  if (cap === "debug:wizard") {
369
379
  return { kind: "debug", action: "wizard" };
370
380
  }
381
+ if (cap === "keymap:write") {
382
+ return { kind: "keymap", action: "write" };
383
+ }
384
+ if (cap === "query:snapshot") return { kind: "query", action: "snapshot" };
385
+ if (cap === "policy:install") return { kind: "policy", action: "install" };
386
+ if (cap === "hook:respond") return { kind: "hook", action: "respond" };
387
+ if (cap === "registry:declare") return { kind: "registry-declaration", action: "declare" };
388
+ if (cap === "ui:region") return { kind: "ui-region", action: "write" };
371
389
  const ui = UI_RE.exec(cap);
372
390
  if (ui) {
373
391
  return { kind: "ui", region: ui[1] as string, action: "replace" };
@@ -434,7 +452,7 @@ function grantCovers(grant: ParsedCapability, request: ParsedCapability): boolea
434
452
  /* Exact match only. See parseCapability: `registry:*` does not reach
435
453
  * here, so a mod holding the override wildcard still cannot take the
436
454
  * display without asking for it by name. */
437
- return grant.kind === "display";
455
+ return grant.kind === "display" && grant.action === request.action;
438
456
  case "backup":
439
457
  /* Exact match only, same reasoning as "display" - there is exactly one
440
458
  * backup capability and no wildcard grant could ever cover it. */
@@ -458,6 +476,18 @@ function grantCovers(grant: ParsedCapability, request: ParsedCapability): boolea
458
476
  * experience and the acquirement too. Exactly the #261 lesson, caught by
459
477
  * adding the second action rather than by shipping it. */
460
478
  return grant.kind === "debug" && grant.action === request.action;
479
+ case "keymap":
480
+ return grant.kind === "keymap" && grant.action === request.action;
481
+ case "query":
482
+ return grant.kind === "query" && grant.action === request.action;
483
+ case "policy":
484
+ return grant.kind === "policy" && grant.action === request.action;
485
+ case "hook":
486
+ return grant.kind === "hook" && grant.action === request.action;
487
+ case "registry-declaration":
488
+ return grant.kind === "registry-declaration" && grant.action === request.action;
489
+ case "ui-region":
490
+ return grant.kind === "ui-region" && grant.action === request.action;
461
491
  case "ui":
462
492
  /* Per region, with one wildcard. A `display` grant is NOT accepted here
463
493
  * and a `ui` grant is not accepted above: owning the dungeon and owning