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