1 /*
2 * traps.c
3 * Copyright (C) 2009-2018 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
21 #include "display.h"
22 #include "effects.h"
23 #include "game.h"
24 #include "extdefs.h"
25 #include "player.h"
26 #include "random.h"
27 #include "traps.h"
28
29 const trap_data traps[TT_MAX] =
30 {
31 /*
32 trap type - effect type - glyph colour
33 trigger chance - effect chance - base damage
34 description
35 player trigger message
36 effect message
37 monster trigger message
38 */
39 {
40 TT_NONE, ET_NONE, COLOURLESS,
41 0, 0, 0,
42 NULL,
43 NULL,
44 NULL,
45 NULL,
46 },
47 {
48 TT_ARROW, ET_POISON, CYAN,
49 75, 50, 10,
50 "arrow trap",
51 "You are hit by an arrow.",
52 "The arrow was poisoned.",
53 "The %s is hit by an arrow.",
54 },
55 {
56 TT_DART, ET_POISON, CYAN,
57 75, 50, 5,
58 "dart trap",
59 "You are hit by a dart.",
60 "The dart was poisoned.",
61 "The %s is hit by a dart.",
62 },
63 {
64 TT_TELEPORT, ET_NONE, MAGENTA,
65 55, 0, 0,
66 "teleport trap",
67 "Zaaaappp! You've been teleported!",
68 NULL,
69 "The %s has been teleported away.",
70 },
71 {
72 TT_PIT, ET_TRAPPED, BROWN,
73 80, 100, 6,
74 "pit",
75 "You fall into a pit!",
76 NULL,
77 "The %s falls into a pit.",
78 },
79 {
80 TT_SPIKEDPIT, ET_POISON, BROWN,
81 80, 60, 12,
82 "pit full of spikes",
83 "You fall into a pit full of spikes!",
84 NULL,
85 "The %s falls into a pit full of spikes.",
86 },
87 {
88 TT_SLEEPGAS, ET_SLEEP, MAGENTA,
89 75, 100, 0,
90 "sleeping gas trap",
91 "A cloud of gas engulfs you.",
92 NULL,
93 "A cloud of gas engulfs the %s.",
94 },
95 {
96 TT_MANADRAIN, ET_NONE, BROWN,
97 75, 0, 0,
98 "magic energy drain trap",
99 "You feel your magical energy drained away!",
100 NULL,
101 NULL,
102 },
103 {
104 TT_TRAPDOOR, ET_NONE, BROWN,
105 75, 0, 5,
106 "trapdoor",
107 "You fall through a trap door!",
108 NULL,
109 "The %s falls through a trap door!",
110 },
111 };
112
modified_effect_chance(trap_t trap,effect_t et,int level)113 static int modified_effect_chance(trap_t trap, effect_t et, int level)
114 {
115 const int base_chance = trap_effect_chance(trap);
116 if (et == ET_POISON && level < 5)
117 {
118 /* lower poison chance in early levels */
119 return (base_chance * level / 5);
120 }
121
122 return base_chance;
123 }
124
player_trap_trigger(player * p,trap_t trap,int force)125 int player_trap_trigger(player *p, trap_t trap, int force)
126 {
127 /* additional time of turn, if any */
128 int ttime = 0;
129
130 const int dex = player_get_dex(p);
131
132 /* chance to trigger the trap on target tile */
133 int possibility = trap_chance(trap);
134
135 /* the value of the player's burden effect */
136 int bval = player_effect(p, ET_BURDENED);
137
138 if (player_memory_of(p, p->pos).trap == trap)
139 {
140 // Dex decreases the chance of triggering a known trap.
141 if (dex >= 22)
142 possibility = 0;
143 else
144 possibility = (22 - dex)/2;
145 }
146
147 /* Check if the player triggers the trap.
148 Being burdened increases the chance due to clumsy movement. */
149 if (force || chance(possibility + bval))
150 {
151 /* log the trap's triggered message */
152 log_add_entry(nlarn->log, trap_p_message(trap));
153
154 /* refresh player's knowledge of trap */
155 player_memory_of(p, p->pos).trap = trap;
156
157 if (trap_damage(trap))
158 {
159 /* deal more damage the deeper the dungeon
160 level and if the player is burdened */
161 damage *dam = damage_new(DAM_PHYSICAL, ATT_NONE,
162 rand_1n(trap_damage(trap) + bval) + Z(p->pos),
163 DAMO_TRAP, NULL);
164
165 player_damage_take(p, dam, PD_TRAP, trap);
166 }
167
168 switch (trap)
169 {
170 case TT_TRAPDOOR:
171 ttime += player_map_enter(p, game_map(nlarn, Z(p->pos) + 1), TRUE);
172 break;
173
174 case TT_TELEPORT:
175 p->pos = map_find_space(game_map(nlarn, Z(p->pos)), LE_MONSTER, FALSE);
176 break;
177
178 case TT_MANADRAIN:
179 if (p->mp > 1)
180 {
181 p->mp -= rand_1n(p->mp / 2);
182 }
183 break;
184
185 default:
186 if (trap == TT_SPIKEDPIT)
187 {
188 const trap_t trap2 = TT_PIT;
189
190 if (trap_effect(trap2)
191 && chance(modified_effect_chance(trap2, trap_effect(trap2),
192 Z(p->pos))))
193 {
194 /* display message if there is one */
195 if (trap_e_message(trap2))
196 log_add_entry(nlarn->log, trap_e_message(trap2));
197
198 player_effect_add(p, effect_new(trap_effect(trap2)));
199 }
200 }
201
202 /* if there is an effect on the trap add it to player's effects. */
203 if (trap_effect(trap)
204 && chance(modified_effect_chance(trap,
205 trap_effect(trap),Z(p->pos))))
206 {
207 /* display message if there is one */
208 if (trap_e_message(trap))
209 {
210 log_add_entry(nlarn->log, trap_e_message(trap));
211 }
212
213 player_effect_add(p, effect_new(trap_effect(trap)));
214 }
215 }
216 }
217 /* not triggering */
218 else if (player_memory_of(p, p->pos).trap == trap)
219 {
220 log_add_entry(nlarn->log, "You evade the %s.", trap_description(trap));
221 }
222 else if (chance((dex-12)/2))
223 {
224 /* detect the trap despite not setting it off */
225 log_add_entry(nlarn->log, "You notice there's a %s here!",
226 trap_description(trap));
227 player_memory_of(p, p->pos).trap = trap;
228 }
229
230 return ttime;
231 }
232
monster_trap_trigger(monster * m)233 monster *monster_trap_trigger(monster *m)
234 {
235 g_assert (m != NULL);
236
237 /* the trap */
238 trap_t trap = map_trap_at(monster_map(m), monster_pos(m));
239
240 /* flying monsters are only affected by sleeping gas traps */
241 if ((monster_flags(m, FLY) || monster_effect(m, ET_LEVITATION))
242 && (trap != TT_SLEEPGAS))
243 {
244 return m;
245 }
246
247 /* return if the monster has not triggered the trap */
248 if (!chance(trap_chance(trap)))
249 {
250 return m;
251 }
252
253 if (trap_m_message(trap) != NULL && monster_in_sight(m))
254 {
255 log_add_entry(nlarn->log, trap_m_message(trap), monster_name(m));
256
257 /* set player's knowledge of trap */
258 player_memory_of(nlarn->p, monster_pos(m)).trap = trap;
259 }
260
261 /* monster triggered the trap */
262 switch (trap)
263 {
264 case TT_TRAPDOOR:
265 monster_level_enter(m, game_map(nlarn, Z(monster_pos(m)) + 1));
266 break;
267
268 case TT_TELEPORT:
269 monster_pos_set(m, monster_map(m), map_find_space(game_map(nlarn,
270 Z(monster_pos(m))), LE_MONSTER, FALSE));
271 break;
272
273 case TT_SPIKEDPIT:
274 {
275 const trap_t trap2 = TT_PIT;
276
277 if (trap_effect(trap2) && chance(trap_effect_chance(trap2)))
278 {
279 monster_effect_add(m, effect_new(trap_effect(trap2)));
280 }
281 }
282 // intentional fall-through
283
284 default:
285 /* if there is an effect on the trap add it to the
286 * monster's list of effects. */
287 if (trap_effect(trap))
288 {
289 (void)monster_effect_add(m, effect_new(trap_effect(trap)));
290 }
291 } /* switch (trap) */
292
293 /* inflict damage caused by the trap */
294 if (trap_damage(trap))
295 {
296 damage *dam = damage_new(DAM_PHYSICAL, ATT_NONE,
297 rand_1n(trap_damage(trap)), DAMO_TRAP, NULL);
298
299 m = monster_damage_take(m, dam);
300 }
301
302 return m;
303 }
304
trap_disarm(struct player * p)305 guint trap_disarm(struct player *p)
306 {
307 map *cmap = game_map(nlarn, Z(p->pos));
308 trap_t tt = map_trap_at(cmap, p->pos);
309 gboolean magical_trap = (tt == TT_TELEPORT || tt == TT_MANADRAIN);
310
311 if (tt == TT_NONE)
312 {
313 log_add_entry(nlarn->log, "There is no trap here.");
314 return 0;
315 }
316
317 if (tt == TT_PIT || tt == TT_SPIKEDPIT)
318 {
319 log_add_entry(nlarn->log, "How do you think you can disable a %s? "
320 "Fill it with rubble?", trap_description(tt));
321
322 return 0;
323 }
324
325 /* determine the player's chance to disable the trap */
326 guint prop = ((100 - trap_chance(tt)) / 10) + p->level;
327
328 if (magical_trap)
329 {
330 prop += player_get_int(p);
331 }
332 else
333 {
334 prop += player_get_dex(p);
335 }
336
337 /* Disarming traps is supposed to be tricky. */
338 prop /= 2;
339
340 /* Determine the number of turns required to disable the trap. */
341 guint turns = trap_chance(tt) / 5;
342
343 if (!player_make_move(p, turns, TRUE, "disabling the %s",
344 trap_description(tt)))
345 return 0;
346
347 if (chance(prop))
348 {
349 log_add_entry(nlarn->log, "You manage to %s the %s!",
350 magical_trap ? "dispel" : "disarm",
351 trap_description(tt));
352
353 map_trap_set(cmap, p->pos, TT_NONE);
354 player_memory_of(p, p->pos).trap = TT_NONE;
355
356 /* arrow traps may drop some arrows */
357 if (tt == TT_ARROW && chance(34))
358 {
359 item *arrows = item_new(IT_AMMO, AMT_ARROW);
360 arrows->count = rand_1n(6);
361 inv_add(map_ilist_at(cmap, p->pos), arrows);
362 }
363 }
364 else if (chance(trap_chance(tt)))
365 {
366 /* player has triggered the trap */
367 log_add_entry(nlarn->log, "You accidentally trigger the %s.",
368 trap_description(tt));
369 return player_trap_trigger(p, tt, TRUE);
370 }
371 else
372 {
373 log_add_entry(nlarn->log, "You fail to disarm the %s.",
374 trap_description(tt));
375 }
376
377 return 0;
378 }
379
380