1 /*
2  * sobjects.c
3  * Copyright (C) 2009-2020 Joachim de Groot <jdegroot@web.de>
4  *
5  * This program 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  * This program 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 "game.h"
23 #include "map.h"
24 #include "extdefs.h"
25 #include "sobjects.h"
26 #include "player.h"
27 #include "random.h"
28 
29 const sobject_data sobjects[LS_MAX] =
30 {
31     /* type             gly   color         desc                                   pa tr */
32     { LS_NONE,          ' ',  COLOURLESS,   NULL,                                  1, 1, },
33     { LS_ALTAR,         '_',  WHITE,     "a holy altar",                        1, 1, },
34     { LS_THRONE,        '\\', MAGENTA,   "a handsome, jewel-encrusted throne",  1, 1, },
35     { LS_THRONE2,       '\\', MAGENTA,   "a handsome, jewel-encrusted throne",  1, 1, },
36     { LS_DEADTHRONE,    '\\', LIGHTGRAY, "a massive throne",                    1, 1, },
37     { LS_STAIRSDOWN,    '>',  WHITE,     "a circular staircase",                1, 1, },
38     { LS_STAIRSUP,      '<',  WHITE,     "a circular staircase",                1, 1, },
39     { LS_ELEVATORDOWN,  'I',  LIGHTGRAY, "a volcanic shaft leading downward",   1, 1, },
40     { LS_ELEVATORUP,    'I',  WHITE,     "the base of a volcanic shaft",        1, 1, },
41     { LS_FOUNTAIN,      '{',  BLUE,      "a bubbling fountain",                 1, 1, },
42     { LS_DEADFOUNTAIN,  '{',  LIGHTGRAY, "a dead fountain",                     1, 1, },
43     { LS_STATUE,        '|',  LIGHTGRAY, "a great marble statue",               1, 1, },
44     { LS_URN,           'u',  YELLOW,    "a golden urn",                        1, 1, },
45     { LS_MIRROR,        '|',  WHITE,     "a mirror",                            1, 1, },
46     { LS_OPENDOOR,      '/',  BROWN,     "an open door",                        1, 1, },
47     { LS_CLOSEDDOOR,    '+',  BROWN,     "a closed door",                       0, 0, },
48     { LS_CAVERNS_ENTRY, 'O',  LIGHTGRAY, "the entrance to the caverns",         1, 1, },
49     { LS_CAVERNS_EXIT,  'O',  WHITE,     "the exit to town",                    1, 1, },
50     { LS_HOME,          'H',  LIGHTGRAY, "your home",                           1, 0, },
51     { LS_DNDSTORE,      'D',  LIGHTGRAY, "a DND store",                         1, 0, },
52     { LS_TRADEPOST,     'T',  LIGHTGRAY, "the Larn trading Post",               1, 0, },
53     { LS_LRS,           'L',  LIGHTGRAY, "an LRS office",                       1, 0, },
54     { LS_SCHOOL,        'S',  LIGHTGRAY, "the College of Larn",                 1, 0, },
55     { LS_BANK,          'B',  LIGHTGRAY, "the bank of Larn",                    1, 0, },
56     { LS_BANK2,         'B',  WHITE,     "a branch office of the bank of Larn", 1, 0, },
57     { LS_MONASTERY,     'M',  WHITE,     "the Monastery of Larn",               1, 0, },
58 };
59 
60 static void monster_appear(monster_t type, position mpos);
61 static void flood_affect_area(position pos, int radius, int type, int duration);
62 static gboolean sobject_blast_hit(position pos, const damage_originator *damo,
63                                   gpointer data1, gpointer data2);
64 
player_altar_desecrate(player * p)65 int player_altar_desecrate(player *p)
66 {
67     g_assert (p != NULL);
68 
69     map *current = game_map(nlarn, Z(p->pos));
70 
71     if (map_sobject_at(current, p->pos) != LS_ALTAR)
72     {
73         log_add_entry(nlarn->log, "There is no altar here.");
74         return FALSE;
75     }
76 
77     log_add_entry(nlarn->log, "You try to desecrate the altar.");
78 
79     if (chance(60))
80     {
81         /* try to find a space for the monster near the altar */
82         position mpos = map_find_space_in(current, rect_new_sized(p->pos, 1),
83                                           LE_MONSTER, FALSE);
84 
85         if (pos_valid(mpos))
86         {
87             /* create a monster - should be very dangerous */
88             monster_appear(MT_RED_DRAGON, mpos);
89         }
90 
91         effect *e = effect_new(ET_AGGRAVATE_MONSTER);
92         e->turns = 2500;
93         player_effect_add(p, e);
94     }
95     else if (chance(30))
96     {
97         /* destroy altar */
98         log_add_entry(nlarn->log, "The altar crumbles into a pile of dust before your eyes.");
99         map_sobject_set(current, p->pos, LS_NONE);
100     }
101     else
102     {
103         log_add_entry(nlarn->log, "You fail to destroy the altar.");
104     }
105 
106     return TRUE;
107 }
108 
player_altar_pray(player * p)109 int player_altar_pray(player *p)
110 {
111     effect *e = NULL;
112     item **armour = NULL;
113     map *current;
114 
115     g_assert (p != NULL);
116 
117     current = game_map(nlarn, Z(p->pos));
118 
119     if (map_sobject_at(current, p->pos) != LS_ALTAR)
120     {
121         log_add_entry(nlarn->log, "There is no altar here.");
122         return FALSE;
123     }
124 
125     const guint player_gold = player_get_gold(p);
126     const guint total_gold  = player_gold + p->bank_account;
127     if (total_gold == 0)
128     {
129         log_add_entry(nlarn->log, "You don't have any money to donate.");
130         return FALSE;
131     }
132 
133     // Use a sensible default value, so you don't anger the gods without
134     // meaning to.
135     const guint donation = display_get_count("How much gold do you want to donate?",
136                                            200);
137 
138     /* 0 gold donations are likely to be the result of escaping the prompt */
139     if (!donation)
140     {
141         log_add_entry(nlarn->log, "So you decide not to donate anything.");
142         return FALSE;
143     }
144 
145     if (donation > total_gold)
146     {
147         log_add_entry(nlarn->log, "You don't have that much money!");
148         return FALSE;
149     }
150 
151     // First pay with carried gold, then pay the rest from the bank account.
152     if (donation >= player_gold)
153     {
154         player_remove_gold(p, player_gold);
155         p->bank_account -= (donation - player_gold);
156     }
157     else
158     {
159         player_remove_gold(p, donation);
160     }
161     p->stats.gold_spent_donation += donation;
162 
163     log_add_entry(nlarn->log, "You donate %d gold at the altar and pray.",
164                   donation);
165 
166     // The higher the donation, the more likely is a favourable outcome.
167     const int event = min(8, rand_0n(donation/50));
168 
169     if (event > 0)
170         log_add_entry(nlarn->log, "Thank you!");
171     else
172         log_add_entry(nlarn->log, "The gods are displeased with you.");
173 
174     int afflictions = 0;
175     gboolean cured_affliction = FALSE;
176     switch (event)
177     {
178     case 8:
179         if (!player_effect(p, ET_UNDEAD_PROTECTION))
180         {
181             log_add_entry(nlarn->log, "You have been heard!");
182             e = effect_new(ET_UNDEAD_PROTECTION);
183             player_effect_add(p, e);
184             break;
185         }
186         // intentional fall through
187     case 7:
188         afflictions += rand_1n(5);
189         // intentional fall through
190     case 6:
191         afflictions += rand_1n(5);
192         // intentional fall through
193     case 5:
194         afflictions++;
195 
196         while (afflictions-- > 0)
197         {
198             if ((e = player_effect_get(p, ET_PARALYSIS)))
199             {
200                 player_effect_del(p, e);
201                 cured_affliction = TRUE;
202                 continue;
203             }
204             if ((e = player_effect_get(p, ET_BLINDNESS)))
205             {
206                 player_effect_del(p, e);
207                 cured_affliction = TRUE;
208                 continue;
209             }
210             if (afflictions >= 5 && (e = player_effect_get(p, ET_DIZZINESS)))
211             {
212                 player_effect_del(p, e);
213                 cured_affliction = TRUE;
214                 afflictions -= 5;
215                 continue;
216             }
217             if ((e = player_effect_get(p, ET_CONFUSION)))
218             {
219                 player_effect_del(p, e);
220                 cured_affliction = TRUE;
221                 continue;
222             }
223             if ((e = player_effect_get(p, ET_POISON)))
224             {
225                 player_effect_del(p, e);
226                 cured_affliction = TRUE;
227                 continue;
228             }
229 
230             effect_t et;
231             for (et = ET_DEC_CON; et <= ET_DEC_WIS; et++)
232             {
233                 if ((e = player_effect_get(p, et)))
234                 {
235                     player_effect_del(p, e);
236                     cured_affliction = TRUE;
237                     break;
238                 }
239             }
240             if (et <= ET_DEC_WIS)
241                 continue;
242 
243             /* nothing else to cure */
244             break;
245         }
246 
247         if (cured_affliction)
248             break;
249 
250         /* fall-through */
251     case 4:
252         if (chance(10) && p->eq_weapon)
253         {
254             if (p->eq_weapon->bonus < 3)
255             {
256                 /* enchant weapon */
257                 log_add_entry(nlarn->log, "Your %s vibrates for a moment.",
258                               weapon_name(p->eq_weapon));
259 
260                 item_enchant(p->eq_weapon);
261                 break;
262             }
263         }
264         // intentional fall through
265     case 3:
266         if (chance(10) && (armour = player_get_random_armour(p, TRUE)))
267         {
268             if ((*armour)->bonus < 3)
269             {
270                 log_add_entry(nlarn->log, "Your %s vibrates for a moment.",
271                               armour_name(*armour));
272 
273                 item_enchant(*armour);
274                 break;
275             }
276         }
277         // intentional fall through
278     case 2:
279         if (chance(50) && !player_effect(p, ET_PROTECTION))
280         {
281             e = effect_new(ET_PROTECTION);
282             e->turns = 500;
283             player_effect_add(p, e);
284             break;
285         }
286         // intentional fall through
287     case 1:
288         log_add_entry(nlarn->log, "Nothing seems to have happened.");
289         break;
290     case 0:
291         {
292         /* create a monster, it should be very dangerous */
293         position mpos = map_find_space_in(current, rect_new_sized(p->pos, 1),
294                                           LE_MONSTER, FALSE);
295 
296         if (pos_valid(mpos))
297             monster_appear(MT_MAX, mpos);
298 
299         if (donation < rand_1n(100) || !pos_valid(mpos))
300         {
301             e = effect_new(ET_AGGRAVATE_MONSTER);
302             e->turns = 200;
303             player_effect_add(p, e);
304         }
305         }
306     }
307 
308     return TRUE;
309 }
310 
player_building_enter(player * p)311 int player_building_enter(player *p)
312 {
313     int moves_count = 0;
314 
315     switch (map_sobject_at(game_map(nlarn, Z(p->pos)), p->pos))
316     {
317     case LS_BANK:
318     case LS_BANK2:
319         moves_count = building_bank(p);
320         break;
321 
322     case LS_DNDSTORE:
323         moves_count = building_dndstore(p);
324         break;
325 
326     case LS_HOME:
327         moves_count = building_home(p);
328         break;
329 
330     case LS_LRS:
331         moves_count = building_lrs(p);
332         break;
333 
334     case LS_SCHOOL:
335         moves_count = building_school(p);
336         break;
337 
338     case LS_TRADEPOST:
339         moves_count = building_tradepost(p);
340         break;
341 
342     case LS_MONASTERY:
343         moves_count = building_monastery(p);
344         break;
345 
346     default:
347         log_add_entry(nlarn->log, "There is nothing to enter here.");
348     }
349 
350     return moves_count;
351 }
352 
player_door_close(player * p)353 int player_door_close(player *p)
354 {
355     if (!player_movement_possible(p))
356         return 0;
357 
358     /* position used to interact with stationaries */
359     position pos;
360 
361     /* possible directions of actions */
362     int *dirs;
363 
364     /* direction of action */
365     direction dir = GD_NONE;
366 
367     /* a counter and another one */
368     int count, num;
369 
370     /* the current map */
371     map *pmap = game_map(nlarn, Z(p->pos));
372 
373     dirs = map_get_surrounding(pmap, p->pos, LS_OPENDOOR);
374 
375     for (count = 0, num = 1; num < GD_MAX; num++)
376     {
377         if (dirs[num])
378         {
379             count++;
380             dir = num;
381         }
382     }
383 
384     if (count > 1)
385     {
386         dir = display_get_direction("Close which door?", dirs);
387     }
388     /* dir has been set in the for loop above if count == 1 */
389     else if (count == 0)
390     {
391         dir = GD_NONE;
392     }
393 
394     g_free(dirs);
395 
396     /* select random direction if player is confused */
397     if (player_effect(p, ET_CONFUSION))
398     {
399         dir = rand_0n(GD_MAX);
400     }
401 
402     if (dir)
403     {
404         pos = pos_move(p->pos, dir);
405         if (pos_valid(pos) && (map_sobject_at(pmap, pos) == LS_OPENDOOR))
406         {
407 
408             /* check if player is standing in the door */
409             if (pos_identical(pos, p->pos))
410             {
411                 log_add_entry(nlarn->log, "Please step out of the doorway.");
412                 return 0;
413             }
414 
415             /* check for monster in the doorway */
416             monster *m = map_get_monster_at(pmap, pos);
417 
418             if (m && monster_in_sight(m))
419             {
420                 gboolean visible = monster_in_sight(m);
421 
422                 log_add_entry(nlarn->log,
423                               "You cannot close the door. %s %s is in the way.",
424                               visible ? "The" : "An", monster_get_name(m));
425                 return 0;
426             }
427 
428             /* check for items in the doorway */
429             if (m || *map_ilist_at(pmap, pos))
430             {
431                 log_add_entry(nlarn->log,
432                               "You cannot close the door. There is something in the way.");
433                 return 0;
434             }
435 
436             map_sobject_set(pmap, pos, LS_CLOSEDDOOR);
437             log_add_entry(nlarn->log, "You close the door.");
438         }
439         else
440         {
441             log_add_entry(nlarn->log, "Huh?");
442             return 0;
443         }
444     }
445     else
446     {
447         log_add_entry(nlarn->log, "Which door are you talking about?");
448         return 0;
449     }
450 
451     return 1;
452 }
453 
player_door_open(player * p,int dir)454 int player_door_open(player *p, int dir)
455 {
456     if (!player_movement_possible(p))
457         return 0;
458 
459     /* position used to interact with stationaries */
460     position pos;
461 
462     /* the current map */
463     map *pmap = game_map(nlarn, Z(p->pos));
464 
465     if (dir == GD_NONE)
466     {
467         /* number of valid directions for actions */
468         int count = 0;
469 
470         /* possible directions of actions */
471         int *dirs = map_get_surrounding(pmap, p->pos, LS_CLOSEDDOOR);
472 
473         for (int num = 1; num < GD_MAX; num++)
474         {
475             if (dirs[num])
476             {
477                 count++;
478                 dir = num;
479             }
480         }
481 
482         if (count > 1)
483         {
484             dir = display_get_direction("Open which door?", dirs);
485         }
486         /* dir has been set in the for loop above if count == 1 */
487         else if (count == 0)
488         {
489             dir = GD_NONE;
490         }
491 
492         g_free(dirs);
493 
494         /* select random direction if player is confused */
495         if (player_effect(p, ET_CONFUSION))
496         {
497             dir = rand_1n(GD_MAX);
498         }
499     }
500 
501     if (dir)
502     {
503         pos = pos_move(p->pos, dir);
504 
505         if (pos_valid(pos) && (map_sobject_at(pmap, pos) == LS_CLOSEDDOOR))
506         {
507             map_sobject_set(pmap, pos, LS_OPENDOOR);
508             log_add_entry(nlarn->log, "You open the door.");
509         }
510         else
511         {
512             log_add_entry(nlarn->log, "Huh?");
513             return 0;
514         }
515     }
516     else
517     {
518         log_add_entry(nlarn->log, "What exactly do you want to open?");
519         return 0;
520     }
521 
522     return 1;
523 }
524 
player_fountain_drink(player * p)525 int player_fountain_drink(player *p)
526 {
527     g_assert (p != NULL);
528 
529     map *pmap = game_map(nlarn, Z(p->pos));
530 
531     if (map_sobject_at(pmap, p->pos) == LS_DEADFOUNTAIN)
532     {
533         log_add_entry(nlarn->log, "There is no water to drink.");
534         return 0;
535     }
536 
537     if (map_sobject_at(pmap, p->pos) != LS_FOUNTAIN)
538     {
539         log_add_entry(nlarn->log, "There is no fountain to drink from here.");
540         return 0;
541     }
542 
543     log_add_entry(nlarn->log, "You drink from the fountain.");
544 
545     gint event = rand_1n(101);
546     int amount = 0;
547     effect_t et = ET_NONE;
548 
549     if (event < 7)
550     {
551         et = ET_SICKNESS;
552     }
553     else if (event < 13)
554     {
555         /* see invisible */
556         et = ET_INFRAVISION;
557     }
558     else if (event < 45)
559     {
560         log_add_entry(nlarn->log, "Nothing seems to have happened.");
561     }
562     else if (chance(67))
563     {
564         /* positive effect */
565         switch (rand_1n(9))
566         {
567         case 1:
568             et = ET_INC_STR;
569             break;
570 
571         case 2:
572             et = ET_INC_INT;
573             break;
574 
575         case 3:
576             et = ET_INC_WIS;
577             break;
578 
579         case 4:
580             et = ET_INC_CON;
581             break;
582 
583         case 5:
584             et = ET_INC_DEX;
585             break;
586 
587         case 6:
588             amount = rand_1n(Z(p->pos) + 1);
589             log_add_entry(nlarn->log, "You gain %d hit point%s.",
590                           amount, plural(amount));
591 
592             player_hp_gain(p, amount);
593             break;
594 
595         case 7:
596             amount = rand_1n(Z(p->pos) + 1);
597             log_add_entry(nlarn->log, "You just gained %d mana point%s.",
598                           amount, plural(amount));
599 
600             player_mp_gain(p, amount);
601             break;
602 
603         case 8:
604             amount = 5 * rand_1n((Z(p->pos) + 1) * (Z(p->pos) + 1));
605 
606             log_add_entry(nlarn->log, "You just gained experience.");
607             player_exp_gain(p, amount);
608             break;
609         }
610     }
611     else
612     {
613         /* negative effect */
614         switch (rand_1n(9))
615         {
616         case 1:
617             et = ET_DEC_STR;
618             break;
619 
620         case 2:
621             et = ET_DEC_INT;
622             break;
623 
624         case 3:
625             et = ET_DEC_WIS;
626             break;
627 
628         case 4:
629             et = ET_DEC_CON;
630             break;
631 
632         case 5:
633             et = ET_DEC_DEX;
634             break;
635 
636         case 6:
637             amount = rand_1n(Z(p->pos) + 1);
638             log_add_entry(nlarn->log, "You lose %d hit point%s!",
639                               amount, plural(amount));
640 
641             player_hp_lose(p, amount, PD_SOBJECT, LS_FOUNTAIN);
642             break;
643         case 7:
644             amount = rand_1n(Z(p->pos) + 1);
645             log_add_entry(nlarn->log, "You just lost %d mana point%s.",
646                           amount, plural(amount));
647 
648             player_mp_lose(p, amount);
649             break;
650 
651         case 8:
652             amount = 5 * rand_1n((Z(p->pos) + 1) * (Z(p->pos) + 1));
653 
654             log_add_entry(nlarn->log, "You just lost experience.");
655             player_exp_lose(p, amount);
656             break;
657         }
658     }
659 
660     /* Create an effect if the RNG decided to */
661     if (et)
662     {
663         player_effect_add(p, effect_new(et));
664     }
665 
666     if (chance(25))
667     {
668         log_add_entry(nlarn->log, "The fountains bubbling slowly quiets.");
669         map_sobject_set(pmap, p->pos, LS_DEADFOUNTAIN);
670     }
671 
672     return 1;
673 }
674 
player_fountain_wash(player * p)675 int player_fountain_wash(player *p)
676 {
677     g_assert (p != NULL);
678 
679     map *pmap = game_map(nlarn, Z(p->pos));
680 
681     if (map_sobject_at(pmap, p->pos) == LS_DEADFOUNTAIN)
682     {
683         log_add_entry(nlarn->log, "The fountain is dry.");
684         return 0;
685     }
686 
687     if (map_sobject_at(pmap, p->pos) != LS_FOUNTAIN)
688     {
689         log_add_entry(nlarn->log, "There is no fountain to wash at here.");
690         return 0;
691     }
692 
693     log_add_entry(nlarn->log, "You wash yourself at the fountain.");
694 
695     if (chance(10))
696     {
697         log_add_entry(nlarn->log, "Oh no! The water was foul!");
698 
699         damage *dam = damage_new(DAM_POISON, ATT_NONE,
700                                  rand_1n((Z(p->pos) << 2) + 2),
701                                  DAMO_SOBJECT, NULL);
702 
703         player_damage_take(p, dam, PD_SOBJECT, LS_FOUNTAIN);
704 
705         return 1;
706     }
707     else if (chance(60))
708     {
709         effect *e = NULL;
710         if ((e = player_effect_get(p, ET_ITCHING)))
711         {
712             if (chance(50))
713             {
714                 log_add_entry(nlarn->log, "You got the dirt off!");
715                 player_effect_del(p, e);
716             }
717             else
718             {
719                 log_add_entry(nlarn->log,
720                       "This water seems to be hard water! "
721                       "The dirt didn't come off!");
722             }
723 
724             return 1;
725         }
726     }
727     else if (chance(35))
728     {
729         /* try to find a space for the monster near the player */
730         position mpos = map_find_space_in(pmap, rect_new_sized(p->pos, 1),
731                                           LE_MONSTER, FALSE);
732 
733         if (pos_valid(mpos))
734         {
735             /* make water lord */
736             monster_appear(MT_WATER_LORD, mpos);
737         }
738 
739         return 1;
740     }
741 
742     log_add_entry(nlarn->log, "Nothing seems to have happened.");
743 
744     return 1;
745 }
746 
player_stairs_down(player * p)747 int player_stairs_down(player *p)
748 {
749     map *nlevel = NULL;
750     gboolean show_msg = FALSE;
751     map *pmap = game_map(nlarn, Z(p->pos));
752     sobject_t ms = map_sobject_at(pmap, p->pos);
753 
754     if (!player_movement_possible(p))
755         return FALSE;
756 
757     /* the stairs down are unreachable while levitating */
758     if (player_effect(p, ET_LEVITATION))
759     {
760         log_add_entry(nlarn->log, "You cannot reach reach the stairs..");
761         return FALSE;
762     }
763 
764     switch (ms)
765     {
766     case LS_STAIRSDOWN:
767         show_msg = TRUE;
768         nlevel = game_map(nlarn, Z(p->pos) + 1);;
769         break;
770 
771     case LS_ELEVATORDOWN:
772         /* first volcano map */
773         show_msg = TRUE;
774         nlevel = game_map(nlarn, MAP_CMAX);
775         break;
776 
777     case LS_CAVERNS_ENTRY:
778         if (Z(p->pos) == 0)
779             nlevel = game_map(nlarn, 1);
780         else
781             log_add_entry(nlarn->log, "Climb up to return to town.");
782         break;
783 
784     default:
785     {
786         trap_t trap = map_trap_at(pmap, p->pos);
787         if (trap == TT_TRAPDOOR || trap == TT_TELEPORT)
788             return player_trap_trigger(p, trap, TRUE);
789         else
790             return FALSE;
791     }
792     }
793 
794     /* display additional message */
795     if (show_msg)
796     {
797         log_add_entry(nlarn->log, "You climb down %s.", so_get_desc(ms));
798     }
799 
800     /* if told to switch level, do so */
801     if (nlevel != NULL)
802     {
803         /* check if the player is burdened and cause some damage if so */
804         int bval = player_effect(p, ET_BURDENED);
805 
806         if (bval > 0)
807         {
808             log_add_entry(nlarn->log, "You slip!");
809             damage *dam = damage_new(DAM_PHYSICAL, ATT_NONE,
810                                      rand_1n(bval + nlevel->nlevel),
811                                      DAMO_SOBJECT, NULL);
812 
813             player_damage_take(p, dam, PD_SOBJECT, ms);
814         }
815 
816         return player_map_enter(p, nlevel, FALSE);
817     }
818 
819     return 0;
820 }
821 
player_stairs_up(player * p)822 int player_stairs_up(player *p)
823 {
824     map *nlevel = NULL;
825     gboolean show_msg = FALSE;
826     sobject_t ms = map_sobject_at(game_map(nlarn, Z(p->pos)), p->pos);
827 
828     if (!player_movement_possible(p))
829         return 0;
830 
831     switch (ms)
832     {
833     case LS_STAIRSUP:
834         show_msg = TRUE;
835         nlevel = game_map(nlarn, Z(p->pos) - 1);
836         break;
837 
838     case LS_ELEVATORUP:
839     case LS_CAVERNS_EXIT:
840         /* return to town */
841         show_msg = TRUE;
842         nlevel = game_map(nlarn, 0);
843         break;
844 
845     case LS_CAVERNS_ENTRY:
846         log_add_entry(nlarn->log, "Climb down to enter the caverns.");
847         return 0;
848 
849     default:
850         log_add_entry(nlarn->log, "I see no stairway up here.");
851         return 0;
852     }
853 
854     /* display additional message */
855     if (show_msg)
856     {
857         log_add_entry(nlarn->log, "You climb up %s.", so_get_desc(ms));
858     }
859 
860     /* if told to switch level, do so */
861     if (nlevel != NULL)
862     {
863         return player_map_enter(p, nlevel, FALSE);
864     }
865 
866     return 0;
867 }
868 
player_throne_pillage(player * p)869 int player_throne_pillage(player *p)
870 {
871     g_assert (p != NULL);
872 
873     int count = 0; /* gems created */
874 
875     /* current map */
876     map *pmap = game_map(nlarn, Z(p->pos));
877 
878     /* type of object at player's position */
879     sobject_t ms = map_sobject_at(pmap, p->pos);
880 
881     if ((ms != LS_THRONE) && (ms != LS_THRONE2) && (ms != LS_DEADTHRONE))
882     {
883         log_add_entry(nlarn->log, "There is no throne here.");
884         return 0;
885     }
886 
887     log_add_entry(nlarn->log, "You try to remove the gems from the throne.");
888 
889     if (ms == LS_DEADTHRONE)
890     {
891         log_add_entry(nlarn->log, "There are no gems left on this throne.");
892         return 0;
893     }
894 
895     if (chance(2 * player_get_dex(p)))
896     {
897         for (guint i = 0; i < rand_1n(4); i++)
898         {
899             /* gems pop off the throne */
900             inv_add(map_ilist_at(pmap, p->pos), item_new_random(IT_GEM, FALSE));
901             count++;
902         }
903 
904         log_add_entry(nlarn->log, "You manage to pry off %s gem%s.",
905                       count > 1 ? "some" : "a", plural(count));
906 
907         map_sobject_set(pmap, p->pos, LS_DEADTHRONE);
908         p->stats.vandalism++;
909     }
910     else if (chance(40) && (ms == LS_THRONE))
911     {
912         /* try to find a space for the monster near the player */
913         position mpos = map_find_space_in(pmap, rect_new_sized(p->pos, 1),
914                                           LE_MONSTER, FALSE);
915 
916         if (pos_valid(mpos))
917         {
918             /* make gnome king */
919             monster_appear(MT_GNOME_KING, mpos);
920 
921             /* next time there will be no gnome king */
922             map_sobject_set(pmap, p->pos, LS_THRONE2);
923         }
924     }
925     else
926     {
927         log_add_entry(nlarn->log, "You fail to remove the gems.");
928     }
929 
930     return 1 + count;
931 }
932 
player_throne_sit(player * p)933 int player_throne_sit(player *p)
934 {
935     g_assert (p != NULL);
936 
937     map *pmap = game_map(nlarn, Z(p->pos));
938     sobject_t st = map_sobject_at(pmap, p->pos);
939 
940     if ((st != LS_THRONE) && (st != LS_THRONE2) && (st != LS_DEADTHRONE))
941     {
942         log_add_entry(nlarn->log, "There is no throne here.");
943         return 0;
944     }
945 
946     log_add_entry(nlarn->log, "You sit on the throne.");
947 
948     if (chance(30) && (st == LS_THRONE))
949     {
950         /* try to find a space for the monster near the player */
951         position mpos = map_find_space_in(pmap, rect_new_sized(p->pos, 1),
952                                           LE_MONSTER, FALSE);
953 
954         if (pos_valid(mpos))
955         {
956             /* make a gnome king */
957             monster_appear(MT_GNOME_KING, mpos);
958 
959             /* next time there will be no gnome king */
960             map_sobject_set(pmap, p->pos, LS_THRONE2);
961         }
962     }
963     else if (chance(35))
964     {
965         log_add_entry(nlarn->log, "Zaaaappp! You've been teleported!");
966         p->pos = map_find_space(pmap, LE_MONSTER, FALSE);
967     }
968     else
969     {
970         log_add_entry(nlarn->log, "Nothing seems to have happened.");
971     }
972 
973     return 1;
974 }
975 
sobject_destroy_at(player * p,map * dmap,position pos)976 void sobject_destroy_at(player *p, map *dmap, position pos)
977 {
978     position mpos;      /* position for monster that might be generated */
979     const char *desc = NULL;
980 
981     mpos = map_find_space_in(dmap, rect_new_sized(pos, 1), LE_MONSTER, FALSE);
982 
983     switch (map_sobject_at(dmap, pos))
984     {
985     case LS_NONE:
986         /* NOP */
987         break;
988 
989     case LS_ALTAR:
990     {
991         log_add_entry(nlarn->log, "You destroy the altar.", desc);
992         map_sobject_set(dmap, pos, LS_NONE);
993         p->stats.vandalism++;
994 
995         log_add_entry(nlarn->log, "Lightning comes crashing down from above!");
996 
997         /* flood the area surrounding the altar with lightning */
998         damage_originator damo = { DAMO_GOD, NULL };
999         damage *dam = damage_new(DAM_ELECTRICITY, ATT_MAGIC,
1000                                  25 + p->level + rand_0n(25 + p->level),
1001                                  damo.ot, damo.originator);
1002 
1003         area_blast(pos, 3, &damo, sobject_blast_hit, dam, NULL, '*', LIGHTCYAN);
1004         damage_free(dam);
1005 
1006         break;
1007     }
1008 
1009     case LS_FOUNTAIN:
1010         log_add_entry(nlarn->log, "You destroy the fountain.", desc);
1011         map_sobject_set(dmap, pos, LS_NONE);
1012         p->stats.vandalism++;
1013 
1014         /* create a permanent shallow pool and place a water lord */
1015         log_add_entry(nlarn->log, "A flood of water gushes forth!");
1016         flood_affect_area(pos, 3 + rand_0n(2), LT_WATER, 0);
1017         if (pos_valid(mpos))
1018             monster_new(MT_WATER_LORD, mpos, NULL);
1019         break;
1020 
1021     case LS_STATUE:
1022         /* chance of finding a book:
1023            diff 0-1: 100%, diff 2: 2/3, diff 3: 50%, ..., diff N: 2/(N+1) */
1024         if (rand_0n(game_difficulty(nlarn)+1) <= 1)
1025         {
1026             item *it = item_new(IT_BOOK, rand_0n(item_max_id(IT_BOOK)));
1027             inv_add(map_ilist_at(dmap, pos), it);
1028         }
1029 
1030         desc = "statue";
1031         break;
1032 
1033     case LS_THRONE:
1034     case LS_THRONE2:
1035         if (pos_valid(mpos))
1036             monster_new(MT_GNOME_KING, mpos, NULL);
1037 
1038         desc = "throne";
1039         break;
1040 
1041     case LS_DEADFOUNTAIN:
1042     case LS_DEADTHRONE:
1043         map_sobject_set(dmap, pos, LS_NONE);
1044         break;
1045 
1046     case LS_CLOSEDDOOR:
1047         map_sobject_set(dmap, pos, LS_NONE);
1048 
1049         if(fov_get(p->fv, pos))
1050         {
1051             log_add_entry(nlarn->log, "The door shatters!");
1052         }
1053         break;
1054 
1055     default:
1056         log_add_entry(nlarn->log, "Somehow that did not work.");
1057         /* NOP */
1058     }
1059 
1060     if (desc)
1061     {
1062         log_add_entry(nlarn->log, "You destroy the %s.", desc);
1063         map_sobject_set(dmap, pos, LS_NONE);
1064         p->stats.vandalism++;
1065     }
1066 }
1067 
monster_appear(monster_t type,position mpos)1068 static void monster_appear(monster_t type, position mpos)
1069 {
1070     monster *m;
1071 
1072     if (type == MT_MAX)
1073     {
1074         m = monster_new_by_level(mpos);
1075     }
1076     else
1077     {
1078         m = monster_new(type, mpos, NULL);
1079     }
1080 
1081     if (m && monster_in_sight(m))
1082     {
1083         log_add_entry(nlarn->log, "An angry %s appears!",
1084                       monster_name(m));
1085     }
1086     else
1087         log_add_entry(nlarn->log, "Nothing seems to have happened.");
1088 }
1089 
flood_affect_area(position pos,int radius,int type,int duration)1090 static void flood_affect_area(position pos, int radius, int type, int duration)
1091 {
1092     area *obstacles = map_get_obstacles(game_map(nlarn, Z(pos)), pos, radius, FALSE);
1093     area *range = area_new_circle_flooded(pos, radius, obstacles);
1094 
1095     map_set_tiletype(game_map(nlarn, Z(pos)), range, type, duration);
1096     area_destroy(range);
1097 }
1098 
sobject_blast_hit(position pos,const damage_originator * damo,gpointer data1,gpointer data2)1099 static gboolean sobject_blast_hit(position pos,
1100                                   const damage_originator *damo,
1101                                   gpointer data1,
1102                                   gpointer data2 __attribute__((unused)))
1103 {
1104     damage *dam = (damage *)data1;
1105     map *cmap = game_map(nlarn, Z(pos));
1106     sobject_t mst = map_sobject_at(cmap, pos);
1107     monster *m = map_get_monster_at(cmap, pos);
1108 
1109     if (mst == LS_STATUE)
1110     {
1111         /* The blast hit a statue. */
1112         sobject_destroy_at(damo->originator, cmap, pos);
1113         return TRUE;
1114     }
1115     else if (m != NULL)
1116     {
1117         /* the blast hit a monster */
1118         if (monster_in_sight(m))
1119             log_add_entry(nlarn->log, "The lightning hits the %s.",
1120                           monster_get_name(m));
1121 
1122         monster_damage_take(m, damage_copy(dam));
1123 
1124         return TRUE;
1125     }
1126     else if (pos_identical(nlarn->p->pos, pos))
1127     {
1128         /* the blast hit the player */
1129         int evasion = nlarn->p->level/(2+game_difficulty(nlarn)/2)
1130                       + player_get_dex(nlarn->p)
1131                       - 10
1132                       - game_difficulty(nlarn);
1133 
1134         // Automatic hit if paralysed or overstrained.
1135         if (player_effect(nlarn->p, ET_PARALYSIS)
1136             || player_effect(nlarn->p, ET_OVERSTRAINED))
1137             evasion = 0;
1138         else
1139         {
1140             if (player_effect(nlarn->p, ET_BLINDNESS))
1141                 evasion /= 4;
1142             if (player_effect(nlarn->p, ET_CONFUSION))
1143                 evasion /= 2;
1144             if (player_effect(nlarn->p, ET_BURDENED))
1145                 evasion /= 2;
1146         }
1147 
1148         if (evasion >= (int)rand_1n(21))
1149         {
1150             if (!player_effect(nlarn->p, ET_BLINDNESS))
1151                 log_add_entry(nlarn->log, "The lightning whizzes by you!");
1152 
1153             /* missed */
1154             return FALSE;
1155         }
1156 
1157         log_add_entry(nlarn->log, "The lightning hits you!");
1158         /* FIXME: correctly state that the player has been killed by the
1159                   wrath of a god */
1160         player_damage_take(nlarn->p, damage_copy(dam), PD_SPELL, SP_LIT);
1161 
1162         /* hit */
1163         return TRUE;
1164     }
1165 
1166     return FALSE;
1167 }
1168