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