@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.
- package/README.md +1 -1
- package/dist/capabilities.d.ts +23 -1
- package/dist/capabilities.d.ts.map +1 -1
- package/dist/capabilities.js +32 -3
- package/dist/capabilities.js.map +1 -1
- package/dist/manifest.d.ts +12 -0
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +24 -0
- package/dist/manifest.js.map +1 -1
- package/docs/MOD_COMPATIBILITY.md +2 -0
- package/docs/MOD_LIFECYCLE.md +20 -16
- package/docs/MOD_REACH.md +2 -2
- package/docs/PLUGINS.md +26 -1
- package/docs/README.md +1 -1
- package/docs/tutorials/01-tweak-a-value.md +4 -4
- package/docs/tutorials/02-add-an-item.md +21 -21
- package/docs/tutorials/03-add-a-monster.md +23 -23
- package/docs/tutorials/04-change-a-spell.md +5 -5
- package/docs/tutorials/05-hook-behaviour.md +8 -8
- package/docs/tutorials/06-add-an-option.md +7 -7
- package/docs/tutorials/07-add-an-artifact.md +13 -13
- package/docs/tutorials/README.md +21 -18
- package/package.json +1 -1
- package/src/capabilities.ts +35 -5
- package/src/manifest.ts +39 -0
|
@@ -10,7 +10,7 @@ folder is and how to load one.
|
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
-
##
|
|
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
|
-
-
|
|
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
|
-
-
|
|
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
|
-
-
|
|
84
|
-
-
|
|
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
|
-
-
|
|
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
|
-
-
|
|
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
|
-
##
|
|
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
|
-
##
|
|
115
|
+
## Variations to try
|
|
116
116
|
|
|
117
|
-
- Make it
|
|
118
|
-
- Make it
|
|
119
|
-
- Add a
|
|
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
|
|
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
|
-
-
|
|
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
|
-
-
|
|
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
|
-
-
|
|
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
|
-
-
|
|
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
|
-
-
|
|
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
|
-
-
|
|
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
|
-
-
|
|
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
|
-
##
|
|
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
|
-
##
|
|
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
|
|
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
|
-
-
|
|
73
|
+
- `speed`: 110 is normal walking pace, the same as an unhasted player. 120
|
|
74
74
|
is fast enough to be genuinely dangerous.
|
|
75
|
-
-
|
|
76
|
-
-
|
|
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
|
-
-
|
|
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
|
-
-
|
|
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
|
-
-
|
|
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
|
-
##
|
|
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
|
-
##
|
|
94
|
+
## Variations to try
|
|
95
95
|
|
|
96
|
-
- Make it a
|
|
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
|
|
99
|
-
- Make it
|
|
100
|
-
- Add a whole
|
|
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
|
-
##
|
|
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
|
-
-
|
|
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
|
-
-
|
|
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
|
-
-
|
|
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
|
-
-
|
|
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
|
-
-
|
|
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
|
-
-
|
|
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
|
-
##
|
|
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
|
-
##
|
|
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
|
-
##
|
|
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
|
-
##
|
|
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
|
-
##
|
|
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
|
-
##
|
|
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
|
-
##
|
|
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
|
-
##
|
|
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
|
-
##
|
|
118
|
+
## Variations to try
|
|
119
119
|
|
|
120
|
-
- Congratulate
|
|
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
|
|
124
|
-
other seven; see [MOD_SEAMS.md](../MOD_SEAMS.md) for what each one is asked
|
|
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
|
-
##
|
|
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
|
-
##
|
|
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
|
-
##
|
|
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
|
-
-
|
|
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
|
-
-
|
|
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
|
-
##
|
|
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
|
-
##
|
|
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
|
-
##
|
|
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
|
-
-
|
|
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
|
-
-
|
|
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
|
-
##
|
|
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
|
-
-
|
|
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
|
-
-
|
|
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
|
-
##
|
|
177
|
+
## Artifact fields
|
|
178
178
|
|
|
179
179
|
Everything below `base-object` is the fun part.
|
|
180
180
|
|
|
181
|
-
-
|
|
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
|
-
-
|
|
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
|
-
-
|
|
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
|
-
-
|
|
189
|
+
- `flags` are the yes-or-no properties: `SEE_INVIS`, `PROT_FEAR`,
|
|
190
190
|
`FREE_ACT`, `REGEN`, and a long list more.
|
|
191
|
-
-
|
|
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
|
-
##
|
|
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
|
-
##
|
|
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
|
package/docs/tutorials/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Make a mod
|
|
2
2
|
|
|
3
|
-
**Never made a mod before? That is fine.** These
|
|
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
|
|
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.**
|
|
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.
|
|
36
|
-
|
|
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
|
-
-
|
|
60
|
+
- The base game stays untouched. Your change is a layer over it, not a
|
|
58
61
|
modification of it.
|
|
59
|
-
-
|
|
62
|
+
- It has an off switch, and turning it off gives back the unmodified game
|
|
60
63
|
exactly.
|
|
61
|
-
-
|
|
62
|
-
-
|
|
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
|
-
-
|
|
67
|
+
- You can expose settings so the people using it can tune it without editing
|
|
65
68
|
it.
|
|
66
|
-
-
|
|
67
|
-
-
|
|
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
|
-
-
|
|
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
|
-
-
|
|
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
|
-
-
|
|
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
|
-
##
|
|
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.
|
|
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)",
|
package/src/capabilities.ts
CHANGED
|
@@ -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
|