1 /*
2  * spells.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 <string.h>
21 
22 #include "display.h"
23 #include "map.h"
24 #include "extdefs.h"
25 #include "random.h"
26 #include "sobjects.h"
27 #include "spells.h"
28 #include "spheres.h"
29 
30 static gboolean spell_type_player(spell *s, struct player *p);
31 static gboolean spell_type_point(spell *s, struct player *p);
32 static gboolean spell_type_ray(spell *s, struct player *p);
33 static gboolean spell_type_flood(spell *s, struct player *p);
34 static gboolean spell_type_blast(spell *s, struct player *p);
35 
36 static gboolean spell_alter_reality(spell *s, struct player *p);
37 static gboolean spell_create_sphere(spell *s, struct player *p);
38 static gboolean spell_cure_poison(spell *s, struct player *p);
39 static gboolean spell_cure_blindness(spell *s, struct player *p);
40 static gboolean spell_phantasmal_forces(spell *s, struct player *p);
41 static gboolean spell_scare_monsters(spell *s, struct player *p);
42 static gboolean spell_summon_demon(spell *s, struct player *p);
43 static gboolean spell_make_wall(spell *s, struct player *p);
44 
45 const spell_data spells[SP_MAX] =
46 {
47     {
48         SP_PRO, "pro","protection",
49         SC_PLAYER, DAM_NONE, ET_PROTECTION, spell_type_player,
50         "Generates a protection field",
51         NULL, NULL,
52         COLOURLESS, 1, 260, TRUE
53     },
54     {
55         SP_MLE, "mle", "magic missile",
56         SC_RAY, DAM_MAGICAL, ET_NONE, spell_type_ray,
57         "Creates and hurls a missile magic of magical energy at a target",
58         "The missile hits the %s.",
59         "The missile bounces off the %s.",
60         COLOURLESS, 1, 320, TRUE
61     },
62     {
63         SP_DEX, "dex", "dexterity",
64         SC_PLAYER, DAM_NONE, ET_INC_DEX, spell_type_player,
65         "Improves the caster's dexterity",
66         NULL, NULL,
67         COLOURLESS, 1, 260, FALSE
68     },
69     {
70         SP_SLE, "sle", "sleep",
71         SC_POINT, DAM_NONE, ET_SLEEP, spell_type_point,
72         "Causes some monsters to go to sleep",
73         NULL,
74         "The %s doesn't sleep.",
75         COLOURLESS, 1, 260, TRUE
76     },
77     {
78         SP_CHM, "chm", "charm monster",
79         SC_POINT, DAM_NONE, ET_CHARM_MONSTER, spell_type_point,
80         "Some monsters may be awed at your magnificence",
81         NULL, "The %s isn't impressed.",
82         COLOURLESS, 1, 260, FALSE
83     },
84     {
85         SP_SSP, "ssp", "sonic spear",
86         SC_RAY, DAM_PHYSICAL, ET_NONE, spell_type_ray,
87         "Causes your hands to emit a screeching sound toward what they point",
88         "The sound damages the %s.",
89         "The %s can't hear the noise.",
90         LIGHTCYAN, 2, 480, FALSE
91     },
92     {
93         SP_STR, "str", "strength",
94         SC_PLAYER, DAM_NONE, ET_INC_STR, spell_type_player,
95         "Increase the caster's strength for a short term",
96         NULL, NULL,
97         COLOURLESS, 2, 460, FALSE
98     },
99     {
100         SP_CPO, "cpo", "cure poison",
101         SC_PLAYER, DAM_NONE, ET_NONE, spell_cure_poison,
102         "The caster is cured from poison",
103         NULL, NULL,
104         COLOURLESS, 2, 460, TRUE
105     },
106     {
107         SP_HEL, "hel", "healing",
108         SC_PLAYER, DAM_NONE, ET_INC_HP, spell_type_player,
109         "Restores some HP to the caster",
110         NULL, NULL,
111         COLOURLESS, 2, 500, TRUE
112     },
113     {
114         SP_CBL, "cbl", "cure blindness",
115         SC_PLAYER, DAM_NONE, ET_NONE, spell_cure_blindness,
116         "Restores sight to one so unfortunate as to be blinded",
117         NULL, NULL,
118         COLOURLESS, 2, 400, TRUE
119     },
120     {
121         SP_CRE, "cre", "create monster",
122         SC_OTHER, DAM_NONE, ET_NONE, spell_create_monster,
123         "Creates a monster near the caster appropriate for the location",
124         NULL, NULL,
125         COLOURLESS, 2, 400, FALSE
126     },
127     {
128         SP_PHA, "pha", "phantasmal forces",
129         SC_OTHER, DAM_NONE, ET_NONE, spell_phantasmal_forces,
130         "Creates illusions, and if believed, the monster flees",
131         "The %s believed!",
132         "The %s didn't believe the illusions!",
133         COLOURLESS, 2, 600, FALSE
134     },
135     {
136         SP_INV, "inv", "invisibility",
137         SC_PLAYER, DAM_NONE, ET_INVISIBILITY, spell_type_player,
138         "The caster becomes invisible",
139         NULL, NULL,
140         COLOURLESS, 2, 600, FALSE
141     },
142     {
143         SP_BAL, "bal", "fireball",
144         SC_BLAST, DAM_FIRE, ET_NONE, spell_type_blast,
145         "Makes a ball of fire that burns on what it hits",
146         "The fireball hits the %s.",
147         NULL,
148         LIGHTRED, 3, 1200, FALSE
149     },
150     {
151         SP_CLD, "cld", "cone of cold",
152         SC_RAY, DAM_COLD, ET_NONE, spell_type_ray,
153         "Sends forth a cone of cold which freezes what it touches",
154         "The cone of cold strikes the %s.",
155         NULL,
156         WHITE, 3, 1200, FALSE
157     },
158     {
159         SP_PLY, "ply", "polymorph",
160         SC_POINT, DAM_NONE, ET_NONE, spell_type_point,
161         "You can find out what this does for yourself",
162         NULL,
163         "The %s resists.",
164         COLOURLESS, 3, 950, FALSE
165     },
166     {
167         SP_CAN, "can", "cancellation",
168         SC_PLAYER, DAM_NONE, ET_CANCELLATION, spell_type_player,
169         "Protects the caster against spheres of annihilation",
170         NULL, NULL,
171         COLOURLESS, 3, 950, FALSE
172     },
173     {
174         SP_HAS, "has", "haste self",
175         SC_PLAYER, DAM_NONE, ET_SPEED, spell_type_player,
176         "Speeds up the caster's movements",
177         NULL, NULL,
178         COLOURLESS, 3, 950, FALSE
179     },
180     {
181         SP_CKL, "ckl", "killing cloud",
182         SC_FLOOD, DAM_ACID, ET_NONE, spell_type_flood,
183         "Creates a fog of poisonous gas which kills all that is within it",
184         "The %s gasps for air.",
185         NULL,
186         COLOURLESS, 3, 1200, FALSE
187     },
188     {
189         SP_VPR, "vpr", "vaporize rock",
190         SC_OTHER, DAM_NONE, ET_NONE, spell_vaporize_rock,
191         "This changes rock to air",
192         NULL, NULL,
193         COLOURLESS, 3, 950, FALSE
194     },
195     {
196         SP_DRY, "dry", "dehydration",
197         SC_POINT, DAM_PHYSICAL, ET_NONE, spell_type_point,
198         "Dries up water in the immediate vicinity",
199         "The %s shrivels up.",
200         "The %s isn't affected.",
201         COLOURLESS, 4, 1600, FALSE
202     },
203     {
204         SP_LIT, "lit", "lightning",
205         SC_RAY, DAM_ELECTRICITY, ET_NONE, spell_type_ray,
206         "Your finger will emit a lightning bolt when this spell is cast",
207         "A lightning bolt hits the %s.",
208         "The %s loves fire and lightning!",
209         YELLOW, 4, 1600, FALSE
210     },
211     {
212         SP_DRL, "drl", "drain life",
213         SC_POINT, DAM_PHYSICAL, ET_NONE, spell_type_point,
214         "Subtracts hit points from both you and a monster",
215         NULL, NULL,
216         COLOURLESS, 4, 1400, FALSE
217     },
218     {
219         SP_GLO, "glo", "invulnerability",
220         SC_PLAYER, DAM_NONE, ET_INVULNERABILITY, spell_type_player,
221         "This globe helps to protect the player from physical attack",
222         NULL, NULL,
223         COLOURLESS, 4, 1400, FALSE
224     },
225     {
226         SP_FLO, "flo", "flood",
227         SC_FLOOD, DAM_WATER, ET_NONE, spell_type_flood,
228         "This creates an avalanche of H2O to flood the immediate chamber",
229         "The %s struggles for air in the flood!",
230         "The %s loves the water!",
231         COLOURLESS, 4, 1600, FALSE
232     },
233     {
234         SP_FGR, "fgr", "finger of death",
235         SC_POINT, DAM_PHYSICAL, ET_NONE, spell_type_point,
236         "This is a holy spell and calls upon your god to back you up",
237         "The %s's heart stopped.",
238         "The %s isn't affected.",
239         COLOURLESS, 4, 1600, FALSE
240     },
241     {
242         SP_SCA, "sca", "scare monsters",
243         SC_OTHER, DAM_NONE, ET_NONE, spell_scare_monsters,
244         "Terrifies nearby monsters so that hopefully they flee the magic user",
245         NULL, NULL,
246         COLOURLESS, 5, 2000, FALSE
247     },
248     {
249         SP_HLD, "hld", "hold monster",
250         SC_POINT, DAM_NONE, ET_HOLD_MONSTER, spell_type_point,
251         "The monster is frozen in his tracks if this is successful",
252         NULL, NULL,
253         COLOURLESS, 5, 2000, FALSE
254     },
255     {
256         SP_STP, "stp", "time stop",
257         SC_PLAYER, DAM_NONE, ET_TIMESTOP, spell_type_player,
258         "All movement in the caverns ceases for a limited duration",
259         NULL, NULL,
260         COLOURLESS, 5, 2500, FALSE
261     },
262     {
263         SP_TEL, "tel", "teleport away",
264         SC_POINT, DAM_NONE, ET_NONE, spell_type_point,
265         "Moves a particular monster around the caverns",
266         NULL, NULL,
267         COLOURLESS, 5, 2000, FALSE
268     },
269     {
270         SP_MFI, "mfi", "magic fire",
271         SC_FLOOD, DAM_FIRE, ET_NONE, spell_type_flood,
272         "This causes a curtain of fire to appear all around you",
273         "The %s cringes from the flame.",
274         NULL,
275         COLOURLESS, 5, 2500, FALSE
276     },
277     {
278         SP_MKW, "mkw", "make wall",
279         SC_OTHER, DAM_NONE, ET_NONE, spell_make_wall,
280         "Makes a wall in the specified place",
281         NULL, NULL,
282         COLOURLESS, 6, 3000, FALSE
283     },
284     {
285         SP_SPH, "sph", "sphere of annihilation",
286         SC_OTHER, DAM_NONE, ET_NONE, spell_create_sphere,
287         "Anything caught in this sphere is instantly killed",
288         NULL, NULL,
289         COLOURLESS, 6, 3500, FALSE
290     },
291     {
292         SP_SUM, "sum", "summon demon",
293         SC_OTHER, DAM_NONE, ET_NONE, spell_summon_demon,
294         "Summons a demon who hopefully helps you out",
295         NULL, NULL,
296         COLOURLESS, 6, 3500, FALSE
297     },
298     {
299         SP_WTW, "wtw", "walk through walls",
300         SC_PLAYER, DAM_NONE, ET_WALL_WALK, spell_type_player,
301         "Allows the caster to walk through walls for a short period of time",
302         NULL, NULL,
303         COLOURLESS, 6, 3800, FALSE
304     },
305     {
306         SP_ALT, "alt", "alter reality",
307         SC_OTHER, DAM_NONE, ET_NONE, spell_alter_reality,
308         "God only knows what this will do",
309         NULL,
310         "Polinneaus won't let you mess with his caverns!",
311         COLOURLESS, 6, 3800, FALSE
312     },
313 };
314 
315 struct book_obfuscation_s
316 {
317     const char* desc;
318     const int weight;
319     const int colour;
320 }
321 book_obfuscation[SP_MAX] =
322 {
323     { "parchment-bound", 800, BROWN,     },
324     { "thick",          1200, RED,       },
325     { "dusty",           800, LIGHTGRAY, },
326     { "leather-bound",   800, BROWN,     },
327     { "heavy",          1200, GREEN,     },
328     { "ancient",         800, DARKGRAY,  },
329     { "buckram",         800, LIGHTGRAY, },
330     { "gilded",          800, YELLOW,    },
331     { "embossed",        800, BLUE,      },
332     { "old",             800, LIGHTGRAY, },
333     { "thin",            400, GREEN,     },
334     { "light",           400, WHITE,     },
335     { "large",          1200, BLUE,      },
336     { "vellum",          800, BROWN,     },
337     { "tan",             800, BROWN,     },
338     { "papyrus",         800, BROWN,     },
339     { "linen",           800, WHITE,     },
340     { "musty",           800, GREEN,     },
341     { "faded",           800, DARKGRAY,  },
342     { "antique",         800, DARKGRAY,  },
343     { "worn out",        800, DARKGRAY,  },
344     { "tattered",        800, LIGHTGRAY, },
345     { "aged",            800, DARKGRAY,  },
346     { "ornate",          800, BLUE,      },
347     { "inconspicuous",   800, LIGHTGRAY, },
348     { "awe-inspiring",   800, WHITE,     },
349     { "stained",         800, BROWN,     },
350     { "mottled",         800, RED,       },
351     { "plaid",           800, RED,       },
352     { "wax-lined",       800, BROWN,     },
353     { "bamboo",          800, YELLOW,    },
354     { "clasped",         800, YELLOW,    },
355     { "well-thumbed",    800, BLUE,      },
356     { "ragged",          800, LIGHTGRAY, },
357     { "dull",            800, DARKGRAY,  },
358     { "canvas",          800, YELLOW,    },
359 /*
360     reserve descriptions for unimplemented spells:
361     chambray
362 */
363 };
364 
365 /* the last cast spell */
366 static spell *last_spell = NULL;
367 
368 /* local functions */
369 static int spell_cast(player *p, spell *s);
370 static void spell_print_success_message(spell *s, monster *m);
371 static void spell_print_failure_message(spell *s, monster *m);
372 static int count_adjacent_water_squares(position pos);
373 static int try_drying_ground(position pos);
374 
375 /* simple wrapper for spell_area_pos_hit() */
376 static gboolean spell_traj_pos_hit(const GList *traj,
377         const damage_originator *damo,
378         gpointer data1, gpointer data2);
379 
380 static gboolean spell_area_pos_hit(position pos,
381         const damage_originator *damo,
382         gpointer data1, gpointer data2);
383 
spell_new(spell_id id)384 spell *spell_new(spell_id id)
385 {
386     spell *nspell;
387 
388     g_assert(id < SP_MAX);
389 
390     nspell = g_malloc0(sizeof(spell));
391     nspell->id = id;
392     nspell->knowledge = 1;
393 
394     return nspell;
395 }
396 
spell_destroy(spell * s)397 void spell_destroy(spell *s)
398 {
399     g_assert(s != NULL);
400     g_free(s);
401 }
402 
spell_serialize(spell * s)403 cJSON *spell_serialize(spell *s)
404 {
405     cJSON *sser = cJSON_CreateObject();
406 
407     cJSON_AddNumberToObject(sser, "id", s->id);
408     cJSON_AddNumberToObject(sser, "knowledge", s->knowledge);
409     cJSON_AddNumberToObject(sser, "used", s->used);
410 
411     return sser;
412 }
413 
spell_deserialize(cJSON * sser)414 spell *spell_deserialize(cJSON *sser)
415 {
416     spell *s = g_malloc0(sizeof(spell));
417 
418     s->id = cJSON_GetObjectItem(sser, "id")->valueint;
419     s->knowledge = cJSON_GetObjectItem(sser, "knowledge")->valueint;
420     s->used = cJSON_GetObjectItem(sser, "used")->valueint;
421 
422     return s;
423 }
424 
spells_serialize(GPtrArray * sparr)425 cJSON *spells_serialize(GPtrArray *sparr)
426 {
427     cJSON *sser = cJSON_CreateArray();
428 
429     for (guint idx = 0; idx < sparr->len; idx++)
430     {
431         spell *s = g_ptr_array_index(sparr, idx);
432         cJSON_AddItemToArray(sser, spell_serialize(s));
433     }
434 
435     return sser;
436 }
437 
spells_deserialize(cJSON * sser)438 GPtrArray *spells_deserialize(cJSON *sser)
439 {
440     GPtrArray *n_spells = g_ptr_array_new_with_free_func(
441             (GDestroyNotify)spell_destroy);
442 
443     for (int idx = 0; idx < cJSON_GetArraySize(sser); idx++)
444     {
445         spell *s = spell_deserialize(cJSON_GetArrayItem(sser, idx));
446         g_ptr_array_add(n_spells, s);
447     }
448 
449     return n_spells;
450 }
451 
spell_sort(gconstpointer a,gconstpointer b)452 int spell_sort(gconstpointer a, gconstpointer b)
453 {
454     gint order;
455     spell *spell_a = *((spell**)a);
456     spell *spell_b = *((spell**)b);
457 
458     if (spell_a->id == spell_b->id)
459         order = 0;
460     else
461         order = strcmp(spell_code(spell_a), spell_code(spell_b));
462 
463     return order;
464 }
465 
466 /* Knowledge of a spell and intelligence make casting easier. */
spell_success_value(player * p,spell * sp)467 static int spell_success_value(player *p, spell *sp)
468 {
469     g_assert(p != NULL && sp != NULL);
470 
471     if (player_get_int(p) < (3 * spell_level(sp)))
472         return 0;
473 
474     return (player_get_int(p) - 2 * (spell_level(sp) - sp->knowledge));
475 }
476 
477 
spell_cast_new(struct player * p)478 int spell_cast_new(struct player *p)
479 {
480     /* check if the player knows any spell */
481     if (!p->known_spells || !p->known_spells->len)
482     {
483         log_add_entry(nlarn->log, "You don't know any spells.");
484         return 0;
485     }
486 
487     /* spell casting is impossible when confused */
488     if (player_effect(p, ET_CONFUSION))
489     {
490         log_add_entry(nlarn->log, "You can't aim your magic!");
491         return 0;
492     }
493 
494     /* show spell selection dialogue */
495     last_spell = display_spell_select("Select a spell to cast", p);
496 
497     /* player aborted spell selection by pressing ESC */
498     if (!last_spell)
499         return 0;
500 
501     return spell_cast(p, last_spell);
502 }
503 
spell_cast_previous(struct player * p)504 int spell_cast_previous(struct player *p)
505 {
506     /* spell casting is impossible when confused */
507     if (player_effect(p, ET_CONFUSION))
508     {
509         log_add_entry(nlarn->log, "You can't aim your magic!");
510         return 0;
511     }
512 
513     /* not casted any spell before */
514     if (!last_spell)
515     {
516         return spell_cast_new(p);
517     }
518 
519     return spell_cast(p, last_spell);
520 }
521 
spell_learn(player * p,spell_id spell_type)522 int spell_learn(player *p, spell_id spell_type)
523 {
524     g_assert(p != NULL && spell_type < SP_MAX);
525 
526     if (!spell_known(p, spell_type))
527     {
528         /* Check if the player's intelligence is sufficient to learn the spell */
529         if ((spells[spell_type].level * 3) > (int)player_get_int(p))
530             /* spell is beyond the players scope */
531             return FALSE;
532 
533         /* Check if the player's level is spell sufficient to learn the spell */
534         if (spells[spell_type].level > (int)p->level)
535             /* spell is beyond the players scope */
536             return FALSE;
537 
538         spell *s = spell_new(spell_type);
539         g_ptr_array_add(p->known_spells, s);
540         return s->knowledge;
541     }
542     else
543     {
544         /* spell already known, improve knowledge */
545         for (guint idx = 0; idx < p->known_spells->len; idx++)
546         {
547             /* search spell */
548             spell *s = (spell *)g_ptr_array_index(p->known_spells, idx);
549 
550             if (s->id == spell_type)
551             {
552                 /* found it */
553                 s->knowledge++;
554                 return s->knowledge;
555             }
556         }
557     }
558 
559     /* should not reach this point, but who knows.. */
560     return FALSE;
561 }
562 
spell_known(player * p,spell_id spell_type)563 int spell_known(player *p, spell_id spell_type)
564 {
565     g_assert(p != NULL && spell_type < SP_MAX);
566 
567     for (guint idx = 0; idx < p->known_spells->len; idx++)
568     {
569         spell *s = g_ptr_array_index(p->known_spells, idx);
570         if (s->id == spell_type)
571         {
572             return s->knowledge;
573         }
574     }
575 
576     return FALSE;
577 }
578 
spell_desc_by_id(spell_id sid)579 gchar* spell_desc_by_id(spell_id sid)
580 {
581     GString *desc = g_string_new(spells[sid].description);
582 
583     const char *stdesc;
584     switch(spell_type_by_id(sid))
585     {
586         case SC_PLAYER:
587             stdesc = "affects the player";
588             break;
589         case SC_POINT:
590             stdesc = "affects a single monster";
591             break;
592         case SC_RAY:
593             stdesc = "emits a ray";
594             break;
595         case SC_FLOOD:
596             stdesc = "creates an effect which fills an area";
597             break;
598         case SC_BLAST:
599             stdesc = "creates an explosion";
600             break;
601         default:
602             stdesc = NULL;
603     }
604 
605     if (stdesc)
606     {
607         g_string_append_printf(desc, " (%s)", stdesc);
608     }
609 
610     g_string_append_c(desc, '.');
611 
612     return g_string_free(desc, FALSE);
613 }
614 
spell_type_player(spell * s,struct player * p)615 static gboolean spell_type_player(spell *s, struct player *p)
616 {
617     effect *e = NULL;
618 
619     g_assert(s != NULL && p != NULL && (spell_type(s) == SC_PLAYER));
620 
621     /* check if the player is already affected by the effect */
622     if ((e = player_effect_get(p, spell_effect(s))))
623     {
624         /* player has cast this spell before */
625         if (effect_type_inc_amount(e->type))
626         {
627             /* The effect amount can be incremented.
628              * Increase the amount of the effect up to the base
629              * effect value * spell knowledge value. */
630             if (e->amount < (effect_type_amount(e->type) * (int)s->knowledge))
631             {
632                 e->amount += effect_type_amount(e->type);
633                 log_add_entry(nlarn->log, "You have extended the power of %s.",
634                         spell_name(s));
635 
636                 /* force recalculation of burdened
637                    status if extending strength */
638                 if (e->type == ET_INC_STR)
639                 {
640                     player_inv_weight_recalc(p->inventory, NULL);
641                 }
642             }
643             else
644             {
645                 /* maximum reached -> indicate failure */
646                 log_add_entry(nlarn->log, "You have already extended the "
647                         "power of %s to the extent of your knowledge.",
648                         spell_name(s));
649 
650                 return FALSE;
651             }
652         }
653         else if (effect_type_inc_duration(e->type))
654         {
655             /* The duration of this effect can be incremented.
656              * Increase the duration of the effect up to the base
657              * effect duration * spell knowledge value. */
658             if (e->turns + effect_type_duration(e->type)
659                 < (effect_type_duration(e->type) * s->knowledge))
660             {
661                 e->turns += effect_type_duration(e->type);
662                 log_add_entry(nlarn->log, "You have extended the duration "
663                         "of %s.", spell_name(s));
664             }
665             else
666             {
667                 /* maximum reached -> indicate failure */
668                 log_add_entry(nlarn->log, "You have already extended the "
669                         "duration of %s to the extent of your knowledge.",
670                         spell_name(s));
671 
672                 return FALSE;
673             }
674         }
675 
676         return TRUE;
677     }
678 
679     e = effect_new(spell_effect(s));
680 
681     /* make effects that are permanent by default non-permanent */
682     /* unless it is the spell of healing, which does work this way */
683     if ((e->turns == 1) && (e->type != ET_INC_HP))
684     {
685         e->turns = 100 * s->knowledge;
686     }
687 
688     if (e->type == ET_INC_HP)
689     {
690         e->amount *= s->knowledge;
691     }
692 
693     player_effect_add(p, e);
694 
695     return TRUE;
696 }
697 
spell_type_point(spell * s,struct player * p)698 static gboolean spell_type_point(spell *s, struct player *p)
699 {
700     monster *m = NULL;
701     position pos;
702     effect *e;
703     char buffer[61];
704     int amount = 0;
705 
706     g_assert(s != NULL && p != NULL && (spell_type(s) == SC_POINT));
707 
708     g_snprintf(buffer, 60, "Select a target for %s.", spell_name(s));
709 
710     /* Allow non-visible positions if the player is blinded. */
711     pos = display_get_position(p, buffer, FALSE, FALSE, 0, FALSE,
712             !player_effect(p, ET_BLINDNESS));
713 
714     /* player pressed ESC */
715     if (!pos_valid(pos))
716     {
717         log_add_entry(nlarn->log, "Aborted.");
718         return FALSE;
719     }
720 
721     if (pos_identical(pos, p->pos))
722     {
723         log_add_entry(nlarn->log, "This spell only works on monsters.");
724         return FALSE;
725     }
726 
727     /* When the player is blinded, check if the position can be reached.
728        As it is possible to target any position, it might be a position
729        the player could not target under normal circumstances. */
730     if (player_effect(p, ET_BLINDNESS) &&
731             !map_pos_is_visible(game_map(nlarn, Z(p->pos)), p->pos, pos))
732     {
733         /* be sure to waste the MPs and give no hints. */
734         return TRUE;
735     }
736 
737     m = map_get_monster_at(game_map(nlarn, Z(p->pos)), pos);
738 
739     if (!m)
740     {
741         if (s->id == SP_DRY)
742             return try_drying_ground(pos);
743 
744         if (!player_effect(p, ET_BLINDNESS))
745         {
746             log_add_entry(nlarn->log, "The is no monster there.");
747             return FALSE;
748         }
749         else
750         {
751             /* The spell didn't do anything, but as the player is blinded
752                assume it was targeted at the position intentionally probing
753                for targets. */
754             return TRUE;
755         }
756     }
757 
758     switch (s->id)
759     {
760         /* charm monster */
761     case SP_CHM:
762         if ((rand_m_n(5, 30) * monster_level(m) - player_get_wis(p)) < 30)
763         {
764             e = effect_new(spell_effect(s));
765             e->turns *= s->knowledge;
766             monster_effect_add(m, e);
767         }
768         else
769         {
770             spell_print_failure_message(s, m);
771         }
772 
773         break; /* SP_CHM */
774 
775         /* dehydration */
776     case SP_DRY:
777         amount = (100 * s->knowledge) + p->level;
778         spell_print_success_message(s, m);
779         monster_damage_take(m, damage_new(DAM_MAGICAL, ATT_MAGIC, amount,
780                                                 DAMO_PLAYER, p));
781         break; /* SP_DRY */
782 
783         /* drain life */
784     case SP_DRL:
785         amount = min(p->hp - 1, (int)p->hp_max / 2);
786 
787         spell_print_success_message(s, m);
788         monster_damage_take(m, damage_new(DAM_MAGICAL, ATT_MAGIC, amount,
789                                                 DAMO_PLAYER, p));
790 
791         player_damage_take(p, damage_new(DAM_MAGICAL, ATT_MAGIC, amount,
792                                          DAMO_PLAYER, NULL), PD_SPELL, SP_DRL);
793 
794         break; /* SP_DRL */
795 
796         /* finger of death */
797     case SP_FGR:
798     {
799         // Lower chances of working against undead and demons.
800         const int roll = (monster_flags(m, UNDEAD) ? 40 :
801                           monster_flags(m, DEMON)  ? 30 : 20);
802 
803         if ((player_get_wis(p) + s->knowledge) > (guint)rand_m_n(10, roll))
804         {
805             spell_print_success_message(s, m);
806             monster_damage_take(m, damage_new(DAM_MAGICAL, ATT_MAGIC, 2000,
807                                                     DAMO_PLAYER, p));
808         }
809         else
810             spell_print_failure_message(s, m);
811         break; /* SP_FGR */
812     }
813 
814         /* polymorph */
815     case SP_PLY:
816         if (chance(5 * (monster_level(m) - 2 * s->knowledge)))
817         {
818             /* It didn't work */
819             spell_print_failure_message(s, m);
820         }
821         else
822         {
823             monster_polymorph(m);
824         }
825         break;
826 
827         /* teleport */
828     case SP_TEL:
829         if (monster_in_sight(m))
830         {
831             log_add_entry(nlarn->log, "The %s disappears.",
832                           monster_name(m));
833         }
834 
835         map *mmap = game_map(nlarn, Z(monster_pos(m)));
836         monster_pos_set(m, mmap, map_find_space(mmap, LE_MONSTER, FALSE));
837         break; /* SP_TEL */
838 
839     default:
840         /* spell has an effect, add that to the monster */
841         g_assert(spell_effect(s) != ET_NONE);
842 
843         e = effect_new(spell_effect(s));
844 
845         if (!e->amount)
846         {
847             e->amount = p->intelligence;
848         }
849 
850         e->amount *= s->knowledge;
851         e = monster_effect_add(m, e);
852 
853         if (e)
854             spell_print_success_message(s, m);
855         else
856             spell_print_failure_message(s, m);
857 
858         break;
859     }
860 
861     return TRUE;
862 }
863 
spell_type_ray(spell * s,struct player * p)864 static gboolean spell_type_ray(spell *s, struct player *p)
865 {
866     g_assert(s != NULL && p != NULL && (spell_type(s) == SC_RAY));
867 
868     char buffer[61];
869 
870     g_snprintf(buffer, 60, "Select a target for the %s.", spell_name(s));
871     /* Allow non-visible positions if the player is blinded. */
872     position target = display_get_position(p, buffer, TRUE, FALSE, 0, FALSE,
873                                            !player_effect(p, ET_BLINDNESS));
874 
875     /* player pressed ESC */
876     if (!pos_valid(target))
877     {
878         log_add_entry(nlarn->log, "Aborted.");
879         return FALSE;
880     }
881 
882     if (pos_identical(target, p->pos))
883     {
884         log_add_entry(nlarn->log, "Why would you want to do that?");
885         return FALSE;
886     }
887 
888     damage_originator damo = { DAMO_PLAYER, p };
889     damage *dam = damage_new(spells[s->id].damage_type, ATT_MAGIC, 0,
890                              damo.ot, damo.originator);
891 
892     /* determine amount of damage */
893     switch (s->id)
894     {
895     case SP_MLE:
896         dam->amount = (1 + rand_1n(5)) * s->knowledge + p->level;
897         break;
898 
899     case SP_SSP:
900         dam->amount = (2 + rand_1n(10)) * s->knowledge + p->level;
901         break;
902 
903     case SP_CLD:
904         dam->amount = (3 + rand_1n(15)) * s->knowledge + p->level;
905         break;
906 
907     case SP_LIT:
908         dam->amount = (4 + rand_1n(20)) * s->knowledge + p->level;
909         break;
910     default:
911         /* this shouldn't happen */
912         break;
913     }
914 
915     /* throw a ray to the selected target */
916     map_trajectory(p->pos, target, &damo, spell_traj_pos_hit,
917                    s, dam, TRUE, '*', spell_colour(s), TRUE);
918 
919     /* The callback functions give a copy of the damage to the specific
920        functions, thus the original has to be destroyed here. */
921     damage_free(dam);
922 
923     return TRUE;
924 }
925 
spell_type_flood(spell * s,struct player * p)926 static gboolean spell_type_flood(spell *s, struct player *p)
927 {
928     position pos;
929     char buffer[81];
930     int radius = 0;
931     int amount = 0;
932     map_tile_t type = LT_NONE;
933 
934     g_assert(s != NULL && p != NULL && (spell_type(s) == SC_FLOOD));
935 
936     g_snprintf(buffer, 60, "Where do you want to place the %s?", spell_name(s));
937     pos = display_get_position(p, buffer, FALSE, FALSE, 0, FALSE, TRUE);
938 
939     /* player pressed ESC */
940     if (!pos_valid(pos))
941     {
942         log_add_entry(nlarn->log, "Aborted.");
943         return FALSE;
944     }
945 
946     switch (s->id)
947     {
948     case SP_CKL:
949         radius = 3;
950         type = LT_CLOUD;
951         amount = (10 * s->knowledge) + p->level;
952         break;
953 
954     case SP_FLO:
955         radius = 4;
956         type = LT_WATER;
957         amount = (25 * s->knowledge) + p->level;
958         break;
959 
960     case SP_MFI:
961         radius = 4;
962         type = LT_FIRE;
963         amount = (15 * s->knowledge) + p->level;
964         break;
965 
966     default:
967         /* this shouldn't happen */
968         break;
969     }
970 
971     area *obstacles = map_get_obstacles(game_map(nlarn, Z(pos)), pos, radius, FALSE);
972     area *range = area_new_circle_flooded(pos, radius, obstacles);
973 
974     if (area_pos_get(range, p->pos)
975             && !display_get_yesno("The spell is going to hit you. " \
976                                   "Cast anyway?", NULL, NULL, NULL))
977     {
978         log_add_entry(nlarn->log, "Aborted.");
979         area_destroy(range);
980         return FALSE;
981     }
982 
983     map_set_tiletype(game_map(nlarn, Z(pos)), range, type, amount);
984     area_destroy(range);
985 
986     return TRUE;
987 }
988 
spell_type_blast(spell * s,struct player * p)989 static gboolean spell_type_blast(spell *s, struct player *p)
990 {
991     g_assert(s != NULL && p != NULL && (spell_type(s) == SC_BLAST));
992 
993     area *ball;
994     position pos;
995     char buffer[61];
996     int amount = 0;
997     int radius = 0;
998     damage_originator damo = { DAMO_PLAYER, p };
999     map *cmap = game_map(nlarn, Z(p->pos));
1000 
1001     switch (s->id)
1002     {
1003         /* currently there is only the fireball */
1004     case SP_BAL:
1005     default:
1006         radius = 2;
1007         amount = (3 + rand_1n(15)) * s->knowledge + p->level;
1008         break;
1009     }
1010 
1011     g_snprintf(buffer, 60, "Point to the center of the %s.", spell_name(s));
1012     /* Allow non-visible positions if the player is blinded. */
1013     pos = display_get_position(p, buffer, FALSE, TRUE, radius, FALSE,
1014             !player_effect(p, ET_BLINDNESS));
1015 
1016     /* player pressed ESC */
1017     if (!pos_valid(pos))
1018     {
1019         log_add_entry(nlarn->log, "Aborted.");
1020         return FALSE;
1021     }
1022 
1023     /* get the affected area to determine if the player would be hit */
1024     ball = area_new_circle_flooded(pos, radius, map_get_obstacles(cmap, pos,
1025                 radius, TRUE));
1026 
1027     gboolean player_affected = area_pos_get(ball, p->pos);
1028     area_destroy(ball);
1029 
1030     if (player_affected
1031         && !display_get_yesno("The spell is going to hit you. Cast anyway?", NULL, NULL, NULL))
1032     {
1033         log_add_entry(nlarn->log, "Aborted.");
1034         return FALSE;
1035     }
1036 
1037     damage *dam = damage_new(spells[s->id].damage_type, ATT_MAGIC,
1038                              amount, DAMO_PLAYER, p);
1039     area_blast(pos, radius, &damo, spell_area_pos_hit, s, dam, '*', spell_colour(s));
1040 
1041     /* destroy the damage as the callbacks deliver a copy */
1042     damage_free(dam);
1043 
1044     return TRUE;
1045 }
1046 
spell_alter_reality(spell * s,player * p)1047 static gboolean spell_alter_reality(spell *s, player *p)
1048 {
1049     map *nlevel;
1050     position pos = { { 0, 0, Z(p->pos) } };
1051 
1052     if (Z(p->pos) == 0)
1053     {
1054         log_add_entry(nlarn->log, spell_msg_fail(s));
1055         return FALSE;
1056     }
1057 
1058     /* reset the player's memory of the current map */
1059     memset(&player_memory_of(p, pos), 0,
1060            MAP_MAX_Y * MAP_MAX_X * sizeof(player_tile_memory));
1061 
1062     map_destroy(game_map(nlarn, Z(p->pos)));
1063 
1064     /* create new map */
1065     nlevel = nlarn->maps[Z(p->pos)] = map_new(Z(p->pos), nlarn_mazefile);
1066 
1067     /* reposition player (if needed) */
1068     if (!map_pos_passable(nlevel, p->pos))
1069     {
1070         p->pos = map_find_space(nlevel, LE_MONSTER, FALSE);
1071     }
1072 
1073     return TRUE;
1074 }
1075 
spell_create_monster(spell * s,struct player * p)1076 gboolean spell_create_monster(spell *s __attribute__((unused)), struct player *p)
1077 {
1078     position mpos;
1079 
1080     /* this spell doesn't work in town */
1081     if (Z(p->pos) == 0)
1082     {
1083         log_add_entry(nlarn->log, "Nothing happens.");
1084         return FALSE;
1085     }
1086 
1087     /* try to find a space for the monster near the player */
1088     mpos = map_find_space_in(game_map(nlarn, Z(p->pos)),
1089                              rect_new_sized(p->pos, 2), LE_MONSTER, FALSE);
1090 
1091     if (pos_valid(mpos))
1092     {
1093         monster_new_by_level(mpos);
1094         return TRUE;
1095     }
1096     else
1097     {
1098         log_add_entry(nlarn->log, "You feel failure.");
1099         return FALSE;
1100     }
1101 }
1102 
spell_create_sphere(spell * s,struct player * p)1103 static gboolean spell_create_sphere(spell *s, struct player *p)
1104 {
1105     g_assert(p != NULL);
1106 
1107     position pos = display_get_new_position(p, p->pos,
1108             "Where do you want to place the sphere?",
1109             FALSE, FALSE, FALSE, 0, TRUE, TRUE);
1110 
1111     if (pos_valid(pos))
1112     {
1113         sphere *sph = sphere_new(pos, p, p->level * 10 * s->knowledge);
1114         g_ptr_array_add(nlarn->spheres, sph);
1115 
1116         return TRUE;
1117     }
1118     else
1119     {
1120         log_add_entry(nlarn->log, "Huh?");
1121 
1122         return FALSE;
1123     }
1124 }
1125 
spell_cure_poison(spell * s,struct player * p)1126 static gboolean spell_cure_poison(spell *s __attribute__((unused)), struct player *p)
1127 {
1128     effect *eff = NULL;
1129 
1130     g_assert(p != NULL);
1131 
1132     if ((eff = player_effect_get(p, ET_POISON)))
1133     {
1134         player_effect_del(p, eff);
1135         return TRUE;
1136     }
1137     else
1138     {
1139         log_add_entry(nlarn->log, "You weren't even poisoned!");
1140         return FALSE;
1141     }
1142 }
1143 
spell_cure_blindness(spell * s,struct player * p)1144 static gboolean spell_cure_blindness(spell *s __attribute__((unused)), struct player *p)
1145 {
1146     effect *eff = NULL;
1147 
1148     g_assert(p != NULL);
1149 
1150     if ((eff = player_effect_get(p, ET_BLINDNESS)))
1151     {
1152         player_effect_del(p, eff);
1153         return TRUE;
1154     }
1155     else
1156     {
1157         log_add_entry(nlarn->log, "You weren't even blinded!");
1158         return FALSE;
1159     }
1160 }
1161 
spell_phantasmal_forces(spell * s,struct player * p)1162 static gboolean spell_phantasmal_forces(spell *s, struct player *p)
1163 {
1164     position mpos;
1165     monster *m = NULL;
1166 
1167     mpos = display_get_position(p, "Choose a target for phantasmal forces.",
1168                                 FALSE, FALSE, 0, TRUE, TRUE);
1169 
1170     if (!pos_valid(mpos))
1171     {
1172         return FALSE;
1173     }
1174 
1175     m = map_get_monster_at(game_map(nlarn, Z(mpos)), mpos);
1176 
1177     if (m == NULL)
1178     {
1179         return FALSE;
1180     }
1181 
1182     if ((player_get_int(p) + s->knowledge) > monster_int(m))
1183     {
1184         if (monster_in_sight(m))
1185         {
1186             log_add_entry(nlarn->log, spell_msg_succ(s), monster_name(m));
1187         }
1188 
1189         monster_effect_add(m, effect_new(ET_SCARED));
1190         return TRUE;
1191     }
1192     else
1193     {
1194         if (monster_in_sight(m))
1195         {
1196             log_add_entry(nlarn->log, spell_msg_fail(s), monster_name(m));
1197         }
1198         return FALSE;
1199     }
1200 }
1201 
spell_scare_monsters(spell * s,struct player * p)1202 static gboolean spell_scare_monsters(spell *s, struct player *p)
1203 {
1204     monster *m;
1205     int count = 0;
1206     position pos = pos_invalid;
1207     map *cmap = game_map(nlarn, Z(p->pos));
1208     Z(pos) = Z(p->pos);
1209 
1210     /* the radius of this spell is determined by the player's level of
1211        spell knowledge */
1212     area *a = area_new_circle(p->pos, 1 + s->knowledge, FALSE);
1213 
1214     for (int y = a->start_y; y < a->start_y + a->size_y; y++)
1215     {
1216         Y(pos) = y;
1217 
1218         for (int x = a->start_x; x < a->start_x + a->size_x; x++)
1219         {
1220             X(pos) = x;
1221 
1222             if (!pos_valid(pos))
1223             {
1224                 /* possibly reached the level boundary */
1225                 continue;
1226             }
1227 
1228             m = map_get_monster_at(cmap, pos);
1229 
1230             /* no monster at position? */
1231             if (m == NULL) continue;
1232 
1233             /* there is a monster, check if it is affected */
1234             if (player_get_int(p) > (int)monster_int(m))
1235             {
1236                 monster_effect_add(m, effect_new(ET_SCARED));
1237                 count++;
1238             }
1239         }
1240     }
1241 
1242     /* free allocated memory */
1243     area_destroy(a);
1244 
1245     return (count > 0);
1246 }
1247 
spell_summon_demon(spell * s,struct player * p)1248 static gboolean spell_summon_demon(spell *s, struct player *p)
1249 {
1250     monster *demon;
1251     position pos;
1252 
1253     /* find a place near the player for the demon servant */
1254     pos = map_find_space_in(game_map(nlarn, Z(p->pos)),
1255                             rect_new_sized(p->pos, 2),
1256                             LE_MONSTER, FALSE);
1257 
1258     if (!pos_valid(pos))
1259         return FALSE;
1260 
1261     /* generate a demon */
1262     demon = monster_new(min(MT_DEMONLORD_I + (s->knowledge - 1),
1263                             MT_DEMONLORD_VII), pos, NULL);
1264 
1265     /* turn the demon into a servant */
1266     monster_update_action(demon, MA_SERVE);
1267 
1268     return TRUE;
1269 }
1270 
spell_make_wall(spell * s,player * p)1271 static gboolean spell_make_wall(spell *s __attribute__((unused)), player *p)
1272 {
1273     position pos;
1274 
1275     pos = display_get_new_position(p, p->pos,
1276                                    "Select a position where you want to place a wall.",
1277                                    FALSE, FALSE, FALSE, 0, FALSE, TRUE);
1278 
1279     if (pos_identical(pos, p->pos))
1280     {
1281         log_add_entry(nlarn->log, "You are actually standing there.");
1282         return FALSE;
1283     }
1284     else if (!pos_valid(pos))
1285     {
1286         log_add_entry(nlarn->log, "No wall today.");
1287         return FALSE;
1288     }
1289 
1290     map *pmap = game_map(nlarn, Z(p->pos));
1291     if (map_tiletype_at(pmap, pos) != LT_WALL)
1292     {
1293         map_tile *tile = map_tile_at(pmap, pos);
1294 
1295         /* destroy all items at that position */
1296         if (tile->ilist != NULL)
1297         {
1298             inv_destroy(tile->ilist, TRUE);
1299             tile->ilist = NULL;
1300         }
1301 
1302         sobject_destroy_at(p, pmap, pos);
1303 
1304         log_add_entry(nlarn->log, "You have created a wall.");
1305 
1306         tile->type = tile->base_type = LT_WALL;
1307 
1308         monster *m;
1309         if ((m = map_get_monster_at(pmap, pos)))
1310         {
1311             if (monster_type(m) != MT_XORN)
1312             {
1313                 if (monster_in_sight(m))
1314                 {
1315                     /* briefly display the new monster before it dies */
1316                     display_paint_screen(nlarn->p);
1317                     g_usleep(250000);
1318 
1319                     log_add_entry(nlarn->log, "The %s is trapped in the wall!",
1320                                   monster_get_name(m));
1321                 }
1322 
1323                 monster_die(m, nlarn->p);
1324             }
1325         }
1326 
1327         return TRUE;
1328     }
1329     else
1330     {
1331         log_add_entry(nlarn->log, "There was a wall already..");
1332         return FALSE;
1333     }
1334 }
1335 
spell_vaporize_rock(spell * sp,player * p)1336 gboolean spell_vaporize_rock(spell *sp __attribute__((unused)), player *p)
1337 {
1338     monster *m;
1339     position pos;
1340     map *pmap = game_map(nlarn, Z(p->pos));
1341 
1342     pos = display_get_new_position(p, p->pos,
1343                                    "What do you want to vaporize?",
1344                                    FALSE, FALSE, FALSE, 0, FALSE, TRUE);
1345 
1346     if (!pos_valid(pos))
1347     {
1348         log_add_entry(nlarn->log, "So you chose not to vaporize anything.");
1349         return FALSE;
1350     }
1351 
1352     if (map_tiletype_at(pmap, pos) == LT_WALL)
1353     {
1354         map_tiletype_set(pmap, pos, LT_FLOOR);
1355         p->stats.vandalism++;
1356     }
1357 
1358     if ((m = map_get_monster_at(pmap, pos)))
1359     {
1360         /* xorns take damage from vpr */
1361         if (monster_type(m) == MT_XORN)
1362         {
1363             monster_damage_take(m, damage_new(DAM_PHYSICAL, ATT_NONE,
1364                                               divert(200, 10), DAMO_PLAYER, p));
1365         }
1366         else if (monster_in_sight(m))
1367         {
1368             log_add_entry(nlarn->log, "The %s can't be vaporized.",
1369                           monster_get_name(m));
1370         }
1371     }
1372 
1373     sobject_destroy_at(p, pmap, pos);
1374 
1375     return TRUE;
1376 }
1377 
1378 
book_desc(spell_id book_id)1379 char *book_desc(spell_id book_id)
1380 {
1381     g_assert(book_id < SP_MAX);
1382     return (char *)book_obfuscation[nlarn->book_desc_mapping[book_id]].desc;
1383 }
1384 
book_weight(item * book)1385 int book_weight(item *book)
1386 {
1387     g_assert (book != NULL && book->type == IT_BOOK);
1388     return book_obfuscation[nlarn->book_desc_mapping[book->id]].weight;
1389 }
1390 
book_colour(item * book)1391 int book_colour(item *book)
1392 {
1393     g_assert (book != NULL && book->type == IT_BOOK);
1394     return book_obfuscation[nlarn->book_desc_mapping[book->id]].colour;
1395 }
1396 
book_read(struct player * p,item * book)1397 item_usage_result book_read(struct player *p, item *book)
1398 {
1399     item_usage_result result = { FALSE, FALSE };
1400     gchar *desc = item_describe(book, player_item_known(p, book),
1401                                 TRUE, TRUE);
1402 
1403     if (player_effect(p, ET_BLINDNESS))
1404     {
1405         log_add_entry(nlarn->log, "As you are blind you can't read %s.",
1406                       desc);
1407 
1408         g_free(desc);
1409         return result;
1410     }
1411 
1412     if (book->cursed && book->blessed_known)
1413     {
1414         log_add_entry(nlarn->log, "You'd rather not read this cursed book.");
1415         g_free(desc);
1416         return result;
1417     }
1418 
1419     log_add_entry(nlarn->log, "You start reading %s.", desc);
1420 
1421     /*
1422      * Try to complete reading the book.
1423      * Reading a book takes ten turns per spell level.
1424      */
1425     if (!player_make_move(p, 10 * spell_level_by_id(book->id),
1426                           TRUE, "reading %s", desc))
1427     {
1428         /* the action has been aborted */
1429         g_free(desc);
1430         return result;
1431     }
1432 
1433     g_free(desc);
1434 
1435     /* the book has successfully been read - increase number of books read */
1436     p->stats.books_read++;
1437 
1438     /* cursed spell books have nasty effects */
1439     if (book->cursed)
1440     {
1441         log_add_entry(nlarn->log, "There was something wrong with this book! " \
1442                       "It crumbles to dust.");
1443 
1444         player_mp_lose(p, rand_0n(p->mp));
1445         result.used_up = TRUE;
1446     }
1447     else
1448     {
1449         switch (spell_learn(p, book->id))
1450         {
1451         case 0:
1452             log_add_entry(nlarn->log, "You cannot understand the content of this book.");
1453             // Bad pun.
1454             if (strcmp(book_desc(book->id), "dull") == 0)
1455                 log_add_entry(nlarn->log, "It seems really boring, though.");
1456             break;
1457 
1458         case 1:
1459             /* learnt spell */
1460             log_add_entry(nlarn->log, "You master the spell %s.", book_name(book));
1461 
1462             result.used_up = TRUE;
1463             result.identified = TRUE;
1464             break;
1465 
1466         default:
1467             /* improved knowledge of spell */
1468             log_add_entry(nlarn->log, "You improved your knowledge of the spell %s.",
1469                           book_name(book));
1470 
1471             result.used_up = TRUE;
1472             result.identified = TRUE;
1473             break;
1474         }
1475 
1476         /* five percent chance to increase intelligence */
1477         if (result.used_up && chance(2))
1478         {
1479             log_add_entry(nlarn->log, "Reading makes you ingenious.");
1480             p->intelligence++;
1481         }
1482     }
1483 
1484     return result;
1485 }
1486 
spell_cast(player * p,spell * s)1487 static int spell_cast(player *p, spell *s)
1488 {
1489     int turns = 0;
1490     gboolean well_done = FALSE;
1491 
1492     /* insufficient mana */
1493     if (p->mp < spell_level(s))
1494     {
1495         log_add_entry(nlarn->log, "You lack the power to cast %s.",
1496                       spell_name(s));
1497 
1498         return 0;
1499     }
1500     else if (spell_success_value(p, s) < 1)
1501     {
1502         log_add_entry(nlarn->log, "This spell is too difficult for you.");
1503         return 0;
1504     }
1505 
1506     log_add_entry(nlarn->log, "You cast %s.", spell_name(s));
1507 
1508     /* time usage */
1509     turns = 1;
1510 
1511     /* bad luck, low intelligence */
1512     if (chance(1) || spell_success_value(p, s) < (int)rand_1n(16))
1513     {
1514         log_add_entry(nlarn->log, "It didn't work!");
1515         player_mp_lose(p, spell_level(s));
1516 
1517         return turns;
1518     }
1519 
1520     /* call the spell's function */
1521     well_done = spells[s->id].function(s, p);
1522 
1523     if (!well_done)
1524         return 0;
1525 
1526     /* spell has been cast successfully, set mp usage accordingly */
1527     player_mp_lose(p, spell_level(s));
1528 
1529     /* increase number of spells cast */
1530     p->stats.spells_cast++;
1531 
1532     /* increase usage counter for this specific spell */
1533     s->used++;
1534 
1535     return turns;
1536 }
1537 
spell_print_success_message(spell * s,monster * m)1538 static void spell_print_success_message(spell *s, monster *m)
1539 {
1540     g_assert(s != NULL && m != NULL);
1541 
1542     /* invisible monster -> no message */
1543     if (!monster_in_sight(m))
1544     {
1545         log_add_entry(nlarn->log, "You think you've hit something.");
1546         return;
1547     }
1548 
1549     /* no message defined */
1550     if (spell_msg_succ(s) == NULL)
1551         return;
1552 
1553     log_add_entry(nlarn->log, spell_msg_succ(s), monster_get_name(m));
1554 }
1555 
spell_print_failure_message(spell * s,monster * m)1556 static void spell_print_failure_message(spell *s, monster *m)
1557 {
1558     g_assert(s != NULL && m != NULL);
1559 
1560     /* invisible monster -> no message */
1561     if (!monster_in_sight(m))
1562         return;
1563 
1564     /* no message defined */
1565     if (spell_msg_fail(s) == NULL)
1566         return;
1567 
1568     log_add_entry(nlarn->log, spell_msg_fail(s), monster_get_name(m));
1569 }
1570 
count_adjacent_water_squares(position pos)1571 static int count_adjacent_water_squares(position pos)
1572 {
1573     position p = pos_invalid;
1574     Z(p) = Z(pos);
1575 
1576     int count = 0;
1577     for (X(p) = X(pos) - 1; X(p) <= X(pos) + 1; X(p)++)
1578         for (Y(p) = Y(pos) - 1; Y(p) <= Y(pos) + 1; Y(p)++)
1579         {
1580             if (!pos_valid(p))
1581                 continue;
1582 
1583             if (pos_identical(p, pos))
1584                 continue;
1585 
1586             const map_tile *tile = map_tile_at(game_map(nlarn, Z(pos)), p);
1587             if (tile->type == LT_WATER || tile->type == LT_DEEPWATER)
1588                 count++;
1589         }
1590 
1591     return count;
1592 }
1593 
try_drying_ground(position pos)1594 static int try_drying_ground(position pos)
1595 {
1596     map_tile *tile = map_tile_at(game_map(nlarn, Z(pos)), pos);
1597     if (tile->type == LT_DEEPWATER)
1598     {
1599         /* success chance depends on number of adjacent water squares */
1600         const int adj_water = count_adjacent_water_squares(pos);
1601         if ((int)rand_1n(9) <= adj_water)
1602         {
1603             log_add_entry(nlarn->log, "Nothing happens.");
1604             return FALSE;
1605         }
1606 
1607         tile->type = LT_WATER;
1608         log_add_entry(nlarn->log, "The water is more shallow now.");
1609         return TRUE;
1610     }
1611     else if (tile->type == LT_WATER)
1612     {
1613         /* success chance depends on number of adjacent water squares */
1614         const int adj_water = count_adjacent_water_squares(pos);
1615         if ((int)rand_1n(9) <= adj_water)
1616         {
1617             log_add_entry(nlarn->log, "Nothing happens.");
1618             return FALSE;
1619         }
1620 
1621         if (tile->base_type == LT_NONE)
1622             tile->type = LT_DIRT;
1623         else
1624             tile->type = tile->base_type;
1625 
1626         if (tile->timer)
1627             tile->timer = 0;
1628 
1629         log_add_entry(nlarn->log, "The water evaporates!");
1630         return TRUE;
1631     }
1632     return FALSE;
1633 }
1634 
spell_traj_pos_hit(const GList * traj,const damage_originator * damo,gpointer data1,gpointer data2)1635 static gboolean spell_traj_pos_hit(const GList *traj,
1636         const damage_originator *damo,
1637         gpointer data1, gpointer data2)
1638 {
1639     position pos;
1640     pos_val(pos) = GPOINTER_TO_UINT(traj->data);
1641 
1642     return spell_area_pos_hit(pos, damo, data1, data2);
1643 }
1644 
spell_area_pos_hit(position pos,const damage_originator * damo,gpointer data1,gpointer data2)1645 static gboolean spell_area_pos_hit(position pos,
1646         const damage_originator *damo,
1647         gpointer data1, gpointer data2)
1648 {
1649     spell *sp = (spell *)data1;
1650     damage *dam = (damage *)data2;
1651     map *cmap = game_map(nlarn, Z(pos));
1652     sobject_t mst = map_sobject_at(cmap, pos);
1653     monster *m = map_get_monster_at(cmap, pos);
1654     item_erosion_type iet;
1655     gboolean terminated = FALSE;
1656 
1657     /* determine if the spell causes item erosion */
1658     switch (sp->id)
1659     {
1660     case SP_BAL:
1661         iet = IET_BURN;
1662         break;
1663 
1664     default:
1665         iet = IET_NONE;
1666         break;
1667     }
1668 
1669     /* The spell hit a sobject. */
1670     if (mst > LS_NONE)
1671     {
1672         if (mst == LS_MIRROR && fov_get(nlarn->p->fv, pos))
1673         {
1674             /* reflection is handled in map_trajectory, but we need
1675                to generate a message here if the mirror is visible */
1676             log_add_entry(nlarn->log, "The mirror reflects the %s!",spell_name(sp));
1677             return terminated;
1678         }
1679 
1680         if (mst == LS_STATUE
1681             && (sp->id == SP_BAL || sp->id == SP_LIT)
1682             && (game_difficulty(nlarn) <= 2))
1683         {
1684         /* fireball and lightning destroy statues up to diff. level 2 */
1685             sobject_destroy_at(damo->originator, cmap, pos);
1686             terminated = TRUE;
1687         }
1688 
1689         if (mst == LS_CLOSEDDOOR && (spell_level(sp) > 2))
1690         {
1691             /* Blast the door away */
1692             sobject_destroy_at(damo->originator, cmap, pos);
1693             terminated = TRUE;
1694         }
1695     }
1696 
1697     /* The spell hit a monster */
1698     if (m != NULL)
1699     {
1700         spell_print_success_message(sp, m);
1701 
1702         /* erode the monster's inventory */
1703         if (iet > IET_NONE)
1704             inv_erode(monster_inv(m), iet, FALSE, NULL);
1705 
1706         monster_damage_take(m, damage_copy(dam));
1707 
1708         /*
1709          * If the monster is at least of human size, the spell stops at
1710          * the monster, otherwise it passes and may hit other monsters
1711          */
1712         if (monster_size(m) >= MEDIUM)
1713             terminated = TRUE;
1714     }
1715 
1716     /* The spell hit the player */
1717     if (pos_identical(nlarn->p->pos, pos))
1718     {
1719         if (player_effect(nlarn->p, ET_REFLECTION))
1720         {
1721             /* The player reflects the spell. Actual handling of the reflection
1722                is done in map_trajectory, just give a message here. */
1723             log_add_entry(nlarn->log, "Your amulet reflects the %s!", spell_name(sp));
1724         }
1725         else
1726         {
1727             int evasion = nlarn->p->level/(2+game_difficulty(nlarn)/2)
1728                           + player_get_dex(nlarn->p)
1729                           - 10
1730                           - game_difficulty(nlarn);
1731 
1732             // Automatic hit if paralysed or overstrained.
1733             if (player_effect(nlarn->p, ET_PARALYSIS)
1734                 || player_effect(nlarn->p, ET_OVERSTRAINED))
1735                 evasion = 0;
1736             else
1737             {
1738                 if (player_effect(nlarn->p, ET_BLINDNESS))
1739                     evasion /= 4;
1740                 if (player_effect(nlarn->p, ET_CONFUSION))
1741                     evasion /= 2;
1742                 if (player_effect(nlarn->p, ET_BURDENED))
1743                     evasion /= 2;
1744             }
1745 
1746             if (evasion >= (int)rand_1n(21))
1747             {
1748                 if (!player_effect(nlarn->p, ET_BLINDNESS))
1749                 {
1750                     log_add_entry(nlarn->log, "The %s whizzes by you!", spell_name(sp));
1751                 }
1752 
1753                 /* missed */
1754                 terminated = FALSE;
1755             }
1756             else
1757             {
1758                 log_add_entry(nlarn->log, "The %s hits you!", spell_name(sp));
1759                 player_damage_take(nlarn->p, damage_copy(dam), PD_SPELL, sp->id);
1760 
1761                 /* erode the player's inventory */
1762                 if (iet > IET_NONE)
1763                 {
1764                     /*
1765                      * Filter equipped and exposed items, e.g.
1766                      * a body armour will not be affected by erosion
1767                      * when the player wears a cloak over it.
1768                      */
1769                     inv_erode(&(nlarn->p->inventory), iet, TRUE,
1770                             player_item_filter_unequippable);
1771                 }
1772 
1773                 /* hit */
1774                 terminated = TRUE;
1775             }
1776         } /* The spell wasn't reflected */
1777     } /* The spell hit the player's position */
1778 
1779     if (iet > IET_NONE && map_ilist_at(cmap, pos))
1780     {
1781         /* there are items at the given map position, erode them */
1782         inv_erode(map_ilist_at(cmap, pos), iet,
1783                 fov_get(nlarn->p->fv, pos), NULL);
1784     }
1785 
1786     return terminated;
1787 }
1788