1 /*
2 * monsters.c
3 * Copyright (C) 2009-2020 Joachim de Groot <jdegroot@web.de>
4 *
5 * NLarn is free software: you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the
7 * Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * NLarn is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 * See the GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include <glib.h>
20 #include <stdlib.h>
21 #include <string.h>
22
23 #include "display.h"
24 #include "fov.h"
25 #include "game.h"
26 #include "items.h"
27 #include "map.h"
28 #include "monsters.h"
29 #include "extdefs.h"
30 #include "pathfinding.h"
31 #include "random.h"
32
33 DEFINE_ENUM(monster_flag, MONSTER_FLAG_ENUM)
34 DEFINE_ENUM(monster_t, MONSTER_TYPE_ENUM)
35
36 /* local monster definition for data storage */
37 typedef struct {
38 const char *name; /* monster's name */
39 const char *plural_name;
40 const char glyph;
41 int colour;
42 int exp; /* xp granted to player */
43 int gold_chance;
44 int gold;
45 int reroll_chance;
46 int ac;
47 int hp_max;
48 int level;
49 int intelligence; /* used to choose movement */
50 speed speed;
51 size size;
52 int flags;
53 attack attacks[2];
54 monster_action_t default_ai;
55 const char *sound;
56 } monster_data_t;
57
58 /* monster information hiding */
59 struct _monster
60 {
61 monster_t type;
62 gpointer oid; /* monsters id inside the monster hash */
63 gint32 hp_max;
64 gint32 hp;
65 position pos;
66 fov *fv;
67 int movement;
68 monster_action_t action; /* current action */
69 guint32 lastseen; /* number of turns since when player was last seen; 0 = never */
70 position player_pos; /* last known position of player */
71 inventory *inv;
72 item *eq_weapon;
73 GPtrArray *effects;
74 guint number; /* random value for some monsters */
75 gpointer leader; /* for pack monsters: ID of the leader */
76 guint32
77 unknown: 1; /* monster is unknown (mimic) */
78 };
79
80 const char *monster_ai_desc[] =
81 {
82 NULL, /* MA_NONE */
83 "fleeing", /* MA_FLEE */
84 "idling", /* MA_REMAIN */
85 "wandering", /* MA_WANDER */
86 "attacking", /* MA_ATTACK */
87 "puzzled", /* MA_CONFUSION */
88 "obedient", /* MA_SERVE */
89 "working", /* MA_CIVILIAN */
90 };
91
92 const char *monster_attack_verb[] =
93 {
94 NULL,
95 "hits", /* ATT_WEAPON */
96 "points at", /* ATT_MAGIC */
97 "claws", /* ATT_CLAW */
98 "bites", /* ATT_BITE */
99 "stings", /* ATT_STING */
100 "slams", /* ATT_SLAM */
101 "kicks", /* ATT_KICK */
102 "touches", /* ATT_TOUCH */
103 "breathes at", /* ATT_BREATH */
104 "gazes at", /* ATT_GAZE */
105 };
106
107 static struct _monster_breath_data
108 {
109 const char *desc;
110 const char glyph;
111 int colour;
112 } monster_breath_data[] =
113 {
114 { NULL, 0, 0 }, /* DAM_NONE */
115 { NULL, 0, 0 }, /* DAM_PHYSICAL */
116 { "psionic blast", '*', WHITE }, /* DAM_MAGICAL */
117 { "burst of fire", '~', RED }, /* DAM_FIRE */
118 { "beam of frost", '*', LIGHTCYAN }, /* DAM_COLD */
119 { "gush of acid", '*', LIGHTGREEN }, /* DAM_ACID */
120 { "flood of water", '~', BLUE }, /* DAM_WATER */
121 { "ray of lightning", '*', YELLOW }, /* DAM_ELECTRICITY */
122 { "burst of noxious fumes", '%', GREEN }, /* DAM_POISON */
123 };
124
125 monster_data_t monster_data[] = {
126 { /* MT_GIANT_BAT */
127 .name = "giant bat", .glyph = 'b', .colour = RED,
128 .exp = 1, .ac = 0, .hp_max = 2,
129 .level = 1, .intelligence = 3, .speed = XFAST, .size = SMALL,
130 .flags = HEAD | FLY | INFRAVISION,
131 .attacks = {
132 { .type = ATT_BITE, .base = 1, .damage = DAM_PHYSICAL },
133 }, .default_ai = MA_WANDER
134 },
135 { /* MT_GNOME */
136 .name = "gnome", .glyph = 'g', .colour = BROWN,
137 .exp = 2, .gold_chance = 80, .gold = 30, .ac = 0, .hp_max = 6,
138 .level = 1, .intelligence = 8, .speed = NORMAL, .size = SMALL,
139 .flags = HEAD | HANDS,
140 .attacks = {
141 { .type = ATT_WEAPON, .damage = DAM_PHYSICAL },
142 }, .default_ai = MA_WANDER
143 },
144 { /* MT_HOBGOBLIN */
145 .name = "hobgoblin", .glyph = 'H', .colour = BROWN,
146 .exp = 2, .gold_chance = 30, .gold = 40, .ac = 1, .hp_max = 8,
147 .level = 1, .intelligence = 5, .speed = SLOW, .size = MEDIUM,
148 .flags = HEAD | HANDS | INFRAVISION,
149 .attacks = {
150 { .type = ATT_WEAPON, .damage = DAM_PHYSICAL },
151 }, .default_ai = MA_WANDER
152 },
153 { /* MT_JACKAL */
154 .name = "jackal", .glyph = 'J', .colour = BROWN,
155 .exp = 1, .ac = 0, .hp_max = 3,
156 .level = 1, .intelligence = 4, .speed = FAST, .size = SMALL,
157 .flags = HEAD,
158 .attacks = {
159 { .type = ATT_BITE, .base = 1, .damage = DAM_PHYSICAL },
160 }, .default_ai = MA_WANDER, .sound = "growl"
161 },
162 { /* MT_KOBOLD */
163 .name = "kobold", .glyph = 'k', .colour = BROWN,
164 .exp = 1, .gold_chance = 10, .gold = 100, .ac = 0, .hp_max = 4,
165 .level = 1, .intelligence = 7, .speed = NORMAL, .size = SMALL,
166 .flags = HEAD | HANDS | INFRAVISION,
167 .attacks = {
168 { .type = ATT_WEAPON, .damage = DAM_PHYSICAL },
169 }, .default_ai = MA_WANDER
170 },
171 { /* MT_ORC */
172 .name = "orc", .glyph = 'O', .colour = RED,
173 .exp = 2, .gold_chance = 50, .gold = 80, .ac = 3, .hp_max = 12,
174 .level = 2, .intelligence = 9, .speed = NORMAL, .size = MEDIUM,
175 .flags = HEAD | HANDS | INFRAVISION | PACK,
176 .attacks = {
177 { .type = ATT_WEAPON, .damage = DAM_PHYSICAL },
178 }, .default_ai = MA_WANDER, .sound = "shout"
179 },
180 { /* MT_SNAKE */
181 .name = "snake", .glyph = 'S', .colour = LIGHTGREEN,
182 .exp = 1, .ac = 1, .hp_max = 3,
183 .level = 2, .intelligence = 3, .speed = NORMAL, .size = TINY,
184 .flags = HEAD,
185 .attacks = {
186 { .type = ATT_BITE, .base = 1, .damage = DAM_PHYSICAL },
187 { .type = ATT_BITE, .base = 2, .damage = DAM_POISON },
188 }, .default_ai = MA_WANDER, .sound = "hiss"
189 },
190 { /* MT_CENTIPEDE */
191 .name = "giant centipede", .glyph = 'c', .colour = YELLOW,
192 .exp = 2, .ac = 1, .hp_max = 5,
193 .level = 2, .intelligence = 2, .speed = NORMAL, .size = SMALL,
194 .flags = HEAD,
195 .attacks = {
196 { .type = ATT_BITE, .base = 50, .damage = DAM_DEC_STR },
197 { .type = ATT_BITE, .base = 1, .damage = DAM_PHYSICAL },
198 }, .default_ai = MA_WANDER
199 },
200 { /* MT_JACULUS */
201 .name = "jaculus", .plural_name = "jaculi", .glyph = 'j', .colour = GREEN,
202 .exp = 1, .ac = 3, .hp_max = 8,
203 .level = 2, .intelligence = 3, .speed = XFAST, .size = MEDIUM,
204 .flags = HEAD | FLY,
205 .attacks = {
206 { .type = ATT_BITE, .base = 2, .damage = DAM_PHYSICAL },
207 { .type = ATT_CLAW, .base = 2, .damage = DAM_PHYSICAL },
208 }, .default_ai = MA_WANDER
209 },
210 { /* MT_TROGLODYTE */
211 .name = "troglodyte", .glyph = 't', .colour = BROWN,
212 .exp = 3, .gold_chance = 25, .gold = 320, .ac = 4, .hp_max = 10,
213 .level = 2, .intelligence = 5, .speed = NORMAL, .size = MEDIUM,
214 .flags = HEAD | HANDS,
215 .attacks = {
216 { .type = ATT_WEAPON, .damage = DAM_PHYSICAL },
217 }, .default_ai = MA_WANDER
218 },
219 { /* MT_GIANT_ANT */
220 .name = "giant ant", .glyph = 'A', .colour = BROWN,
221 .exp = 5, .ac = 2, .hp_max = 6,
222 .level = 2, .intelligence = 3, .speed = NORMAL, .size = SMALL,
223 .flags = HEAD | PACK,
224 .attacks = {
225 { .type = ATT_BITE, .base = 75, .damage = DAM_DEC_STR },
226 { .type = ATT_BITE, .base = 1, .damage = DAM_PHYSICAL },
227 }, .default_ai = MA_WANDER
228 },
229 { /* MT_FLOATING_EYE */
230 .name = "floating eye", .glyph = 'E', .colour = BLUE,
231 .exp = 2, .ac = 2, .hp_max = 12,
232 .level = 3, .intelligence = 3, .speed = XSLOW, .size = MEDIUM,
233 .flags = FLY | INFRAVISION | RES_CONF,
234 .attacks = {
235 { .type = ATT_GAZE, .base = 66, .damage = DAM_PARALYSIS },
236 }, .default_ai = MA_WANDER
237 },
238 { /* MT_LEPRECHAUN */
239 .name = "leprechaun", .glyph = 'L', .colour = GREEN,
240 .exp = 45, .gold = 1500, .ac = 6, .hp_max = 13,
241 .level = 3, .intelligence = 6, .speed = NORMAL, .size = SMALL,
242 .flags = HEAD | HANDS,
243 .attacks = {
244 { .type = ATT_CLAW, .base = 2, .damage = DAM_PHYSICAL },
245 { .type = ATT_TOUCH, .damage = DAM_STEAL_GOLD },
246 }, .default_ai = MA_WANDER
247 },
248 { /* MT_NYMPH */
249 .name = "nymph", .glyph = 'n', .colour = RED,
250 .exp = 45, .ac = 5, .hp_max = 18,
251 .level = 3, .intelligence = 9, .speed = NORMAL, .size = MEDIUM,
252 .flags = HEAD | HANDS,
253 .attacks = {
254 { .type = ATT_TOUCH, .damage = DAM_STEAL_ITEM },
255 }, .default_ai = MA_WANDER
256 },
257 { /* MT_QUASIT */
258 .name = "quasit", .glyph = 'Q', .colour = BLUE,
259 .exp = 15, .ac = 6, .hp_max = 10,
260 .level = 3, .intelligence = 3, .speed = FAST, .size = SMALL,
261 .flags = HEAD | HANDS | DEMON,
262 .attacks = {
263 { .type = ATT_BITE, .base = 3, .damage = DAM_PHYSICAL },
264 { .type = ATT_CLAW, .base = 66, .damage = DAM_DEC_DEX },
265 }, .default_ai = MA_WANDER
266 },
267 { /* MT_RUST_MONSTER */
268 .name = "rust monster", .glyph = 'R', .colour = BROWN,
269 .exp = 25, .ac = 6, .hp_max = 18,
270 .level = 3, .intelligence = 3, .speed = NORMAL, .size = MEDIUM,
271 .flags = HEAD | METALLIVORE,
272 .attacks = {
273 { .type = ATT_BITE, .base = 3, .damage = DAM_PHYSICAL },
274 { .type = ATT_TOUCH, .base = 1, .damage = DAM_RUST },
275 }, .default_ai = MA_WANDER
276 },
277 { /* MT_ZOMBIE */
278 .name = "zombie", .glyph = 'Z', .colour = LIGHTGRAY,
279 .exp = 7, .ac = 2, .hp_max = 12,
280 .level = 3, .intelligence = 3, .speed = VSLOW, .size = MEDIUM,
281 .flags = HEAD | HANDS | UNDEAD | RES_SLEEP | RES_POISON | RES_CONF,
282 .attacks = {
283 { .type = ATT_BITE, .base = 2, .damage = DAM_PHYSICAL },
284 { .type = ATT_CLAW, .base = 2, .damage = DAM_PHYSICAL },
285 }, .default_ai = MA_WANDER, .sound = "groan"
286 },
287 { /* MT_ASSASSIN_BUG */
288 .name = "assassin bug", .glyph = 'a', .colour = GREEN,
289 .exp = 15, .ac = 1, .hp_max = 20,
290 .level = 4, .intelligence = 3, .speed = FAST, .size = TINY,
291 .flags = HEAD | RES_POISON,
292 .attacks = {
293 { .type = ATT_BITE, .base = 3, .damage = DAM_PHYSICAL },
294 { .type = ATT_BITE, .base = 3, .damage = DAM_POISON },
295 }, .default_ai = MA_WANDER
296 },
297 { /* MT_BUGBEAR */
298 .name = "bugbear", .glyph = 'B', .colour = BROWN,
299 .exp = 35, .gold_chance = 10, .gold = 400, .ac = 5, .hp_max = 20,
300 .level = 4, .intelligence = 5, .speed = SLOW, .size = MEDIUM,
301 .flags = HEAD | HANDS | INFRAVISION,
302 .attacks = {
303 { .type = ATT_BITE, .base = 5, .damage = DAM_PHYSICAL, .rand = 10 },
304 { .type = ATT_WEAPON, .damage = DAM_PHYSICAL },
305 }, .default_ai = MA_WANDER
306 },
307 { /* MT_HELLHOUND */
308 .name = "hell hound", .glyph = 'h', .colour = LIGHTRED,
309 .exp = 35, .ac = 5, .hp_max = 16,
310 .level = 4, .intelligence = 6, .speed = FAST, .size = SMALL,
311 .flags = HEAD | RES_FIRE | RES_MAGIC,
312 .attacks = {
313 { .type = ATT_BITE, .base = 2, .damage = DAM_PHYSICAL },
314 { .type = ATT_BREATH, .base = 8, .damage = DAM_FIRE, .rand = 15 },
315 }, .default_ai = MA_WANDER, .sound = "bark"
316 },
317 { /* MT_ICE_LIZARD */
318 .name = "ice lizard", .glyph = 'i', .colour = LIGHTCYAN,
319 .exp = 25, .ac = 4, .hp_max = 20,
320 .level = 4, .intelligence = 6, .speed = SLOW, .size = MEDIUM,
321 .flags = HEAD | SWIM | RES_COLD,
322 .attacks = {
323 { .type = ATT_CLAW, .base = 2, .damage = DAM_PHYSICAL },
324 { .type = ATT_SLAM, .base = 14, .damage = DAM_PHYSICAL },
325 }, .default_ai = MA_WANDER
326 },
327 { /* MT_CENTAUR */
328 .name = "centaur", .glyph = 'C', .colour = BROWN,
329 .exp = 45, .gold_chance = 50, .gold = 80, .ac = 6, .hp_max = 24,
330 .level = 4, .intelligence = 10, .speed = FAST, .size = LARGE,
331 .flags = HEAD | HANDS | PACK,
332 .attacks = {
333 { .type = ATT_KICK, .base = 6, .damage = DAM_PHYSICAL },
334 { .type = ATT_WEAPON, .damage = DAM_PHYSICAL },
335 }, .default_ai = MA_WANDER
336 },
337 { /* MT_TROLL */
338 .name = "troll", .glyph = 'T', .colour = BROWN,
339 .exp = 300, .gold_chance = 20, .gold = 400, .ac = 8, .hp_max = 50,
340 .level = 5, .intelligence = 9, .speed = SLOW, .size = LARGE,
341 .flags = HEAD | HANDS | REGENERATE,
342 .attacks = {
343 { .type = ATT_CLAW, .base = 5, .damage = DAM_PHYSICAL },
344 { .type = ATT_WEAPON, .damage = DAM_PHYSICAL },
345 }, .default_ai = MA_WANDER, .sound = "grunt"
346 },
347 { /* MT_YETI */
348 .name = "yeti", .glyph = 'Y', .colour = WHITE,
349 .exp = 100, .gold_chance = 10, .gold = 200, .ac = 4, .hp_max = 35,
350 .level = 5, .intelligence = 5, .speed = NORMAL, .size = LARGE,
351 .flags = HEAD | HANDS | RES_COLD,
352 .attacks = {
353 { .type = ATT_CLAW, .base = 4, .damage = DAM_PHYSICAL },
354 }, .default_ai = MA_WANDER
355 },
356 { /* MT_ELF */
357 .name = "elf", .plural_name = "elves", .glyph = 'e', .colour = WHITE,
358 .exp = 35, .gold_chance = 50, .gold = 150, .ac = 6, .hp_max = 22,
359 .level = 5, .intelligence = 15, .speed = FAST, .size = MEDIUM,
360 .flags = HEAD | HANDS | INFRAVISION,
361 .attacks = {
362 { .type = ATT_WEAPON, .damage = DAM_PHYSICAL },
363 }, .default_ai = MA_WANDER
364 },
365 { /* MT_GELATINOUSCUBE */
366 .name = "gelatinous cube", .glyph = 'g', .colour = CYAN,
367 .exp = 45, .ac = 1, .hp_max = 22,
368 .level = 5, .intelligence = 3, .speed = XSLOW, .size = LARGE,
369 .flags = METALLIVORE | RES_SLEEP | RES_POISON | RES_CONF,
370 .attacks = {
371 { .type = ATT_SLAM, .base = 1, .damage = DAM_ACID },
372 }, .default_ai = MA_WANDER
373 },
374 { /* MT_WHITE_DRAGON */
375 .name = "white dragon", .glyph = 'd', .colour = WHITE,
376 .exp = 1000, .gold = 500, .ac = 8, .hp_max = 55,
377 .level = 5, .intelligence = 16, .speed = NORMAL, .size = GIANT,
378 .flags = HEAD | DRAGON | RES_COLD,
379 .attacks = {
380 { .type = ATT_BITE, .base = 4, .damage = DAM_PHYSICAL },
381 { .type = ATT_CLAW, .base = 4, .damage = DAM_PHYSICAL },
382 }, .default_ai = MA_WANDER
383 },
384 { /* MT_METAMORPH */
385 .name = "metamorph", .glyph = 'm', .colour = WHITE,
386 .exp = 40, .ac = 3, .hp_max = 30,
387 .level = 6, .intelligence = 3, .speed = NORMAL, .size = MEDIUM,
388 .flags = 0,
389 .attacks = {
390 { .type = ATT_WEAPON, .base = 3, .damage = DAM_PHYSICAL },
391 }, .default_ai = MA_WANDER
392 },
393 { /* MT_VORTEX */
394 .name = "vortex", .plural_name = "vortexes", .glyph = 'v', .colour = CYAN,
395 .exp = 55, .ac = 6, .hp_max = 30,
396 .level = 6, .intelligence = 3, .speed = VFAST, .size = TINY,
397 .flags = RES_SLEEP | RES_POISON | FLY | RES_ELEC | RES_CONF,
398 .attacks = {
399 { .type = ATT_SLAM, .base = 3, .damage = DAM_PHYSICAL },
400 }, .default_ai = MA_WANDER
401 },
402 { /* MT_ZILLER */
403 .name = "ziller", .glyph = 'z', .colour = CYAN,
404 .exp = 35, .ac = 8, .hp_max = 30,
405 .level = 6, .intelligence = 3, .speed = SLOW, .size = MEDIUM,
406 .flags = HEAD | RES_CONF,
407 .attacks = {
408 { .type = ATT_CLAW, .base = 3, .damage = DAM_PHYSICAL },
409 { .type = ATT_CLAW, .base = 70, .damage = DAM_DEC_RND },
410 }, .default_ai = MA_WANDER
411 },
412 { /* MT_VIOLET_FUNGUS */
413 .name = "violet fungus", .plural_name = "violet fungi", .glyph = 'F', .colour = MAGENTA,
414 .exp = 100, .ac = 0, .hp_max = 38,
415 .level = 6, .intelligence = 3, .speed = XSLOW, .size = MEDIUM,
416 .flags = RES_SLEEP | RES_POISON | RES_CONF,
417 .attacks = {
418 { .type = ATT_SLAM, .base = 3, .damage = DAM_PHYSICAL },
419 { .type = ATT_SLAM, .base = 4, .damage = DAM_POISON },
420 }, .default_ai = MA_WANDER
421 },
422 { /* MT_WRAITH */
423 .name = "wraith", .glyph = 'W', .colour = LIGHTGRAY,
424 .exp = 325, .ac = 7, .hp_max = 30,
425 .level = 6, .intelligence = 3, .speed = NORMAL, .size = MEDIUM,
426 .flags = HEAD | HANDS | UNDEAD | RES_SLEEP | RES_POISON | RES_CONF,
427 .attacks = {
428 { .type = ATT_TOUCH, .base = 50, .damage = DAM_DRAIN_LIFE },
429 }, .default_ai = MA_WANDER
430 },
431 { /* MT_FORVALAKA */
432 .name = "forvalaka", .glyph = 'f', .colour = LIGHTGRAY,
433 .exp = 280, .ac = 4, .hp_max = 50,
434 .level = 6, .intelligence = 7, .speed = DOUBLE, .size = MEDIUM,
435 .flags = HEAD | UNDEAD | INFRAVISION | RES_POISON,
436 .attacks = {
437 { .type = ATT_BITE, .base = 5, .damage = DAM_PHYSICAL },
438 }, .default_ai = MA_WANDER
439 },
440 { /* MT_LAMA_NOBE */
441 .name = "lama nobe", .glyph = 'l', .colour = RED,
442 .exp = 80, .ac = 3, .hp_max = 35,
443 .level = 7, .intelligence = 6, .speed = NORMAL, .size = MEDIUM,
444 .flags = HEAD,
445 .attacks = {
446 { .type = ATT_BITE, .base = 3, .damage = DAM_PHYSICAL },
447 { .type = ATT_GAZE, .base = 25, .damage = DAM_BLINDNESS },
448 }, .default_ai = MA_WANDER
449 },
450 { /* MT_OSQUIP */
451 .name = "osquip", .glyph = 'o', .colour = BROWN,
452 .exp = 100, .ac = 5, .hp_max = 35,
453 .level = 7, .intelligence = 4, .speed = VFAST, .size = SMALL,
454 .flags = HEAD | PACK,
455 .attacks = {
456 { .type = ATT_BITE, .base = 10, .damage = DAM_PHYSICAL, .rand = 15 },
457 }, .default_ai = MA_WANDER
458 },
459 { /* MT_ROTHE */
460 .name = "rothe", .glyph = 'r', .colour = BROWN,
461 .exp = 250, .ac = 5, .hp_max = 50,
462 .level = 7, .intelligence = 5, .speed = VFAST, .size = LARGE,
463 .flags = HEAD | INFRAVISION | PACK,
464 .attacks = {
465 { .type = ATT_BITE, .base = 5, .damage = DAM_PHYSICAL },
466 { .type = ATT_CLAW, .base = 3, .damage = DAM_PHYSICAL },
467 }, .default_ai = MA_WANDER
468 },
469 { /* MT_XORN */
470 .name = "xorn", .glyph = 'X', .colour = BROWN,
471 .exp = 300, .ac = 10, .hp_max = 60,
472 .level = 7, .intelligence = 13, .speed = NORMAL, .size = MEDIUM,
473 .flags = INFRAVISION | RES_COLD | RES_FIRE,
474 .attacks = {
475 { .type = ATT_BITE, .base = 6, .damage = DAM_PHYSICAL },
476 }, .default_ai = MA_WANDER
477 },
478 { /* MT_VAMPIRE */
479 .name = "vampire", .glyph = 'V', .colour = RED,
480 .exp = 1000, .ac = 7, .hp_max = 50,
481 .level = 7, .intelligence = 17, .speed = NORMAL, .size = MEDIUM,
482 .flags = HEAD | HANDS | FLY | UNDEAD | INFRAVISION | REGENERATE | RES_SLEEP | RES_POISON | RES_CONF,
483 .attacks = {
484 { .type = ATT_BITE, .base = 75, .damage = DAM_DRAIN_LIFE },
485 { .type = ATT_WEAPON, .damage = DAM_PHYSICAL },
486 }, .default_ai = MA_WANDER
487 },
488 { /* MT_STALKER */
489 .name = "invisible stalker", .glyph = 'I', .colour = LIGHTGRAY,
490 .exp = 350, .ac = 7, .hp_max = 50,
491 .level = 7, .intelligence = 14, .speed = FAST, .size = MEDIUM,
492 .flags = HEAD | FLY | INVISIBLE,
493 .attacks = {
494 { .type = ATT_SLAM, .base = 6, .damage = DAM_PHYSICAL },
495 }, .default_ai = MA_WANDER
496 },
497 { /* MT_POLTERGEIST */
498 .name = "poltergeist", .glyph = 'p', .colour = WHITE,
499 .exp = 450, .ac = 6, .hp_max = 50,
500 .level = 8, .intelligence = 5, .speed = NORMAL, .size = MEDIUM,
501 .flags = FLY | UNDEAD | INVISIBLE | RES_SLEEP | RES_POISON,
502 .attacks = {
503 { .type = ATT_WEAPON, .damage = DAM_PHYSICAL },
504 }, .default_ai = MA_WANDER, .sound = "laugh"
505 },
506 { /* MT_DISENCHANTRESS */
507 .name = "disenchantress", .plural_name = "disenchantresses", .glyph = 'q', .colour = WHITE,
508 .exp = 500, .ac = 7, .hp_max = 50,
509 .level = 8, .intelligence = 5, .speed = NORMAL, .size = MEDIUM,
510 .flags = HEAD | HANDS | METALLIVORE,
511 .attacks = {
512 { .type = ATT_TOUCH, .damage = DAM_REM_ENCH },
513 }, .default_ai = MA_WANDER
514 },
515 { /* MT_SHAMBLINGMOUND */
516 .name = "shambling mound", .glyph = 's', .colour = GREEN,
517 .exp = 400, .ac = 8, .hp_max = 45,
518 .level = 8, .intelligence = 6, .speed = VSLOW, .size = GIANT,
519 .flags = RES_SLEEP | RES_POISON | RES_ELEC,
520 .attacks = {
521 { .type = ATT_SLAM, .base = 5, .damage = DAM_PHYSICAL },
522 }, .default_ai = MA_WANDER
523 },
524 { /* MT_YELLOW_MOLD */
525 .name = "yellow mold", .glyph = 'y', .colour = YELLOW,
526 .exp = 250, .ac = 0, .hp_max = 35,
527 .level = 8, .intelligence = 3, .speed = XSLOW, .size = SMALL,
528 .flags = RES_SLEEP | RES_POISON | RES_CONF,
529 .attacks = {
530 { .type = ATT_TOUCH, .base = 4, .damage = DAM_PHYSICAL },
531 }, .default_ai = MA_WANDER
532 },
533 { /* MT_UMBER_HULK */
534 .name = "umber hulk", .glyph = 'U', .colour = YELLOW,
535 .exp = 600, .ac = 12, .hp_max = 65,
536 .level = 8, .intelligence = 14, .speed = SLOW, .size = GIANT,
537 .flags = HEAD | HANDS | INFRAVISION | RES_CONF,
538 .attacks = {
539 { .type = ATT_CLAW, .base = 7, .damage = DAM_PHYSICAL },
540 { .type = ATT_GAZE, .base = 75, .damage = DAM_CONFUSION },
541 }, .default_ai = MA_WANDER
542 },
543 { /* MT_GNOME_KING */
544 .name = "gnome king", .glyph = 'G', .colour = RED,
545 .exp = 3000, .gold = 2000, .reroll_chance = 80, .ac = 11, .hp_max = 100,
546 .level = 9, .intelligence = 18, .speed = NORMAL, .size = SMALL,
547 .flags = HEAD | HANDS | RES_SLEEP,
548 .attacks = {
549 { .type = ATT_WEAPON, .damage = DAM_PHYSICAL },
550 }, .default_ai = MA_WANDER
551 },
552 { /* MT_MIMIC */
553 .name = "mimic", .glyph = 'M', .colour = BROWN,
554 .exp = 99, .ac = 5, .hp_max = 55,
555 .level = 9, .intelligence = 8, .speed = SLOW, .size = MEDIUM,
556 .flags = MIMIC,
557 .attacks = {
558 { .type = ATT_SLAM, .base = 6, .damage = DAM_PHYSICAL },
559 }, .default_ai = MA_WANDER
560 },
561 { /* MT_WATER_LORD */
562 .name = "water lord", .glyph = 'w', .colour = LIGHTBLUE,
563 .exp = 15000, .ac = 12, .hp_max = 150,
564 .level = 9, .intelligence = 20, .speed = NORMAL, .size = LARGE,
565 .flags = HEAD | NOBEHEAD | HANDS | RES_SLEEP | SWIM,
566 .attacks = {
567 { .type = ATT_WEAPON, .damage = DAM_PHYSICAL },
568 }, .default_ai = MA_WANDER
569 },
570 { /* MT_PURPLE_WORM */
571 .name = "purple worm", .glyph = 'P', .colour = MAGENTA,
572 .exp = 15000, .ac = 12, .hp_max = 120,
573 .level = 9, .intelligence = 3, .speed = VSLOW, .size = GARGANTUAN,
574 .flags = HEAD | RES_POISON,
575 .attacks = {
576 { .type = ATT_BITE, .base = 11, .damage = DAM_PHYSICAL },
577 { .type = ATT_STING, .base = 6, .damage = DAM_POISON },
578 }, .default_ai = MA_WANDER
579 },
580 { /* MT_XVART */
581 .name = "xvart", .glyph = 'x', .colour = LIGHTGRAY,
582 .exp = 1000, .ac = 11, .hp_max = 90,
583 .level = 9, .intelligence = 13, .speed = NORMAL, .size = SMALL,
584 .flags = HEAD | HANDS | INFRAVISION,
585 .attacks = {
586 { .type = ATT_WEAPON, .damage = DAM_PHYSICAL },
587 }, .default_ai = MA_WANDER
588 },
589 { /* MT_BRONZE_DRAGON */
590 .name = "bronze dragon", .glyph = 'D', .colour = BROWN,
591 .exp = 4000, .gold = 300, .ac = 10, .hp_max = 80,
592 .level = 9, .intelligence = 16, .speed = NORMAL, .size = GIANT,
593 .flags = HEAD | FLY | DRAGON,
594 .attacks = {
595 { .type = ATT_BITE, .base = 9, .damage = DAM_PHYSICAL },
596 { .type = ATT_CLAW, .base = 9, .damage = DAM_PHYSICAL },
597 }, .default_ai = MA_WANDER
598 },
599 { /* MT_GREEN_DRAGON */
600 .name = "green dragon", .glyph = 'D', .colour = LIGHTGREEN,
601 .exp = 2500, .gold = 200, .ac = 12, .hp_max = 70,
602 .level = 9, .intelligence = 15, .speed = NORMAL, .size = GIANT,
603 .flags = HEAD | FLY | DRAGON | RES_POISON,
604 .attacks = {
605 { .type = ATT_BREATH, .base = 8, .damage = DAM_POISON },
606 { .type = ATT_SLAM, .base = 25, .damage = DAM_PHYSICAL },
607 }, .default_ai = MA_WANDER
608 },
609 { /* MT_SILVER_DRAGON */
610 .name = "silver dragon", .glyph = 'D', .colour = LIGHTGRAY,
611 .exp = 10000, .gold = 700, .ac = 13, .hp_max = 100,
612 .level = 10, .intelligence = 20, .speed = NORMAL, .size = GIANT,
613 .flags = HEAD | FLY | DRAGON,
614 .attacks = {
615 { .type = ATT_BITE, .base = 12, .damage = DAM_PHYSICAL },
616 { .type = ATT_CLAW, .base = 12, .damage = DAM_PHYSICAL },
617 }, .default_ai = MA_WANDER
618 },
619 { /* MT_PLATINUM_DRAGON */
620 .name = "platinum dragon", .glyph = 'D', .colour = WHITE,
621 .exp = 24000, .gold = 1000, .ac = 15, .hp_max = 130,
622 .level = 11, .intelligence = 22, .speed = NORMAL, .size = GIANT,
623 .flags = HEAD | FLY | DRAGON | RES_CONF,
624 .attacks = {
625 { .type = ATT_BITE, .base = 15, .damage = DAM_PHYSICAL },
626 { .type = ATT_BREATH, .base = 15, .damage = DAM_MAGICAL, .rand = 30 },
627 }, .default_ai = MA_WANDER
628 },
629 { /* MT_RED_DRAGON */
630 .name = "red dragon", .glyph = 'D', .colour = LIGHTRED,
631 .exp = 14000, .gold = 800, .ac = 14, .hp_max = 110,
632 .level = 11, .intelligence = 19, .speed = NORMAL, .size = GIANT,
633 .flags = HEAD | FLY | DRAGON | RES_FIRE,
634 .attacks = {
635 { .type = ATT_BREATH, .base = 20, .damage = DAM_FIRE, .rand = 25 },
636 { .type = ATT_CLAW, .base = 13, .damage = DAM_PHYSICAL },
637 }, .default_ai = MA_WANDER
638 },
639 { /* MT_SPIRIT_NAGA */
640 .name = "spirit naga", .glyph = 'N', .colour = MAGENTA,
641 .exp = 20000, .ac = 16, .hp_max = 95,
642 .level = 11, .intelligence = 23, .speed = FAST, .size = LARGE,
643 .flags = HEAD | NOBEHEAD | FLY | SPIRIT | INFRAVISION | RES_SLEEP | RES_POISON | RES_CONF | RES_MAGIC,
644 .attacks = {
645 { .type = ATT_BITE, .base = 12, .damage = DAM_PHYSICAL },
646 { .type = ATT_MAGIC, .base = 1, .damage = DAM_RANDOM },
647 }, .default_ai = MA_WANDER
648 },
649 { /* MT_GREEN_URCHIN */
650 .name = "green urchin", .glyph = 'u', .colour = GREEN,
651 .exp = 5000, .ac = 17, .hp_max = 85,
652 .level = 10, .intelligence = 3, .speed = SLOW, .size = SMALL,
653 .flags = 0,
654 .attacks = {
655 { .type = ATT_STING, .base = 12, .damage = DAM_PHYSICAL },
656 { .type = ATT_STING, .base = 50, .damage = DAM_BLINDNESS },
657 }, .default_ai = MA_WANDER
658 },
659 { /* MT_DEMONLORD_I */
660 .name = "type I demon lord", .glyph = '&', .colour = LIGHTRED,
661 .exp = 50000, .ac = 17, .hp_max = 140,
662 .level = 12, .intelligence = 20, .speed = FAST, .size = MEDIUM,
663 .flags = HEAD | NOBEHEAD | HANDS | FLY | INVISIBLE | INFRAVISION | DEMON | RES_POISON | RES_MAGIC,
664 .attacks = {
665 { .type = ATT_BITE, .base = 18, .damage = DAM_PHYSICAL },
666 { .type = ATT_CLAW, .base = 18, .damage = DAM_PHYSICAL },
667 }, .default_ai = MA_WANDER
668 },
669 { /* MT_DEMONLORD_II */
670 .name = "type II demon lord", .glyph = '&', .colour = LIGHTRED,
671 .exp = 75000, .ac = 18, .hp_max = 160,
672 .level = 13, .intelligence = 21, .speed = FAST, .size = MEDIUM,
673 .flags = HEAD | NOBEHEAD | HANDS | FLY | INVISIBLE | INFRAVISION | DEMON | RES_POISON | RES_MAGIC,
674 .attacks = {
675 { .type = ATT_BITE, .base = 18, .damage = DAM_PHYSICAL },
676 { .type = ATT_CLAW, .base = 18, .damage = DAM_PHYSICAL },
677 }, .default_ai = MA_WANDER
678 },
679 { /* MT_DEMONLORD_III */
680 .name = "type III demon lord", .glyph = '&', .colour = LIGHTRED,
681 .exp = 100000, .ac = 19, .hp_max = 180,
682 .level = 14, .intelligence = 22, .speed = FAST, .size = MEDIUM,
683 .flags = HEAD | NOBEHEAD | HANDS | FLY | INVISIBLE | INFRAVISION | DEMON | RES_POISON | RES_MAGIC,
684 .attacks = {
685 { .type = ATT_BITE, .base = 18, .damage = DAM_PHYSICAL },
686 { .type = ATT_CLAW, .base = 18, .damage = DAM_PHYSICAL },
687 }, .default_ai = MA_WANDER
688 },
689 { /* MT_DEMONLORD_IV */
690 .name = "type IV demon lord", .glyph = '&', .colour = LIGHTRED,
691 .exp = 125000, .ac = 20, .hp_max = 200,
692 .level = 15, .intelligence = 23, .speed = FAST, .size = MEDIUM,
693 .flags = HEAD | NOBEHEAD | HANDS | FLY | INVISIBLE | INFRAVISION | DEMON | RES_POISON | RES_MAGIC,
694 .attacks = {
695 { .type = ATT_BITE, .base = 20, .damage = DAM_PHYSICAL },
696 { .type = ATT_CLAW, .base = 20, .damage = DAM_PHYSICAL },
697 }, .default_ai = MA_WANDER
698 },
699 { /* MT_DEMONLORD_V */
700 .name = "type V demon lord", .glyph = '&', .colour = LIGHTRED,
701 .exp = 150000, .ac = 21, .hp_max = 220,
702 .level = 16, .intelligence = 24, .speed = FAST, .size = MEDIUM,
703 .flags = HEAD | NOBEHEAD | HANDS | FLY | INVISIBLE | INFRAVISION | DEMON | RES_POISON | RES_MAGIC,
704 .attacks = {
705 { .type = ATT_BITE, .base = 22, .damage = DAM_PHYSICAL },
706 { .type = ATT_CLAW, .base = 22, .damage = DAM_PHYSICAL },
707 }, .default_ai = MA_WANDER
708 },
709 { /* MT_DEMONLORD_VI */
710 .name = "type VI demon lord", .glyph = '&', .colour = LIGHTRED,
711 .exp = 175000, .ac = 22, .hp_max = 240,
712 .level = 17, .intelligence = 25, .speed = FAST, .size = LARGE,
713 .flags = HEAD | NOBEHEAD | HANDS | FLY | INVISIBLE | INFRAVISION | DEMON | RES_POISON | RES_MAGIC,
714 .attacks = {
715 { .type = ATT_BITE, .base = 24, .damage = DAM_PHYSICAL },
716 { .type = ATT_CLAW, .base = 24, .damage = DAM_PHYSICAL },
717 }, .default_ai = MA_WANDER
718 },
719 { /* MT_DEMONLORD_VII */
720 .name = "type VII demon lord", .glyph = '&', .colour = LIGHTRED,
721 .exp = 200000, .ac = 23, .hp_max = 260,
722 .level = 18, .intelligence = 26, .speed = FAST, .size = GIANT,
723 .flags = HEAD | NOBEHEAD | HANDS | FLY | INVISIBLE | INFRAVISION | DEMON | RES_POISON | RES_CONF | RES_MAGIC,
724 .attacks = {
725 { .type = ATT_BITE, .base = 27, .damage = DAM_PHYSICAL },
726 { .type = ATT_CLAW, .base = 27, .damage = DAM_PHYSICAL },
727 }, .default_ai = MA_WANDER
728 },
729 { /* MT_DEMON_PRINCE */
730 .name = "demon prince", .glyph = '&', .colour = RED,
731 .exp = 300000, .ac = 25, .hp_max = 345,
732 .level = 25, .intelligence = 28, .speed = FAST, .size = GIANT,
733 .flags = HEAD | NOBEHEAD | HANDS | FLY | INVISIBLE | INFRAVISION | DEMON | RES_FIRE | RES_SLEEP | RES_POISON | RES_CONF | RES_MAGIC,
734 .attacks = {
735 { .type = ATT_BITE, .base = 30, .damage = DAM_PHYSICAL },
736 { .type = ATT_CLAW, .base = 30, .damage = DAM_PHYSICAL },
737 }, .default_ai = MA_WANDER
738 },
739 { /* MT_TOWN_PERSON */
740 .name = "human", .glyph = '@', .colour = BROWN,
741 .exp = 0, .ac = 0, .hp_max = 10,
742 .level = 1, .intelligence = 10, .speed = NORMAL, .size = MEDIUM,
743 .flags = HEAD | HANDS,
744 .attacks = {
745 { .type = ATT_SLAM, .base = 0, .damage = DAM_PHYSICAL },
746 }, .default_ai = MA_CIVILIAN
747 },
748 };
749
750 static inline monster_action_t monster_default_ai(monster *m);
751 static gboolean monster_player_visible(monster *m);
752 static gboolean monster_attack_available(monster *m, attack_t type);
753 static item *monster_weapon_select(monster *m);
754 static void monster_weapon_wield(monster *m, item *weapon);
755 static gboolean monster_item_disenchant(monster *m, struct player *p);
756 static gboolean monster_item_rust(monster *m, struct player *p);
757 static gboolean monster_player_rob(monster *m, struct player *p, item_t item_type);
758
759 static position monster_find_next_pos_to(monster *m, position dest);
760 static position monster_move_wander(monster *m, struct player *p);
761 static position monster_move_attack(monster *m, struct player *p);
762 static position monster_move_confused(monster *m, struct player *p);
763 static position monster_move_flee(monster *m, struct player *p);
764 static position monster_move_serve(monster *m, struct player *p);
765 static position monster_move_civilian(monster *m, struct player *p);
766
767 static gboolean monster_breath_hit(const GList *traj,
768 const damage_originator *damo,
769 gpointer data1, gpointer data2);
770
monster_new(monster_t type,position pos,gpointer leader)771 monster *monster_new(monster_t type, position pos, gpointer leader)
772 {
773 g_assert(type < MT_MAX && pos_valid(pos));
774
775 monster *nmonster;
776 item_t itype; /* item type */
777
778 /* check if supplied position is suitable for a monster */
779 if (!map_pos_validate(game_map(nlarn, Z(pos)), pos, LE_MONSTER, FALSE))
780 {
781 return NULL;
782 }
783
784 /* don't create genocided monsters */
785 if (monster_is_genocided(type))
786 {
787 /* try to find a replacement for the demon prince */
788 if (type == MT_DEMON_PRINCE)
789 return monster_new(MT_DEMONLORD_I + rand_0n(7), pos, NULL);
790 else
791 return NULL;
792 }
793
794 /* make room for monster */
795 nmonster = g_malloc0(sizeof(monster));
796
797 nmonster->type = type;
798
799 /* determine max hp; prevent the living dead */
800 nmonster->hp_max = nmonster->hp = max(1, divert(monster_type_hp_max(type), 10));
801
802 nmonster->effects = g_ptr_array_new();
803 nmonster->inv = inv_new(nmonster);
804
805 /* fill monsters inventory */
806 if (monster_gold_amount(nmonster) > 0)
807 {
808 const int gold_chance = monster_gold_chance(nmonster);
809 if (gold_chance == 0 || chance(gold_chance))
810 {
811 /* add gold to monster's inventory, randomize the amount */
812 int gcount = max(divert(monster_gold_amount(nmonster), 30), 1);
813 inv_add(&nmonster->inv, item_new(IT_GOLD, gcount));
814 }
815 }
816
817 /* add special items */
818 switch (type)
819 {
820 case MT_LEPRECHAUN:
821 if (chance(25))
822 {
823 inv_add(&nmonster->inv, item_new_random(IT_GEM, FALSE));
824 }
825 break;
826
827 case MT_TROGLODYTE:
828 case MT_NYMPH:
829 case MT_PLATINUM_DRAGON:
830 case MT_RED_DRAGON:
831 case MT_GNOME_KING:
832 /* add something that is not a container */
833 do
834 {
835 itype = rand_1n(IT_MAX);
836 }
837 while (itype == IT_CONTAINER);
838
839 inv_add(&nmonster->inv, item_new_by_level(itype, Z(pos)));
840 break;
841
842 case MT_TOWN_PERSON:
843 /* initialize name counter */
844 nmonster->number = rand_1n(40);
845 break;
846
847 default:
848 /* no fancy stuff... */
849 break;
850 }
851
852 /* generate a weapon if monster can use it */
853 if (monster_attack_available(nmonster, ATT_WEAPON))
854 {
855 int weapon_count = 3;
856 int wpns[3]; /* choice of weapon types */
857 item *weapon;
858
859 /* preset weapon types */
860 switch (type)
861 {
862 case MT_HOBGOBLIN:
863 case MT_ORC:
864 wpns[0] = WT_ODAGGER;
865 wpns[1] = WT_OSHORTSWORD;
866 wpns[2] = WT_OSPEAR;
867 break;
868
869 case MT_TROLL:
870 wpns[0] = WT_CLUB;
871 wpns[1] = WT_CLUB;
872 wpns[2] = WT_BATTLEAXE;
873 break;
874
875 case MT_ELF:
876 wpns[0] = WT_ESHORTSWORD;
877 wpns[1] = WT_ESPEAR;
878 weapon_count = 2;
879 break;
880
881 case MT_BUGBEAR:
882 case MT_CENTAUR:
883 case MT_POLTERGEIST:
884 wpns[0] = WT_MACE;
885 wpns[1] = WT_FLAIL;
886 wpns[2] = WT_BATTLEAXE;
887 break;
888
889 case MT_VAMPIRE:
890 case MT_GNOME_KING:
891 case MT_WATER_LORD:
892 case MT_XVART:
893 wpns[0] = WT_LONGSWORD;
894 wpns[1] = WT_2SWORD;
895 wpns[2] = WT_SWORDSLASHING;
896 break;
897
898 default:
899 wpns[0] = WT_DAGGER;
900 wpns[1] = WT_SPEAR;
901 wpns[2] = WT_SHORTSWORD;
902 break;
903 }
904
905 weapon = item_new(IT_WEAPON, wpns[rand_0n(weapon_count)]);
906 item_new_finetouch(weapon);
907
908 inv_add(&nmonster->inv, weapon);
909
910 /* wield the new weapon */
911 monster_weapon_wield(nmonster, weapon);
912 } /* finished initializing weapons */
913
914 /* initialize mimics */
915 if (monster_flags(nmonster, MIMIC))
916 {
917 const int possible_types[] = { IT_AMULET, IT_GOLD, IT_RING, IT_GEM,
918 IT_CONTAINER, IT_BOOK, IT_POTION,
919 IT_SCROLL
920 };
921
922 /* put mimicked item into monster inventory */
923 const int chosen_type = possible_types[rand_0n(8)];
924 item *itm = item_new_by_level(chosen_type, Z(nmonster->pos));
925 inv_add(&nmonster->inv, itm);
926
927 /* the mimic is not known to be a monster */
928 nmonster->unknown = TRUE;
929 }
930
931 /* initialize AI */
932 nmonster->action = monster_default_ai(nmonster);
933 nmonster->player_pos = pos_invalid;
934 nmonster->leader = leader;
935
936 /* register monster with game */
937 nmonster->oid = game_monster_register(nlarn, nmonster);
938
939 /* set position */
940 nmonster->pos = pos;
941
942 /* link monster to tile */
943 map_set_monster_at(game_map(nlarn, Z(pos)), pos, nmonster);
944
945 /* add some members to the pack if we created a pack monster */
946 if (monster_flags(nmonster, PACK) && !leader)
947 {
948 guint count = rand_1n(5);
949 while (count > 0)
950 {
951 position mpos = map_find_space_in(game_map(nlarn, Z(pos)),
952 rect_new_sized(pos, 4), LE_MONSTER, FALSE);
953 /* no space left? */
954 if (!pos_valid(mpos)) break;
955
956 /* valid position returned, place a pack member there */
957 monster_new(type, mpos, nmonster->oid);
958 count--;
959 }
960 }
961
962 /* increment monster count */
963 game_map(nlarn, Z(pos))->mcount++;
964
965 return nmonster;
966 }
967
monster_new_by_level(position pos)968 monster *monster_new_by_level(position pos)
969 {
970 g_assert(pos_valid(pos));
971
972 const int mlevel[] = {
973 MT_KOBOLD, // D1: 5
974 MT_GIANT_ANT, // D2: 11
975 MT_ZOMBIE, // D3: 17
976 MT_CENTAUR, // D4: 22
977 MT_WHITE_DRAGON, // D5: 27
978 MT_FORVALAKA, // D6: 33
979 MT_STALKER, // D7: 39
980 MT_SHAMBLINGMOUND, // D8: 42
981 MT_MIMIC, // D9: 46
982 MT_BRONZE_DRAGON, // D10: 50
983 MT_PLATINUM_DRAGON, // V1: 53
984 MT_GREEN_URCHIN, // V2: 56
985 MT_DEMON_PRINCE // V3
986 };
987
988 const int nlevel = Z(pos);
989 int monster_id;
990
991 if (nlevel == 0)
992 {
993 /* only town persons in town */
994 monster_id = MT_TOWN_PERSON;
995 }
996 else
997 {
998 /* everything else in the caverns */
999 int minstep = nlevel - 4;
1000 int maxstep = nlevel - 1;
1001
1002 int monster_id_min;
1003 int monster_id_max;
1004
1005 if (chance(2 * game_difficulty(nlarn)))
1006 maxstep += 2;
1007 else if (chance(7 * (game_difficulty(nlarn) + 1)))
1008 maxstep++;
1009 else if (chance(10))
1010 minstep--;
1011
1012 if (minstep < 0)
1013 monster_id_min = MT_GIANT_BAT;
1014 else
1015 monster_id_min = mlevel[minstep] + 1;
1016
1017 if (maxstep < 0)
1018 maxstep = 0;
1019 else if (maxstep > MAP_MAX - 2)
1020 maxstep = MAP_MAX - 2;
1021
1022 monster_id_max = mlevel[maxstep];
1023
1024 do
1025 {
1026 monster_id = rand_m_n(monster_id_min, monster_id_max);
1027 }
1028 while ((monster_id < 0)
1029 || (monster_id >= MT_MAX)
1030 || nlarn->monster_genocided[monster_id]
1031 || chance(monster_type_reroll_chance(monster_id)));
1032 }
1033
1034 return monster_new(monster_id, pos, NULL);
1035 }
1036
monster_destroy(monster * m)1037 void monster_destroy(monster *m)
1038 {
1039 g_assert(m != NULL && m->type < MT_MAX);
1040
1041 /* free effects */
1042 while (m->effects->len > 0)
1043 {
1044 gpointer effect_id = g_ptr_array_remove_index(m->effects, m->effects->len - 1);
1045 effect *e = game_effect_get(nlarn, effect_id);
1046 effect_destroy(e);
1047 }
1048
1049 g_ptr_array_free(m->effects, TRUE);
1050
1051 /* free inventory */
1052 if (m->inv)
1053 inv_destroy(m->inv, TRUE);
1054
1055 /* unregister monster */
1056 game_monster_unregister(nlarn, m->oid);
1057
1058 /* decrement monster count */
1059 game_map(nlarn, Z(m->pos))->mcount--;
1060
1061 /* free monster's FOV if existing */
1062 if (m->fv)
1063 fov_free(m->fv);
1064
1065 g_free(m);
1066 }
1067
monster_serialize(gpointer oid,monster * m,cJSON * root)1068 void monster_serialize(gpointer oid, monster *m, cJSON *root)
1069 {
1070 cJSON *mval;
1071
1072 cJSON_AddItemToArray(root, mval = cJSON_CreateObject());
1073 cJSON_AddNumberToObject(mval, "type", monster_type(m));
1074 cJSON_AddNumberToObject(mval, "oid", GPOINTER_TO_UINT(oid));
1075 cJSON_AddNumberToObject(mval, "hp_max", m->hp_max);
1076 cJSON_AddNumberToObject(mval, "hp", m->hp);
1077 cJSON_AddNumberToObject(mval,"pos", pos_val(m->pos));
1078 cJSON_AddNumberToObject(mval, "movement", m->movement);
1079 cJSON_AddNumberToObject(mval, "action", m->action);
1080
1081 if (m->eq_weapon != NULL)
1082 cJSON_AddNumberToObject(mval, "eq_weapon",
1083 GPOINTER_TO_UINT(m->eq_weapon->oid));
1084
1085 if (m->number)
1086 cJSON_AddNumberToObject(mval, "number", m->number);
1087
1088 if (m->leader)
1089 cJSON_AddNumberToObject(mval, "leader", GPOINTER_TO_UINT(m->leader));
1090
1091 if (m->unknown)
1092 cJSON_AddTrueToObject(mval, "unknown");
1093
1094 if (m->lastseen != 0)
1095 {
1096 cJSON_AddNumberToObject(mval,"lastseen", m->lastseen);
1097 cJSON_AddNumberToObject(mval,"player_pos", pos_val(m->player_pos));
1098 }
1099
1100 /* inventory */
1101 if (inv_length(m->inv) > 0)
1102 {
1103 cJSON_AddItemToObject(mval, "inventory", inv_serialize(m->inv));
1104 }
1105
1106 /* effects */
1107 if (m->effects->len > 0)
1108 {
1109 cJSON_AddItemToObject(mval, "effects", effects_serialize(m->effects));
1110 }
1111 }
1112
monster_deserialize(cJSON * mser,game * g)1113 void monster_deserialize(cJSON *mser, game *g)
1114 {
1115 cJSON *obj;
1116 guint oid;
1117 monster *m = g_malloc0(sizeof(monster));
1118
1119 m->type = cJSON_GetObjectItem(mser, "type")->valueint;
1120 oid = cJSON_GetObjectItem(mser, "oid")->valueint;
1121 m->oid = GUINT_TO_POINTER(oid);
1122 m->hp_max = cJSON_GetObjectItem(mser, "hp_max")->valueint;
1123 m->hp = cJSON_GetObjectItem(mser, "hp")->valueint;
1124 pos_val(m->pos) = cJSON_GetObjectItem(mser, "pos")->valueint;
1125 m->movement = cJSON_GetObjectItem(mser, "movement")->valueint;
1126 m->action = cJSON_GetObjectItem(mser, "action")->valueint;
1127
1128 if ((obj = cJSON_GetObjectItem(mser, "eq_weapon")))
1129 m->eq_weapon = game_item_get(nlarn, GUINT_TO_POINTER(obj->valueint));
1130
1131 if ((obj = cJSON_GetObjectItem(mser, "number")))
1132 m->number = obj->valueint;
1133
1134 if ((obj = cJSON_GetObjectItem(mser, "leader")))
1135 {
1136 guint leader = obj->valueint;
1137 m->leader = GUINT_TO_POINTER(leader);
1138 }
1139
1140 if ((obj = cJSON_GetObjectItem(mser, "unknown")))
1141 m->unknown = obj->valueint;
1142
1143 if ((obj = cJSON_GetObjectItem(mser, "lastseen")))
1144 m->lastseen = obj->valueint;
1145
1146 if ((obj = cJSON_GetObjectItem(mser, "player_pos")))
1147 pos_val(m->player_pos) = obj->valueint;
1148
1149 /* inventory */
1150 if ((obj = cJSON_GetObjectItem(mser, "inventory")))
1151 m->inv = inv_deserialize(obj);
1152 else
1153 m->inv = inv_new(m);
1154
1155 /* effects */
1156 if ((obj = cJSON_GetObjectItem(mser, "effects")))
1157 m->effects = effects_deserialize(obj);
1158 else
1159 m->effects = g_ptr_array_new();
1160
1161 /* add monster to game */
1162 g_hash_table_insert(g->monsters, m->oid, m);
1163
1164 /* increase max_id to match used ids */
1165 if (oid > g->monster_max_id)
1166 g->monster_max_id = oid;
1167
1168 /* increment the count of monsters of the map the monster is on */
1169 game_map(g, Z(m->pos))->mcount++;
1170 }
1171
monster_hp_max(monster * m)1172 int monster_hp_max(monster *m)
1173 {
1174 g_assert(m != NULL && m->type < MT_MAX);
1175 return m->hp_max;
1176 }
1177
monster_hp(monster * m)1178 int monster_hp(monster *m)
1179 {
1180 g_assert(m != NULL && m->type < MT_MAX);
1181 return m->hp;
1182 }
1183
monster_hp_inc(monster * m,int amount)1184 void monster_hp_inc(monster *m, int amount)
1185 {
1186 g_assert(m != NULL && m->type < MT_MAX);
1187 m->hp = min(m->hp + amount, m->hp_max);
1188 }
1189
monster_oid(monster * m)1190 gpointer monster_oid(monster *m)
1191 {
1192 g_assert (m != NULL);
1193 return m->oid;
1194 }
1195
monster_pos(monster * m)1196 position monster_pos(monster *m)
1197 {
1198 g_assert(m != NULL && m->type < MT_MAX);
1199 return m->pos;
1200 }
1201
monster_map_element(monster * m)1202 static int monster_map_element(monster *m)
1203 {
1204 if (monster_type(m) == MT_XORN)
1205 return LE_XORN;
1206
1207 if (monster_flags(m, FLY))
1208 return LE_FLYING_MONSTER;
1209
1210 if (monster_flags(m, SWIM))
1211 return LE_SWIMMING_MONSTER;
1212
1213 return LE_MONSTER;
1214 }
1215
monster_valid_dest(map * m,position pos,int map_elem)1216 int monster_valid_dest(map *m, position pos, int map_elem)
1217 {
1218 /* only civilians use LE_GROUND and can't move through the player */
1219 if (map_elem == LE_GROUND && pos_identical(pos, nlarn->p->pos))
1220 return FALSE;
1221
1222 switch (map_tiletype_at(m, pos))
1223 {
1224 case LT_WALL:
1225 return (map_elem == LE_XORN);
1226
1227 case LT_DEEPWATER:
1228 if (map_elem == LE_SWIMMING_MONSTER)
1229 return TRUE;
1230 // else fall through
1231 case LT_LAVA:
1232 return (map_elem == LE_FLYING_MONSTER);
1233
1234 default:
1235 /* the map tile must be passable */
1236 return map_pos_passable(m, pos);
1237 }
1238 }
1239
monster_pos_set(monster * m,map * mp,position target)1240 int monster_pos_set(monster *m, map *mp, position target)
1241 {
1242 g_assert(m != NULL && mp != NULL && pos_valid(target));
1243
1244 if (map_pos_validate(mp, target, monster_map_element(m), FALSE))
1245 {
1246 /* remove current reference to monster from tile */
1247 map_set_monster_at(monster_map(m), m->pos, NULL);
1248
1249 /* set new position */
1250 m->pos = target;
1251
1252 /* set reference to monster on tile */
1253 map_set_monster_at(mp, target, m);
1254
1255 return TRUE;
1256 }
1257
1258 return FALSE;
1259 }
1260
monster_type(monster * m)1261 monster_t monster_type(monster *m)
1262 {
1263 g_assert(m != NULL);
1264 return m->type;
1265 }
1266
monster_unknown(monster * m)1267 gboolean monster_unknown(monster *m)
1268 {
1269 g_assert (m != NULL);
1270 return m->unknown;
1271 }
1272
monster_unknown_set(monster * m,gboolean what)1273 void monster_unknown_set(monster *m, gboolean what)
1274 {
1275 g_assert (m != NULL);
1276 m->unknown = what;
1277 }
1278
monster_inv(monster * m)1279 inventory **monster_inv(monster *m)
1280 {
1281 g_assert (m != NULL);
1282 return &m->inv;
1283 }
1284
monster_nearby(monster * m)1285 static gboolean monster_nearby(monster *m)
1286 {
1287 /* different level */
1288 if (Z(m->pos) != Z(nlarn->p->pos))
1289 return FALSE;
1290
1291 return fov_get(nlarn->p->fv, m->pos);
1292 }
1293
monster_in_sight(monster * m)1294 gboolean monster_in_sight(monster *m)
1295 {
1296 g_assert (m != NULL);
1297
1298 /* player is blind */
1299 if (player_effect(nlarn->p, ET_BLINDNESS))
1300 return FALSE;
1301
1302 /* different level */
1303 if (Z(m->pos) != Z(nlarn->p->pos))
1304 return FALSE;
1305
1306 /* invisible monster, player has no infravision */
1307 if (monster_flags(m, INVISIBLE) && !player_effect(nlarn->p, ET_INFRAVISION))
1308 return FALSE;
1309
1310 return fov_get(nlarn->p->fv, m->pos);
1311 }
1312
get_town_person_name(int value)1313 static const char *get_town_person_name(int value)
1314 {
1315 // various jobs
1316 const char *npc_desc[] = { "peasant woman", "old man", "old woman",
1317 "little boy", "young girl", "fisherman",
1318 "midwife", "errand boy", "bar maid",
1319 "stable-lad", "innkeeper", "woodcutter",
1320 "carpenter", "clerk", "barber",
1321 "teacher", "town guard", "postman",
1322 "cobbler", "baker", "merchant",
1323 "clergyman", "student", "blacksmith",
1324 "nurse", "seamstress", "cartwright",
1325 "student", "sales clerk", "miller"
1326 };
1327 if (value >= 30)
1328 return "peasant";
1329
1330 return npc_desc[value];
1331 }
1332
monster_action(monster * m)1333 monster_action_t monster_action(monster *m)
1334 {
1335 g_assert (m != NULL);
1336
1337 return m->action;
1338 }
1339
1340 // Takes visibility into account.
1341 // For the real name, use monster_name() directly.
monster_get_name(monster * m)1342 const char *monster_get_name(monster *m)
1343 {
1344 /* only show real names of invisible monsters in
1345 * wizard mode when full visibility is enabled */
1346 if (!monster_in_sight(m) && !game_fullvis(nlarn))
1347 return ("unseen monster");
1348
1349 if (monster_type(m) == MT_TOWN_PERSON)
1350 return get_town_person_name(m->number);
1351
1352 return (monster_name(m));
1353 }
1354
monster_type_plural_name(monster_t mt,const int count)1355 const char* monster_type_plural_name(monster_t mt, const int count)
1356 {
1357 if (count > 1)
1358 {
1359 if (monster_data[mt].plural_name == NULL)
1360 {
1361 /* need a static buffer to return to calling functions */
1362 static char buf[61] = { 0 };
1363 g_snprintf(buf, 60, "%ss", monster_type_name(mt));
1364 return buf;
1365 }
1366 else
1367 {
1368 return monster_data[mt].plural_name;
1369 }
1370 }
1371
1372 return monster_type_name(mt);
1373 }
1374
monster_die(monster * m,struct player * p)1375 void monster_die(monster *m, struct player *p)
1376 {
1377 g_assert(m != NULL);
1378
1379 /* if the player can see the monster describe the event */
1380 /* Also give a message for invisible monsters you killed yourself
1381 (the xp gain gives this away anyway). */
1382 if (monster_in_sight(m)
1383 || (p != NULL && map_pos_is_visible(monster_map(m),
1384 p->pos, monster_pos(m))))
1385 {
1386 const char *message;
1387
1388 /* give a different message if a servant is expired */
1389 if (monster_action(m) == MA_SERVE && m->number == 0)
1390 message = "The %s disappears!";
1391 else
1392 message = "The %s dies!";
1393
1394 log_add_entry(nlarn->log, message, monster_get_name(m));
1395 }
1396
1397 /* make sure mimics never leave the mimicked item behind */
1398 if (monster_flags(m, MIMIC) && inv_length(m->inv) > 0)
1399 {
1400 inv_del(&m->inv, 0);
1401 }
1402
1403 /* drop stuff the monster carries */
1404 if (inv_length(m->inv))
1405 {
1406 /* Did it fall into water? */
1407 const int tile = map_tiletype_at(monster_map(m), monster_pos(m));
1408 if (tile == LT_DEEPWATER || tile == LT_LAVA)
1409 {
1410 int count = 0;
1411 while (inv_length(m->inv) > 0)
1412 {
1413 item *it = inv_get(m->inv, 0);
1414 if (item_is_unique(it))
1415 {
1416 /* teleport the item to safety */
1417 inv_del_element(&m->inv, it);
1418 map_item_add(game_map(nlarn, Z(nlarn->p->pos)), it);
1419 }
1420 else
1421 {
1422 inv_del(&m->inv, 0);
1423 count++;
1424 }
1425 }
1426 if (count && monster_nearby(m))
1427 log_add_entry(nlarn->log, "You hear a splash!");
1428 }
1429 else
1430 {
1431 /* dump items on the floor */
1432 inventory **floor = map_ilist_at(monster_map(m), monster_pos(m));
1433 while (inv_length(m->inv) > 0)
1434 {
1435 inv_add(floor, inv_get(m->inv, 0));
1436 inv_del(&m->inv, 0);
1437 }
1438 }
1439 }
1440
1441 /* reward experience, but not for summoned monsters */
1442 if (p != NULL && (monster_action(m) != MA_SERVE))
1443 {
1444 player_exp_gain(p, monster_exp(m));
1445 p->stats.monsters_killed[m->type] += 1;
1446 }
1447
1448 /* unlink the monster from its map */
1449 map_set_monster_at(monster_map(m), m->pos, NULL);
1450
1451 /* assure that the monster's hp indicates that the monster is dead */
1452 if (m->hp > 0)
1453 m->hp = 0;
1454
1455 /* add the monster to the list of dead monsters */
1456 g_ptr_array_add(nlarn->dead_monsters, m);
1457 }
1458
monster_level_enter(monster * m,struct map * l)1459 void monster_level_enter(monster *m, struct map *l)
1460 {
1461 g_assert (m != NULL && l != NULL);
1462
1463 sobject_t source = map_sobject_at(monster_map(m), m->pos);
1464 sobject_t target;
1465 position npos;
1466 const char *what = NULL;
1467 const char *how = "comes";
1468
1469 /* check if the monster used the stairs */
1470 switch (source)
1471 {
1472 case LS_CAVERNS_EXIT:
1473 target = LS_CAVERNS_ENTRY;
1474 what = "through";
1475 break;
1476
1477 case LS_CAVERNS_ENTRY:
1478 target = LS_CAVERNS_EXIT;
1479 what = "through";
1480 break;
1481
1482 case LS_STAIRSDOWN:
1483 target = LS_STAIRSUP;
1484 what = "down";
1485 break;
1486
1487 case LS_STAIRSUP:
1488 target = LS_STAIRSDOWN;
1489 what = "up";
1490 break;
1491
1492 case LS_ELEVATORDOWN:
1493 target = LS_ELEVATORUP;
1494 what = "down";
1495 break;
1496
1497 case LS_ELEVATORUP:
1498 target = LS_ELEVATORDOWN;
1499 what = "up";
1500 break;
1501
1502 default:
1503 target = LS_NONE;
1504 }
1505
1506 /* determine new position */
1507 if (target)
1508 {
1509 /* monster came through a map entrance */
1510 npos = map_find_sobject(l, target);
1511 }
1512 else
1513 {
1514 /* monster fell through a trap door */
1515 npos = map_find_space(l, LE_MONSTER, FALSE);
1516 }
1517
1518 /* validate new position */
1519 if (pos_identical(nlarn->p->pos, npos))
1520 {
1521 /* player is standing at the target position */
1522 how = "squeezes past";
1523 npos = map_find_space_in(l, rect_new_sized(npos, 1), LE_MONSTER, FALSE);
1524 }
1525 else
1526
1527 if (!map_pos_validate(l, npos, LE_MONSTER, FALSE))
1528 {
1529 /* the position somehow isn't valid */
1530 return;
1531 }
1532
1533 /* remove monster from old map */
1534 map *oldmap = game_map(nlarn, Z(m->pos));
1535 map_set_monster_at(oldmap, m->pos, NULL);
1536
1537 /* put monster into map */
1538 monster_pos_set(m, l, npos);
1539
1540 /* reset the information of the player's last known position */
1541 m->lastseen = 0;
1542
1543 /* log the event */
1544 if (monster_in_sight(m) && target)
1545 {
1546 log_add_entry(nlarn->log, "The %s %s %s %s.", monster_get_name(m),
1547 how, what, so_get_desc(target));
1548 }
1549 }
1550
monster_move(gpointer * oid,monster * m,game * g)1551 void monster_move(gpointer *oid __attribute__((unused)), monster *m, game *g)
1552 {
1553 /* monster's new position */
1554 position m_npos;
1555
1556 /* expire summoned monsters */
1557 if (monster_action(m) == MA_SERVE
1558 && !monster_effect(m, ET_CHARM_MONSTER))
1559 {
1560 m->number--;
1561
1562 if (m->number == 0)
1563 {
1564 /* expired */
1565 monster_die(m, g->p);
1566 return;
1567 }
1568 }
1569
1570 if (monster_hp(m) < 1)
1571 /* Monster is already dead. */
1572 return;
1573
1574 position mpos = monster_pos(m);
1575
1576 /* modify effects */
1577 monster_effects_expire(m);
1578
1579 /* regenerate / inflict poison upon monster. */
1580 if (!monster_regenerate(m, g->gtime, g->difficulty))
1581 /* the monster died */
1582 return;
1583
1584 /* damage caused by map effects */
1585 damage *dam = map_tile_damage(monster_map(m), monster_pos(m),
1586 monster_flags(m, FLY)
1587 || monster_effect(m, ET_LEVITATION));
1588
1589 /* deal damage caused by floor effects */
1590 if ((dam != NULL) && !(m = monster_damage_take(m, dam)))
1591 /* the monster died */
1592 return;
1593
1594 /* move the monster only if it is on the same map as the player or
1595 an adjacent map */
1596 gboolean map_adjacent = (Z(mpos) == Z(g->p->pos)
1597 || (Z(mpos) == Z(g->p->pos) - 1)
1598 || (Z(mpos) == Z(g->p->pos) + 1)
1599 || (Z(mpos) == MAP_CMAX && Z(g->p->pos) == 0)
1600 );
1601 if (!map_adjacent)
1602 return;
1603
1604 /* Update the monster's knowledge of player's position.
1605 Not for civilians or servants: the first don't care,
1606 the latter just know. This allows to use player_pos
1607 and lastseen for other purposes. */
1608 monster_action_t ma = monster_action(m);
1609
1610 if ((ma != MA_SERVE && ma != MA_CIVILIAN)
1611 && (monster_player_visible(m)
1612 || (player_effect(g->p, ET_AGGRAVATE_MONSTER)
1613 && pos_distance(m->pos, g->p->pos) < 15)))
1614 {
1615 monster_update_player_pos(m, g->p->pos);
1616 }
1617
1618 /* add the monster's speed to the monster's movement points */
1619 m->movement += monster_speed(m);
1620
1621 /* let the monster make a move as long it has movement points left */
1622 while (m->movement >= NORMAL)
1623 {
1624 /* reduce the monster's movement points */
1625 m->movement -= NORMAL;
1626
1627 /* update monsters action */
1628 if (monster_update_action(m, MA_NONE) && monster_in_sight(m))
1629 {
1630 /* the monster has chosen a new action and the player
1631 can see the new action, so let's describe it */
1632
1633 if (m->action == MA_ATTACK && monster_sound(m))
1634 {
1635 const char *sound = monster_sound(m);
1636 log_add_entry(g->log, "The %s %s%ss!",
1637 monster_name(m), sound,
1638 sound[strlen(sound) - 1] == 's' ? "e": "");
1639 }
1640 else if (m->action == MA_FLEE)
1641 {
1642 log_add_entry(g->log, "The %s turns to flee!",
1643 monster_get_name(m));
1644 }
1645 }
1646
1647 /* let the monster have a look at the items at it's current position
1648 if it chose to pick up something, the turn is over */
1649 if (monster_items_pickup(m))
1650 return;
1651
1652 /* determine monster's next move */
1653 m_npos = monster_pos(m);
1654
1655 switch (m->action)
1656 {
1657 case MA_FLEE:
1658 m_npos = monster_move_flee(m, g->p);
1659 break;
1660
1661 case MA_REMAIN:
1662 /* Sgt. Stan Still - do nothing */
1663 break;
1664
1665 case MA_WANDER:
1666 m_npos = monster_move_wander(m, g->p);
1667 break;
1668
1669 case MA_ATTACK:
1670 /* monster tries a ranged attack */
1671 if (monster_player_visible(m)
1672 && monster_player_ranged_attack(m, g->p))
1673 return;
1674
1675 m_npos = monster_move_attack(m, g->p);
1676 break;
1677
1678 case MA_CONFUSION:
1679 m_npos = monster_move_confused(m, g->p);
1680 break;
1681
1682 case MA_SERVE:
1683 m_npos = monster_move_serve(m, g->p);
1684 break;
1685
1686 case MA_CIVILIAN:
1687 m_npos = monster_move_civilian(m, g->p);
1688 break;
1689
1690 case MA_NONE:
1691 /* possibly a bug */
1692 break;
1693 }
1694
1695 /* ******** if new position has been found - move the monster ********* */
1696 if (!pos_identical(m_npos, monster_pos(m)))
1697 {
1698 /* get the monster's current map */
1699 map *mmap = monster_map(m);
1700
1701 /* get stationary object at the monster's target position */
1702 sobject_t target_st = map_sobject_at(mmap, m_npos);
1703
1704 /* vampires won't step onto mirrors */
1705 if ((m->type == MT_VAMPIRE) && (target_st == LS_MIRROR))
1706 {
1707 /* No movement - FIXME: should try to move around it */
1708 }
1709
1710 else if (pos_identical(g->p->pos, m_npos))
1711 {
1712 /* The monster bumps into the player who is invisible to the
1713 monster. Thus the monster gains knowledge over the player's
1714 current position. */
1715 monster_update_player_pos(m, g->p->pos);
1716
1717 log_add_entry(g->log, "The %s bumps into you.", monster_get_name(m));
1718 }
1719
1720 /* check for door */
1721 else if ((target_st == LS_CLOSEDDOOR) && monster_flags(m, HANDS))
1722 {
1723 /* dim-witted or confused monster are unable to open doors */
1724 if (monster_int(m) < 4 || monster_effect_get(m, ET_CONFUSION))
1725 {
1726 /* notify the player if the door is visible */
1727 if (monster_in_sight(m))
1728 {
1729 log_add_entry(g->log, "The %s bumps into the door.",
1730 monster_get_name(m));
1731 }
1732 }
1733 else
1734 {
1735 /* the monster is capable of opening the door */
1736 map_sobject_set(mmap, m_npos, LS_OPENDOOR);
1737
1738 /* notify the player if the door is visible */
1739 if (monster_in_sight(m))
1740 {
1741 log_add_entry(g->log, "The %s opens the door.",
1742 monster_get_name(m));
1743 }
1744 }
1745 }
1746
1747 /* set the monsters new position */
1748 else
1749 {
1750 /* check if the new position is valid for this monster */
1751 if (map_pos_validate(mmap, m_npos, monster_map_element(m), FALSE))
1752 {
1753 /* the new position is valid -> reposition the monster */
1754 monster_pos_set(m, mmap, m_npos);
1755 }
1756 else
1757 {
1758 /* the new position is invalid */
1759 map_tile_t nle = map_tiletype_at(mmap, m_npos);
1760
1761 switch (nle)
1762 {
1763 case LT_TREE:
1764 case LT_WALL:
1765 if (monster_in_sight(m))
1766 {
1767 log_add_entry(g->log, "The %s bumps into %s.",
1768 monster_get_name(m), mt_get_desc(nle));
1769 }
1770 break;
1771
1772 case LT_LAVA:
1773 case LT_DEEPWATER:
1774 if (monster_in_sight(m)) {
1775 log_add_entry(g->log, "The %s sinks into %s.",
1776 monster_get_name(m), mt_get_desc(nle));
1777 }
1778 monster_die(m, g->p);
1779 break;
1780
1781 default:
1782 /* just do not move.. */
1783 break;
1784 }
1785 }
1786
1787 /* check for traps */
1788 if (map_trap_at(mmap, monster_pos(m)))
1789 {
1790 if (!monster_trap_trigger(m))
1791 return; /* trap killed the monster */
1792 }
1793
1794 } /* end new position */
1795 } /* end monster repositioning */
1796 } /* while movement >= NORMAL */
1797
1798 /* increment count of turns since when player was last seen */
1799 if (m->lastseen) m->lastseen++;
1800 }
1801
monster_polymorph(monster * m)1802 void monster_polymorph(monster *m)
1803 {
1804 g_assert (m != NULL);
1805
1806 /* make sure mimics never leave the mimicked item behind */
1807 if (monster_flags(m, MIMIC) && inv_length(m->inv) > 0)
1808 {
1809 inv_del(&m->inv, 0);
1810 }
1811
1812 const map_element_t old_elem = monster_map_element(m);
1813 do
1814 {
1815 m->type = rand_1n(MT_DEMON_PRINCE);
1816 }
1817 while (monster_is_genocided(m->type));
1818
1819 /* if the new monster can't survive in this terrain, kill it */
1820 const map_element_t new_elem = monster_map_element(m);
1821
1822 /* We need to temporarily remove the monster from it's tile
1823 as monster_valid_dest() tests if there is a monster on
1824 the tile and hence would always return false. */
1825 map_set_monster_at(monster_map(m), m->pos, NULL);
1826
1827 /* check if the position would be valid.. */
1828 gboolean valid_pos = monster_valid_dest(monster_map(m), m->pos, new_elem);
1829
1830 /* ..and restore the monster to it's position */
1831 map_set_monster_at(monster_map(m), m->pos, m);
1832
1833 if (!valid_pos)
1834 {
1835 if (monster_in_sight(m))
1836 {
1837 /* briefly display the new monster before it dies */
1838 display_paint_screen(nlarn->p);
1839 g_usleep(250000);
1840
1841 switch (old_elem)
1842 {
1843 case LE_FLYING_MONSTER:
1844 log_add_entry(nlarn->log, "The %s falls into the %s!",
1845 monster_get_name(m),
1846 mt_get_desc(map_tiletype_at(monster_map(m), m->pos)));
1847 break;
1848 case LE_SWIMMING_MONSTER:
1849 log_add_entry(nlarn->log, "The %s sinks like a rock!",
1850 monster_get_name(m));
1851 break;
1852 case LE_XORN:
1853 log_add_entry(nlarn->log, "The %s is trapped in the wall!",
1854 monster_get_name(m));
1855 break;
1856 default:
1857 break;
1858 }
1859 }
1860 monster_die(m, nlarn->p);
1861 }
1862 else
1863 {
1864 /* get the relative amount of hp left */
1865 float relative_hp = (float)m->hp / (float)m->hp_max;
1866
1867 /* Determine the new maximum hitpoints for the new monster
1868 type and set the monster's current hit points to the
1869 relative value of the monster's remaining hit points. */
1870 m->hp_max = divert(monster_type_hp_max(m->type), 10);
1871 m->hp = (int)(m->hp_max * relative_hp);
1872 }
1873 }
1874
monster_items_pickup(monster * m)1875 int monster_items_pickup(monster *m)
1876 {
1877 g_assert(m != NULL);
1878
1879 // The town people never take your stuff.
1880 if (monster_type(m) == MT_TOWN_PERSON)
1881 return FALSE;
1882
1883 /* monsters affected by levitation can't pick up stuff */
1884 if (monster_effect(m, ET_LEVITATION))
1885 return FALSE;
1886
1887 /* TODO: gelatinous cube digests items, rust monster eats metal stuff */
1888 /* FIXME: time management */
1889
1890 gboolean pick_up = FALSE;
1891 item *it;
1892
1893 for (guint idx = 0; idx < inv_length(*map_ilist_at(monster_map(m), m->pos)); idx++)
1894 {
1895 it = inv_get(*map_ilist_at(monster_map(m), m->pos), idx);
1896
1897 if (m->type == MT_LEPRECHAUN
1898 && ((it->type == IT_GEM) || (it->type == IT_GOLD)))
1899 {
1900 /* leprechauns collect treasures */
1901 pick_up = TRUE;
1902 }
1903 else if (it->type == IT_WEAPON && monster_attack_available(m, ATT_WEAPON))
1904 {
1905 /* monster can attack with weapons */
1906 if (m->eq_weapon == NULL)
1907 pick_up = TRUE;
1908 else
1909 {
1910 /* compare this weapon with the weapon the monster wields */
1911 if (m->eq_weapon == NULL || (weapon_damage(m->eq_weapon)
1912 < weapon_damage(it)))
1913 pick_up = TRUE;
1914 }
1915 }
1916
1917 if (pick_up)
1918 {
1919 /* The monster has picked up the item.
1920
1921 Determine if the item is a weapon.
1922 This has to be done before adding the item to the monster's
1923 inventory as the item might be destroyed after calling inv_add().
1924 (Stackable items get destroyed if an item of the kind exists
1925 in the target inventory!).
1926 */
1927 gboolean new_weapon = (it->type == IT_WEAPON);
1928
1929 if (monster_in_sight(m))
1930 {
1931 gchar *buf = item_describe(it, player_item_identified(nlarn->p, it),
1932 FALSE, FALSE);
1933
1934 log_add_entry(nlarn->log, "The %s picks up %s.",
1935 monster_get_name(m), buf);
1936 g_free(buf);
1937 }
1938
1939 inv_del_element(map_ilist_at(monster_map(m), m->pos), it);
1940 inv_add(&m->inv, it);
1941
1942 if (new_weapon)
1943 {
1944 /* find out if the new weapon is better than the old one */
1945 item *best = monster_weapon_select(m);
1946
1947 /* If the new item is a weapon, 'it' is still a valid pointer
1948 to the item picked up at this point as weapons are not
1949 stackable. */
1950 if (it == best)
1951 {
1952 monster_weapon_wield(m, best);
1953 }
1954 }
1955 /* finish this turn after picking up an item */
1956 return TRUE;
1957 } /* end if pick_up */
1958 } /* end foreach item */
1959
1960 return FALSE;
1961 }
1962
monster_attack_count(monster * m)1963 guint monster_attack_count(monster *m)
1964 {
1965 guint count = 0;
1966
1967 while (count < G_N_ELEMENTS(monster_data[m->type].attacks)
1968 && monster_data[m->type].attacks[count].type != ATT_NONE)
1969 {
1970 count++;
1971 }
1972
1973 return count;
1974 }
1975
monster_attack(monster * m,guint num)1976 attack monster_attack(monster *m, guint num)
1977 {
1978 g_assert (m != NULL && num <= monster_attack_count(m));
1979
1980 return monster_data[m->type].attacks[num - 1];
1981 }
1982
monster_breath_attack(monster * m,player * p,attack att)1983 static int monster_breath_attack(monster *m, player *p, attack att)
1984 {
1985 g_assert(att.type == ATT_BREATH);
1986
1987 /* generate damage */
1988 damage *dam = damage_new(att.damage, att.type, att.base + game_difficulty(nlarn),
1989 DAMO_MONSTER, m);
1990
1991 /* the attack might have a random amount */
1992 if (att.rand > 0)
1993 dam->amount += rand_0n(att.rand);
1994
1995 if (monster_in_sight(m))
1996 {
1997 log_add_entry(nlarn->log, "The %s breathes a %s!", monster_get_name(m),
1998 monster_breath_data[att.damage].desc);
1999 }
2000 else
2001 {
2002 log_add_entry(nlarn->log, "A %s spews forth from nowhere!",
2003 monster_breath_data[att.damage].desc);
2004 }
2005
2006 /* handle the breath */
2007 map_trajectory(m->pos, p->pos, &(dam->dam_origin),
2008 monster_breath_hit, dam, NULL, TRUE,
2009 monster_breath_data[att.damage].glyph,
2010 monster_breath_data[att.damage].colour, TRUE);
2011
2012 /* the damage is copied in monster_breath_hit(), thus destroy the
2013 original damage here */
2014 damage_free(dam);
2015
2016 return FALSE;
2017 }
2018
modified_attack_amount(int amount,int damage_type)2019 static int modified_attack_amount(int amount, int damage_type)
2020 {
2021 if (damage_type == DAM_POISON)
2022 return amount + (game_difficulty(nlarn) + 1)/2;
2023
2024 return amount + game_difficulty(nlarn)/2;
2025 }
2026
monster_player_attack(monster * m,player * p)2027 void monster_player_attack(monster *m, player *p)
2028 {
2029 g_assert(m != NULL && p != NULL);
2030
2031 map *mmap = game_map(nlarn, Z(m->pos));
2032
2033 /* the player is invisible and the monster bashes into thin air */
2034 if (!pos_identical(m->player_pos, p->pos))
2035 {
2036 if (!map_is_monster_at(mmap, p->pos) && monster_in_sight(m))
2037 {
2038 log_add_entry(nlarn->log, "The %s bashes into thin air.",
2039 monster_get_name(m));
2040 }
2041
2042 m->lastseen++;
2043
2044 return;
2045 }
2046
2047 /* player is invisible and monster tries to hit player */
2048 if (player_effect(p, ET_INVISIBILITY) && !(monster_flags(m, INFRAVISION)
2049 || monster_effect(m, ET_INFRAVISION))
2050 && chance(65))
2051 {
2052 if (monster_in_sight(m))
2053 {
2054 log_add_entry(nlarn->log, "The %s misses wildly.",
2055 monster_get_name(m));
2056 }
2057 return;
2058 }
2059
2060 /* choose a random attack type */
2061 attack att = monster_attack(m, rand_1n(monster_attack_count(m) + 1));
2062
2063 /* No attack has been found. Return to calling function. */
2064 if (att.type == ATT_NONE) return;
2065
2066 /* handle breath attacks separately */
2067 if (att.type == ATT_BREATH)
2068 {
2069 monster_breath_attack(m, p, att);
2070 return;
2071 }
2072
2073 /* generate damage */
2074 damage *dam = damage_new(att.damage, att.type,
2075 modified_attack_amount(att.base, att.damage),
2076 DAMO_MONSTER, m);
2077
2078 /* deal with random damage (spirit naga) */
2079 if (dam->type == DAM_RANDOM)
2080 dam->type = rand_1n(DAM_MAX);
2081
2082 if (dam->type == DAM_DEC_RND)
2083 dam->type = rand_m_n(DAM_DEC_CON, DAM_DEC_RND);
2084
2085 /* set damage for weapon attacks */
2086 if (att.type == ATT_WEAPON)
2087 {
2088 /* make monster size affect weapon damage */
2089 /* FIXME: handle the vorpal blade */
2090 dam->amount = (m->eq_weapon != NULL) ? weapon_damage(m->eq_weapon) : 1
2091 + (int)rand_0n(game_difficulty(nlarn) + 2)
2092 + monster_level(m)
2093 + 2 * ((monster_size(m) - MEDIUM)) / 25;
2094 }
2095 else if (dam->type == DAM_PHYSICAL)
2096 {
2097 /* increase damage with difficulty */
2098 dam->amount = att.base
2099 + monster_level(m)
2100 + rand_0n(game_difficulty(nlarn) + 2);
2101 }
2102
2103 /* add variable damage */
2104 if (att.rand) dam->amount += rand_1n(att.rand);
2105
2106 /* half damage if player is protected against spirits */
2107 if (player_effect(p, ET_SPIRIT_PROTECTION) && monster_flags(m, SPIRIT))
2108 {
2109 if (dam->type == DAM_PHYSICAL)
2110 {
2111 /* halve physical damage */
2112 dam->amount >>= 1;
2113 }
2114 else
2115 {
2116 /* FIXME: give log message */
2117 damage_free(dam);
2118
2119 return;
2120 }
2121 }
2122
2123 /* handle some damage types here */
2124 switch (dam->type)
2125 {
2126 case DAM_STEAL_GOLD:
2127 case DAM_STEAL_ITEM:
2128 if (monster_player_rob(m, p, (dam->type == DAM_STEAL_GOLD) ? IT_GOLD : IT_ALL))
2129 {
2130 /* teleport away */
2131 monster_pos_set(m, mmap, map_find_space(mmap, LE_MONSTER, FALSE));
2132 }
2133
2134 damage_free(dam);
2135 break;
2136
2137 case DAM_RUST:
2138 log_add_entry(nlarn->log, "The %s %s you.", monster_get_name(m),
2139 monster_attack_verb[att.type]);
2140
2141 monster_item_rust(m, p);
2142 p->attacked = TRUE;
2143 damage_free(dam);
2144 break;
2145
2146 case DAM_REM_ENCH:
2147 monster_item_disenchant(m, p);
2148 p->attacked = TRUE;
2149 damage_free(dam);
2150 break;
2151
2152 default:
2153 if (att.type != ATT_GAZE || !player_effect(p, ET_BLINDNESS))
2154 {
2155 /* log the attack */
2156 log_add_entry(nlarn->log, "The %s %s you.", monster_get_name(m),
2157 monster_attack_verb[att.type]);
2158 }
2159 /* 50% chance of reflecting adjacent gazes */
2160 if (att.type == ATT_GAZE && player_effect(p, ET_REFLECTION)
2161 && chance(50))
2162 {
2163 if (!player_effect(p, ET_BLINDNESS))
2164 log_add_entry(nlarn->log, "The gaze is reflected harmlessly.");
2165 }
2166 else
2167 player_damage_take(p, dam, PD_MONSTER, m->type);
2168 break;
2169 }
2170 }
2171
monster_player_ranged_attack(monster * m,player * p)2172 int monster_player_ranged_attack(monster *m, player *p)
2173 {
2174 g_assert(m != NULL && p != NULL);
2175
2176 /* choose a random attack type */
2177 attack att = monster_attack(m, rand_1n(monster_attack_count(m) + 1));
2178 if (att.type == ATT_GAZE && chance(att.base/3))
2179 {
2180 if (!player_effect(p, ET_BLINDNESS))
2181 {
2182 log_add_entry(nlarn->log, "The %s %s you.", monster_get_name(m),
2183 monster_attack_verb[att.type]);
2184 }
2185 if (player_effect(p, ET_REFLECTION))
2186 {
2187 if (!player_effect(p, ET_BLINDNESS))
2188 log_add_entry(nlarn->log, "The gaze is reflected harmlessly.");
2189 }
2190 else
2191 {
2192 damage *dam = damage_new(att.damage, att.type,
2193 att.base + game_difficulty(nlarn), DAMO_MONSTER, m);
2194
2195 player_damage_take(p, dam, PD_MONSTER, m->type);
2196 }
2197 return TRUE;
2198 }
2199 if (att.type != ATT_BREATH)
2200 return FALSE;
2201
2202 return monster_breath_attack(m, p, att);
2203 }
2204
monster_damage_take(monster * m,damage * dam)2205 monster *monster_damage_take(monster *m, damage *dam)
2206 {
2207 struct player *p = NULL;
2208
2209 g_assert(m != NULL && dam != NULL);
2210
2211 if (dam->dam_origin.ot == DAMO_PLAYER)
2212 p = (player *)dam->dam_origin.originator;
2213
2214 if (game_wizardmode(nlarn) && fov_get(nlarn->p->fv, m->pos))
2215 log_add_entry(nlarn->log, damage_to_str(dam));
2216
2217 int hp_orig = m->hp;
2218
2219 switch (dam->type)
2220 {
2221 case DAM_PHYSICAL:
2222 dam->amount -= monster_ac(m);
2223 if (dam->amount < 1 && monster_in_sight(m))
2224 {
2225 log_add_entry(nlarn->log, "The %s isn't hurt.",
2226 monster_get_name(m));
2227 }
2228 break;
2229
2230 case DAM_MAGICAL:
2231 if (monster_flags(m, RES_MAGIC))
2232 {
2233 dam->amount /= monster_level(m);
2234 if (monster_in_sight(m))
2235 {
2236 log_add_entry(nlarn->log, "The %s %sresists the magic.",
2237 monster_get_name(m), dam->amount > 0 ? "partly " : "");
2238 }
2239 }
2240 break;
2241
2242 case DAM_FIRE:
2243 if (monster_flags(m, RES_FIRE))
2244 {
2245 /*
2246 * The monster's fire resistance reduces the damage taken
2247 * by 10% per monster level
2248 */
2249 dam->amount -= (guint)(((float)dam->amount / 100) *
2250 /* prevent uint wrap around for monsters with level > 10 */
2251 (min(monster_level(m), 10) * 10));
2252 if (monster_in_sight(m))
2253 {
2254 log_add_entry(nlarn->log, "The %s %sresists the flames.",
2255 monster_get_name(m), dam->amount > 0 ? "partly " : "");
2256 }
2257 }
2258 break;
2259
2260 case DAM_COLD:
2261 if (monster_flags(m, RES_COLD))
2262 {
2263 dam->amount = 0;
2264 if (monster_in_sight(m))
2265 {
2266 log_add_entry(nlarn->log, "The %s loves the cold!",
2267 monster_get_name(m));
2268 }
2269 }
2270 break;
2271
2272 case DAM_WATER:
2273 if (monster_flags(m, SWIM))
2274 dam->amount = 0;
2275 break;
2276
2277 case DAM_ELECTRICITY:
2278 if (monster_flags(m, RES_ELEC))
2279 {
2280 dam->amount = 0;
2281 if (monster_in_sight(m))
2282 {
2283 log_add_entry(nlarn->log, "The %s is not affected!",
2284 monster_get_name(m));
2285 }
2286 }
2287 /* double damage for flying monsters */
2288 else if (monster_flags(m, FLY) || monster_effect(m, ET_LEVITATION))
2289 {
2290 dam->amount *= 2;
2291 // special message?
2292 }
2293 break;
2294
2295 default:
2296 break;
2297 }
2298
2299 /* subtract damage from HP;
2300 * prevent adding to HP after resistance has lowered damage amount */
2301 m->hp -= max(0, dam->amount);
2302
2303 if (game_wizardmode(nlarn) && fov_get(nlarn->p->fv, m->pos))
2304 log_add_entry(nlarn->log, "[applied: %d]", hp_orig - m->hp);
2305
2306 if (m->hp < hp_orig)
2307 {
2308 /* monster has been hit */
2309 if (m->type == MT_METAMORPH)
2310 {
2311 /*
2312 * The metamorph transforms if HP is low.
2313 * Get the percentage of hitpoints the metamorph has left.
2314 * If this is less than 80%, the metamorph will turn into
2315 * another monster that will usually be more dangerous.
2316 */
2317 float relative_hp = (float)m->hp / (float)m->hp_max;
2318
2319 if ((m->hp > 0) && (relative_hp < 0.8))
2320 {
2321 char *wdesc = NULL;
2322 const char *old_name = monster_name(m);
2323 gboolean seen_old = monster_in_sight(m);
2324 m->type = MT_BRONZE_DRAGON + rand_0n(9);
2325 gboolean seen_new = monster_in_sight(m);
2326
2327 /* Determine the new maximum hitpoints for the new monster
2328 type and set the monster's current hit points to the
2329 relative value of the metamorphs remaining hit points. */
2330 if (relative_hp < 0.1) relative_hp = 0.1;
2331 m->hp_max = divert(monster_type_hp_max(m->type), 10);
2332 m->hp = (int)(m->hp_max * relative_hp);
2333
2334 /* Drop the weapon if the monster the metamorph turned
2335 into can not handle weapons. */
2336 if (m->eq_weapon && !monster_attack_available(m, ATT_WEAPON))
2337 {
2338 /* If the monster stepped on a trap p is NULL, thus we
2339 need to use nlarn->p here. */
2340 wdesc = item_describe(m->eq_weapon,
2341 player_item_known(nlarn->p, m->eq_weapon),
2342 FALSE, FALSE);
2343
2344 inv_del_element(&m->inv, m->eq_weapon);
2345 inv_add(map_ilist_at(monster_map(m), m->pos), m->eq_weapon);
2346 m->eq_weapon = NULL;
2347 }
2348
2349 if (p && (seen_old || seen_new))
2350 {
2351 if (seen_old && wdesc != NULL)
2352 {
2353 log_add_entry(nlarn->log, "The %s drops %s.",
2354 old_name, wdesc);
2355 }
2356
2357 if (seen_old && seen_new)
2358 {
2359 log_add_entry(nlarn->log, "The %s turns into a %s!",
2360 old_name, monster_get_name(m));
2361 }
2362 else if (seen_old)
2363 {
2364 log_add_entry(nlarn->log, "The %s vanishes!",
2365 old_name);
2366 }
2367 else
2368 {
2369 log_add_entry(nlarn->log, "A %s suddenly appears!",
2370 monster_get_name(m));
2371 }
2372 }
2373
2374 if (wdesc != NULL) g_free(wdesc);
2375 }
2376 }
2377 }
2378
2379 if (m->hp < 1)
2380 {
2381 /* monster dies */
2382 monster_die(m, p);
2383 m = NULL;
2384 }
2385
2386 g_free(dam);
2387
2388 return m;
2389 }
2390
monster_update_action(monster * m,monster_action_t override)2391 gboolean monster_update_action(monster *m, monster_action_t override)
2392 {
2393 monster_action_t naction; /* new action */
2394 guint mtime; /* max. number of turns a monster will look for the player */
2395 gboolean low_hp;
2396
2397 if (override > MA_NONE)
2398 {
2399 /* set the monster's action to the requested value */
2400 m->action = override;
2401
2402 /* if the monster is to be a servant, set its lifetime */
2403 if (override == MA_SERVE)
2404 {
2405 /* FIXME: it would be nice to have a variable amount of turns */
2406 m->number = 100;
2407 }
2408 return TRUE;
2409 }
2410
2411 /* handle some easy action updates before the more difficult decisions */
2412 switch (m->action)
2413 {
2414 case MA_SERVE: /* once servant, forever servant */
2415 case MA_CIVILIAN: /* town people never change their behaviour */
2416 case MA_CONFUSION: /* confusion is removed by monster_effect_del() */
2417 case MA_REMAIN: /* status set by hold monster/sleep/being trapped */
2418 return FALSE;
2419 break;
2420
2421 default:
2422 /* continue to evaluate... */
2423 break;
2424 }
2425
2426 mtime = monster_int(m) + 25 + (5 * game_difficulty(nlarn));
2427 low_hp = (m->hp < (monster_hp_max(m) / 4 ));
2428
2429 if (monster_flags(m, MIMIC) && m->unknown)
2430 {
2431 /* stationary monsters */
2432 naction = MA_REMAIN;
2433 }
2434 else if ((low_hp && (monster_int(m) > 4)) || monster_effect(m, ET_SCARED))
2435 {
2436 /* low HP or very scared => FLEE from player */
2437 naction = MA_FLEE;
2438 }
2439 else if (m->lastseen && (m->lastseen < mtime))
2440 {
2441 /* after having spotted the player, aggressive monster will follow
2442 the player for a certain amount of time turns, afterwards loose
2443 interest. More peaceful monsters will do something else. */
2444 /* TODO: need to test for aggressiveness */
2445 naction = MA_ATTACK;
2446 }
2447 else
2448 {
2449 /* if no action could be found, return to original behaviour */
2450 naction = MA_WANDER;
2451 }
2452
2453 if (naction != m->action)
2454 {
2455 m->action = naction;
2456 return TRUE;
2457 }
2458
2459 return FALSE;
2460 }
2461
monster_update_player_pos(monster * m,position ppos)2462 void monster_update_player_pos(monster *m, position ppos)
2463 {
2464 g_assert (m != NULL);
2465
2466 m->player_pos = ppos;
2467 m->lastseen = 1;
2468 }
2469
monster_regenerate(monster * m,time_t gtime,int difficulty)2470 gboolean monster_regenerate(monster *m, time_t gtime, int difficulty)
2471 {
2472 /* number of turns between occasions */
2473 int frequency;
2474
2475 /* temporary var for effect */
2476 effect *e;
2477
2478 g_assert(m != NULL);
2479
2480 /* modify frequency by difficulty: more regeneration, less poison */
2481 frequency = difficulty << 1;
2482
2483 /* handle regeneration */
2484 if (monster_flags(m, REGENERATE) && (m->hp < monster_hp_max(m)))
2485 {
2486 /* regenerate every (10 - difficulty) turns */
2487 if (gtime % (10 - difficulty) == 0)
2488 m->hp++;
2489 }
2490
2491 /* handle poison */
2492 if ((e = monster_effect_get(m, ET_POISON)))
2493 {
2494 if ((gtime - e->start) % (22 + frequency) == 0)
2495 {
2496 m->hp -= e->amount;
2497 }
2498
2499 if (m->hp < 1)
2500 {
2501 /* monster died from poison */
2502 monster_die(m, NULL);
2503 return FALSE;
2504 }
2505 }
2506
2507 return TRUE;
2508 }
2509
get_mimic_item(monster * m)2510 item *get_mimic_item(monster *m)
2511 {
2512 g_assert(m && monster_flags(m, MIMIC));
2513
2514 /* polymorphed mimics may not pose as items */
2515 if (inv_length(m->inv) > 0)
2516 return inv_get(m->inv, 0);
2517
2518 return NULL;
2519 }
2520
monster_desc(monster * m)2521 char *monster_desc(monster *m)
2522 {
2523 int hp_rel;
2524 GString *desc;
2525 const char *injury = NULL;
2526
2527 g_assert (m != NULL);
2528
2529 desc = g_string_new(NULL);
2530
2531 /* describe mimic as mimicked item */
2532 if (monster_unknown(m) && inv_length(m->inv) > 0)
2533 {
2534 item *it = get_mimic_item(m);
2535 gchar *item_desc= item_describe(it, player_item_known(nlarn->p, it),
2536 FALSE, FALSE);
2537
2538 g_string_append_printf(desc, "You see %s there", item_desc);
2539 g_free(item_desc);
2540
2541 return g_string_free(desc, FALSE);
2542 }
2543
2544 hp_rel = (((float)m->hp / (float)monster_hp_max(m)) * 100);
2545
2546 /* prepare health status description */
2547 if (m->hp == monster_hp_max(m))
2548 injury = "uninjured";
2549 else if (hp_rel > 80)
2550 injury = "slightly injured";
2551 else if (hp_rel > 20)
2552 injury = "injured";
2553 else if (hp_rel > 10)
2554 injury = "heavily injured";
2555 else
2556 injury = "critically injured";
2557
2558 g_string_append_printf(desc, "%s %s, %s %s", a_an(injury), injury,
2559 monster_ai_desc[m->action], monster_get_name(m));
2560
2561 if (game_wizardmode(nlarn))
2562 {
2563 /* show monster's hp and max hp in wizard mode */
2564 g_string_append_printf(desc, " %s(%d/%d hp)",
2565 m->leader ? "(pack member) " : "",
2566 m->hp, m->hp_max);
2567 }
2568
2569 if (m->eq_weapon != NULL)
2570 {
2571 /* describe the weapon the monster wields */
2572 gchar *weapon_desc = item_describe(m->eq_weapon,
2573 player_item_known(nlarn->p, m->eq_weapon),
2574 TRUE, FALSE);
2575
2576 g_string_append_printf(desc, ", armed with %s", weapon_desc);
2577 g_free(weapon_desc);
2578 }
2579
2580 /* add effect description */
2581 if (m->effects->len > 0)
2582 {
2583 char **desc_list = strv_new();
2584
2585 for (guint i = 0; i < m->effects->len; i++)
2586 {
2587 effect *e = game_effect_get(nlarn, g_ptr_array_index(m->effects, i));
2588
2589 if (effect_get_desc(e))
2590 {
2591 strv_append_unique(&desc_list, effect_get_desc(e));
2592 }
2593 }
2594
2595 char *effects = g_strjoinv(", ", desc_list);
2596 g_strfreev(desc_list);
2597
2598 g_string_append_printf(desc, " (%s)", effects);
2599
2600 g_free(effects);
2601 }
2602
2603 return g_string_free(desc, FALSE);
2604 }
2605
monster_glyph(monster * m)2606 char monster_glyph(monster *m)
2607 {
2608 g_assert (m != NULL);
2609
2610 if (m->unknown && inv_length(m->inv) > 0)
2611 {
2612 item *it = inv_get(m->inv, 0);
2613 return item_glyph(it->type);
2614 }
2615 else
2616 {
2617 return monster_data[m->type].glyph;
2618 }
2619 }
2620
monster_color(monster * m)2621 int monster_color(monster *m)
2622 {
2623 g_assert (m != NULL);
2624
2625 if (m->unknown && inv_length(m->inv) > 0)
2626 {
2627 item *it = inv_get(m->inv, 0);
2628 return item_colour(it);
2629 }
2630 else
2631 {
2632 return monster_data[m->type].colour;
2633 }
2634 }
2635
monster_genocide(monster_t monster_id)2636 void monster_genocide(monster_t monster_id)
2637 {
2638 GList *mlist;
2639
2640 g_assert(monster_id < MT_MAX);
2641
2642 nlarn->monster_genocided[monster_id] = TRUE;
2643 mlist = g_hash_table_get_values(nlarn->monsters);
2644
2645 /* purge genocided monsters */
2646 for (GList *iter = mlist; iter != NULL; iter = iter->next)
2647 {
2648 monster *monst = (monster *)iter->data;
2649 if (monster_is_genocided(monst->type))
2650 {
2651 /* add the monster to the game's list of dead monsters */
2652 g_ptr_array_add(nlarn->dead_monsters, monst);
2653 }
2654 }
2655
2656 /* free the memory returned by g_hash_table_get_values() */
2657 g_list_free(mlist);
2658
2659 /* destroy all monsters that have been genocided */
2660 game_remove_dead_monsters(nlarn);
2661 }
2662
monster_is_genocided(monster_t monster_id)2663 int monster_is_genocided(monster_t monster_id)
2664 {
2665 g_assert(monster_id < MT_MAX);
2666 return nlarn->monster_genocided[monster_id];
2667 }
2668
monster_effect_add(monster * m,effect * e)2669 effect *monster_effect_add(monster *m, effect *e)
2670 {
2671 g_assert(m != NULL && e != NULL);
2672 gboolean vis_effect = FALSE;
2673
2674 if (e->type == ET_SLEEP && monster_flags(m, RES_SLEEP))
2675 {
2676 /* the monster is resistant to sleep */
2677 effect_destroy(e);
2678 e = NULL;
2679 }
2680 else if (e->type == ET_POISON && monster_flags(m, RES_POISON))
2681 {
2682 /* the monster is poison resistant */
2683 effect_destroy(e);
2684 e = NULL;
2685 }
2686 else if (e->type == ET_LEVITATION && monster_flags(m, FLY))
2687 {
2688 /* levitation has no effect on flying monsters */
2689 effect_destroy(e);
2690 e = NULL;
2691 }
2692 else if (e->type == ET_CONFUSION && monster_flags(m, RES_CONF))
2693 {
2694 /* the monster is resistant against confusion */
2695 effect_destroy(e);
2696 e = NULL;
2697 }
2698
2699 /* one time effects */
2700 if (e && e->turns == 1)
2701 {
2702 switch (e->type)
2703 {
2704 case ET_INC_HP:
2705 {
2706 int hp_orig = m->hp;
2707 m->hp += min ((m->hp_max * e->amount) / 100, m->hp_max);
2708
2709 if (m->hp > hp_orig)
2710 vis_effect = TRUE;
2711 }
2712
2713 break;
2714
2715 case ET_MAX_HP:
2716 if (m->hp < m->hp_max)
2717 {
2718 m->hp = m->hp_max;
2719 vis_effect = TRUE;
2720 }
2721 break;
2722
2723 default:
2724 /* nothing happens.. */
2725 break;
2726 }
2727 }
2728 else if (e)
2729 {
2730 /* multi-turn effects */
2731 e = effect_add(m->effects, e);
2732
2733 /* if it's confusion, set the monster's "AI" accordingly */
2734 if (e && e->type == ET_CONFUSION) {
2735 monster_update_action(m, MA_CONFUSION);
2736 }
2737
2738 /* charm monster turns monsters into servants */
2739 if (e && e->type == ET_CHARM_MONSTER) {
2740 monster_update_action(m, MA_SERVE);
2741 }
2742
2743 /* no action if monster is held or sleeping */
2744 if (e && (e->type == ET_HOLD_MONSTER || e->type == ET_SLEEP || e->type == ET_TRAPPED))
2745 {
2746 monster_update_action(m, MA_REMAIN);
2747 }
2748 }
2749
2750 /* show message if monster is visible */
2751 if (e && monster_in_sight(m)
2752 && effect_get_msg_m_start(e)
2753 && (e->turns > 0 || vis_effect))
2754 {
2755 log_add_entry(nlarn->log, effect_get_msg_m_start(e),
2756 monster_get_name(m));
2757 }
2758
2759 /* clean up one-time effects */
2760 if (e && e->turns == 1)
2761 {
2762 effect_destroy(e);
2763 e = NULL;
2764 }
2765
2766 return e;
2767 }
2768
monster_effect_del(monster * m,effect * e)2769 int monster_effect_del(monster *m, effect *e)
2770 {
2771 int result;
2772
2773 g_assert(m != NULL && e != NULL);
2774
2775 /* log info if the player can see the monster */
2776 if (monster_in_sight(m) && effect_get_msg_m_stop(e))
2777 {
2778 log_add_entry(nlarn->log, effect_get_msg_m_stop(e), monster_get_name(m));
2779 }
2780
2781 if ((result = effect_del(m->effects, e)))
2782 {
2783 /* if confusion or charm is finished, set the AI back to the default */
2784 if (e->type == ET_CONFUSION || e->type == ET_CHARM_MONSTER) {
2785 monster_update_action(m, monster_default_ai(m));
2786 }
2787
2788 /* end of holding, sleeping or being trapped */
2789 if (e->type == ET_HOLD_MONSTER || e->type == ET_SLEEP || e->type == ET_TRAPPED)
2790 {
2791 /* only reset the AI when no other "idling" effect is left */
2792 if (!monster_effect(m, ET_HOLD_MONSTER)
2793 && !monster_effect(m, ET_SLEEP)
2794 && !monster_effect(m, ET_TRAPPED))
2795 {
2796 monster_update_action(m, monster_default_ai(m));
2797 }
2798 }
2799
2800 effect_destroy(e);
2801 }
2802
2803 return result;
2804 }
2805
monster_effect_get(monster * m,effect_t type)2806 effect *monster_effect_get(monster *m , effect_t type)
2807 {
2808 g_assert(m != NULL && type < ET_MAX);
2809 return effect_get(m->effects, type);
2810 }
2811
monster_effect(monster * m,effect_t type)2812 int monster_effect(monster *m, effect_t type)
2813 {
2814 g_assert(m != NULL && type < ET_MAX);
2815 return effect_query(m->effects, type);
2816 }
2817
monster_effects_expire(monster * m)2818 void monster_effects_expire(monster *m)
2819 {
2820 guint idx = 0;
2821
2822 g_assert(m != NULL);
2823
2824 while (idx < m->effects->len)
2825 {
2826 gpointer effect_id = g_ptr_array_index(m->effects, idx);;
2827 effect *e = game_effect_get(nlarn, effect_id);
2828
2829 if (e->type == ET_TRAPPED)
2830 {
2831 /* if the monster is incapable of movement don't decrease
2832 trapped counter */
2833 if (monster_effect(m, ET_HOLD_MONSTER)
2834 || monster_effect(m, ET_SLEEP))
2835 {
2836 idx++;
2837 }
2838 }
2839
2840 if (effect_expire(e) == -1)
2841 {
2842 /* effect has expired */
2843 monster_effect_del(m, e);
2844 }
2845 else
2846 {
2847 idx++;
2848 }
2849 }
2850 }
2851
monster_default_ai(monster * m)2852 static inline monster_action_t monster_default_ai(monster *m)
2853 {
2854 return monster_data[m->type].default_ai;
2855 }
2856
monster_player_visible(monster * m)2857 static gboolean monster_player_visible(monster *m)
2858 {
2859 /* monster is blinded */
2860 if (monster_effect(m, ET_BLINDNESS))
2861 return FALSE;
2862
2863 /* FIXME: this ought to be different per monster type */
2864 int monster_visrange = 7;
2865
2866 if (player_effect(nlarn->p, ET_STEALTH))
2867 {
2868 /* if the player is stealthy monsters will only recognize him when
2869 standing next to him */
2870 monster_visrange = 1;
2871 }
2872 else if (monster_effect(m, ET_TRAPPED))
2873 monster_visrange = 2;
2874
2875 /* determine if the monster can see the player */
2876 if (pos_distance(monster_pos(m), nlarn->p->pos) > monster_visrange)
2877 return FALSE;
2878
2879 /* check if the player is invisible and if the monster has infravision */
2880 if (player_effect(nlarn->p, ET_INVISIBILITY)
2881 && !(monster_flags(m, INFRAVISION) || monster_effect(m, ET_INFRAVISION)))
2882 return FALSE;
2883
2884 /* determine if player's position is visible from monster's position */
2885 return map_pos_is_visible(monster_map(m), m->pos, nlarn->p->pos);
2886 }
2887
monster_attack_available(monster * m,attack_t type)2888 static gboolean monster_attack_available(monster *m, attack_t type)
2889 {
2890 gboolean available = FALSE;
2891 int pos = 1;
2892 int c = monster_attack_count(m);
2893
2894 while (pos <= c)
2895 {
2896 attack att = monster_attack(m, pos);
2897
2898 if (att.type == type)
2899 {
2900 available = TRUE;
2901 break;
2902 }
2903
2904 pos++;
2905 }
2906
2907 return available;
2908 }
2909
monster_weapon_select(monster * m)2910 static item *monster_weapon_select(monster *m)
2911 {
2912 item *best = NULL;
2913
2914 for (guint idx = 0; idx < inv_length(m->inv); idx++)
2915 {
2916 item *curr = inv_get(m->inv, idx);
2917
2918 if (curr->type == IT_WEAPON)
2919 {
2920 if (best == NULL)
2921 {
2922 best = curr;
2923 }
2924 else if (weapon_damage(curr) > weapon_damage(best))
2925 {
2926 best = curr;
2927 }
2928 }
2929 }
2930
2931 return best;
2932 }
2933
monster_weapon_wield(monster * m,item * weapon)2934 static void monster_weapon_wield(monster *m, item *weapon)
2935 {
2936 /* FIXME: time management */
2937 /* FIXME: weapon effects */
2938 m->eq_weapon = weapon;
2939
2940 /* show message if monster is visible */
2941 if (monster_in_sight(m))
2942 {
2943 gchar *buf = item_describe(weapon, player_item_identified(nlarn->p,
2944 weapon), TRUE, FALSE);
2945
2946 log_add_entry(nlarn->log, "The %s wields %s.", monster_get_name(m), buf);
2947 g_free(buf);
2948 }
2949 }
2950
monster_item_disenchant(monster * m,struct player * p)2951 static gboolean monster_item_disenchant(monster *m, struct player *p)
2952 {
2953 item *it;
2954
2955 g_assert (m != NULL && p != NULL);
2956
2957 /* disenchant random item */
2958 if (!inv_length(p->inventory))
2959 {
2960 /* empty inventory */
2961 return FALSE;
2962 }
2963
2964 it = inv_get(p->inventory, rand_0n(inv_length(p->inventory)));
2965
2966 /* log the attack */
2967 log_add_entry(nlarn->log, "The %s hits you.",
2968 monster_get_name(m));
2969
2970 /* Don't destroy the potion of cure dianthroritis. */
2971 if (it->type == IT_POTION && it->id == PO_CURE_DIANTHR)
2972 return (inv_length(p->inventory) > 1);
2973
2974 // Blessed items have a 50% chance of resisting the disenchantment.
2975 if (it->blessed && chance(50))
2976 {
2977 gchar *desc = item_describe(it, player_item_known(nlarn->p, it),
2978 (it->count == 1), TRUE);
2979
2980 desc[0] = g_ascii_toupper(desc[0]);
2981 log_add_entry(nlarn->log, "%s resist%s the attack.",
2982 desc, (it->count == 1) ? "s" : "");
2983
2984 it->blessed_known = TRUE;
2985 g_free(desc);
2986 return TRUE;
2987 }
2988 log_add_entry(nlarn->log, "You feel a sense of loss.");
2989
2990 if (item_is_optimizable(it->type))
2991 {
2992 item_disenchant(it);
2993 }
2994 else
2995 {
2996 player_item_destroy(p, it);
2997 }
2998
2999 return TRUE;
3000 }
3001
3002 /**
3003 * Special monster attack: rust players armour.
3004 *
3005 * @param the attacking monster
3006 * @param the player
3007 *
3008 */
monster_item_rust(monster * m,struct player * p)3009 static gboolean monster_item_rust(monster *m __attribute__((unused)), struct player *p)
3010 {
3011 item **it;
3012
3013 g_assert(m != NULL && p != NULL);
3014
3015 /* get a random piece of armour to damage */
3016 if ((it = player_get_random_armour(p, FALSE)))
3017 {
3018 *it = item_erode(&p->inventory, *it, IET_RUST, TRUE);
3019 return TRUE;
3020 }
3021 else
3022 {
3023 log_add_entry(nlarn->log, "Nothing happens.");
3024 return FALSE;
3025 }
3026 }
3027
monster_player_rob(monster * m,struct player * p,item_t item_type)3028 static gboolean monster_player_rob(monster *m, struct player *p, item_t item_type)
3029 {
3030 item *it = NULL;
3031
3032 g_assert (m != NULL && p != NULL);
3033
3034 /* if player has a device of no theft abort the theft */
3035 if (player_effect(p, ET_NOTHEFT))
3036 return FALSE;
3037
3038 /* Leprechaun robs only gold */
3039 if (item_type == IT_GOLD)
3040 {
3041 /* get amount of gold pieces carried by the player */
3042 guint player_gold = player_get_gold(p);
3043
3044 if (player_gold > 0)
3045 {
3046 /* steel gold carried by the player */
3047 it = item_new(IT_GOLD, rand_1n(1 + (player_gold >> 1)));
3048 player_remove_gold(p, it->count);
3049
3050 log_add_entry(nlarn->log, "The %s picks your pocket. " \
3051 "Your purse feels lighter.", monster_get_name(m));
3052 }
3053 else
3054 {
3055 /* grab gold at player's position */
3056 inventory **floor = map_ilist_at(monster_map(m), p->pos);
3057 if (inv_length_filtered(*floor, item_filter_gold))
3058 {
3059 it = inv_get_filtered(*floor, 0, item_filter_gold);
3060 inv_del_element(floor, it);
3061
3062 if (monster_in_sight(m))
3063 {
3064 log_add_entry(nlarn->log,
3065 "The %s picks up some gold at your feet.",
3066 monster_get_name(m));
3067 }
3068 }
3069 }
3070 }
3071 else if (item_type == IT_ALL) /* must be the nymph */
3072 {
3073 if (inv_length(p->inventory))
3074 {
3075 gboolean was_equipped = FALSE;
3076
3077 it = inv_get(p->inventory, rand_0n(inv_length(p->inventory)));
3078 gchar *buf = item_describe(it, player_item_known(p, it), FALSE, FALSE);
3079
3080 if ((was_equipped = player_item_is_equipped(p, it)))
3081 {
3082 if (it->cursed)
3083 {
3084 /* cursed items can't be stolen.. */
3085 log_add_entry(nlarn->log, "The %s tries to steal %s but fails.",
3086 monster_get_name(m), buf);
3087
3088 it->blessed_known = TRUE;
3089 g_free(buf);
3090
3091 /* return true as there are things to steal */
3092 return TRUE;
3093 }
3094
3095 player_item_unequip(p, NULL, it, TRUE);
3096 }
3097
3098 if (it->count > 1)
3099 {
3100 /* the player has multiple items. Steal only one. */
3101 it = item_split(it, rand_1n(it->count));
3102 g_free(buf);
3103 buf = item_describe(it, player_item_known(p, it), FALSE, FALSE);
3104 }
3105 else
3106 {
3107 /* this item's count is one. Steal exactly this one. */
3108 inv_del_element(&p->inventory, it);
3109 }
3110
3111 if (was_equipped)
3112 {
3113 log_add_entry(nlarn->log, "The %s nimbly removes %s and steals it.",
3114 monster_get_name(m), buf);
3115 }
3116 else
3117 {
3118 log_add_entry(nlarn->log, "The %s picks your pocket and steals %s.",
3119 monster_get_name(m), buf);
3120 }
3121
3122 g_free(buf);
3123 }
3124 }
3125
3126 /* if item / gold has been stolen, add it to the monster's inventory */
3127 if (it)
3128 {
3129 inv_add(&m->inv, it);
3130 return TRUE;
3131 }
3132 else
3133 {
3134 log_add_entry(nlarn->log, "The %s couldn't find anything to steal.",
3135 monster_get_name(m));
3136
3137 return FALSE;
3138 }
3139 }
3140
monster_get_fortune(const char * fortune_file)3141 static char *monster_get_fortune(const char *fortune_file)
3142 {
3143 /* array of pointers to fortunes */
3144 static GPtrArray *fortunes = NULL;
3145
3146 if (!fortunes)
3147 {
3148 /* read in the fortunes */
3149 char buffer[80];
3150 FILE *fortune_fd;
3151
3152 /* open the file */
3153 fortune_fd = fopen(fortune_file, "r");
3154 if (fortune_fd == NULL)
3155 {
3156 /* can't find file */
3157 return "Help me! I can't find the fortune file!";
3158 }
3159
3160 fortunes = g_ptr_array_new();
3161
3162 /* read in the entire fortune file */
3163 while((fgets(buffer, 79, fortune_fd)))
3164 {
3165 /* replace EOL with \0 */
3166 size_t len = (size_t)(strchr(buffer, '\n') - (char *)&buffer);
3167 buffer[len] = '\0';
3168
3169 /* keep the line */
3170 char *tmp = g_malloc((len + 1) * sizeof(char));
3171 memcpy(tmp, &buffer, (len + 1));
3172 g_ptr_array_add(fortunes, tmp);
3173 }
3174
3175 fclose(fortune_fd);
3176 }
3177
3178 return g_ptr_array_index(fortunes, rand_0n(fortunes->len));
3179 }
3180
monster_find_next_pos_to(monster * m,position dest)3181 static position monster_find_next_pos_to(monster *m, position dest)
3182 {
3183 g_assert(m != NULL);
3184 g_assert(pos_valid(dest));
3185
3186 /* next position */
3187 position npos = monster_pos(m);
3188
3189 /* find the next step in the direction of dest */
3190 path *path = path_find(monster_map(m), monster_pos(m), dest,
3191 monster_map_element(m));
3192
3193 if (path && !g_queue_is_empty(path->path))
3194 {
3195 path_element *el = g_queue_pop_head(path->path);
3196 npos = el->pos;
3197 }
3198
3199 /* clean up */
3200 if (path) path_destroy(path);
3201
3202 return npos;
3203 }
3204
monster_move_wander(monster * m,struct player * p)3205 static position monster_move_wander(monster *m, struct player *p __attribute__((unused)))
3206 {
3207 int tries = 0;
3208 position npos;
3209
3210 if (m->leader)
3211 {
3212 /* monster is part of a pack */
3213 monster *leader = game_monster_get(nlarn, m->leader);
3214
3215 if (!leader)
3216 {
3217 /* is seems that the leader was killed.
3218 From now on, wander aimlessly */
3219 m->leader = NULL;
3220 } else {
3221 /* stay close to the pack leader */
3222 if (pos_distance(monster_pos(m), monster_pos(leader)) > 4)
3223 return monster_find_next_pos_to(m, monster_pos(leader));
3224 }
3225 }
3226
3227 do
3228 {
3229 npos = pos_move(m->pos, rand_1n(GD_MAX));
3230 tries++;
3231 }
3232 while (tries < GD_MAX
3233 && !map_pos_validate(monster_map(m), npos, monster_map_element(m),
3234 FALSE));
3235
3236 /* new position has not been found, reset to current position */
3237 if (tries == GD_MAX) npos = monster_pos(m);
3238
3239 return npos;
3240 }
3241
monster_move_attack(monster * m,struct player * p)3242 static position monster_move_attack(monster *m, struct player *p)
3243 {
3244 position npos = monster_pos(m);
3245
3246 /* monster is standing next to player */
3247 if (pos_adjacent(monster_pos(m), m->player_pos) && (m->lastseen == 1))
3248 {
3249 monster_player_attack(m, p);
3250
3251 /* monster's position might have changed (teleport) */
3252 if (!pos_identical(npos, monster_pos(m)))
3253 log_add_entry(nlarn->log, "The %s vanishes.", monster_name(m));
3254
3255 return monster_pos(m);
3256 }
3257
3258 /* monster is standing on a map exit and the player has left the map */
3259 if (pos_identical(monster_pos(m), m->player_pos)
3260 && map_is_exit_at(monster_map(m), monster_pos(m)))
3261 {
3262 int newmap;
3263
3264 switch (map_sobject_at(monster_map(m), monster_pos(m)))
3265 {
3266 case LS_STAIRSDOWN:
3267 case LS_CAVERNS_ENTRY:
3268 newmap = Z(m->pos) + 1;
3269 break;
3270
3271 case LS_STAIRSUP:
3272 case LS_CAVERNS_EXIT:
3273 newmap = Z(m->pos) - 1;
3274 break;
3275
3276 case LS_ELEVATORDOWN:
3277 /* move into the volcano from the town */
3278 newmap = MAP_CMAX + 1;
3279 break;
3280
3281 case LS_ELEVATORUP:
3282 /* volcano monster enters the town */
3283 newmap = 0;
3284 break;
3285
3286 default:
3287 newmap = Z(m->pos);
3288 break;
3289 }
3290
3291 /* change the map */
3292 monster_level_enter(m, game_map(nlarn, newmap));
3293
3294 return monster_pos(m);
3295 }
3296
3297 /* monster heads into the direction of the player. */
3298 npos = monster_find_next_pos_to(m, m->player_pos);
3299
3300 /* No path found. Stop following player */
3301 if (!pos_valid(npos)) m->lastseen = 0;
3302
3303 return npos;
3304 }
3305
monster_move_confused(monster * m,struct player * p)3306 static position monster_move_confused(monster *m,
3307 __attribute__ ((unused)) struct player *p)
3308 {
3309 /* as the monster is confused, choose a random movement direction */
3310 return pos_move(monster_pos(m), rand_1n(GD_MAX));
3311 }
3312
monster_move_flee(monster * m,struct player * p)3313 static position monster_move_flee(monster *m, struct player *p)
3314 {
3315 int dist = 0;
3316 position npos_tmp;
3317 position npos = monster_pos(m);
3318
3319 for (int tries = 1; tries < GD_MAX; tries++)
3320 {
3321 /* try all fields surrounding the monster if the
3322 * distance between monster & player is greater */
3323 if (tries == GD_CURR)
3324 continue;
3325
3326 npos_tmp = pos_move(monster_pos(m), tries);
3327
3328 if (map_pos_validate(monster_map(m), npos_tmp, monster_map_element(m),
3329 FALSE)
3330 && pos_distance(p->pos, npos_tmp) > dist)
3331 {
3332 /* distance is bigger than current distance */
3333 npos = npos_tmp;
3334 dist = pos_distance(m->player_pos, npos_tmp);
3335 }
3336 }
3337
3338 return npos;
3339 }
3340
monster_move_serve(monster * m,struct player * p)3341 static position monster_move_serve(monster *m, struct player *p)
3342 {
3343 /* If a new position cannot be found, keep the current position */
3344 position npos = m->pos;
3345
3346 /* generate a fov structure if not yet available */
3347 if (!m->fv)
3348 m->fv = fov_new();
3349
3350 /* calculate the monster's fov */
3351 /* the monster gets a fov radius of 6 for now*/
3352 fov_calculate(m->fv, monster_map(m), m->pos, 6,
3353 monster_flags(m, INFRAVISION)
3354 || monster_effect(m, ET_INFRAVISION));
3355
3356 /* a good servant always knows the masters position */
3357 if (pos_distance(monster_pos(m), p->pos) > 5)
3358 {
3359 /* if the distance to the player is too large, follow */
3360 npos = monster_find_next_pos_to(m, p->pos);
3361 }
3362 else
3363 {
3364 /* look for worthy foes */
3365 /* TODO: implement */
3366 }
3367
3368 return npos;
3369 }
3370
monster_move_civilian(monster * m,struct player * p)3371 static position monster_move_civilian(monster *m, struct player *p)
3372 {
3373 position npos = m->pos;
3374
3375 /* civilians will pick a random location on the map, travel and remain
3376 there for the number of turns that is determined by their town
3377 person number. */
3378
3379 if (!pos_valid(m->player_pos))
3380 {
3381 /* No target set -> find a new location to travel to.
3382 Civilians stay inside the town area. */
3383 rectangle town = rect_new(3, 4, MAP_MAX_X - 25, MAP_MAX_Y - 3);
3384 do
3385 {
3386 /* Ensure that the townsfolk do not loiter in locations
3387 important for the player. */
3388 m->player_pos = map_find_space_in(monster_map(m), town, LE_GROUND, FALSE);
3389 } while (map_sobject_at(monster_map(m), m->player_pos) != LS_NONE);
3390 }
3391
3392 if (pos_identical(m->pos, m->player_pos)
3393 && m->lastseen >= (m->number + 25))
3394 {
3395 /* The person has stayed at the target position for long
3396 enough, thus reset the target position. */
3397 m->player_pos = pos_invalid;
3398 }
3399 else if (pos_valid(m->player_pos) && !pos_identical(m->pos, m->player_pos))
3400 {
3401 /* travel to the selected location */
3402 npos = monster_find_next_pos_to(m, m->player_pos);
3403
3404 if (pos_identical(npos, m->player_pos))
3405 {
3406 /* arrived at target, thus reset the lastseen counter */
3407 m->lastseen = 1;
3408 }
3409 else if (pos_identical(npos, monster_pos(m)) || map_is_monster_at(monster_map(m), npos))
3410 {
3411 /* it seems there is no path to the target, or the destianation
3412 is blocked by another townsperson, thus get a new destination */
3413 m->player_pos = pos_invalid;
3414 }
3415 }
3416
3417 /* check if the player is next to the civilian and not inside a building */
3418 if (pos_adjacent(m->pos, p->pos) && chance(40)
3419 && so_is_transparent(map_sobject_at(monster_map(m), p->pos)))
3420 {
3421 /* talk */
3422 log_add_entry(nlarn->log, "The %s says, \"%s\"",
3423 monster_get_name(m),
3424 monster_get_fortune(nlarn_fortunes));
3425 }
3426
3427 /* change the town person's name from time to time */
3428 if (!fov_get(p->fv, m->pos) && chance(10))
3429 {
3430 m->number = rand_1n(40);
3431 }
3432
3433 return npos;
3434 }
3435
monster_is_carrying_item(monster * m,item_t type)3436 int monster_is_carrying_item(monster *m, item_t type)
3437 {
3438 inventory *inv = m->inv;
3439
3440 for (guint idx = 0; idx < inv_length(inv); idx++)
3441 {
3442 item *it = inv_get(inv, idx);
3443 if (it->type == type)
3444 return TRUE;
3445 }
3446 return FALSE;
3447 }
3448
monster_name(monster * m)3449 inline const char *monster_name(monster *m) {
3450 return monster_data[m->type].name;
3451 }
3452
monster_level(monster * m)3453 inline int monster_level(monster *m)
3454 {
3455 return monster_data[m->type].level;
3456 }
3457
monster_ac(monster * m)3458 inline int monster_ac(monster *m)
3459 {
3460 return monster_data[m->type].ac;
3461 }
3462
monster_int(monster * m)3463 inline guint monster_int(monster *m)
3464 {
3465 return monster_data[m->type].intelligence
3466 + monster_effect(m, ET_HEROISM)
3467 - monster_effect(m, ET_DIZZINESS);
3468 }
3469
monster_gold_chance(monster * m)3470 inline int monster_gold_chance(monster *m)
3471 {
3472 return monster_data[m->type].gold_chance;
3473 }
3474
monster_gold_amount(monster * m)3475 inline int monster_gold_amount(monster *m)
3476 {
3477 return monster_data[m->type].gold;
3478 }
3479
monster_exp(monster * m)3480 inline int monster_exp(monster *m)
3481 {
3482 return monster_data[m->type].exp;
3483 }
3484
monster_size(monster * m)3485 inline int monster_size(monster *m)
3486 {
3487 return monster_data[m->type].size;
3488 }
3489
monster_speed(monster * m)3490 inline int monster_speed(monster *m)
3491 {
3492 return monster_data[m->type].speed
3493 + monster_effect(m, ET_SPEED)
3494 + (monster_effect(m, ET_HEROISM) * 5)
3495 - monster_effect(m, ET_SLOWNESS)
3496 - (monster_effect(m, ET_DIZZINESS) * 5);
3497 }
3498
monster_flags(monster * m,monster_flag f)3499 inline int monster_flags(monster *m, monster_flag f)
3500 {
3501 return monster_data[m->type].flags & f;
3502 }
3503
monster_sound(monster * m)3504 inline const char *monster_sound(monster *m) {
3505 return monster_data[m->type].sound;
3506 }
3507
monster_type_hp_max(monster_t type)3508 inline int monster_type_hp_max(monster_t type)
3509 {
3510 return monster_data[type].hp_max;
3511 }
3512
monster_type_glyph(monster_t type)3513 inline char monster_type_glyph(monster_t type)
3514 {
3515 return monster_data[type].glyph;
3516 }
3517
monster_type_name(monster_t type)3518 inline const char *monster_type_name(monster_t type)
3519 {
3520 return monster_data[type].name;
3521 }
3522
monster_type_reroll_chance(monster_t type)3523 inline int monster_type_reroll_chance(monster_t type)
3524 {
3525 return monster_data[type].reroll_chance;
3526 }
3527
3528
monster_breath_hit(const GList * traj,const damage_originator * damo,gpointer data1,gpointer data2)3529 static gboolean monster_breath_hit(const GList *traj,
3530 const damage_originator *damo __attribute__((unused)),
3531 gpointer data1,
3532 gpointer data2 __attribute__((unused)))
3533 {
3534 monster *m;
3535 damage *dam = (damage *)data1;
3536 item_erosion_type iet;
3537 gboolean terminated = FALSE;
3538 position pos; pos_val(pos) = GPOINTER_TO_UINT(traj->data);
3539 map *mp = game_map(nlarn, Z(pos));
3540
3541 /* determine if items should be eroded */
3542 switch (dam->type)
3543 {
3544 case DAM_FIRE:
3545 iet = IET_BURN;
3546 break;
3547
3548 case DAM_ACID:
3549 iet = IET_CORRODE;
3550 break;
3551
3552 case DAM_WATER:
3553 iet = IET_RUST;
3554 break;
3555
3556 default:
3557 iet = IET_NONE;
3558 break;
3559 }
3560
3561 if ((m = map_get_monster_at(mp, pos)))
3562 {
3563 /* The breath hit a monster. */
3564 if (monster_in_sight(m))
3565 log_add_entry(nlarn->log, "The %s hits the %s.",
3566 monster_breath_data[dam->type].desc,
3567 monster_get_name(m));
3568
3569 /* erode the monster's inventory */
3570 if (iet && monster_inv(m))
3571 inv_erode(monster_inv(m), iet, FALSE, NULL);
3572
3573 monster_damage_take(m, damage_copy(dam));
3574
3575 /* the breath will sweep over small monsters */
3576 if (monster_size(m) >= LARGE)
3577 terminated = TRUE;
3578 }
3579
3580 if (pos_identical(pos, nlarn->p->pos))
3581 {
3582 /* The breath hit the player */
3583 if (player_effect(nlarn->p, ET_REFLECTION))
3584 {
3585 /* The player reflects the breath. Actual handling of the reflection
3586 is done in map_trajectory, just give a message here. */
3587 log_add_entry(nlarn->log, "Your amulet reflects the %s!",
3588 monster_breath_data[dam->type].desc);
3589 }
3590 else
3591 {
3592 /* No reflection -> player takes the damage */
3593 /* TODO: evasion!!! */
3594 log_add_entry(nlarn->log, "The %s hits you!",
3595 monster_breath_data[dam->type].desc);
3596 player_damage_take(nlarn->p, damage_copy(dam), PD_MONSTER,
3597 monster_type(dam->dam_origin.originator));
3598
3599 /* erode the player's inventory */
3600 if (iet != IET_NONE)
3601 {
3602 /*
3603 * Filter equipped and exposed items, e.g.
3604 * a body armour will not be affected by erosion
3605 * when the player wears a cloak over it.
3606 */
3607 inv_erode(&nlarn->p->inventory, iet, TRUE,
3608 player_item_filter_unequippable);
3609 }
3610
3611 terminated = TRUE;
3612 }
3613 }
3614
3615 if (iet > IET_NONE && map_ilist_at(mp, pos))
3616 {
3617 /* erode affected items */
3618 inv_erode(map_ilist_at(mp, pos), iet, fov_get(nlarn->p->fv, pos), NULL);
3619 }
3620
3621
3622 if (map_sobject_at(mp, pos) == LS_MIRROR && fov_get(nlarn->p->fv, pos))
3623 {
3624 /* A mirror will reflect the breath. Actual handling of the reflection
3625 is done in map_trajectory, just give a message here if the
3626 mirror is visible by the player. */
3627 log_add_entry(nlarn->log, "The mirror reflects the %s!",
3628 monster_breath_data[dam->type].desc);
3629 }
3630
3631 return terminated;
3632 }
3633