1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
|
|||||oy
#####R /----------------------------------------\
#####R < Adding a new quest >
#####R \----------------------------------------/
#####R=== Introduction ===
Adding a quest involves a bit more work, and there is, in some ways, rather
more potential for things to go wrong! But it's a great way of showing just
WHAT can be done with lua scripting. It proves just how much a lua patch can
change the overall feel of the game. And it will give you a much better idea of
how lua interfaces with the game source. You should have read the
*****lua_intr.txt*0[scripting introduction], *****lua_pow.txt*0[racial power tutorial]
and *****lua_skil.txt*0[adding new skills tutorial] before going much
further. All of the above files contain some fairly fundamental information
which you will find necessary for full understanding of this file.
The script we're looking at is going to create a quest entrance in the middle
of Bree. Entering the quest you see a little girl who has had her necklace
stolen. Your job is to travel down a corridor, killing some monsters on the
way, pick up the amulet and return it to the girl. Once done, she'll reveal the
stairs back to Bree, and give you a (randomly generated) ring. If you feel the
monsters are too hard, the only thing to do is talk to the little girl who will
reveal the stairs again, failing the quest for you, and also block off the
entrance to the amulet so that you can't cheat and make off with the amulet!
#####R=== Getting started ===
Open amulet.lua (you have downloaded the example scripts from
[[[[[Ghttp://www.moppy.co.uk/angband.htm], haven't you?). The first thing you
should see is that yet again we're calling a function that takes its arguments
from a table, making it easy to read what's going on in the script.
This time our function is add_quest and we have the following keys and values:
#####B["global"] = "AMULET_QUEST",
#####B["name"] = "Hannahs lost amulet",
#####B["desc"] = {
#####B "Retrieve an amulet for Hannah Cooke. It's guarded!"
#####B },
#####B["level"] = 5,
#####B["hooks"] = {
[[[[[B"global" = ] is a constant that we set when we refer to this quest in
various places...
[[[[[B"name" = ] Obviously a long name for the quest. This will appear in the
quest screen (Ctrl-Q) and we may use in some map files too.
[[[[[B"desc" = ] This is a long description for the quest. Again this is what
will appear in the quest screen, and each line should be not more than 80
characters.
[[[[[B"level" = ] This is a rough indicator of how hard the quest is, and again,
appears in the quest screen
[[[[[B"hooks" = ] This is the real 'meat' of the quest. Like the [[[[[B"spell_list"] key
in the [[[[[Badd_magic] function, this is another sub-table.
To understand fully the structure of the "hooks" key it's worth taking a bit of
a detour at this point and discussing how the scripting interface works in
general.
#####R=== How the scripts work (ish) ===
Essentially there's a list of events that happen in the game. As each of these
events happen they run a check to see if there's any functions that they need
to run at that event. When we ran the add_mkey part of adding a new skill
power, we essentially said "when the 'm' key is pressed in the game, perform
the [[[[[Bexecute_magic(constructor_powers)] function". Likewise we did a similar
thing with adding the racial power, only we hooked onto the pressing of the
'U' key.
All of this was partly hidden because of the way that the [[[[[Badd_magic] and
[[[[[Badd_power] functions work. But here in the [[[[[Badd_quest] function it's a bit more
specific. We are going to specify what events we're going to hook onto, and
what functions we want to trigger at that event.
A full list of hooks can be found in the source-file util.pkg.
#####R=== The hooks ===
#####B[HOOK_BIRTH_OBJECTS] = function()
#####B quest(AMULET_QUEST).status = QUEST_STATUS_TAKEN
#####Bend,
So here we are with our first hook. We've declared that we're adding it to the
birth of your character. That is, the function will be called when you create
your character. And what we're doing here is automatically declaring the quest
as being taken, like the Dol Guldur quest is. Each quest has 7 different
statuses:
[[[[[BQUEST_STATUS_IGNORED -1 ] This is unused, but the quest is
ignored (will not be taken and has not been taken).
[[[[[BQUEST_STATUS_UNTAKEN 0 ] The quest has not been accepted yet
[[[[[BQUEST_STATUS_TAKEN 1 ] You have accepted the quest
[[[[[BQUEST_STATUS_COMPLETED 2 ] You have completed the quest
successfully but not been rewarded for it
[[[[[BQUEST_STATUS_REWARDED 3 ] You've completed and rewarded the quest
[[[[[BQUEST_STATUS_FAILED 4 ] You've failed the quest
[[[[[BQUEST_STATUS_FINISHED 5 ] The quest is completely finished
successfully.
[[[[[BQUEST_STATUS_FAILED_DONE 6 ] The quest is completely finished
unsuccessfully.
You see that we've used the constant we defined in the "global" section is
passed as an argument to [[[[[Bquest.status].
Next hook then:
#####B[HOOK_GEN_QUEST] = function()
#####B if (player.inside_quest ~= AMULET_QUEST) then
#####B return FALSE
#####B else
#####B load_map("amulet.map", 2, 2)
#####B return TRUE
#####B end
#####Bend,
Ok, we're hooking onto the generation of the quest here. This is specifically
triggered in this instance by going down the quest entrance stairs in Bree.
Once you've gone down the stairs, you are technically inside the quest, which
means we can say if the person is not inside the amulet quest, then ignore this
function, otherwise load the file 'amulet.map' at co-ordinates x=2 y=2. You'll
find the amulet.map file in the edit directory, make sure you check it out. The
syntax for map files is fairly simple, though I might get round to writing a
tutorial on them some day! In the mean time holler for me at the usual email
address if you're unsure.
#####B[HOOK_FEELING] = function()
#####B if (player.inside_quest ~= AMULET_QUEST) then
#####B return FALSE
#####B else
#####B cmsg_print(TERM_L_BLUE, "Hannah speaks to you:")
#####B cmsg_print(TERM_YELLOW, "'Some nasty monsters stole my
#####B favourite necklace.'")
#####B cmsg_print(TERM_YELLOW, "'It's hidden at the back of that
#####B corridor! Please fetch it for me'")
#####B return TRUE
#####B end
#####Bend,
We're moving into some rather more obvious territory here, and getting into the
meat of the quest. The [[[[[BHOOK_FEELING] is triggered at the point when the level
feeling appears. It's important that this is run only if the player is inside
the amulet quest, as otherwise it will trigger EVERY time a level feeling
occurs, when you go down a level in the barrow-downs, whenever! Returning TRUE
will replace the level feeling with what's above, returning FALSE will still
perform the function but will amend the normal level feeling - so here if we'd
returned false we'd still get our custom messages, but they'd follow with
'looks like a typical quest level'. Of course returning false may cause you
other problems (see end of this file!) depending on what else you have in your
function.
#####B[HOOK_GIVE] = function(m_idx, item)
#####B m_ptr = monster(m_idx)
#####B o_ptr = get_object(item)
#####B if (m_ptr.r_idx == test_monster_name("Hannah Cooke, a little girl"))
#####B and (o_ptr.tval == TV_AMULET) and (o_ptr.sval == 2) then
#####B cmsg_print(TERM_YELLOW, "'Thank-you!'")
#####B inven_item_increase(item, -1)
#####B inven_item_optimize(item)
#####B quest(AMULET_QUEST).status = QUEST_STATUS_COMPLETED
#####B cave_set_feat(7, 6, 6)
#####B cmsg_print(TERM_YELLOW, "'Here, take this pretty ring I found
#####B as a token of gratitude!'")
#####B random_type = randint(57)
#####B reward = create_object(TV_RING, random_type)
#####B drop_near(reward, -1, py, px)
#####B quest(AMULET_QUEST).status = QUEST_STATUS_REWARDED
#####B return TRUE
#####B else
#####B return FALSE
#####B end
#####Bend,
This is a fairly long function, but don't be intimidated. It's not really
difficult to understand. As you can see we're hooking into the giving of an
object to a monster (the 'y' key). Because of this, the function takes two
arguments - [[[[[Bm_idx] (the monster that you're giving to) and [[[[[Bitem] (the item that
you're giving).
We then make it possible to work with the monster and item variables by
referencing them to two functions which identify them from the edit files:
[[[[[Bmonster()] and [[[[[Bget_object()]. This enables us to now say, 'if the name of the
monster is "Hannah Cooke, a little girl" and the type of item is an amulet and
that amulet is an amulet of adornment, then carry out the following commands'.
We then say call the function [[[[[Binven_item_increase()] which places an object in
the inventory. It takes two arguments, the first being what object to put in
the inventory and the second being how many of that type of objects to put in
the inventory. You can see that by placing -1 as the second argument it fairly
obviously subtracts that item from the inventory. The [[[[[Binven_item_optimize()]
function checks that there are no empty inventory slots, and if there are,
erases them.
The quest is then completed, and the stairs are revealed using the
[[[[[Bcave_set_feat()] function. This function takes three arguments, the first is the
x co-ordinate of the cave square you wish to change (counted from top left) the
second is the y co-ordinate, and the third is the index number of the feature
you wish the square to become as defined in f_info.txt.
We then set about rewarding the player. As you can see we call [[[[[Bcreate_object()]
which takes two variables: the first is the type of object (these are all
listed in object.pkg) and the second is the sub-type of that object. I searched
k_info.txt to see how many different types of ring there were (57) and used a
randomly selected number with a maximum value of 57 as that specific sub-type.
We then drop the object (although it's been created, it has only been created
in the game's memory, it's nowhere that the player can interact with it until
we drop it). The [[[[[Bdrop_near()] function takes 3 variables, the first being the
object that you wish to drop, the second being the chance that it disappears
(like an arrow, or mimicked creature) on drop. If you set it to -1, it won't
ever disappear. The last two are the co-ordinates at which the object will be
dropped. py and px are the global variables defined by where the player is
standing, so in this case it will drop under the player. You could do
[[[[[Binven_item_increase(reward, 1)] if you wanted, but I wanted to show a variety of
ways of handling objects.
OK, let's take a look at the next hook:
#####B[HOOK_CHAT] = function(m_idx)
#####B m_ptr = monster(m_idx)
#####B if (m_ptr.r_idx == test_monster_name("Hannah Cooke, a little girl")) then
#####B if (quest(AMULET_QUEST).status == QUEST_STATUS_REWARDED) then
#####B cmsg_print(TERM_YELLOW, "'Bye!'")
#####B else
#####B cmsg_print(TERM_YELLOW, "'Are the monsters too tough?
#####B Do you want to leave?'")
#####B if (get_check("Really leave and fail the quest?") ==
#####B FALSE)
#####B then
#####B cmsg_print(TERM_YELLOW, "'Go and get my
#####B amulet then!'")
#####B else
#####B cmsg_print(TERM_YELLOW, "'Awww. Never
#####B mind. It was only a bit of rabbits foot'")
#####B quest(AMULET_QUEST).status =
#####B QUEST_STATUS_FAILED
#####B cave_set_feat(7, 6, 6)
#####B cave_set_feat(12, 5, 60)
#####B end
#####B end
#####B return TRUE
#####B end
#####B return FALSE
#####Bend,
This only looks complicated because of the nested 'if' statements. It's easy to
lose your way when doing this kind of thing, always make sure you close all the
statements and put the returns in the right place. [[[[[BHOOK_CHAT] functions have one
argument - the monster you are chatting to. As you can see, we perform a check
to make sure it's the right monster and then away we go.... If the player wants
to leave the quest without completion they talk to Hannah, who gives them a
chance to change their mind! If the player asks to leave the entrance to the
corridor is blocked off (the second cave_set_feat()) so that the user can't
then go and get the amulet. Gumband or Zangband players may at this point think
they've lost out on the rabbits foot of burglary! (they haven't though as it
doesn't exist in ToME).
#####B[HOOK_CHAR_DUMP] = function()
#####B if (quest(AMULET_QUEST).status == QUEST_STATUS_FAILED) then
#####B print_hook("\n You chickened out of rescuing a necklace and
#####B made a little girl sad. ")
#####B elseif (quest(AMULET_QUEST).status == QUEST_STATUS_COMPLETED) or
#####B (quest(AMULET_QUEST).status == QUEST_STATUS_REWARDED) or
#####B (quest(AMULET_QUEST).status == QUEST_STATUS_FINISHED) then
#####B print_hook("\n You rescued little Hannah Cooke's necklace from
#####B the nasty monsters ")
#####B end
#####B return FALSE
#####Bend,
This quite simply and obviously prints an appropriate line in the character
dump based on the status of the quest. The [[[[[B\n] bit ensures the text goes on a
new line, so make sure you include it! Also you should return FALSE as
returning TRUE will stop executing all the other character dump lines (and you
may get other quests not having their lines printed).
=== A word about returning TRUE and FALSE ===
As I mentioned above, you need to be careful what you return when dealing with
HOOKS as you can mess up the game a bit. Bear in mind that if you add a
function to [[[[[BHOOK_GEN_QUEST], every time a quest is generated, that function will
run. If you return TRUE, then no further functions attached to that hook will
run. If you return FALSE, it continues processing functions on that hook.
That is pretty much it. Do take a look at the other included scripts that I
haven't gone into any detail about in the files, as you'll pick up some useful
techniques there too. Especially worthy of note is the hina.lua file which uses
hooks outside of the quest structure and also global variables and variables in
a table. If you have any questions, let me know at the email addy below.
Back to the *****lua.hlp*0[lua help index] .
[[[[[gThis file by fearoffours (fearoffours@moppy.co.uk)]
|