1 /*
2  * player.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 <math.h>
21 #include <stdlib.h>
22 #include <string.h>
23 
24 #include "cJSON.h"
25 #include "config.h"
26 #include "container.h"
27 #include "display.h"
28 #include "fov.h"
29 #include "game.h"
30 #include "extdefs.h"
31 #include "player.h"
32 #include "random.h"
33 #include "scoreboard.h"
34 #include "sobjects.h"
35 
36 const char *player_sex_str[PS_MAX] = {"not defined", "male", "female"};
37 
38 static const char aa1[] = "mighty evil master";
39 static const char aa2[] = "apprentice demi-god";
40 static const char aa3[] = "minor demi-god";
41 static const char aa4[] = "major demi-god";
42 static const char aa5[] = "minor deity";
43 static const char aa6[] = "major deity";
44 static const char aa7[] = "novice guardian";
45 static const char aa8[] = "apprentice guardian";
46 
47 static const char *player_level_desc[] =
48 {
49     "novice explorer",     "apprentice explorer", "practiced explorer", /* -3 */
50     "expert explorer",     "novice adventurer",   "adventurer",         /* -6 */
51     "apprentice conjurer", "conjurer",            "master conjurer",    /*  -9 */
52     "apprentice mage",     "mage",                "experienced mage",   /* -12 */
53     "master mage",         "apprentice warlord",  "novice warlord",     /* -15 */
54     "expert warlord",      "master warlord",      "apprentice gorgon",  /* -18 */
55     "gorgon",              "practiced gorgon",    "master gorgon",      /* -21 */
56     "demi-gorgon",         "evil master",         "great evil master",  /* -24 */
57     aa1, aa1, aa1, /* -27*/
58     aa1, aa1, aa1, /* -30*/
59     aa1, aa1, aa1, /* -33*/
60     aa1, aa1, aa1, /* -36*/
61     aa1, aa1, aa1, /* -39*/
62     aa2, aa2, aa2, /* -42*/
63     aa2, aa2, aa2, /* -45*/
64     aa2, aa2, aa2, /* -48*/
65     aa3, aa3, aa3, /* -51*/
66     aa3, aa3, aa3, /* -54*/
67     aa3, aa3, aa3, /* -57*/
68     aa4, aa4, aa4, /* -60*/
69     aa4, aa4, aa4, /* -63*/
70     aa4, aa4, aa4, /* -66*/
71     aa5, aa5, aa5, /* -69*/
72     aa5, aa5, aa5, /* -72*/
73     aa5, aa5, aa5, /* -75*/
74     aa6, aa6, aa6, /* -78*/
75     aa6, aa6, aa6, /* -81*/
76     aa6, aa6, aa6, /* -84*/
77     aa7, aa7, aa7, /* -87*/
78     aa8, aa8, aa8, /* -90*/
79     aa8, aa8, aa8, /* -93*/
80     "earth guardian", "air guardian",  "fire guardian",    /* -96*/
81     "water guardian", "time guardian", "ethereal guardian",/* -99*/
82     "The Creator", /* 100*/
83 };
84 
85 /*
86     table of experience needed to be a certain level of player
87     FIXME: this is crap. Use a formula instead an get rid of this...
88  */
89 static const guint32 player_lvl_exp[] =
90 {
91     0, 10, 20, 40, 80, 160, 320, 640, 1280, 2560, 5120,                                        /*  1-11 */
92     10240, 20480, 40960, 100000, 200000, 400000, 700000, 1000000,                              /* 12-19 */
93     2000000, 3000000, 4000000, 5000000, 6000000, 8000000, 10000000,                            /* 20-26 */
94     12000000, 14000000, 16000000, 18000000, 20000000, 22000000, 24000000, 26000000, 28000000,  /* 27-35 */
95     30000000, 32000000, 34000000, 36000000, 38000000, 40000000, 42000000, 44000000, 46000000,  /* 36-44 */
96     48000000, 50000000, 52000000, 54000000, 56000000, 58000000, 60000000, 62000000, 64000000,  /* 45-53 */
97     66000000, 68000000, 70000000, 72000000, 74000000, 76000000, 78000000, 80000000, 82000000,  /* 54-62 */
98     84000000, 86000000, 88000000, 90000000, 92000000, 94000000, 96000000, 98000000, 100000000, /* 63-71 */
99     105000000, 110000000, 115000000, 120000000, 125000000, 130000000, 135000000, 140000000,    /* 72-79 */
100     145000000, 150000000, 155000000, 160000000, 165000000, 170000000, 175000000, 180000000,    /* 80-87 */
101     185000000, 190000000, 195000000, 200000000, 210000000, 220000000, 230000000, 240000000,    /* 88-95 */
102     250000000, 260000000, 270000000, 280000000, 290000000, 300000000                           /* 96-101*/
103 };
104 
105 /* function declarations */
106 static void player_autopickup(player *p);
107 static guint player_item_pickup(player *p, inventory **inv, item *it, gboolean ask);
player_item_pickup_all(player * p,inventory ** inv,item * it)108 static inline void player_item_pickup_all(player *p, inventory **inv, item *it)
109 {
110     player_item_pickup(p, inv, it, FALSE);
111 }
112 
player_item_pickup_ask(player * p,inventory ** inv,item * it)113 static inline void player_item_pickup_ask(player *p, inventory **inv, item *it)
114 {
115     player_item_pickup(p, inv, it, TRUE);
116 }
117 
118 static void player_sobject_memorize(player *p, sobject_t sobject, position pos);
119 static int player_sobjects_sort(gconstpointer a, gconstpointer b);
120 static cJSON *player_memory_serialize(player *p, position pos);
121 static void player_memory_deserialize(player *p, position pos, cJSON *mser);
122 static char *player_equipment_list(player *p);
123 static char *player_create_obituary(player *p, score_t *score, GList *scores);
124 static void player_memorial_file_save(player *p, const char *text);
125 static int item_filter_equippable(item *it);
126 static int item_filter_dropable(item *it);
127 static int player_item_filter_multiple(player *p, item *it);
128 
player_new()129 player *player_new()
130 {
131     player *p;
132     item *it;
133 
134     /* initialize player */
135     p = g_malloc0(sizeof(player));
136 
137     p->strength     = 12;
138     p->constitution = 12;
139     p->intelligence = 12;
140     p->dexterity    = 12;
141     p->wisdom       = 12;
142 
143     // some base stats
144     p->hp = p->hp_max = (p->constitution + 5);
145     p->mp = p->mp_max = (p->intelligence + 5);
146 
147     p->level = p->stats.max_level = 1;
148 
149     /* set the player's default speed */
150     p->speed = NORMAL;
151 
152     p->known_spells = g_ptr_array_new_with_free_func(
153             (GDestroyNotify)spell_destroy);
154     p->effects = g_ptr_array_new();
155     p->inventory = inv_new(p);
156 
157     inv_callbacks_set(p->inventory, &player_inv_pre_add, &player_inv_weight_recalc,
158                       NULL, &player_inv_weight_recalc);
159 
160     it = item_new(IT_ARMOUR, AT_LEATHER);
161     it->bonus = 1;
162     player_item_identify(p, NULL, it);
163     inv_add(&p->inventory, it);
164     player_item_equip(p, NULL, it);
165 
166     it = item_new(IT_WEAPON, WT_DAGGER);
167     player_item_identify(p, NULL, it);
168     inv_add(&p->inventory, it);
169     player_item_equip(p, NULL, it);
170 
171     /* some items are always known */
172     p->identified_potions[PO_CURE_DIANTHR] = TRUE;
173     p->identified_potions[PO_WATER] = TRUE;
174     p->identified_scrolls[ST_BLANK] = TRUE;
175     /* identify all non-unique armour items */
176     for (armour_t at = 0; at < AT_MAX; at++)
177         if (!armours[at].unique) p->identified_armour[at] = TRUE;
178 
179     /* initialize the field of vision */
180     p->fv = fov_new();
181 
182     return p;
183 }
184 
185 static const char preset_min = 'a';
186 static char preset_max = 'f';
187 
188 const char *player_bonus_stat_desc[] = {
189     "Strong character",
190     "Agile character",
191     "Tough character",
192     "Smart character",
193     "Randomly selected character preset",
194     "All stats assigned randomly"
195 };
196 
player_select_bonus_stats()197 char player_select_bonus_stats()
198 {
199     int selection = 0;
200     GString *text = g_string_new("\n");
201     for (int idx = preset_min; idx <=  preset_max; idx++)
202     {
203         g_string_append_printf(text, "  `lightgreen`%c`end`) %s\n",
204                 idx, player_bonus_stat_desc[idx - preset_min]);
205     }
206      g_string_append_c(text, '\n');
207 
208     while (selection < preset_min || selection > preset_max)
209     {
210         selection = display_show_message("Choose a character build", text->str, 0);
211     }
212     g_string_free(text, TRUE);
213 
214     return selection;
215 }
216 
player_assign_bonus_stats(player * p,char preset)217 gboolean player_assign_bonus_stats(player *p, char preset)
218 {
219     if (preset < preset_min || preset > preset_max)
220         return FALSE;
221 
222     if (preset == 'e')
223         preset = 'a' + rand_0n(4);
224 
225     // Allow choice between:
226     // * strong Fighter (Str 20  Dex 15  Con 16  Int 12  Wis 12)
227     // * agile Rogue    (Str 15  Dex 20  Con 14  Int 12  Wis 14)
228     // * hardy Fighter  (Str 16  Dex 12  Con 20  Int 12  Wis 15)
229     // * arcane scholar (Str 12  Dex 14  Con 12  Int 20  Wis 17)
230     switch (preset)
231     {
232     case 'a': // strong Fighter
233         p->strength     += 8;
234         p->dexterity    += 3;
235         p->constitution += 4;
236         p->intelligence += 0;
237         p->wisdom       += 0;
238         break;
239     case 'b': // Rogue-style character
240         p->strength     += 3;
241         p->dexterity    += 8;
242         p->constitution += 2;
243         p->intelligence += 0;
244         p->wisdom       += 2;
245         break;
246     case 'c': // hardy Fighter
247         p->strength     += 4;
248         p->dexterity    += 0;
249         p->constitution += 8;
250         p->intelligence += 0;
251         p->wisdom       += 3;
252         break;
253     case 'd': // arcane scholar
254         p->strength     += 0;
255         p->dexterity    += 2;
256         p->constitution += 0;
257         p->intelligence += 8;
258         p->wisdom       += 5;
259         break;
260     case 'f': // random character
261     {
262         int bonus = 15;
263         while (bonus-- > 0)
264         {
265             switch (rand_1n(6))
266             {
267             case 1:
268                 p->strength++;
269                 break;
270             case 2:
271                 p->dexterity++;
272                 break;
273             case 3:
274                 p->constitution++;
275                 break;
276             case 4:
277                 p->intelligence++;
278                 break;
279             case 5:
280                 p->wisdom++;
281                 break;
282             }
283         }
284     }
285     }
286 
287     p->stats.str_orig = p->strength;
288     p->stats.con_orig = p->constitution;
289     p->stats.int_orig = p->intelligence;
290     p->stats.dex_orig = p->dexterity;
291     p->stats.wis_orig = p->wisdom;
292 
293     // Recalculate hp and mp because they depend on Con and Int, respectively.
294     p->hp = p->hp_max = (p->constitution + 5);
295     p->mp = p->mp_max = (p->intelligence + 5);
296 
297     return TRUE;
298 }
299 
player_destroy(player * p)300 void player_destroy(player *p)
301 {
302     g_assert(p != NULL);
303 
304     /* release spells */
305     g_ptr_array_free(p->known_spells, TRUE);
306 
307     /* release effects */
308     for (guint idx = 0; idx < p->effects->len; idx++)
309     {
310         gpointer effect_id = g_ptr_array_index(p->effects, idx);
311         effect *e = game_effect_get(nlarn, effect_id);
312 
313         if (e->item == NULL)
314         {
315             effect_destroy(e);
316         }
317     }
318 
319     g_ptr_array_free(p->effects, TRUE);
320 
321     /* free memorized stationary objects */
322     if (p->sobjmem != NULL)
323     {
324         g_array_free(p->sobjmem, TRUE);
325     }
326 
327     inv_destroy(p->inventory, FALSE);
328 
329     if (p->name)
330     {
331         g_free(p->name);
332     }
333 
334     /* clean the FOV */
335     fov_free(p->fv);
336 
337     g_free(p);
338 }
339 
player_serialize(player * p)340 cJSON *player_serialize(player *p)
341 {
342     cJSON *obj;
343     cJSON *pser = cJSON_CreateObject();
344 
345     cJSON_AddStringToObject(pser, "name", p->name);
346     cJSON_AddNumberToObject(pser, "sex", p->sex);
347 
348     cJSON_AddNumberToObject(pser, "strength", p->strength);
349     cJSON_AddNumberToObject(pser, "intelligence", p->intelligence);
350     cJSON_AddNumberToObject(pser, "wisdom", p->wisdom);
351     cJSON_AddNumberToObject(pser, "constitution", p->constitution);
352     cJSON_AddNumberToObject(pser, "dexterity", p->dexterity);
353 
354     cJSON_AddNumberToObject(pser, "hp", p->hp);
355     cJSON_AddNumberToObject(pser, "hp_max", p->hp_max);
356     cJSON_AddNumberToObject(pser, "mp", p->mp);
357     cJSON_AddNumberToObject(pser, "mp_max", p->mp_max);
358     cJSON_AddNumberToObject(pser, "regen_counter", p->regen_counter);
359 
360     cJSON_AddNumberToObject(pser, "bank_account", p->bank_account);
361     cJSON_AddNumberToObject(pser, "bank_ieslvtb", p->bank_ieslvtb);
362     cJSON_AddNumberToObject(pser, "outstanding_taxes", p->outstanding_taxes);
363 
364     cJSON_AddNumberToObject(pser, "experience", p->experience);
365     cJSON_AddNumberToObject(pser, "level", p->level);
366 
367     cJSON_AddNumberToObject(pser, "speed", p->speed);
368     cJSON_AddNumberToObject(pser, "movement", p->movement);
369 
370     /* known spells */
371     if (p->known_spells->len > 0)
372     {
373         cJSON_AddItemToObject(pser, "known_spells",
374                               spells_serialize(p->known_spells));
375     }
376     /* inventory */
377     if (inv_length(p->inventory) > 0)
378     {
379         cJSON_AddItemToObject(pser, "inventory", inv_serialize(p->inventory));
380     }
381 
382     /* effects */
383     if (p->effects->len > 0)
384     {
385         cJSON_AddItemToObject(pser, "effects", effects_serialize(p->effects));
386     }
387 
388     /* equipped items */
389     if (p->eq_amulet)
390         cJSON_AddNumberToObject(pser, "eq_amulet",
391                                 GPOINTER_TO_UINT(p->eq_amulet->oid));
392 
393     if (p->eq_weapon)
394         cJSON_AddNumberToObject(pser, "eq_weapon",
395                                 GPOINTER_TO_UINT(p->eq_weapon->oid));
396 
397     if (p->eq_sweapon)
398         cJSON_AddNumberToObject(pser, "eq_sweapon",
399                                 GPOINTER_TO_UINT(p->eq_sweapon->oid));
400 
401     if (p->eq_quiver)
402         cJSON_AddNumberToObject(pser, "eq_quiver",
403                                 GPOINTER_TO_UINT(p->eq_quiver->oid));
404 
405     if (p->eq_boots)
406         cJSON_AddNumberToObject(pser, "eq_boots",
407                                 GPOINTER_TO_UINT(p->eq_boots->oid));
408 
409     if (p->eq_cloak)
410         cJSON_AddNumberToObject(pser, "eq_cloak",
411                                 GPOINTER_TO_UINT(p->eq_cloak->oid));
412 
413     if (p->eq_gloves)
414         cJSON_AddNumberToObject(pser, "eq_gloves",
415                                 GPOINTER_TO_UINT(p->eq_gloves->oid));
416 
417     if (p->eq_helmet)
418         cJSON_AddNumberToObject(pser, "eq_helmet",
419                                 GPOINTER_TO_UINT(p->eq_helmet->oid));
420 
421     if (p->eq_shield)
422         cJSON_AddNumberToObject(pser, "eq_shield",
423                                 GPOINTER_TO_UINT(p->eq_shield->oid));
424 
425     if (p->eq_suit)
426         cJSON_AddNumberToObject(pser, "eq_suit",
427                                 GPOINTER_TO_UINT(p->eq_suit->oid));
428 
429     if (p->eq_ring_l)
430         cJSON_AddNumberToObject(pser, "eq_ring_l",
431                                 GPOINTER_TO_UINT(p->eq_ring_l->oid));
432 
433     if (p->eq_ring_r)
434         cJSON_AddNumberToObject(pser, "eq_ring_r",
435                                 GPOINTER_TO_UINT(p->eq_ring_r->oid));
436 
437     /* identified items */
438     cJSON_AddItemToObject(pser, "identified_amulets",
439                           cJSON_CreateIntArray((int*)p->identified_amulets, AM_MAX));
440 
441     cJSON_AddItemToObject(pser, "identified_armour",
442                           cJSON_CreateIntArray((int*)p->identified_armour, AT_MAX));
443 
444     cJSON_AddItemToObject(pser, "identified_books",
445                           cJSON_CreateIntArray((int*)p->identified_books, SP_MAX));
446 
447     cJSON_AddItemToObject(pser, "identified_potions",
448                           cJSON_CreateIntArray((int*)p->identified_potions, PO_MAX));
449 
450     cJSON_AddItemToObject(pser, "identified_rings",
451                           cJSON_CreateIntArray((int*)p->identified_rings, RT_MAX));
452 
453     cJSON_AddItemToObject(pser, "identified_scrolls",
454                           cJSON_CreateIntArray((int*)p->identified_scrolls, ST_MAX));
455 
456     cJSON_AddItemToObject(pser, "courses_taken",
457                           cJSON_CreateIntArray(p->school_courses_taken, SCHOOL_COURSE_COUNT));
458 
459     cJSON_AddNumberToObject(pser, "position", pos_val(p->pos));
460 
461     /* store last targeted monster */
462     if (p->ptarget != NULL)
463     {
464         cJSON_AddNumberToObject(pser, "ptarget", GPOINTER_TO_UINT(p->ptarget));
465     }
466 
467     position pos = pos_invalid;
468 
469     /* store players' memory of the map */
470     cJSON_AddItemToObject(pser, "memory", obj = cJSON_CreateArray());
471 
472     for (Z(pos) = 0; Z(pos) < MAP_MAX; Z(pos)++)
473     {
474         cJSON *mm = cJSON_CreateArray();
475 
476         for (Y(pos) = 0; Y(pos) < MAP_MAX_Y; Y(pos)++)
477             for (X(pos) = 0; X(pos) < MAP_MAX_X; X(pos)++)
478                 cJSON_AddItemToArray(mm, player_memory_serialize(p, pos));
479 
480         cJSON_AddItemToArray(obj, mm);
481     }
482 
483     /* store remembered stationary objects */
484     if (p->sobjmem != NULL)
485     {
486         obj = cJSON_CreateArray();
487         cJSON_AddItemToObject(pser, "sobjmem", obj);
488 
489         for (guint idx = 0; idx < p->sobjmem->len; idx++)
490         {
491             player_sobject_memory *som;
492             cJSON *soms = cJSON_CreateObject();
493 
494             som = &g_array_index(p->sobjmem, player_sobject_memory, idx);
495 
496             cJSON_AddNumberToObject(soms, "pos", pos_val(som->pos));
497             cJSON_AddNumberToObject(soms, "sobject", som->sobject);
498 
499             cJSON_AddItemToArray(obj, soms);
500         }
501     }
502 
503     /* statistics */
504     cJSON_AddItemToObject(pser, "stats", obj = cJSON_CreateObject());
505 
506     cJSON_AddNumberToObject(obj, "deepest_level", p->stats.deepest_level);
507     cJSON_AddItemToObject(obj, "monsters_killed",
508                           cJSON_CreateIntArray(p->stats.monsters_killed, MT_MAX));
509 
510     cJSON_AddNumberToObject(obj, "spells_cast", p->stats.spells_cast);
511     cJSON_AddNumberToObject(obj, "potions_quaffed", p->stats.potions_quaffed);
512     cJSON_AddNumberToObject(obj, "scrolls_read", p->stats.scrolls_read);
513     cJSON_AddNumberToObject(obj, "books_read", p->stats.books_read);
514     cJSON_AddNumberToObject(obj, "weapons_wasted", p->stats.weapons_wasted);
515     cJSON_AddNumberToObject(obj, "life_protected", p->stats.life_protected);
516     cJSON_AddNumberToObject(obj, "vandalism", p->stats.vandalism);
517     cJSON_AddNumberToObject(obj, "items_bought", p->stats.items_bought);
518     cJSON_AddNumberToObject(obj, "items_sold", p->stats.items_sold);
519     cJSON_AddNumberToObject(obj, "gems_sold", p->stats.gems_sold);
520     cJSON_AddNumberToObject(obj, "gold_found", p->stats.gold_found);
521     cJSON_AddNumberToObject(obj, "gold_sold_items", p->stats.gold_sold_items);
522     cJSON_AddNumberToObject(obj, "gold_sold_gems", p->stats.gold_sold_gems);
523     cJSON_AddNumberToObject(obj, "gold_bank_interest", p->stats.gold_bank_interest);
524     cJSON_AddNumberToObject(obj, "gold_spent_shop", p->stats.gold_spent_shop);
525     cJSON_AddNumberToObject(obj, "gold_spent_id_repair", p->stats.gold_spent_id_repair);
526     cJSON_AddNumberToObject(obj, "gold_spent_donation", p->stats.gold_spent_donation);
527     cJSON_AddNumberToObject(obj, "gold_spent_college", p->stats.gold_spent_college);
528     cJSON_AddNumberToObject(obj, "gold_spent_taxes", p->stats.gold_spent_taxes);
529 
530     cJSON_AddNumberToObject(obj, "max_level", p->stats.max_level);
531     cJSON_AddNumberToObject(obj, "max_xp", p->stats.max_xp);
532 
533     cJSON_AddNumberToObject(obj, "str_orig", p->stats.str_orig);
534     cJSON_AddNumberToObject(obj, "int_orig", p->stats.int_orig);
535     cJSON_AddNumberToObject(obj, "wis_orig", p->stats.wis_orig);
536     cJSON_AddNumberToObject(obj, "con_orig", p->stats.con_orig);
537     cJSON_AddNumberToObject(obj, "dex_orig", p->stats.dex_orig);
538     return pser;
539 }
540 
player_deserialize(cJSON * pser)541 player *player_deserialize(cJSON *pser)
542 {
543     player *p;
544     cJSON *obj, *elem;
545 
546     p = g_malloc0(sizeof(player));
547 
548     p->name = g_strdup(cJSON_GetObjectItem(pser, "name")->valuestring);
549     p->sex = cJSON_GetObjectItem(pser, "sex")->valueint;
550 
551     p->strength = cJSON_GetObjectItem(pser, "strength")->valueint;
552     p->intelligence = cJSON_GetObjectItem(pser, "intelligence")->valueint;
553     p->wisdom = cJSON_GetObjectItem(pser, "wisdom")->valueint;
554     p->constitution = cJSON_GetObjectItem(pser, "constitution")->valueint;
555     p->dexterity = cJSON_GetObjectItem(pser, "dexterity")->valueint;
556 
557     p->hp = cJSON_GetObjectItem(pser, "hp")->valueint;
558     p->hp_max = cJSON_GetObjectItem(pser, "hp_max")->valueint;
559     p->mp = cJSON_GetObjectItem(pser, "mp")->valueint;
560     p->mp_max = cJSON_GetObjectItem(pser, "mp_max")->valueint;
561     p->regen_counter = cJSON_GetObjectItem(pser, "regen_counter")->valueint;
562 
563     p->bank_account = cJSON_GetObjectItem(pser, "bank_account")->valueint;
564     p->bank_ieslvtb = cJSON_GetObjectItem(pser, "bank_ieslvtb")->valueint;
565     p->outstanding_taxes = cJSON_GetObjectItem(pser, "outstanding_taxes")->valueint;
566 
567     p->experience = cJSON_GetObjectItem(pser, "experience")->valueint;
568     p->level = cJSON_GetObjectItem(pser, "level")->valueint;
569 
570     p->speed = cJSON_GetObjectItem(pser, "speed")->valueint;
571     p->movement = cJSON_GetObjectItem(pser, "movement")->valueint;
572 
573     /* known spells */
574     obj = cJSON_GetObjectItem(pser, "known_spells");
575     if (obj != NULL)
576         p->known_spells = spells_deserialize(obj);
577     else
578         p->known_spells = g_ptr_array_new_with_free_func(
579                 (GDestroyNotify)spell_destroy);
580 
581 
582     /* inventory */
583     obj = cJSON_GetObjectItem(pser, "inventory");
584     if (obj != NULL)
585         p->inventory = inv_deserialize(obj);
586     else
587         p->inventory = inv_new(p);
588 
589     p->inventory->owner = p;
590     inv_callbacks_set(p->inventory, &player_inv_pre_add, &player_inv_weight_recalc,
591                       NULL, &player_inv_weight_recalc);
592 
593 
594     /* effects */
595     obj = cJSON_GetObjectItem(pser, "effects");
596     if (obj != NULL)
597         p->effects = effects_deserialize(obj);
598     else
599         p->effects = g_ptr_array_new();
600 
601     /* equipped items */
602     obj = cJSON_GetObjectItem(pser, "eq_amulet");
603     if (obj != NULL) p->eq_amulet = game_item_get(nlarn, GUINT_TO_POINTER(obj->valueint));
604 
605     obj = cJSON_GetObjectItem(pser, "eq_weapon");
606     if (obj != NULL) p->eq_weapon = game_item_get(nlarn, GUINT_TO_POINTER(obj->valueint));
607 
608     obj = cJSON_GetObjectItem(pser, "eq_sweapon");
609     if (obj != NULL) p->eq_sweapon = game_item_get(nlarn, GUINT_TO_POINTER(obj->valueint));
610 
611     obj = cJSON_GetObjectItem(pser, "eq_quiver");
612     if (obj != NULL) p->eq_quiver = game_item_get(nlarn, GUINT_TO_POINTER(obj->valueint));
613 
614     obj = cJSON_GetObjectItem(pser, "eq_boots");
615     if (obj != NULL) p->eq_boots = game_item_get(nlarn, GUINT_TO_POINTER(obj->valueint));
616 
617     obj = cJSON_GetObjectItem(pser, "eq_cloak");
618     if (obj != NULL) p->eq_cloak = game_item_get(nlarn, GUINT_TO_POINTER(obj->valueint));
619 
620     obj = cJSON_GetObjectItem(pser, "eq_gloves");
621     if (obj != NULL) p->eq_gloves = game_item_get(nlarn, GUINT_TO_POINTER(obj->valueint));
622 
623     obj = cJSON_GetObjectItem(pser, "eq_helmet");
624     if (obj != NULL) p->eq_helmet = game_item_get(nlarn, GUINT_TO_POINTER(obj->valueint));
625 
626     obj = cJSON_GetObjectItem(pser, "eq_shield");
627     if (obj != NULL) p->eq_shield = game_item_get(nlarn, GUINT_TO_POINTER(obj->valueint));
628 
629     obj = cJSON_GetObjectItem(pser, "eq_suit");
630     if (obj != NULL) p->eq_suit = game_item_get(nlarn, GUINT_TO_POINTER(obj->valueint));
631 
632     obj = cJSON_GetObjectItem(pser, "eq_ring_l");
633     if (obj != NULL) p->eq_ring_l = game_item_get(nlarn, GUINT_TO_POINTER(obj->valueint));
634 
635     obj = cJSON_GetObjectItem(pser, "eq_ring_r");
636     if (obj != NULL) p->eq_ring_r = game_item_get(nlarn, GUINT_TO_POINTER(obj->valueint));
637 
638     /* identified items */
639     obj = cJSON_GetObjectItem(pser, "identified_amulets");
640     for (int idx = 0; idx < AM_MAX; idx++)
641         p->identified_amulets[idx] = cJSON_GetArrayItem(obj, idx)->valueint;
642 
643     obj = cJSON_GetObjectItem(pser, "identified_armour");
644     for (int idx = 0; idx < AT_MAX; idx++)
645         p->identified_armour[idx] = cJSON_GetArrayItem(obj, idx)->valueint;
646 
647     obj = cJSON_GetObjectItem(pser, "identified_books");
648     for (int idx = 0; idx < SP_MAX; idx++)
649         p->identified_books[idx] = cJSON_GetArrayItem(obj, idx)->valueint;
650 
651     obj = cJSON_GetObjectItem(pser, "identified_potions");
652     for (int idx = 0; idx < PO_MAX; idx++)
653         p->identified_potions[idx] = cJSON_GetArrayItem(obj, idx)->valueint;
654 
655     obj = cJSON_GetObjectItem(pser, "identified_rings");
656     for (int idx = 0; idx < RT_MAX; idx++)
657         p->identified_rings[idx] = cJSON_GetArrayItem(obj, idx)->valueint;
658 
659     obj = cJSON_GetObjectItem(pser, "identified_scrolls");
660     for (int idx = 0; idx < ST_MAX; idx++)
661         p->identified_scrolls[idx] = cJSON_GetArrayItem(obj, idx)->valueint;
662 
663     obj = cJSON_GetObjectItem(pser, "courses_taken");
664     for (int idx = 0; idx < SCHOOL_COURSE_COUNT; idx++)
665         p->school_courses_taken[idx] = cJSON_GetArrayItem(obj, idx)->valueint;
666 
667     pos_val(p->pos) = cJSON_GetObjectItem(pser, "position")->valueint;
668 
669     /* restore last targeted monster */
670     if ((obj = cJSON_GetObjectItem(pser, "ptarget")) != NULL)
671     {
672         p->ptarget = GUINT_TO_POINTER(obj->valueint);
673     }
674 
675     /* restore players' memory of the map */
676     position pos = pos_invalid;
677     obj = cJSON_GetObjectItem(pser, "memory");
678 
679     for (Z(pos) = 0; Z(pos) < MAP_MAX; Z(pos)++)
680     {
681         elem = cJSON_GetArrayItem(obj, Z(pos));
682 
683         for (Y(pos) = 0; Y(pos) < MAP_MAX_Y; Y(pos)++)
684         {
685             for (X(pos) = 0; X(pos) < MAP_MAX_X; X(pos)++)
686             {
687                 cJSON *tile = cJSON_GetArrayItem(elem, X(pos) + (MAP_MAX_X * Y(pos)));
688                 player_memory_deserialize(p, pos, tile);
689             }
690         }
691     }
692 
693     /* remembered stationary objects */
694     obj = cJSON_GetObjectItem(pser, "sobjmem");
695     if (obj != NULL)
696     {
697         int count = cJSON_GetArraySize(obj);
698 
699         p->sobjmem = g_array_sized_new(FALSE, FALSE, sizeof(player_sobject_memory), count);
700 
701         for (int idx = 0; idx < count; idx++)
702         {
703             player_sobject_memory som;
704             cJSON *soms = cJSON_GetArrayItem(obj, idx);
705 
706             pos_val(som.pos) = cJSON_GetObjectItem(soms, "pos")->valueint;
707             som.sobject = cJSON_GetObjectItem(soms, "sobject")->valueint;
708 
709             g_array_append_val(p->sobjmem, som);
710         }
711     }
712 
713     /* statistics */
714     obj = cJSON_GetObjectItem(pser, "stats");
715 
716     p->stats.deepest_level = cJSON_GetObjectItem(obj, "deepest_level")->valueint;
717 
718     elem = cJSON_GetObjectItem(obj, "monsters_killed");
719     for (int idx = 0; idx < MT_MAX; idx++)
720         p->stats.monsters_killed[idx] = cJSON_GetArrayItem(elem, idx)->valueint;
721 
722     p->stats.spells_cast = cJSON_GetObjectItem(obj, "spells_cast")->valueint;
723     p->stats.potions_quaffed = cJSON_GetObjectItem(obj, "potions_quaffed")->valueint;
724     p->stats.scrolls_read = cJSON_GetObjectItem(obj, "scrolls_read")->valueint;
725     p->stats.books_read = cJSON_GetObjectItem(obj, "books_read")->valueint;
726     p->stats.weapons_wasted = cJSON_GetObjectItem(obj, "weapons_wasted")->valueint;
727     p->stats.life_protected = cJSON_GetObjectItem(obj, "life_protected")->valueint;
728     p->stats.vandalism = cJSON_GetObjectItem(obj, "vandalism")->valueint;
729     p->stats.items_bought = cJSON_GetObjectItem(obj, "items_bought")->valueint;
730     p->stats.items_sold   = cJSON_GetObjectItem(obj, "items_sold")->valueint;
731     p->stats.gems_sold    = cJSON_GetObjectItem(obj, "gems_sold")->valueint;
732     p->stats.gold_found         = cJSON_GetObjectItem(obj, "gold_found")->valueint;
733     p->stats.gold_sold_items    = cJSON_GetObjectItem(obj, "gold_sold_items")->valueint;
734     p->stats.gold_sold_gems     = cJSON_GetObjectItem(obj, "gold_sold_gems")->valueint;
735     p->stats.gold_bank_interest = cJSON_GetObjectItem(obj, "gold_bank_interest")->valueint;
736     p->stats.gold_spent_shop      = cJSON_GetObjectItem(obj, "gold_spent_shop")->valueint;
737     p->stats.gold_spent_id_repair = cJSON_GetObjectItem(obj, "gold_spent_id_repair")->valueint;
738     p->stats.gold_spent_donation  = cJSON_GetObjectItem(obj, "gold_spent_donation")->valueint;
739     p->stats.gold_spent_college   = cJSON_GetObjectItem(obj, "gold_spent_college")->valueint;
740     p->stats.gold_spent_taxes     = cJSON_GetObjectItem(obj, "gold_spent_taxes")->valueint;
741 
742     p->stats.max_level = cJSON_GetObjectItem(obj, "max_level")->valueint;
743     p->stats.max_xp = cJSON_GetObjectItem(obj, "max_xp")->valueint;
744 
745     p->stats.str_orig = cJSON_GetObjectItem(obj, "str_orig")->valueint;
746     p->stats.int_orig = cJSON_GetObjectItem(obj, "int_orig")->valueint;
747     p->stats.wis_orig = cJSON_GetObjectItem(obj, "wis_orig")->valueint;
748     p->stats.con_orig = cJSON_GetObjectItem(obj, "con_orig")->valueint;
749     p->stats.dex_orig = cJSON_GetObjectItem(obj, "dex_orig")->valueint;
750 
751     /* initialize the field of vision */
752     p->fv = fov_new();
753 
754     return p;
755 }
756 
player_make_move(player * p,int turns,gboolean interruptible,const char * desc,...)757 gboolean player_make_move(player *p, int turns, gboolean interruptible, const char *desc, ...)
758 {
759     int frequency; /* number of turns between occasions */
760     int regen = 0; /* amount of regeneration */
761     effect *e; /* temporary var for effect */
762     guint idx = 0;
763     g_autofree char *description = NULL, *popup_desc = NULL;
764 
765     g_assert(p != NULL);
766 
767     /* do do nothing if there is nothing to */
768     if (turns == 0) return FALSE;
769 
770     /* return if the game has not been entirely set up */
771     if (nlarn->p == NULL) return TRUE;
772 
773     // Initialize attacked marker.
774     p->attacked = FALSE;
775 
776     /* assemble message and append it to the buffer */
777     if (desc != NULL)
778     {
779         va_list argp;
780 
781         va_start(argp, desc);
782         description = g_strdup_vprintf(desc, argp);
783         va_end(argp);
784     }
785 
786     display_window *pop = NULL;
787     if (turns > 10 && description)
788     {
789         /* shop popup window */
790         popup_desc = g_strdup_printf("You are %s.",
791                 description);
792         pop = display_popup(2, 2, 40, NULL, popup_desc, 0);
793     }
794 
795     /* modifier for frequency */
796     frequency = game_difficulty(nlarn) << 3;
797 
798     do
799     {
800         /* if the number of movement points exceeds 100 reduce number
801            of turns and handle regeneration and some effects */
802         if (p->movement >= NORMAL)
803         {
804             /* reduce the player's movement points */
805             p->movement -= NORMAL;
806         }
807 
808         /* player's extra moves have expired - finish a turn */
809         if (p->movement < NORMAL)
810         {
811             /* check for time stop */
812             if ((e = player_effect_get(p, ET_TIMESTOP)))
813             {
814                 /* time has been stopped - handle player's movement locally */
815                 p->movement += player_get_speed(p);
816 
817                 /* expire only time stop */
818                 if (effect_expire(e) == -1)
819                 {
820                     /* time stop has expired - remove it*/
821                     player_effect_del(p, e);
822                 }
823                 else
824                 {
825                     /* nothing else happens while the time is stopped */
826                     turns--;
827                     continue;
828                 }
829             }
830 
831             /* move the rest of the world */
832             game_spin_the_wheel(nlarn);
833 
834             /* expire temporary effects */
835             idx = 0; // reset idx for proper expiration during multiturn events
836             while (idx < p->effects->len)
837             {
838                 gpointer effect_id = g_ptr_array_index(p->effects, idx);
839                 e = game_effect_get(nlarn, effect_id);
840 
841                 /* trapped counter only reduces on movement */
842                 if (e->type != ET_TRAPPED && effect_expire(e) == -1)
843                 {
844                     /* effect has expired */
845                     player_effect_del(p, e);
846                 }
847                 else
848                 {
849                     /* give a warning if critical effects are about to time out */
850                     if (e->turns == 5)
851                     {
852                         gboolean interrupt_actions = TRUE;
853                         if (e->type == ET_WALL_WALK)
854                             log_add_entry(nlarn->log, "`lightred`Your attunement to the walls is fading!`end`");
855                         else if (e->type == ET_LEVITATION)
856                             log_add_entry(nlarn->log, "`lightred`You are starting to drift towards the ground!`end`");
857                         else
858                             interrupt_actions = FALSE;
859 
860                         if (interrupt_actions)
861                             p->attacked = TRUE;
862                     }
863                     idx++;
864                 }
865             }
866 
867             /* handle regeneration */
868             if (p->regen_counter == 0)
869             {
870                 p->regen_counter = 22 + frequency;
871 
872                 if (p->hp < player_get_hp_max(p))
873                 {
874                     regen = 1 + player_effect(p, ET_INC_HP_REGEN);
875 
876                     player_hp_gain(p, regen);
877                 }
878 
879                 if (p->mp < player_get_mp_max(p))
880                 {
881                     regen = 1 + player_effect(p, ET_INC_MP_REGEN);
882 
883                     player_mp_gain(p, regen);
884                 }
885             }
886             else
887             {
888                 p->regen_counter--;
889             }
890 
891             /* handle poison */
892             if ((e = player_effect_get(p, ET_POISON)))
893             {
894                 int freq = 15 / (e->amount > 15 ? 15 : e->amount);
895                 int amount = 1 + (e->amount / 15);
896                 if ((game_turn(nlarn) - e->start) % freq == 0)
897                 {
898                     damage *dam = damage_new(DAM_POISON, ATT_NONE,
899                             game_difficulty(nlarn) + amount, DAMO_NONE, NULL);
900 
901                     player_damage_take(p, dam, PD_EFFECT, e->type);
902                 }
903             }
904 
905             /* handle clumsiness */
906             if ((player_effect_get(p, ET_CLUMSINESS)) != NULL)
907             {
908                 if (chance(33) && p->eq_weapon)
909                 {
910                     item *it = p->eq_weapon;
911                     player_item_unequip(p, NULL, it, TRUE);
912                     log_add_entry(nlarn->log, "`lightmagenta`You are unable to hold your weapon.`end`");
913                     player_item_drop(p, &p->inventory, it);
914                 }
915             }
916 
917             /* handle itching */
918             if ((player_effect_get(p, ET_ITCHING)) != NULL)
919             {
920                 item **aslot;
921 
922                 /* when the player is subject to itching, there is a chance
923                    that (s)he takes off a piece of armour */
924                 if (chance(50) && (aslot = player_get_random_armour(p, FALSE)))
925                 {
926                     /* deference the item at the selected armour slot */
927                     item *armour = *aslot;
928 
929                     log_add_entry(nlarn->log, "`lightmagenta`The hysteria of itching forces you to remove your armour!`end`");
930                     player_item_unequip(p, &p->inventory, armour, TRUE);
931                     player_item_drop(p, &p->inventory, armour);
932                 }
933             }
934 
935             /* handle multi-turn actions */
936             if (turns > 1)
937             {
938                 /* repaint the screen and do a little pause when the action
939                    continues, for longer episodes a shorter time. */
940                 if (!interruptible || p->attacked)
941                 {
942                     display_paint_screen(p);
943                     napms((turns > 10) ? 1 : 50);
944                 }
945 
946                 /* offer to abort the action if the player is under attack */
947                 if (p->attacked && interruptible)
948                 {
949                     g_autofree char *question = description
950                         ? g_strdup_printf("Do you want to continue %s?", description)
951                         : g_strdup("Do you want to continue?");
952 
953                     if (!display_get_yesno(question, NULL, NULL, NULL))
954                     {
955                         /* user chose to abort the current action */
956                         if (description != NULL)
957                         {
958                             /* log the event */
959                             log_add_entry(nlarn->log, "You stop %s.", description);
960                         }
961 
962                         /* clean up */
963                         p->attacked = FALSE;
964 
965                         if (pop)
966                         {
967                             /* remove the popup window */
968                             display_window_destroy(pop);
969                         }
970 
971                         return FALSE;
972                     }
973                 }
974                 // Don't reset if only 1 turn left, so the wait/run checks
975                 // can process it correctly.
976                 p->attacked = FALSE;
977             }
978         } else {
979             /* otherwise just clean up monsters killed by the player
980                during the extra turn */
981             game_remove_dead_monsters(nlarn);
982         }
983 
984         /* reduce number of turns */
985         turns--;
986     }
987     while (turns > 0);
988 
989     if (pop)
990     {
991         /* remove the popup window */
992         display_window_destroy(pop);
993     }
994 
995     /* successfully completed the action */
996     return TRUE;
997 }
998 
player_die(player * p,player_cod cause_type,int cause)999 void player_die(player *p, player_cod cause_type, int cause)
1000 {
1001     const char *message = NULL;
1002     const char *title = NULL;
1003     effect *ef = NULL;
1004 
1005     g_assert(p != NULL);
1006 
1007     /* check for life protection */
1008     if ((cause_type < PD_STUCK) && (ef = player_effect_get(p, ET_LIFE_PROTECTION)))
1009     {
1010         log_add_entry(nlarn->log, "`lightcyan`You feel wiiieeeeerrrrrd all over!`end`");
1011 
1012         if (ef->amount > 1)
1013         {
1014             ef->amount--;
1015         }
1016         else
1017         {
1018             player_effect_del(p, ef);
1019         }
1020 
1021         if (cause_type == PD_LASTLEVEL)
1022         {
1023             /* revert effects of drain level */
1024             player_level_gain(p, 1);
1025         }
1026 
1027         p->hp = p->hp_max;
1028         p->stats.life_protected++;
1029 
1030         return;
1031     }
1032 
1033     switch (cause_type)
1034     {
1035     case PD_LASTLEVEL:
1036         message = "You fade to gray...";
1037         title = "In Memoriam";
1038         break;
1039 
1040     case PD_STUCK:
1041         message = "You are trapped in solid rock.";
1042         title = "Ouch!";
1043         break;
1044 
1045     case PD_DROWNED:
1046         message = "You've drowned in deep water.";
1047         title = "Deep the water, short the breath...";
1048         break;
1049 
1050     case PD_MELTED:
1051         message = "You've melted.";
1052         title = "Aaaaaaaaaaaaa!";
1053         break;
1054 
1055     case PD_GENOCIDE:
1056         message = "You've genocided yourself!";
1057         title = "Oops?";
1058         break;
1059 
1060     case PD_TOO_LATE:
1061         message = "You returned home too late!";
1062         title = "The End";
1063         break;
1064 
1065     case PD_WON:
1066         message = "You saved your daughter!";
1067         title = "Congratulations! You won!";
1068         break;
1069 
1070     case PD_LOST:
1071         message = "You didn't manage to save your daughter.";
1072         title = "You lost";
1073         break;
1074 
1075     case PD_QUIT:
1076         message = "You quit.";
1077         title = "The End";
1078         break;
1079 
1080     default:
1081         message = "You die...";
1082         title = "R. I. P.";
1083     }
1084 
1085     log_add_entry(nlarn->log, message);
1086 
1087     /* resume game if wizard mode is enabled */
1088     if (game_wizardmode(nlarn) && (cause_type < PD_TOO_LATE))
1089     {
1090         log_add_entry(nlarn->log, "WIZARD MODE. You stay alive.");
1091 
1092         /* return to full power */
1093         p->hp = p->hp_max;
1094         p->mp = p->mp_max;
1095 
1096         /* return to level 1 if last level has been lost */
1097         if (p->level < 1) p->level = 1;
1098 
1099         /* clear some nasty effects */
1100         if ((ef = player_effect_get(p, ET_PARALYSIS)))
1101             player_effect_del(p, ef);
1102         if ((ef = player_effect_get(p, ET_CONFUSION)))
1103             player_effect_del(p, ef);
1104         if ((ef = player_effect_get(p, ET_BLINDNESS)))
1105             player_effect_del(p, ef);
1106         if ((ef = player_effect_get(p, ET_POISON)))
1107             player_effect_del(p, ef);
1108 
1109         if (player_get_con(p) <= 0)
1110         {
1111             if ((ef = player_effect_get(p, ET_DEC_CON)))
1112                 player_effect_del(p, ef);
1113         }
1114         if (player_get_dex(p) <= 0)
1115         {
1116             if ((ef = player_effect_get(p, ET_DEC_DEX)))
1117                 player_effect_del(p, ef);
1118         }
1119         if (player_get_int(p) <= 0)
1120         {
1121             if ((ef = player_effect_get(p, ET_DEC_INT)))
1122                 player_effect_del(p, ef);
1123         }
1124         if (player_get_str(p) <= 0)
1125         {
1126             if ((ef = player_effect_get(p, ET_DEC_STR)))
1127                 player_effect_del(p, ef);
1128         }
1129         if (player_get_wis(p) <= 0)
1130         {
1131             if ((ef = player_effect_get(p, ET_DEC_WIS)))
1132                 player_effect_del(p, ef);
1133         }
1134 
1135         /* return to the game */
1136         return;
1137     }
1138 
1139     /* We really died! */
1140 
1141     /* do not show scores when in wizard mode */
1142     if (!game_wizardmode(nlarn))
1143     {
1144         /* redraw screen to make sure player can see the cause of his death */
1145         display_paint_screen(p);
1146 
1147         /* sleep a second */
1148         g_usleep(1000000);
1149 
1150         /* flush keyboard input buffer */
1151         flushinp();
1152 
1153         score_t *score = score_new(nlarn, cause_type, cause);
1154         GList *scores = score_add(nlarn, score);
1155 
1156         /* create a description of the player's achievements */
1157         gchar *text = player_create_obituary(p, score, scores);
1158 
1159         /* free the memory allocated for the scores*/
1160         scores_destroy(scores);
1161 
1162         display_show_message(title, text, 0);
1163 
1164         if (display_get_yesno("Do you want to save a memorial " \
1165                               "file for your character?", NULL, NULL, NULL))
1166         {
1167             player_memorial_file_save(p, text);
1168         }
1169 
1170         g_free(text);
1171     }
1172 
1173     game_delete_savefile();
1174     nlarn = game_destroy(nlarn);
1175 
1176     /* JUMP JUMP Everybody JUMP!
1177        Restart game and return to the main menu */
1178     longjmp(nlarn_death_jump, cause_type);
1179 }
1180 
player_calc_score(player * p,int won)1181 guint64 player_calc_score(player *p, int won)
1182 {
1183     guint64 score = 0;
1184 
1185     g_assert (p != NULL);
1186 
1187     /* money */
1188     score = player_get_gold(p) + p->bank_account - p->outstanding_taxes;
1189 
1190     /* value of equipment */
1191     for (guint idx = 0; idx < inv_length(p->inventory); idx++)
1192     {
1193         score += item_price(inv_get(p->inventory, idx));
1194     }
1195 
1196     /* value of stuff stored at home */
1197     for (guint idx = 0; idx < inv_length(nlarn->player_home); idx++)
1198     {
1199         score += item_price(inv_get(nlarn->player_home, idx));
1200     }
1201 
1202     /* experience */
1203     score += p->experience;
1204 
1205     /* give points for remaining time if game has been won */
1206     if (won)
1207     {
1208         score += game_remaining_turns(nlarn) * (game_difficulty(nlarn) + 1);
1209     }
1210 
1211     return score;
1212 }
1213 
player_movement_possible(player * p)1214 gboolean player_movement_possible(player *p)
1215 {
1216     /* no movement if overloaded */
1217     if (player_effect(p, ET_OVERSTRAINED))
1218     {
1219         log_add_entry(nlarn->log, "You cannot move as long you are overstrained.");
1220         return FALSE;
1221     }
1222 
1223     /* no movement if paralyzed */
1224     if (player_effect(p, ET_PARALYSIS))
1225     {
1226         log_add_entry(nlarn->log, "You can't move!");
1227         return FALSE;
1228     }
1229 
1230     /* no movement if trapped */
1231     if (player_effect(p, ET_TRAPPED))
1232     {
1233         effect *e = player_effect_get(p, ET_TRAPPED);
1234         if (effect_expire(e) == -1)
1235         {
1236             /* effect has expired */
1237             log_add_entry(nlarn->log, "You climb out of the pit!");
1238             player_effect_del(p, e);
1239         }
1240         else
1241             log_add_entry(nlarn->log, "You're trapped in a pit!");
1242 
1243         player_make_move(p, 1, FALSE, NULL);
1244         return FALSE;
1245     }
1246 
1247     return TRUE;
1248 }
1249 
player_move(player * p,direction dir,gboolean open_door)1250 int player_move(player *p, direction dir, gboolean open_door)
1251 {
1252     int times = 1;      /* how many time ticks it took */
1253     position target_p;  /* coordinates of target */
1254     map *pmap;          /* shortcut to player's current map */
1255     monster *target_m;  /* monster on target tile (if any) */
1256     sobject_t so;       /* stationary object on target tile (if any) */
1257     gboolean move_possible = FALSE;
1258 
1259     g_assert(p != NULL && dir > GD_NONE && dir < GD_MAX);
1260 
1261     if (!player_movement_possible(p))
1262         return 0;
1263 
1264     /* confusion: random movement */
1265     if (player_effect(p, ET_CONFUSION))
1266         dir = rand_1n(GD_MAX);
1267 
1268     /* determine target position of move */
1269     target_p = pos_move(p->pos, dir);
1270 
1271     /* exceeded map limits */
1272     if (!pos_valid(target_p))
1273         return FALSE;
1274 
1275     /* make a shortcut to the current map */
1276     pmap = game_map(nlarn, Z(p->pos));
1277 
1278     if (open_door && map_sobject_at(pmap, target_p) == LS_CLOSEDDOOR)
1279         return player_door_open(nlarn->p, dir);
1280 
1281     target_m = map_get_monster_at(pmap, target_p);
1282 
1283     if (target_m && monster_unknown(target_m))
1284     {
1285         /* reveal the mimic */
1286         log_add_entry(nlarn->log, "Wait! That is a %s!", monster_get_name(target_m));
1287         monster_unknown_set(target_m, FALSE);
1288         return times;
1289     }
1290 
1291     if (target_m && (monster_action(target_m) == MA_SERVE))
1292     {
1293         /* bump into a servant -> swap position */
1294 
1295         /* reposition the player here, otherwise monster_pos_set will fail */
1296         position old_pos = p->pos;
1297         p->pos = target_p;
1298         monster_pos_set(target_m, pmap, old_pos);
1299 
1300         move_possible = TRUE;
1301 
1302         log_add_entry(nlarn->log, "You swap places with the %s.",
1303                       monster_get_name(target_m));
1304     }
1305     else if (target_m)
1306     {
1307         /* attack - no movement */
1308         return player_attack(p, target_m);
1309     }
1310 
1311     /* check if the move is possible */
1312     if (map_pos_passable(pmap, target_p))
1313     {
1314         /* target tile is passable */
1315         move_possible = TRUE;
1316     }
1317     else if ((map_tiletype_at(pmap, target_p) == LT_WALL)
1318              && player_effect(p, ET_WALL_WALK))
1319     {
1320         /* target tile is a wall and player can walk trough walls */
1321         move_possible = TRUE;
1322     }
1323     else if (map_pos_transparent(pmap, target_p)
1324              && player_effect(p, ET_LEVITATION))
1325     {
1326         /* player is able to float above the obstacle */
1327         move_possible = TRUE;
1328     }
1329 
1330     if (!move_possible)
1331     {
1332         /* return if the move is not possible */
1333 
1334         /* bump into walls when blinded or confused */
1335         if (player_effect(p, ET_BLINDNESS) || player_effect(p, ET_CONFUSION))
1336         {
1337             player_memory_of(p, target_p).type = map_tiletype_at(pmap, target_p);
1338             const int tile = map_tiletype_at(pmap, target_p);
1339             log_add_entry(nlarn->log, "Ouch! You bump into %s!",
1340                           (tile == LT_DEEPWATER || tile == LT_LAVA)
1341                           ? "the railing" : mt_get_desc(tile));
1342             return times;
1343         }
1344 
1345         return FALSE;
1346     }
1347 
1348     /* reposition player */
1349     p->pos = target_p;
1350 
1351     /* trigger the trap */
1352     if (map_trap_at(pmap, target_p))
1353     {
1354         times += player_trap_trigger(p, map_trap_at(pmap, target_p), FALSE);
1355     }
1356 
1357     /* auto-pickup */
1358     player_autopickup(p);
1359 
1360     /* mention stationary objects at this position */
1361     if ((so = map_sobject_at(pmap, p->pos)) && !player_effect(p, ET_BLINDNESS))
1362     {
1363         log_add_entry(nlarn->log, "You see %s here.", so_get_desc(so));
1364     }
1365 
1366     return times;
1367 }
1368 
1369 typedef struct min_max_damage
1370 {
1371     int min_damage;
1372     int max_damage;
1373 } min_max_damage;
1374 
calc_min_max_damage(player * p,monster * m)1375 static min_max_damage calc_min_max_damage(player *p, monster *m)
1376 {
1377     /* without weapon cause a basic unarmed combat damage */
1378     int min_damage = 1;
1379 
1380     if (p->eq_weapon != NULL)
1381     {
1382         /* wielded weapon base damage */
1383         min_damage = weapon_damage(p->eq_weapon);
1384 
1385         // Blessed weapons do 50% bonus damage against demons and undead.
1386         if (p->eq_weapon->blessed
1387                 && (monster_flags(m, DEMON) || monster_flags(m, UNDEAD)))
1388         {
1389             min_damage *= 3;
1390             min_damage /= 2;
1391         }
1392     }
1393 
1394     min_damage += player_effect(p, ET_INC_DAMAGE);
1395     min_damage -= player_effect(p, ET_SICKNESS);
1396 
1397     /* calculate maximum damage: strength bonus and difficulty malus */
1398     int max_damage = min_damage
1399                      + player_get_str(p) - 12
1400                      - game_difficulty(nlarn);
1401 
1402     /* ensure minimal damage */
1403     min_max_damage ret =
1404     {
1405         max(1, min_damage),
1406         max(max(1, min_damage), max_damage)
1407     };
1408 
1409     return ret;
1410 }
1411 
player_instakill_chance(player * p,monster * m)1412 static gboolean player_instakill_chance(player *p, monster *m)
1413 {
1414     if (p->eq_weapon)
1415     {
1416         switch (p->eq_weapon->id)
1417         {
1418             /* Vorpal Blade */
1419         case WT_VORPALBLADE:
1420             if (monster_flags(m, HEAD) && !monster_flags(m, NOBEHEAD))
1421                 return 5;
1422             break;
1423 
1424             /* Lance of Death */
1425         case WT_LANCEOFDEATH:
1426             /* the lance is pretty deadly for non-demons */
1427             if (!monster_flags(m, DEMON))
1428                 return 100;
1429             break;
1430 
1431             /* Slayer */
1432         case WT_SLAYER:
1433             if (monster_flags(m, DEMON))
1434                 return 100;
1435             break;
1436 
1437         default:
1438             break;
1439         }
1440     }
1441     return 0;
1442 }
1443 
calc_real_damage(player * p,monster * m,int allow_chance)1444 static int calc_real_damage(player *p, monster *m, int allow_chance)
1445 {
1446     const int INSTANT_KILL = 10000;
1447     const min_max_damage mmd = calc_min_max_damage(p, m);
1448     int real_damage = rand_m_n(mmd.min_damage, mmd.max_damage + 1);
1449 
1450     /* *** SPECIAL WEAPONS *** */
1451     if (p->eq_weapon)
1452     {
1453         switch (p->eq_weapon->id)
1454         {
1455             /* Vorpal Blade */
1456         case WT_VORPALBLADE:
1457             if (allow_chance && chance(5) && monster_flags(m, HEAD)
1458                     && !monster_flags(m, NOBEHEAD))
1459             {
1460                 log_add_entry(nlarn->log, "You behead the %s with your Vorpal Blade!",
1461                               monster_get_name(m));
1462 
1463                 real_damage = INSTANT_KILL;
1464             }
1465             break;
1466 
1467             /* Lance of Death */
1468         case WT_LANCEOFDEATH:
1469             /* the lance is pretty deadly for non-demons */
1470             if (!monster_flags(m, DEMON))
1471                 real_damage = INSTANT_KILL;
1472             else
1473                 real_damage = 300;
1474             break;
1475 
1476             /* Slayer */
1477         case WT_SLAYER:
1478             if (monster_flags(m, DEMON))
1479                 real_damage = INSTANT_KILL;
1480             break;
1481 
1482         default:
1483             /* triple damage if hitting a dragon and wearing an amulet of
1484                dragon slaying */
1485             if (monster_flags(m, DRAGON)
1486                     && (p->eq_amulet && p->eq_amulet->id == AM_DRAGON_SLAYING))
1487             {
1488                 real_damage *= 3;
1489             }
1490             break;
1491         }
1492     }
1493 
1494     return real_damage;
1495 }
1496 
player_attack(player * p,monster * m)1497 int player_attack(player *p, monster *m)
1498 {
1499     /* disallow attacking other humans */
1500     if (monster_type(m) == MT_TOWN_PERSON)
1501     {
1502         log_add_entry(nlarn->log, "You bump into the %s.", monster_get_name(m));
1503         return 1;
1504     }
1505 
1506     if (chance(5) || chance(weapon_calc_to_hit(p, m, p->eq_weapon, NULL)))
1507     {
1508         damage *dam;
1509         effect *e;
1510 
1511         /* placed a hit */
1512         log_add_entry(nlarn->log, "You hit the %s.", monster_get_name(m));
1513 
1514         dam = damage_new(DAM_PHYSICAL, ATT_WEAPON, calc_real_damage(p, m, TRUE),
1515                          DAMO_PLAYER, p);
1516 
1517         /* weapon damage due to rust when hitting certain monsters */
1518         if (p->eq_weapon && monster_flags(m, METALLIVORE))
1519         {
1520             p->eq_weapon = item_erode(&p->inventory, p->eq_weapon, IET_RUST, TRUE);
1521 
1522             /* count destroyed weapons */
1523             if (p->eq_weapon == NULL) p->stats.weapons_wasted += 1;
1524         }
1525 
1526         /* The weapon may break during usage */
1527         if (p->eq_weapon && chance(item_fragility(p->eq_weapon)))
1528         {
1529             log_add_entry(nlarn->log, "`lightmagenta`Your %s breaks!`end`",
1530                           weapon_name(p->eq_weapon));
1531 
1532             item *weapon = p->eq_weapon;
1533 
1534             /* remove the weapon */
1535             player_item_unequip(p, NULL, p->eq_weapon, TRUE);
1536             inv_del_element(&p->inventory, weapon);
1537             item_destroy(weapon);
1538             p->stats.weapons_wasted += 1;
1539         }
1540 
1541         /* hitting a monster breaks stealth condition */
1542         if ((e = player_effect_get(p, ET_STEALTH)))
1543         {
1544             player_effect_del(p, e);
1545         }
1546 
1547         /* hitting a monster breaks hold monster spell */
1548         if ((e = monster_effect_get(m, ET_HOLD_MONSTER)))
1549         {
1550             monster_effect_del(m, e);
1551         }
1552 
1553         /* inflict damage */
1554         if (!(m = monster_damage_take(m, dam)))
1555         {
1556             /* killed the monster */
1557             return 1;
1558         } else {
1559            /* store the monster for subsequent ranged attacks */
1560            p->ptarget = monster_oid(m);
1561         }
1562 
1563         /* Lance of Death has not killed */
1564         if (p->eq_weapon && (p->eq_weapon->id == WT_LANCEOFDEATH)
1565                 && monster_in_sight(m))
1566         {
1567             log_add_entry(nlarn->log, "Your lance of death tickles the %s!",
1568                           monster_get_name(m));
1569         }
1570 
1571         /* if the player is invisible and the monster does not have infravision,
1572            remember the position where the attack came from
1573          */
1574         if (player_effect(p, ET_INVISIBILITY) && !monster_flags(m, INFRAVISION))
1575         {
1576             monster_update_player_pos(m, p->pos);
1577         }
1578     }
1579     else
1580     {
1581         /* missed */
1582         log_add_entry(nlarn->log, "You miss the %s.", monster_get_name(m));
1583     }
1584 
1585     return 1; /* i.e. turns used */
1586 }
1587 
player_map_enter(player * p,map * l,gboolean teleported)1588 int player_map_enter(player *p, map *l, gboolean teleported)
1589 {
1590     g_assert(p != NULL && l != NULL);
1591 
1592     /* store the last turn player has been on this map */
1593     game_map(nlarn, Z(p->pos))->visited = game_turn(nlarn);
1594 
1595     if (p->stats.deepest_level < l->nlevel)
1596     {
1597         p->stats.deepest_level = l->nlevel;
1598     }
1599 
1600     /* been teleported here or something like that, need a random spot */
1601     if (teleported)
1602     {
1603         effect *e;
1604         if ((e = player_effect_get(p, ET_TRAPPED)))
1605             player_effect_del(p, e);
1606 
1607         p->pos = map_find_space(l, LE_MONSTER, FALSE);
1608     }
1609 
1610     /* beginning of the game */
1611     else if ((l->nlevel == 0) && (game_turn(nlarn) == 1))
1612         p->pos = map_find_sobject(l, LS_HOME);
1613 
1614     /* took the elevator down */
1615     else if ((Z(p->pos) == 0) && (l->nlevel == (MAP_CMAX)))
1616         p->pos = map_find_sobject(l, LS_ELEVATORUP);
1617 
1618     /* took the elevator up */
1619     else if ((Z(p->pos) == (MAP_CMAX)) && (l->nlevel == 0))
1620         p->pos = map_find_sobject(l, LS_ELEVATORDOWN);
1621 
1622     /* climbing up */
1623     else if (Z(p->pos) > l->nlevel)
1624     {
1625         if (l->nlevel == 0)
1626             p->pos = map_find_sobject(l, LS_CAVERNS_ENTRY);
1627         else
1628             p->pos = map_find_sobject(l, LS_STAIRSDOWN);
1629     }
1630     /* climbing down */
1631     else if (l->nlevel > Z(p->pos))
1632     {
1633         if (l->nlevel == 1)
1634             p->pos = map_find_sobject(l, LS_CAVERNS_EXIT);
1635         else
1636             p->pos = map_find_sobject(l, LS_STAIRSUP);
1637     }
1638     /* all cases should have been handled until here */
1639     else
1640     {
1641         g_assert_not_reached();
1642     }
1643 
1644     if (l->nlevel == 0)
1645     {
1646         /* do not log this at the start of the game */
1647         if (nlarn->log->gtime > 1)
1648             log_add_entry(nlarn->log, "You return to town.");
1649     }
1650     else if (l->nlevel == 1 && Z(p->pos) == 0)
1651         log_add_entry(nlarn->log, "You enter the caverns of Larn.");
1652 
1653     /* remove monster that might be at player's position */
1654     if ((map_get_monster_at(l, p->pos)))
1655     {
1656         position mnpos = map_find_space(l, LE_MONSTER, FALSE);
1657         monster_pos_set(map_get_monster_at(l, p->pos),
1658                         game_map(nlarn, Z(p->pos)), mnpos);
1659     }
1660 
1661     /* recalculate FOV to make ensure correct display after entering a level */
1662     player_update_fov(p);
1663 
1664     /* call autopickup */
1665     player_autopickup(p);
1666 
1667     /* automatic save point */
1668     if (game_autosave(nlarn) && (game_turn(nlarn) > 1))
1669     {
1670         game_save(nlarn);
1671     }
1672 
1673     return TRUE;
1674 }
1675 
player_get_random_armour(player * p,int enchantable)1676 item **player_get_random_armour(player *p, int enchantable)
1677 {
1678     GPtrArray *equipped_armour;
1679     item **armour = NULL;
1680 
1681     g_assert (p != NULL);
1682 
1683     equipped_armour = g_ptr_array_new();
1684 
1685     /* add each equipped piece of armour to the pool to choose from */
1686     if (p->eq_boots)  g_ptr_array_add(equipped_armour, &p->eq_boots);
1687     if (p->eq_cloak)  g_ptr_array_add(equipped_armour, &p->eq_cloak);
1688     if (p->eq_gloves) g_ptr_array_add(equipped_armour, &p->eq_gloves);
1689     if (p->eq_helmet) g_ptr_array_add(equipped_armour, &p->eq_helmet);
1690     if (p->eq_shield) g_ptr_array_add(equipped_armour, &p->eq_shield);
1691     if (p->eq_suit)   g_ptr_array_add(equipped_armour, &p->eq_suit);
1692 
1693     if (equipped_armour->len > 0)
1694     {
1695         int tries = 10;
1696         do
1697         {
1698             armour = g_ptr_array_index(equipped_armour, rand_0n(equipped_armour->len));
1699         }
1700         while (enchantable && tries-- > 0 && (*armour)->bonus == 3);
1701     }
1702 
1703     g_ptr_array_free(equipped_armour, TRUE);
1704 
1705     return armour;
1706 }
1707 
player_pickup(player * p)1708 void player_pickup(player *p)
1709 {
1710     g_assert(p != NULL);
1711 
1712     inventory **inv = map_ilist_at(game_map(nlarn, Z(p->pos)), p->pos);
1713 
1714     if (inv_length(*inv) == 0)
1715     {
1716         log_add_entry(nlarn->log, "There is nothing here.");
1717     }
1718     else if (player_effect(p, ET_LEVITATION))
1719     {
1720         log_add_entry(nlarn->log, "You cannot reach the floor!");
1721         return;
1722     }
1723     else if (player_effect(p, ET_PARALYSIS))
1724     {
1725         log_add_entry(nlarn->log, "You can't move!");
1726         return;
1727     }
1728     else if (inv_length(*inv) == 1)
1729     {
1730         player_item_pickup(p, inv, inv_get(*inv, 0), TRUE);
1731     }
1732     else
1733     {
1734         /* define callback functions */
1735         GPtrArray *callbacks = g_ptr_array_new();
1736 
1737         display_inv_callback *callback = g_malloc0(sizeof(display_inv_callback));
1738         callback->description = "(,) get";
1739         callback->helpmsg = "Get the item. In case of an item stack, get the entire stack.";
1740         callback->key = ',';
1741         callback->inv = inv;
1742         callback->function = (display_inv_callback_func)&player_item_pickup_all;
1743         g_ptr_array_add(callbacks, callback);
1744 
1745         callback = g_malloc0(sizeof(display_inv_callback));
1746         callback->description = "(g)et partly";
1747         callback->helpmsg = "Get the item. In case of an item stack, choose how many items to pick up.";
1748         callback->key = 'g';
1749         callback->inv = inv;
1750         callback->checkfun = &player_item_filter_multiple;
1751         callback->function = (display_inv_callback_func)&player_item_pickup_ask;
1752         g_ptr_array_add(callbacks, callback);
1753 
1754         display_inventory("On the floor", p, inv, callbacks, FALSE,
1755                           TRUE, FALSE, NULL);
1756 
1757         /* clean up callbacks */
1758         display_inv_callbacks_clean(callbacks);
1759     }
1760 }
1761 
filter_item_noautopickup(item * i)1762 static int filter_item_noautopickup(item *i)
1763 {
1764     return !(nlarn->p->settings.auto_pickup[i->type] || i->fired);
1765 }
1766 
player_autopickup(player * p)1767 static void player_autopickup(player *p)
1768 {
1769     g_assert (p != NULL && map_ilist_at(game_map(nlarn, Z(p->pos)), p->pos));
1770 
1771 
1772     /* if the player is floating above the ground auto-pickup does not work.. */
1773     if (player_effect(p, ET_LEVITATION))
1774         return;
1775 
1776     /* if the player is blinded, don't do anything */
1777     if(player_effect_get(p, ET_BLINDNESS))
1778         return;
1779 
1780     inventory **floor = map_ilist_at(game_map(nlarn, Z(p->pos)), p->pos);
1781 
1782     for (guint idx = 0; idx < inv_length(*floor); idx++)
1783     {
1784         guint count_orig = inv_length(*floor);
1785         item *i = inv_get(*floor, idx);
1786 
1787         /* the given item is not configured for auto-pickup */
1788         if (filter_item_noautopickup(i)) continue;
1789 
1790         /* try to pick up the item */
1791         if (1 == player_item_pickup(p, floor, i, FALSE))
1792         {
1793             /* pickup has been cancelled by the player */
1794             return;
1795         }
1796 
1797         if (count_orig != inv_length(*floor))
1798         {
1799             /* item has been picked up */
1800             /* go back one item as the following items lowered their number */
1801             idx--;
1802         }
1803     }
1804 
1805     /* If there are some items on the floor which are not configured for
1806        autopickup, describe them. We cannot simply describe all remaining
1807        items, as some may be configured for autopickup, but too heavy to
1808        carry. Those would result in duplicate messages. */
1809     if (inv_length_filtered(*floor, filter_item_noautopickup))
1810     {
1811         log_add_entry(nlarn->log, map_inv_description(
1812             game_map(nlarn, Z(p->pos)), p->pos, "here", filter_item_noautopickup));
1813     }
1814 }
1815 
player_level_gain(player * p,int count)1816 void player_level_gain(player *p, int count)
1817 {
1818     const char *desc_orig, *desc_new;
1819 
1820     g_assert(p != NULL && count > 0);
1821 
1822     /* experience level 100 is the end of the career */
1823     if (p->level == 100)
1824         return;
1825 
1826     desc_orig = player_get_level_desc(p);
1827 
1828     p->level += count;
1829 
1830     desc_new = player_get_level_desc(p);
1831 
1832     if (g_strcmp0(desc_orig, desc_new) != 0)
1833     {
1834         log_add_entry(nlarn->log, "`lightgreen`You gain experience and become %s %s!`end`",
1835                       a_an(desc_new), desc_new);
1836     }
1837     else
1838     {
1839         log_add_entry(nlarn->log, "`lightgreen`You gain experience!`end`");
1840     }
1841 
1842     if (p->level > p->stats.max_level)
1843         p->stats.max_level = p->level;
1844 
1845     if (p->experience < player_lvl_exp[p->level - 1])
1846         /* artificially gained a level, need to adjust XP to match level */
1847         player_exp_gain(p, player_lvl_exp[p->level - 1] - p->experience);
1848 
1849     for (int i = 0; i < count; i++)
1850     {
1851         int base;
1852 
1853         /* increase HP max */
1854         base = (p->constitution - game_difficulty(nlarn)) >> 1;
1855         if (p->level < (guint)max(7 - game_difficulty(nlarn), 0))
1856             base += p->constitution >> 2;
1857 
1858         player_hp_max_gain(p, rand_1n(3) + rand_0n(max(base, 1)));
1859 
1860         /* increase MP max */
1861         base = (p->intelligence - game_difficulty(nlarn)) >> 1;
1862         if (p->level < (guint)max(7 - game_difficulty(nlarn), 0))
1863             base += p->intelligence >> 2;
1864 
1865         player_mp_max_gain(p, rand_1n(3) + rand_0n(max(base, 1)));
1866     }
1867 }
1868 
player_level_lose(player * p,int count)1869 void player_level_lose(player *p, int count)
1870 {
1871     g_assert(p != NULL && count > 0);
1872 
1873     p->level -= count;
1874     log_add_entry(nlarn->log, "`lightred`You return to experience level %d...`end`", p->level);
1875 
1876     /* die if lost level 1 */
1877     if (p->level == 0) player_die(p, PD_LASTLEVEL, 0);
1878 
1879     if (p->experience > player_lvl_exp[p->level - 1])
1880         /* adjust XP to match level */
1881         player_exp_lose(p, p->experience - player_lvl_exp[p->level - 1]);
1882 
1883     for (int i = 0; i < count; i++)
1884     {
1885         int base;
1886 
1887         /* decrease HP max */
1888         base = (p->constitution - game_difficulty(nlarn)) >> 1;
1889         if (p->level < (guint)max(7 - game_difficulty(nlarn), 0))
1890             base += p->constitution >> 2;
1891 
1892         player_hp_max_lose(p, rand_1n(3) + rand_0n(max(base, 1)));
1893 
1894         /* decrease MP max */
1895         base = (p->intelligence - game_difficulty(nlarn)) >> 1;
1896         if (p->level < (guint)max(7 - game_difficulty(nlarn), 0))
1897             base += p->intelligence >> 2;
1898 
1899         player_mp_max_lose(p, rand_1n(3) + rand_0n(max(base, 1)));
1900     }
1901 }
1902 
player_exp_gain(player * p,int count)1903 void player_exp_gain(player *p, int count)
1904 {
1905     if (count <= 0)
1906         return;
1907 
1908     int numlevels = 0;
1909 
1910     g_assert(p != NULL);
1911     p->experience += count;
1912 
1913     if (p->stats.max_xp < p->experience)
1914         p->stats.max_xp = p->experience;
1915 
1916     while (player_lvl_exp[p->level + numlevels] <= p->experience)
1917         numlevels++;
1918 
1919     if (numlevels)
1920         player_level_gain(p, numlevels);
1921 }
1922 
player_exp_lose(player * p,guint count)1923 void player_exp_lose(player *p, guint count)
1924 {
1925     int numlevels = 0;
1926 
1927     g_assert(p != NULL && count > 0);
1928 
1929     if (count > p->experience)
1930         p->experience = 0;
1931     else
1932         p->experience -= count;
1933 
1934     while ((player_lvl_exp[p->level - 1 - numlevels]) > p->experience)
1935         numlevels++;
1936 
1937     if (numlevels)
1938         player_level_lose(p, numlevels);
1939 }
1940 
1941 
player_hp_gain(player * p,int count)1942 int player_hp_gain(player *p, int count)
1943 {
1944     g_assert(p != NULL);
1945 
1946     p->hp += count;
1947     if (p->hp > player_get_hp_max(p))
1948         p->hp = player_get_hp_max(p);
1949 
1950     return p->hp;
1951 }
1952 
player_hp_lose(player * p,int count,player_cod cause_type,int cause)1953 int player_hp_lose(player *p, int count, player_cod cause_type, int cause)
1954 {
1955     g_assert(p != NULL);
1956 
1957     p->hp -= count;
1958 
1959     if (p->hp < 1)
1960     {
1961         player_die(p, cause_type, cause);
1962     }
1963 
1964     return p->hp;
1965 }
1966 
player_damage_take(player * p,damage * dam,player_cod cause_type,int cause)1967 void player_damage_take(player *p, damage *dam, player_cod cause_type, int cause)
1968 {
1969     effect *e = NULL;
1970     int hp_orig;
1971     guint effects_count;
1972 
1973     g_assert(p != NULL && dam != NULL);
1974 
1975     if (game_wizardmode(nlarn))
1976         log_add_entry(nlarn->log, damage_to_str(dam));
1977 
1978     if (dam->dam_origin.ot == DAMO_MONSTER)
1979     {
1980         monster *m = (monster *)dam->dam_origin.originator;
1981 
1982         /* amulet of power cancels demon attacks */
1983         if (monster_flags(m, DEMON) && chance(75)
1984                 && (p->eq_amulet && p->eq_amulet->id == AM_POWER))
1985         {
1986             log_add_entry(nlarn->log, "Your amulet cancels the %s's attack.",
1987                           monster_get_name(m));
1988 
1989             return;
1990         }
1991     }
1992 
1993     if (dam->attack == ATT_GAZE && player_effect_get(p, ET_BLINDNESS))
1994     {
1995         /* it is impossible to see a staring monster when blinded */
1996         return;
1997     }
1998 
1999     /* store player's hit points and the number
2000        of effects before calculating the damage */
2001     hp_orig = p->hp;
2002     effects_count = p->effects->len;
2003 
2004     /* keep damage type and amount for subsequent usage, but free the damage
2005        object itself - when the player dies, the object will be leaked */
2006     damage_t damage_type = dam->type;
2007     gint damage_amount = dam->amount;
2008     g_free(dam);
2009 
2010 
2011     /* check resistances */
2012     switch (damage_type)
2013     {
2014     case DAM_PHYSICAL:
2015         if (damage_amount > (gint)player_get_ac(p))
2016         {
2017             damage_amount -= player_get_ac(p);
2018 
2019             if (damage_amount >= 8 && damage_amount >= (gint)p->hp_max/4)
2020                 log_add_entry(nlarn->log, "`lightred`Ouch, that REALLY hurt!`end`");
2021             else if (damage_amount >= (gint)p->hp_max/10)
2022                 log_add_entry(nlarn->log, "`lightred`Ouch!`end`");
2023 
2024             player_hp_lose(p, damage_amount, cause_type, cause);
2025         }
2026         else
2027         {
2028             log_add_entry(nlarn->log, "Your armour protects you.");
2029         }
2030         break;
2031 
2032     case DAM_MAGICAL:
2033         if (damage_amount > (gint)(guint)player_effect(p, ET_RESIST_MAGIC))
2034         {
2035             damage_amount -= player_effect(p, ET_RESIST_MAGIC);
2036 
2037             if (damage_amount >= 8 && damage_amount >= (gint)p->hp_max/4)
2038                 log_add_entry(nlarn->log, "`lightred`Ouch, that REALLY hurt!`end`");
2039             else if (damage_amount >= (gint)p->hp_max/10)
2040                 log_add_entry(nlarn->log, "`lightred`Ouch!`end`");
2041 
2042             player_hp_lose(p, damage_amount, cause_type, cause);
2043         }
2044         else
2045         {
2046             log_add_entry(nlarn->log, "You resist.");
2047         }
2048 
2049         break;
2050 
2051     case DAM_FIRE:
2052         if (damage_amount > (gint)player_effect(p, ET_RESIST_FIRE))
2053         {
2054             damage_amount -= player_effect(p, ET_RESIST_FIRE);
2055 
2056             log_add_entry(nlarn->log, "You suffer burns.");
2057             player_hp_lose(p, damage_amount, cause_type, cause);
2058         }
2059         else
2060         {
2061             log_add_entry(nlarn->log, "The flames don't phase you.");
2062         }
2063         break;
2064 
2065     case DAM_COLD:
2066         if (damage_amount > (gint)player_effect(p, ET_RESIST_COLD))
2067         {
2068             damage_amount -= player_effect(p, ET_RESIST_COLD);
2069 
2070             log_add_entry(nlarn->log, "You suffer from frostbite.");
2071             player_hp_lose(p, damage_amount, cause_type, cause);
2072         }
2073         else
2074         {
2075             log_add_entry(nlarn->log, "It doesn't seem so cold.");
2076         }
2077         break;
2078 
2079     case DAM_ACID:
2080         if (damage_amount > 0)
2081         {
2082             log_add_entry(nlarn->log, "You are splashed with acid.");
2083             player_hp_lose(p, damage_amount, cause_type, cause);
2084         }
2085         else
2086         {
2087             log_add_entry(nlarn->log, "The acid doesn't affect you.");
2088         }
2089         break;
2090 
2091     case DAM_WATER:
2092         if (damage_amount > 0)
2093         {
2094             log_add_entry(nlarn->log, "You experience near-drowning.");
2095             player_hp_lose(p, damage_amount, cause_type, cause);
2096         }
2097         else
2098         {
2099             log_add_entry(nlarn->log, "The water doesn't affect you.");
2100         }
2101         break;
2102 
2103     case DAM_ELECTRICITY:
2104         /* double damage if levitating */
2105         if (player_effect(p, ET_LEVITATION))
2106             damage_amount *= 2;
2107 
2108         if (damage_amount > 0)
2109         {
2110             log_add_entry(nlarn->log, "Zapp!");
2111             player_hp_lose(p, damage_amount, cause_type, cause);
2112         }
2113         else
2114         {
2115             log_add_entry(nlarn->log, "As you are grounded nothing happens.");
2116         }
2117         break;
2118 
2119     case DAM_POISON:
2120         /* check if the damage is not caused by the effect that is
2121            already attached to the player */
2122         if (cause_type != PD_EFFECT)
2123         {
2124             /* check resistance; prevent negative damage amount */
2125             damage_amount = max(0, damage_amount - rand_0n(player_get_con(p)));
2126 
2127             if (damage_amount > 0)
2128             {
2129                 e = effect_new(ET_POISON);
2130                 e->amount = damage_amount;
2131                 player_effect_add(p, e);
2132             }
2133             else
2134             {
2135                 log_add_entry(nlarn->log, "You resist the poison.");
2136             }
2137         }
2138         else
2139         {
2140             /* damage is caused by the effect of the poison effect () */
2141             log_add_entry(nlarn->log, "You feel poison running through your veins.");
2142             player_hp_lose(p, damage_amount, cause_type, cause);
2143         }
2144         break;
2145 
2146     case DAM_BLINDNESS:
2147         if (chance(damage_amount))
2148         {
2149             player_effect_add(p, effect_new(ET_BLINDNESS));
2150         }
2151         else if (player_effect_get(p, ET_BLINDNESS) == NULL)
2152         {
2153             log_add_entry(nlarn->log, "You are not blinded.");
2154         }
2155 
2156         break;
2157 
2158     case DAM_CONFUSION:
2159         /* check if the player succumbs to the monster's stare */
2160         if (chance(damage_amount - player_get_int(p)))
2161         {
2162             player_effect_add(p, effect_new(ET_CONFUSION));
2163         }
2164         else if (player_effect_get(p, ET_CONFUSION) == NULL)
2165         {
2166             log_add_entry(nlarn->log, "You are not confused.");
2167         }
2168 
2169         break;
2170 
2171     case DAM_PARALYSIS:
2172         /* check if the player succumbs to the monster's stare */
2173         if (chance(damage_amount - player_get_int(p)))
2174         {
2175             player_effect_add(p, effect_new(ET_PARALYSIS));
2176         }
2177         else if (player_effect_get(p, ET_PARALYSIS) == NULL)
2178         {
2179             log_add_entry(nlarn->log, "You avoid eye contact.");
2180         }
2181 
2182         break;
2183 
2184     case DAM_DEC_CON:
2185     case DAM_DEC_DEX:
2186     case DAM_DEC_INT:
2187     case DAM_DEC_STR:
2188     case DAM_DEC_WIS:
2189         if (!player_effect(p, ET_SUSTAINMENT)
2190             && chance(damage_amount -= player_get_con(p)))
2191         {
2192             effect_t et = (ET_DEC_CON + damage_type - DAM_DEC_CON);
2193             e = effect_new(et);
2194             /* the default number of turns is 1 */
2195             e->turns = damage_amount * 10;
2196             (void)player_effect_add(p, e);
2197 
2198             switch (damage_type)
2199             {
2200             case DAM_DEC_CON:
2201                 if (player_get_con(p) < 1)
2202                     player_die(p, PD_EFFECT, ET_DEC_CON);
2203                 break;
2204 
2205             case DAM_DEC_DEX:
2206                 if (player_get_dex(p) < 1)
2207                     player_die(p, PD_EFFECT, ET_DEC_DEX);
2208                 break;
2209 
2210             case DAM_DEC_INT:
2211                 if (player_get_int(p) < 1)
2212                     player_die(p, PD_EFFECT, ET_DEC_INT);
2213                 break;
2214 
2215             case DAM_DEC_STR:
2216                 /* strength has been modified -> recalc burdened status */
2217                 player_inv_weight_recalc(p->inventory, NULL);
2218 
2219                 if (player_get_str(p) < 1)
2220                     player_die(p, PD_EFFECT, ET_DEC_STR);
2221                 break;
2222 
2223             case DAM_DEC_WIS:
2224                 if (player_get_wis(p) < 1)
2225                     player_die(p, PD_EFFECT, ET_DEC_WIS);
2226                 break;
2227 
2228             default:
2229                 break;
2230             }
2231         }
2232         else
2233         {
2234             log_add_entry(nlarn->log, "You are not affected.");
2235         }
2236         break;
2237 
2238     case DAM_DRAIN_LIFE:
2239         if (player_effect(p, ET_UNDEAD_PROTECTION)
2240                 || !chance(damage_amount - player_get_wis(p)))
2241         {
2242             /* undead protection cancels drain life attacks */
2243             log_add_entry(nlarn->log, "You are not affected.");
2244         }
2245         else
2246         {
2247             log_add_entry(nlarn->log, "`lightred`Your life energy is drained.`end`");
2248             player_level_lose(p, 1);
2249 
2250             /* this is the only attack that can not be caught by the test below */
2251             p->attacked = TRUE;
2252         }
2253         break;
2254 
2255     default:
2256         /* the other damage types are not handled here */
2257         break;
2258     }
2259 
2260     if (game_wizardmode(nlarn))
2261         log_add_entry(nlarn->log, "[applied: %d]", hp_orig - p->hp);
2262 
2263     /* check if an attack had an effect */
2264     if (p->hp < hp_orig || p->effects->len > effects_count)
2265     {
2266         /* set the attacked flag */
2267         p->attacked = TRUE;
2268     }
2269 }
2270 
player_hp_max_gain(player * p,int count)2271 int player_hp_max_gain(player *p, int count)
2272 {
2273     g_assert(p != NULL);
2274 
2275     p->hp_max += count;
2276     /* do _NOT_ increase hp */
2277 
2278     return p->hp_max;
2279 }
2280 
player_hp_max_lose(player * p,int count)2281 int player_hp_max_lose(player *p, int count)
2282 {
2283     g_assert(p != NULL);
2284 
2285     p->hp_max -= count;
2286 
2287     if (p->hp_max < 1)
2288         p->hp_max = 1;
2289 
2290     if (p->hp > (int)p->hp_max)
2291         p->hp = p->hp_max;
2292 
2293     return p->hp_max;
2294 }
2295 
player_mp_gain(player * p,int count)2296 int player_mp_gain(player *p, int count)
2297 {
2298     g_assert(p != NULL);
2299 
2300     p->mp += count;
2301     if (p->mp > player_get_mp_max(p))
2302         p->mp = player_get_mp_max(p);
2303 
2304     return p->mp;
2305 }
2306 
player_mp_lose(player * p,int count)2307 int player_mp_lose(player *p, int count)
2308 {
2309     g_assert(p != NULL);
2310 
2311     p->mp -= count;
2312     if (p->mp < 0)
2313         p->mp = 0;
2314 
2315     return p->mp;
2316 }
2317 
player_mp_max_gain(player * p,int count)2318 int player_mp_max_gain(player *p, int count)
2319 {
2320     g_assert(p != NULL);
2321 
2322     p->mp_max += count;
2323     /* do _NOT_ increase mp */
2324 
2325     return p->mp_max;
2326 }
2327 
player_mp_max_lose(player * p,int count)2328 int player_mp_max_lose(player *p, int count)
2329 {
2330     g_assert(p != NULL);
2331 
2332     p->mp_max -= count;
2333 
2334     if (p->mp_max < 1)
2335         p->mp_max = 1;
2336 
2337     if (p->mp > (int)p->mp_max)
2338         p->mp = p->mp_max;
2339 
2340     return p->mp_max;
2341 }
2342 
player_effect_add(player * p,effect * e)2343 effect *player_effect_add(player *p, effect *e)
2344 {
2345     g_assert(p != NULL && e != NULL);
2346 
2347     /* one-time effects are handled here */
2348     if (e->turns == 1)
2349     {
2350         switch (e->type)
2351         {
2352         case ET_INC_CON:
2353             p->constitution += e->amount;
2354             break;
2355 
2356         case ET_INC_DEX:
2357             p->dexterity += e->amount;
2358             break;
2359 
2360         case ET_INC_INT:
2361             p->intelligence += e->amount;
2362             break;
2363 
2364         case ET_INC_STR:
2365             p->strength += e->amount;
2366             player_inv_weight_recalc(p->inventory, NULL);
2367             break;
2368 
2369         case ET_INC_WIS:
2370             p->wisdom += e->amount;
2371             break;
2372 
2373         case ET_INC_RND:
2374             while (e->amount-- > 0)
2375             {
2376                 player_effect_add(p, effect_new(rand_m_n(ET_INC_CON,
2377                                                 ET_INC_WIS)));
2378             }
2379             break;
2380 
2381         case ET_INC_HP_MAX:
2382             {
2383                 float delta = (player_get_hp_max(p) / 100.0) * e->amount;
2384                 int amount = max(1, (int)round(delta));
2385                 p->hp_max += amount;
2386                 player_hp_gain(p, amount);
2387             }
2388             break;
2389 
2390         case ET_INC_MP_MAX:
2391             {
2392                 float delta = (player_get_mp_max(p) / 100.0) * e->amount;
2393                 int amount = max(1, (int)round(delta));
2394                 p->mp_max += amount;
2395                 player_mp_gain(p, amount);
2396             }
2397             break;
2398 
2399         case ET_INC_LEVEL:
2400             player_level_gain(p, e->amount);
2401             break;
2402 
2403         case ET_INC_EXP:
2404             /* looks like a reasonable amount */
2405             player_exp_gain(p, rand_1n(player_lvl_exp[p->level - 1]
2406                                     - player_lvl_exp[p->level - 2]));
2407             break;
2408 
2409         case ET_INC_HP:
2410         case ET_MAX_HP:
2411             {
2412                 /* also cure sickness, if present */
2413                 effect *sickness;
2414                 if ((sickness = player_effect_get(p, ET_SICKNESS)))
2415                 {
2416                     player_effect_del(p, sickness);
2417                 }
2418             }
2419             if (p->hp != (int)p->hp_max)
2420             {
2421                     guint amount = (p->hp_max * e->amount) / 100;
2422                     player_hp_gain(p, amount);
2423             }
2424             else if (e->item)
2425             {
2426                     /*
2427                      * Player's hp is at the max; increase max hp.
2428                      * (only when drinking a potion)
2429                      */
2430                     guint amount = e->amount;
2431 
2432                     effect_destroy(e);
2433                     e = effect_new(ET_INC_HP_MAX);
2434                     /* determine a reasonable percentage max_hp will increase,
2435                        i.e. 5% for the MAX_HP, 1% for INC_HP */
2436                     e->amount = amount / 20;
2437                     return player_effect_add(p, e);
2438             }
2439             break;
2440 
2441         case ET_INC_MP:
2442         case ET_MAX_MP:
2443             if (p->mp != (int)p->mp_max)
2444             {
2445                     guint amount = (p->mp_max * e->amount) / 100;
2446                     player_mp_gain(p, amount);
2447             }
2448             else if (e->item)
2449             {
2450                     /*
2451                      * Player's mp is at the max; increase max mp.
2452                      * (only when drinking a potion)
2453                      */
2454                     guint amount = e->amount;
2455 
2456                     effect_destroy(e);
2457                     e = effect_new(ET_INC_MP_MAX);
2458                     /* determine a reasonable percentage max_mp will increase,
2459                        i.e. 5% for the MAX_MP, 1% for INC_MP */
2460                     e->amount = amount / 20;
2461                     return player_effect_add(p, e);
2462             }
2463             break;
2464 
2465         case ET_DEC_CON:
2466             p->constitution -= e->amount;
2467             break;
2468 
2469         case ET_DEC_DEX:
2470             p->dexterity -= e->amount;
2471             break;
2472 
2473         case ET_DEC_INT:
2474             p->intelligence -= e->amount;
2475             break;
2476 
2477         case ET_DEC_STR:
2478             p->strength -= e->amount;
2479             player_inv_weight_recalc(p->inventory, NULL);
2480             break;
2481 
2482         case ET_DEC_WIS:
2483             p->wisdom -= e->amount;
2484             break;
2485 
2486         case ET_DEC_RND:
2487             player_effect_add(p, effect_new(rand_m_n(ET_DEC_CON, ET_DEC_WIS)));
2488             break;
2489 
2490         default:
2491             /* nop */
2492             break;
2493         }
2494 
2495         if (effect_get_amount(e) > 0 && effect_get_msg_start(e))
2496             log_add_entry(nlarn->log, "%s", effect_get_msg_start(e));
2497         else if (effect_get_amount(e) < 0 && effect_get_msg_stop(e))
2498             log_add_entry(nlarn->log, "%s", effect_get_msg_stop(e));
2499 
2500         effect_destroy(e);
2501         e = NULL;
2502     }
2503     else if (e->type == ET_SLEEP)
2504     {
2505         if (effect_get_msg_start(e))
2506             log_add_entry(nlarn->log, "%s", effect_get_msg_start(e));
2507 
2508         player_make_move(p, e->turns, FALSE, "asleep");
2509 
2510         effect_destroy(e);
2511         e = NULL;
2512     }
2513     else
2514     {
2515         int str_orig = player_get_str(p);
2516 
2517         e = effect_add(p->effects, e);
2518 
2519         /* only log a message if the effect has really been added and
2520            actually has a value */
2521         if (e)
2522         {
2523             if (effect_get_amount(e) > 0 && effect_get_msg_start(e))
2524                 log_add_entry(nlarn->log, "%s", effect_get_msg_start(e));
2525             else if (effect_get_amount(e) < 0 && effect_get_msg_stop(e))
2526                 log_add_entry(nlarn->log, "%s", effect_get_msg_stop(e));
2527 
2528             /* If the effect was caused by a potion, delete the link
2529              * between item and effect now, otherwise the effects caused
2530              * by potions are not returned whenever player_effect_get()
2531              * is used. (e.g. the spell "cure blindness" cannot cure
2532              * blindness) anymore. */
2533             if (e->item && ((item *)game_item_get(nlarn, e->item))->type == IT_POTION)
2534             {
2535                 e->item = NULL;
2536             }
2537         }
2538 
2539         if (str_orig != player_get_str(p))
2540         {
2541             /* strength has been modified -> recalc burdened status */
2542             player_inv_weight_recalc(p->inventory, NULL);
2543         }
2544     }
2545 
2546     return e;
2547 }
2548 
player_effects_add(player * p,GPtrArray * effects)2549 void player_effects_add(player *p, GPtrArray *effects)
2550 {
2551     g_assert (p != NULL);
2552 
2553     /* if effects is NULL */
2554     if (!effects) return;
2555 
2556     for (guint idx = 0; idx < effects->len; idx++)
2557     {
2558         gpointer effect_id = g_ptr_array_index(effects, idx);
2559         effect *e = game_effect_get(nlarn, effect_id);
2560         player_effect_add(p, e);
2561     }
2562 }
2563 
player_effect_del(player * p,effect * e)2564 int player_effect_del(player *p, effect *e)
2565 {
2566     int result, str_orig;
2567 
2568     g_assert(p != NULL && e != NULL && e->type > ET_NONE && e->type < ET_MAX);
2569 
2570     str_orig = player_get_str(p);
2571 
2572     if ((result = effect_del(p->effects, e)))
2573     {
2574         if (effect_get_amount(e) > 0 && effect_get_msg_stop(e))
2575             log_add_entry(nlarn->log, "%s", effect_get_msg_stop(e));
2576         else if (effect_get_amount(e) < 0 && effect_get_msg_start(e))
2577             log_add_entry(nlarn->log, "%s", effect_get_msg_start(e));
2578 
2579         if (str_orig != player_get_str(p))
2580         {
2581             /* strength has been modified -> recalc burdened status */
2582             player_inv_weight_recalc(p->inventory, NULL);
2583         }
2584 
2585         /* finally destroy the effect if its not bound to an item*/
2586         if (!e->item)
2587             effect_destroy(e);
2588     }
2589 
2590     return result;
2591 }
2592 
player_effects_del(player * p,GPtrArray * effects)2593 void player_effects_del(player *p, GPtrArray *effects)
2594 {
2595     g_assert (p != NULL);
2596 
2597     /* if effects is NULL */
2598     if (!effects) return;
2599 
2600     for (guint idx = 0; idx < effects->len; idx++)
2601     {
2602         gpointer effect_id = g_ptr_array_index(effects, idx);
2603         effect *e = game_effect_get(nlarn, effect_id);
2604         player_effect_del(p, e);
2605     }
2606 }
2607 
player_effect_get(player * p,effect_t et)2608 effect *player_effect_get(player *p, effect_t et)
2609 {
2610     g_assert(p != NULL && et > ET_NONE && et < ET_MAX);
2611     return effect_get(p->effects, et);
2612 }
2613 
player_effect(player * p,effect_t et)2614 int player_effect(player *p, effect_t et)
2615 {
2616     g_assert(p != NULL && et > ET_NONE && et < ET_MAX);
2617     return effect_query(p->effects, et);
2618 }
2619 
player_effect_text(player * p)2620 char **player_effect_text(player *p)
2621 {
2622     char **text = strv_new();
2623 
2624     for (guint pos = 0; pos < p->effects->len; pos++)
2625     {
2626         effect *e = game_effect_get(nlarn, g_ptr_array_index(p->effects, pos));
2627 
2628         if (effect_get_desc(e) != NULL)
2629         {
2630             strv_append_unique(&text, effect_get_desc(e));
2631         }
2632     }
2633 
2634     return text;
2635 }
2636 
player_inv_display(player * p)2637 int player_inv_display(player *p)
2638 {
2639     GPtrArray *callbacks;
2640     display_inv_callback *callback;
2641 
2642     g_assert(p != NULL);
2643 
2644     if (inv_length(p->inventory) == 0)
2645     {
2646         /* don't show empty inventory */
2647         log_add_entry(nlarn->log, "You do not carry anything.");
2648         return FALSE;
2649     }
2650 
2651     /* define callback functions */
2652     callbacks = g_ptr_array_new();
2653 
2654     callback = g_malloc0(sizeof(display_inv_callback));
2655     callback->description = "(d)rop";
2656     callback->helpmsg = "Drop the selected item. If the item is a stack of multiple items, you will be prompted for the amount you want to drop.";
2657     callback->key = 'd';
2658     callback->inv = &p->inventory;
2659     callback->function = &player_item_drop;
2660     callback->checkfun = &player_item_is_dropable;
2661     g_ptr_array_add(callbacks, callback);
2662 
2663     callback = g_malloc0(sizeof(display_inv_callback));
2664     callback->description = "(e)quip";
2665     callback->helpmsg = "Equip the selected item.";
2666     callback->key = 'e';
2667     callback->function = &player_item_equip;
2668     callback->checkfun = &player_item_is_equippable;
2669     g_ptr_array_add(callbacks, callback);
2670 
2671     callback = g_malloc0(sizeof(display_inv_callback));
2672     callback->description = "(o)pen";
2673     callback->helpmsg = "Open the selected container.";
2674     callback->key = 'o';
2675     callback->function = &container_open;
2676     callback->checkfun = &player_item_is_container;
2677     g_ptr_array_add(callbacks, callback);
2678 
2679     callback = g_malloc0(sizeof(display_inv_callback));
2680     callback->description = "(s)tore";
2681     callback->helpmsg = "Put the item into a container you carry or one that is on the floor.";
2682     callback->key = 's';
2683     callback->function = &container_item_add;
2684     callback->checkfun = &player_item_can_be_added_to_container;
2685     g_ptr_array_add(callbacks, callback);
2686 
2687     callback = g_malloc0(sizeof(display_inv_callback));
2688     callback->description = "(u)nequip";
2689     callback->helpmsg = "Unequip the selected item.";
2690     callback->key = 'u';
2691     callback->function = &player_item_unequip_wrapper;
2692     callback->checkfun = &player_item_is_unequippable;
2693     g_ptr_array_add(callbacks, callback);
2694 
2695     /* unequip and use should never appear together */
2696     callback = g_malloc0(sizeof(display_inv_callback));
2697     callback->description = "(u)se";
2698     callback->helpmsg = "Use the selected item.";
2699     callback->key = 'u';
2700     callback->function = &player_item_use;
2701     callback->checkfun = &player_item_is_usable;
2702     g_ptr_array_add(callbacks, callback);
2703 
2704     callback = g_malloc0(sizeof(display_inv_callback));
2705     callback->description = "(n)ote";
2706     callback->helpmsg = "Add a note to the selected item or edit or delete an existing note.";
2707     callback->key = 'n';
2708     callback->function = &player_item_notes;
2709     g_ptr_array_add(callbacks, callback);
2710 
2711     /* display inventory */
2712     display_inventory("Inventory", p, &p->inventory, callbacks, FALSE,
2713                       TRUE, FALSE, NULL);
2714 
2715     /* clean up */
2716     display_inv_callbacks_clean(callbacks);
2717 
2718     return TRUE;
2719 }
2720 
player_print_weight(float weight)2721 static char *player_print_weight(float weight)
2722 {
2723     static char buf[21] = "";
2724 
2725     const char *unit = "g";
2726     if (weight > 1000)
2727     {
2728         weight = weight / 1000;
2729         unit = "kg";
2730     }
2731 
2732     g_snprintf(buf, 20, "%g%s", weight, unit);
2733 
2734     return buf;
2735 }
2736 
player_can_carry(player * p)2737 char *player_can_carry(player *p)
2738 {
2739     static char buf[21] = "";
2740     g_snprintf(buf, 20, "%s",
2741                player_print_weight(2000 * 1.3 * (float)player_get_str(p)));
2742     return buf;
2743 }
2744 
player_inv_weight(player * p)2745 char *player_inv_weight(player *p)
2746 {
2747     static char buf[21] = "";
2748     g_snprintf(buf, 20, "%s",
2749                player_print_weight((float)inv_weight(p->inventory)));
2750     return buf;
2751 }
2752 
player_inv_pre_add(inventory * inv,item * it)2753 int player_inv_pre_add(inventory *inv, item *it)
2754 {
2755     player *p;
2756     int pack_weight;
2757     float can_carry;
2758 
2759     p = (player *)inv->owner;
2760 
2761     if (player_effect(p, ET_OVERSTRAINED))
2762     {
2763         log_add_entry(nlarn->log, "You are already overloaded!");
2764         return FALSE;
2765     }
2766 
2767     /* calculate inventory weight */
2768     pack_weight = inv_weight(inv);
2769 
2770     /* player can carry 2kg per str */
2771     can_carry = 2000 * (float)player_get_str(p);
2772 
2773     /* check if item weight can be carried */
2774     if ((pack_weight + item_weight(it)) > (int)(can_carry * 1.3))
2775     {
2776         /* get item description */
2777         gchar *buf = item_describe(it, player_item_known(p, it), FALSE, TRUE);
2778         /* capitalize first letter */
2779         buf[0] = g_ascii_toupper(buf[0]);
2780 
2781         log_add_entry(nlarn->log, "%s %s too heavy for you.", buf,
2782                       is_are(it->count));
2783 
2784         g_free(buf);
2785         return FALSE;
2786     }
2787 
2788     return TRUE;
2789 }
2790 
player_inv_weight_recalc(inventory * inv,item * it)2791 void player_inv_weight_recalc(inventory *inv, item *it __attribute__((unused)))
2792 {
2793     int pack_weight;
2794     float can_carry;
2795     effect *e = NULL;
2796 
2797     player *p;
2798 
2799     g_assert (inv != NULL);
2800 
2801     p = (player *)inv->owner;       /* make shortcut */
2802     pack_weight = inv_weight(inv);  /* calculate inventory weight */
2803 
2804     /* the player can carry 2kg per str */
2805     can_carry = 2000 * (float)player_get_str(p);
2806 
2807     if (pack_weight > (int)(can_carry * 1.3))
2808     {
2809         /* OVERSTRAINED  */
2810         if ((e = player_effect_get(p, ET_BURDENED)))
2811         {
2812             /* get rid of burden effect (mute log to avoid pointless message) */
2813             log_disable(nlarn->log);
2814             player_effect_del(p, e);
2815             log_enable(nlarn->log);
2816         }
2817 
2818         /* make overstrained */
2819         if (!player_effect(p, ET_OVERSTRAINED))
2820         {
2821             player_effect_add(p, effect_new(ET_OVERSTRAINED));
2822         }
2823     }
2824     else if (pack_weight < (int)(can_carry * 1.3) && (pack_weight > can_carry))
2825     {
2826         /* BURDENED */
2827         if ((e = player_effect_get(p, ET_OVERSTRAINED)))
2828         {
2829             /* get rid of overstrained effect */
2830             log_disable(nlarn->log);
2831             player_effect_del(p, e);
2832             log_enable(nlarn->log);
2833         }
2834 
2835         if (!player_effect(p, ET_BURDENED))
2836         {
2837             player_effect_add(p, effect_new(ET_BURDENED));
2838         }
2839     }
2840     else if (pack_weight < can_carry)
2841     {
2842         /* NOT burdened, NOT overstrained */
2843         if ((e = player_effect_get(p, ET_OVERSTRAINED)))
2844         {
2845             player_effect_del(p, e);
2846         }
2847 
2848         if ((e = player_effect_get(p, ET_BURDENED)))
2849         {
2850             player_effect_del(p, e);
2851         }
2852     }
2853 }
2854 
player_paperdoll(player * p)2855 void player_paperdoll(player *p)
2856 {
2857     gchar *equipment = player_equipment_list(p);
2858 
2859     if (strlen(equipment) > 0)
2860         display_show_message("Worn equipment", equipment, 0);
2861     else
2862         log_add_entry(nlarn->log, "You do not wear any equipment.");
2863 
2864     g_free(equipment);
2865 }
2866 
player_item_equip(player * p,inventory ** inv,item * it)2867 void player_item_equip(player *p, inventory **inv __attribute__((unused)), item *it)
2868 {
2869     g_assert(p != NULL && it != NULL);
2870 
2871     /* Check if the player is able to move. */
2872     if (!player_movement_possible(p))
2873         return;
2874 
2875     item **islot = NULL;  /* pointer to chosen item slot */
2876     int atime = 0;        /* time the desired action takes */
2877     gboolean known = player_item_known(p, it);
2878     gchar *desc = item_describe(it, known, FALSE, TRUE);
2879 
2880     /* the idea behind the time values: one turn to take one item off,
2881        one turn to get the item out of the pack */
2882 
2883     switch (it->type)
2884     {
2885     case IT_AMULET:
2886         if (p->eq_amulet == NULL)
2887         {
2888             if (!player_make_move(p, 2, TRUE, "putting %s on", desc))
2889             {
2890                 /* interrupted */
2891                 g_free(desc);
2892                 return;
2893             }
2894 
2895             p->eq_amulet = it;
2896             log_add_entry(nlarn->log, "You put %s on.", desc);
2897             p->identified_amulets[it->id] = TRUE;
2898         }
2899         break;
2900 
2901     case IT_AMMO:
2902         if (p->eq_quiver == NULL)
2903         {
2904             if (!player_make_move(p, 2, TRUE, "putting %s into the quiver", desc))
2905             {
2906                 /* interrupted */
2907                 g_free(desc);
2908                 return;
2909             }
2910 
2911             p->eq_quiver = it;
2912             log_add_entry(nlarn->log, "You put %s into your quiver.", desc);
2913         }
2914         break;
2915 
2916     case IT_ARMOUR:
2917         switch (armour_class(it))
2918         {
2919         case AC_BOOTS:
2920             islot = &(p->eq_boots);
2921             atime = 3;
2922             break;
2923 
2924         case AC_CLOAK:
2925             islot = &(p->eq_cloak);
2926             atime = 2;
2927             break;
2928 
2929         case AC_GLOVES:
2930             islot = &(p->eq_gloves);
2931             atime = 3;
2932             break;
2933 
2934         case AC_HELMET:
2935             islot = &(p->eq_helmet);
2936             atime = 2;
2937             break;
2938 
2939         case AC_SHIELD:
2940             islot = &(p->eq_shield);
2941             atime = 2;
2942             break;
2943 
2944         case AC_SUIT:
2945             islot = &(p->eq_suit);
2946             atime = it->id + 1;
2947             break;
2948 
2949         case AC_MAX:
2950             /* shouldn't happen */
2951             break;
2952         }
2953 
2954         if ((islot != NULL) && (*islot == NULL))
2955         {
2956             if (!player_make_move(p, atime, TRUE, "wearing %s", desc))
2957             {
2958                 /* interrupted */
2959                 g_free(desc);
2960                 return;
2961             }
2962 
2963             /* identify the armour while wearing */
2964             p->identified_armour[it->id] = TRUE;
2965             /* the armour's bonus is revealed when putting it on */
2966             it->bonus_known = TRUE;
2967 
2968             /* Refresh the armour's description before logging. */
2969             g_free(desc);
2970             desc = item_describe(it, known, TRUE, FALSE);
2971             log_add_entry(nlarn->log, "You are now wearing %s.", desc);
2972 
2973             /* put the piece of armour in the equipment slot */
2974             *islot = it;
2975         }
2976         break;
2977 
2978     case IT_RING:
2979         /* determine item slot */
2980         if (p->eq_ring_l == NULL)
2981             islot = &(p->eq_ring_l);
2982         else if (p->eq_ring_r == NULL)
2983             islot = &(p->eq_ring_r);
2984 
2985         if (islot != NULL)
2986         {
2987             if (!player_make_move(p, 2, TRUE, "putting %s on", desc))
2988             {
2989                 /* interrupted */
2990                 g_free(desc);
2991                 return;
2992             }
2993 
2994             log_add_entry(nlarn->log, "You put %s on.", desc);
2995             *islot = it;
2996             p->identified_rings[it->id] = TRUE;
2997 
2998             if (ring_bonus_is_obs(it))
2999                 it->bonus_known = TRUE;
3000         }
3001         break;
3002 
3003     case IT_WEAPON:
3004         if (p->eq_weapon != NULL && p->eq_sweapon == NULL)
3005         {
3006             /* make the primary weapon the secondary one */
3007             weapon_swap(p);
3008         }
3009 
3010         if (p->eq_weapon == NULL)
3011         {
3012             if (!player_make_move(p, 2 + weapon_is_twohanded(it),
3013                                   TRUE, "wielding %s", desc))
3014             {
3015                 /* action aborted */
3016                 g_free(desc);
3017                 return;
3018             }
3019 
3020             p->eq_weapon = it;
3021             log_add_entry(nlarn->log, "You now wield %s.", desc);
3022         }
3023         break;
3024 
3025     default:
3026         /* nop */
3027         break;
3028     }
3029 
3030     /* free memory allocated at the beginning */
3031     g_free(desc);
3032 
3033     if (it->cursed)
3034     {
3035         /* generate a new description with definite article */
3036         desc = item_describe(it, known, TRUE, TRUE);
3037 
3038         /* capitalize first letter */
3039         desc[0] = g_ascii_toupper(desc[0]);
3040         log_add_entry(nlarn->log, "`lightmagenta`%s %s`end`",
3041             desc, it->type == IT_WEAPON
3042                 ? "welds itself into your hand."
3043                 : "feels uncomfortably cold!");
3044         it->blessed_known = TRUE;
3045         g_free(desc);
3046     }
3047 
3048     player_effects_add(p, it->effects);
3049 
3050     if (known != player_item_known(p, it))
3051     {
3052         /* The player identified the item by using it. */
3053         desc = item_describe(it, player_item_known(p, it), FALSE, FALSE);
3054         log_add_entry(nlarn->log, "It seems that this is %s.", desc);
3055         g_free(desc);
3056     }
3057 }
3058 
player_item_unequip_wrapper(player * p,inventory ** inv,item * it)3059 void player_item_unequip_wrapper(player *p, inventory **inv, item *it)
3060 {
3061     player_item_unequip(p, inv, it, FALSE);
3062 }
3063 
player_item_unequip(player * p,inventory ** inv,item * it,gboolean forced)3064 void player_item_unequip(player *p,
3065                          inventory **inv __attribute__((unused)),
3066                          item *it,
3067                          gboolean forced)
3068 {
3069     g_assert(p != NULL && it != NULL);
3070 
3071     /* Check if the player is able to move. */
3072     if (!player_movement_possible(p))
3073         return;
3074 
3075    /* the idea behind the time values: one turn to take one item off,
3076       one turn to get the item out of the pack */
3077 
3078     /* item description */
3079     gchar *desc = item_describe(it, player_item_known(p, it), FALSE, TRUE);
3080 
3081     switch (it->type)
3082     {
3083     case IT_AMULET:
3084         if (p->eq_amulet == it)
3085         {
3086             if (forced || !it->cursed)
3087             {
3088                 if (!forced)
3089                 {
3090                     if (!player_make_move(p, 2, TRUE, "removing %s", desc))
3091                     {
3092                         /* interrupted */
3093                         g_free(desc);
3094                         return;
3095                     }
3096 
3097                     log_add_entry(nlarn->log, "You remove %s.", desc);
3098                 }
3099 
3100                 if (p->eq_amulet)
3101                 {
3102                     player_effects_del(p, p->eq_amulet->effects);
3103                     p->eq_amulet = NULL;
3104                 }
3105             }
3106             else
3107             {
3108                 log_add_entry(nlarn->log, "You can not remove %s.%s", desc,
3109                               it->blessed_known ? "" : " It appears to be cursed.");
3110 
3111                 it->blessed_known = TRUE;
3112             }
3113         }
3114         break;
3115 
3116     case IT_AMMO:
3117         if (p->eq_quiver == it)
3118         {
3119             if (!forced)
3120             {
3121                 if (!player_make_move(p, 2, TRUE, "taking %s out of the quiver", desc))
3122                 {
3123                     /* interrupted */
3124                     g_free(desc);
3125                     return;
3126                 }
3127 
3128                 log_add_entry(nlarn->log, "You take %s out your quiver.", desc);
3129             }
3130 
3131             p->eq_quiver = NULL;
3132         }
3133         break;
3134 
3135         /* take off armour */
3136     case IT_ARMOUR:
3137     {
3138         int atime = 0;
3139         item **aslot = NULL;  /* pointer to armour slot */
3140 
3141         switch (armour_class(it))
3142         {
3143         case AC_BOOTS:
3144             aslot = &(p->eq_boots);
3145             atime = 3;
3146             break;
3147 
3148         case AC_CLOAK:
3149             aslot = &(p->eq_cloak);
3150             atime = 2;
3151             break;
3152 
3153         case AC_GLOVES:
3154             aslot = &(p->eq_gloves);
3155             atime = 3;
3156             break;
3157 
3158         case AC_HELMET:
3159             aslot = &(p->eq_helmet);
3160             atime = 2;
3161             break;
3162 
3163         case AC_SHIELD:
3164             aslot = &(p->eq_shield);
3165             atime = 2;
3166             break;
3167 
3168         case AC_SUIT:
3169             aslot = &(p->eq_suit);
3170             /* the better the armour, the longer it takes to get out of it */
3171             atime = (p->eq_suit)->type + 1;
3172             break;
3173         default:
3174             break;
3175         }
3176 
3177         if ((aslot != NULL) && (*aslot == it))
3178         {
3179             if (forced || !it->cursed)
3180             {
3181                 if (!forced)
3182                 {
3183                     if (!player_make_move(p, atime, TRUE, "taking %s off", desc))
3184                     {
3185                         /* interrupted */
3186                         g_free(desc);
3187                         return;
3188                     }
3189 
3190                     log_add_entry(nlarn->log, "You finish taking off %s.", desc);
3191                 }
3192                 if (*aslot)
3193                 {
3194                     player_effects_del(p, (*aslot)->effects);
3195                     *aslot = NULL;
3196                 }
3197             }
3198             else
3199             {
3200                 log_add_entry(nlarn->log, "You can't take of %s.%s", desc,
3201                               it->blessed_known ? "" : " It appears to be cursed.");
3202 
3203                 it->blessed_known = TRUE;
3204             }
3205         }
3206     }
3207     break;
3208 
3209     case IT_RING:
3210     {
3211         item **rslot = NULL;  /* pointer to ring slot */
3212 
3213         /* determine ring slot */
3214         if (it == p->eq_ring_l)
3215             rslot = &(p->eq_ring_l);
3216         else if (it == p->eq_ring_r)
3217             rslot = &(p->eq_ring_r);
3218 
3219         if (rslot != NULL)
3220         {
3221             if (forced || !it->cursed)
3222             {
3223                 if (!forced)
3224                 {
3225                     if (!player_make_move(p, 2, TRUE, "removing %s", desc))
3226                     {
3227                         /* interrupted */
3228                         g_free(desc);
3229                         return;
3230                     }
3231 
3232                     log_add_entry(nlarn->log, "You remove %s.", desc);
3233                 }
3234 
3235                 if (*rslot)
3236                 {
3237                     player_effects_del(p, (*rslot)->effects);
3238                     *rslot = NULL;
3239                 }
3240             }
3241             else
3242             {
3243                 log_add_entry(nlarn->log, "You can not remove %s.%s", desc,
3244                               it->blessed_known ? "" : " It appears to be cursed.");
3245 
3246                 it->blessed_known = TRUE;
3247             }
3248         }
3249     }
3250     break;
3251 
3252     case IT_WEAPON:
3253         {
3254             item **wslot = NULL;
3255 
3256             if (p->eq_weapon && it == p->eq_weapon) {
3257                 wslot = &(p->eq_weapon);
3258             } else if (p->eq_sweapon && it == p->eq_sweapon) {
3259                 wslot = &(p->eq_sweapon);
3260             }
3261 
3262             if (wslot && (forced || !(*wslot)->cursed))
3263             {
3264                 if (!forced)
3265                 {
3266                     if (!player_make_move(p, 2 + weapon_is_twohanded(*wslot),
3267                                           TRUE, "putting %s away", desc))
3268                     {
3269                         /* interrupted */
3270                         g_free(desc);
3271                         return;
3272                     }
3273 
3274                     log_add_entry(nlarn->log, "You put away %s.", desc);
3275                 }
3276 
3277                 player_effects_del(p, (*wslot)->effects);
3278                 *wslot = NULL;
3279             }
3280             else if (wslot != NULL)
3281             {
3282                 log_add_entry(nlarn->log, "You can't put away %s. " \
3283                               "It's welded into your hands.", desc);
3284             }
3285         }
3286         break;
3287 
3288     default:
3289         break;
3290     }
3291 
3292     g_free(desc);
3293 }
3294 
3295 /* silly filter to get containers */
player_item_is_container(player * p,item * it)3296 int player_item_is_container(player *p __attribute__((unused)), item *it)
3297 {
3298     g_assert(it != NULL && it->type < IT_MAX);
3299 
3300     return (it->type == IT_CONTAINER);
3301 }
3302 
3303 /* silly filter to check if item can be put into a container */
player_item_can_be_added_to_container(player * p,item * it)3304 int player_item_can_be_added_to_container(player *p, item *it)
3305 {
3306     g_assert(p != NULL && it != NULL && it->type < IT_MAX);
3307 
3308     if (it->type == IT_CONTAINER)
3309     {
3310         return FALSE;
3311     }
3312 
3313     if (player_item_is_equipped(p, it))
3314     {
3315         return FALSE;
3316     }
3317 
3318     /* check player's inventory for containers */
3319     for (guint idx = 0; idx < inv_length(p->inventory); idx++)
3320     {
3321         item *i = inv_get(p->inventory, idx);
3322         if (i->type == IT_CONTAINER)
3323         {
3324             return TRUE;
3325         }
3326     }
3327 
3328     /* no match till now, check floor for containers */
3329     inventory **floor = map_ilist_at(game_map(nlarn, Z(p->pos)), p->pos);
3330 
3331     for (guint idx = 0; idx < inv_length(*floor); idx++)
3332     {
3333         item *i = inv_get(*floor, idx);
3334         if (i->type == IT_CONTAINER)
3335         {
3336             return TRUE;
3337         }
3338     }
3339 
3340     /* nope */
3341     return FALSE;
3342 }
3343 
player_item_filter_unequippable(item * it)3344 int player_item_filter_unequippable(item* it)
3345 {
3346     return player_item_is_unequippable(nlarn->p, it);
3347 }
3348 
player_item_is_equipped(player * p,item * it)3349 int player_item_is_equipped(player *p, item *it)
3350 {
3351     g_assert(p != NULL && it != NULL);
3352 
3353     if (!item_is_equippable(it->type))
3354         return FALSE;
3355 
3356     if (it == p->eq_amulet)
3357         return TRUE;
3358 
3359     if (it == p->eq_boots)
3360         return TRUE;
3361 
3362     if (it == p->eq_cloak)
3363         return TRUE;
3364 
3365     if (it == p->eq_gloves)
3366         return TRUE;
3367 
3368     if (it == p->eq_helmet)
3369         return TRUE;
3370 
3371     if (it == p->eq_ring_l)
3372         return TRUE;
3373 
3374     if (it == p->eq_ring_r)
3375         return TRUE;
3376 
3377     if (it == p->eq_shield)
3378         return TRUE;
3379 
3380     if (it == p->eq_suit)
3381         return TRUE;
3382 
3383     if (it == p->eq_weapon)
3384         return TRUE;
3385 
3386     if (it == p->eq_quiver)
3387         return TRUE;
3388 
3389     if (it == p->eq_sweapon)
3390         return TRUE;
3391 
3392     return FALSE;
3393 }
3394 
player_item_is_equippable(player * p,item * it)3395 int player_item_is_equippable(player *p, item *it)
3396 {
3397     g_assert(p != NULL && it != NULL);
3398 
3399     if (!item_is_equippable(it->type))
3400         return FALSE;
3401 
3402     if (player_item_is_equipped(p, it))
3403         return FALSE;
3404 
3405     switch(it->type)
3406     {
3407         /* amulets */
3408         case IT_AMULET:
3409             if (p->eq_amulet) return FALSE;
3410             break;
3411 
3412         /* ammo */
3413         case IT_AMMO:
3414             if (p->eq_quiver) return FALSE;
3415             break;
3416 
3417         /* armour */
3418         case IT_ARMOUR:
3419             switch (armour_class(it))
3420             {
3421                 case AC_BOOTS:
3422                     if (p->eq_boots) return FALSE;
3423                     break;
3424 
3425                 case AC_CLOAK:
3426                     if (p->eq_cloak) return FALSE;
3427                     break;
3428 
3429                 case AC_GLOVES:
3430                     if (p->eq_gloves) return FALSE;
3431                     break;
3432 
3433                 case AC_HELMET:
3434                     if (p->eq_helmet) return FALSE;
3435                     break;
3436 
3437                 case AC_SHIELD:
3438                     if (p->eq_shield) return FALSE;
3439 
3440                     /* shield / two-handed weapon combination */
3441                     if (p->eq_weapon && weapon_is_twohanded(p->eq_weapon))
3442                         return FALSE;
3443                     break;
3444 
3445                 case AC_SUIT:
3446                     if (p->eq_cloak) return FALSE;
3447                     if (p->eq_suit) return FALSE;
3448                     break;
3449                 default:
3450                     g_assert(0);
3451                     break;
3452             }
3453             break;
3454 
3455         /* rings */
3456         case IT_RING:
3457             /* wearing gloves */
3458             if (p->eq_gloves) return FALSE;
3459 
3460             /* already wearing two rings */
3461             if (p->eq_ring_l && p->eq_ring_r) return FALSE;
3462             break;
3463 
3464         /* weapons */
3465         case IT_WEAPON:
3466             /* primary and secondary weapon slot used */
3467             if (p->eq_weapon && p->eq_sweapon) return FALSE;
3468 
3469             /* primary weapon is cursed */
3470             if (p->eq_weapon && p->eq_weapon->cursed) return FALSE;
3471 
3472             /* two-handed weapon / shield combinations */
3473             if (weapon_is_twohanded(it) && (p->eq_shield)) return FALSE;
3474             break;
3475         default:
3476             return FALSE;
3477         }
3478 
3479     return TRUE;
3480 }
3481 
player_item_is_unequippable(player * p,item * it)3482 int player_item_is_unequippable(player *p, item *it)
3483 {
3484     g_assert(it);
3485 
3486     if (!player_item_is_equipped(p, it)) return FALSE;
3487     if (it == p->eq_suit && p->eq_cloak) return FALSE;
3488     if (it->type == IT_RING && p->eq_gloves) return FALSE;
3489 
3490     return TRUE;
3491 }
3492 
player_item_is_usable(player * p,item * it)3493 int player_item_is_usable(player *p __attribute__((unused)), item *it)
3494 {
3495     g_assert(it != NULL);
3496     return item_is_usable(it->type);
3497 }
3498 
player_item_is_dropable(player * p,item * it)3499 int player_item_is_dropable(player *p, item *it)
3500 {
3501     g_assert(p != NULL && it != NULL);
3502     return !player_item_is_equipped(p, it);
3503 }
3504 
player_item_is_damaged(player * p,item * it)3505 int player_item_is_damaged(player *p __attribute__((unused)), item *it)
3506 {
3507     g_assert(it != NULL);
3508 
3509     if (it->corroded) return TRUE;
3510     if (it->burnt) return TRUE;
3511     if (it->rusty) return TRUE;
3512 
3513     return FALSE;
3514 }
3515 
player_item_is_affordable(player * p,item * it)3516 int player_item_is_affordable(player *p, item *it)
3517 {
3518     g_assert(p != NULL && it != NULL);
3519 
3520     return ((item_price(it) <= player_get_gold(p))
3521             || (item_price(it) <= p->bank_account));
3522 }
3523 
player_item_is_sellable(player * p,item * it)3524 int player_item_is_sellable(player *p, item *it)
3525 {
3526     g_assert(p != NULL && it != NULL);
3527 
3528     return (!player_item_is_equipped(p, it));
3529 }
3530 
player_item_is_identifiable(player * p,item * it)3531 int player_item_is_identifiable(player *p, item *it)
3532 {
3533     return (!player_item_identified(p, it));
3534 }
3535 
3536 /* determine if item type is known */
player_item_known(player * p,item * it)3537 int player_item_known(player *p, item *it)
3538 {
3539     g_assert(p != NULL && it != NULL && it->type < IT_MAX);
3540 
3541     switch (it->type)
3542     {
3543     case IT_AMULET:
3544         return p->identified_amulets[it->id];
3545         break;
3546 
3547     case IT_ARMOUR:
3548         return !armour_unique(it) || p->identified_armour[it->id];
3549         break;
3550 
3551     case IT_BOOK:
3552         return p->identified_books[it->id];
3553         break;
3554 
3555     case IT_POTION:
3556         return p->identified_potions[it->id];
3557         break;
3558 
3559     case IT_RING:
3560         return p->identified_rings[it->id];
3561         break;
3562 
3563     case IT_SCROLL:
3564         return p->identified_scrolls[it->id];
3565         break;
3566 
3567     default:
3568         return TRUE;
3569     }
3570 }
3571 
3572 /* determine if a concrete item is fully identified */
player_item_identified(player * p,item * it)3573 int player_item_identified(player *p, item *it)
3574 {
3575     gboolean known = FALSE;
3576 
3577     g_assert(p != NULL && it != NULL);
3578 
3579     /* some items are always identified */
3580     if (!item_is_identifyable(it->type))
3581         return TRUE;
3582 
3583     known = player_item_known(p, it);
3584 
3585     if (!it->blessed_known)
3586         known = FALSE;
3587 
3588     if (item_is_optimizable(it->type) && !it->bonus_known)
3589         known = FALSE;
3590 
3591     return known;
3592 }
3593 
player_item_not_equipped(item * it)3594 int player_item_not_equipped(item *it)
3595 {
3596     return !player_item_is_equipped(nlarn->p, it);
3597 }
3598 
player_item_identified_list(player * p)3599 char *player_item_identified_list(player *p)
3600 {
3601     GString *list;
3602     item *it; /* fake pretend item */
3603 
3604     item_t type_ids[] =
3605     {
3606         IT_AMULET,
3607         IT_ARMOUR,
3608         IT_BOOK,
3609         IT_POTION,
3610         IT_RING,
3611         IT_SCROLL,
3612         IT_NONE,
3613     };
3614 
3615     g_assert (p != NULL);
3616 
3617     list = g_string_new(NULL);
3618     it = item_new(type_ids[1], 1);
3619     it->bonus_known = FALSE;
3620 
3621     for (guint idx = 0; type_ids[idx] != IT_NONE; idx++)
3622     {
3623         guint count = 0;
3624         GString *sublist = g_string_new(NULL);
3625 
3626         /* item category header */
3627         char *heading = g_strdup(item_name_pl(type_ids[idx]));
3628         heading[0] = g_ascii_toupper(heading[0]);
3629 
3630         /* no linefeed before first category */
3631         if (idx > 0) g_string_append_c(sublist, '\n');
3632         g_string_append_printf(sublist, "`yellow`%s`end`\n", heading);
3633 
3634         g_free(heading);
3635 
3636         it->type = type_ids[idx];
3637 
3638         for (guint id = 0; id < item_max_id(type_ids[idx]); id++)
3639         {
3640             it->id = id;
3641 
3642             /* no non-unique armour in the list */
3643             if (it->type == IT_ARMOUR && !armour_unique(it))
3644                 continue;
3645 
3646             if (player_item_known(p, it))
3647             {
3648                 gchar *desc_unid = item_describe(it, FALSE, TRUE, FALSE);
3649                 gchar *desc_id = item_describe(it, TRUE, TRUE, FALSE);
3650 
3651                 g_string_append_printf(sublist, " `lightgreen`%33s`end` - %s \n", desc_unid, desc_id);
3652 
3653                 g_free(desc_id);
3654                 g_free(desc_unid);
3655                 count++;
3656             }
3657         }
3658 
3659         if (count)
3660         {
3661             g_string_append(list, sublist->str);
3662         }
3663 
3664         g_string_free(sublist, TRUE);
3665     }
3666     item_destroy(it);
3667 
3668     if (list->len > 0)
3669     {
3670         /* append trailing newline */
3671         g_string_append_c(list, '\n');
3672         return g_string_free(list, FALSE);
3673     }
3674     else
3675     {
3676         g_string_free(list, TRUE);
3677         return NULL;
3678     }
3679 }
3680 
player_item_identify(player * p,inventory ** inv,item * it)3681 void player_item_identify(player *p, inventory **inv __attribute__((unused)), item *it)
3682 {
3683     g_assert(p != NULL && it != NULL);
3684 
3685     switch (it->type)
3686     {
3687     case IT_AMULET:
3688         p->identified_amulets[it->id] = TRUE;
3689         break;
3690 
3691     case IT_ARMOUR:
3692         p->identified_armour[it->id] = TRUE;
3693         break;
3694 
3695     case IT_BOOK:
3696         p->identified_books[it->id] = TRUE;
3697         break;
3698 
3699     case IT_POTION:
3700         p->identified_potions[it->id] = TRUE;
3701         break;
3702 
3703     case IT_RING:
3704         p->identified_rings[it->id] = TRUE;
3705         break;
3706 
3707     case IT_SCROLL:
3708         p->identified_scrolls[it->id] = TRUE;
3709         break;
3710 
3711     default:
3712         /* NOP */
3713         break;
3714     }
3715 
3716     it->blessed_known = TRUE;
3717     it->bonus_known = TRUE;
3718 }
3719 
player_item_use(player * p,inventory ** inv,item * it)3720 void player_item_use(player *p, inventory **inv __attribute__((unused)), item *it)
3721 {
3722     item_usage_result result;
3723 
3724     g_assert(p != NULL && it != NULL && it->type > IT_NONE && it->type < IT_MAX);
3725 
3726     /* hide windows */
3727     display_windows_hide();
3728 
3729     /* Check if the player is able to move. */
3730     if (!player_movement_possible(p))
3731         return;
3732 
3733     switch (it->type)
3734     {
3735         /* read book */
3736     case IT_BOOK:
3737         result = book_read(p, it);
3738         break;
3739 
3740         /* drink potion */
3741     case IT_POTION:
3742         result = potion_quaff(p, it);
3743         break;
3744 
3745         /* read scroll */
3746     case IT_SCROLL:
3747         result = scroll_read(p, it);
3748         break;
3749 
3750     default:
3751         /* NOP */
3752         return;
3753         break;
3754     }
3755 
3756     if (result.identified)
3757     {
3758         /* identify the item type, not the entire item */
3759         switch (it->type)
3760         {
3761         case IT_BOOK:
3762             p->identified_books[it->id] = TRUE;
3763             break;
3764 
3765         case IT_POTION:
3766             p->identified_potions[it->id] = TRUE;
3767             break;
3768 
3769         case IT_SCROLL:
3770             p->identified_scrolls[it->id] = TRUE;
3771             break;
3772 
3773         default:
3774             /* NOP */
3775             break;
3776         }
3777     }
3778 
3779     if (result.used_up)
3780     {
3781         if (it->count > 1)
3782         {
3783             it->count--;
3784         }
3785         else
3786         {
3787             inv_del_element(&p->inventory, it);
3788             item_destroy(it);
3789         }
3790     }
3791 
3792     /* show windows */
3793     display_windows_show();
3794 }
3795 
player_item_destroy(player * p,item * it)3796 void player_item_destroy(player *p, item *it)
3797 {
3798     gchar *desc = item_describe(it, player_item_known(p, it), FALSE, TRUE);
3799     desc[0] = g_ascii_toupper(desc[0]);
3800 
3801     if (player_item_is_equipped(p, it))
3802     {
3803         player_item_unequip(p, &p->inventory, it, TRUE);
3804     }
3805 
3806     log_add_entry(nlarn->log, "`lightmagenta`%s %s destroyed!`end`", desc, is_are(it->count));
3807 
3808     int count = 0;
3809     if (it->content)
3810     {
3811         count = container_move_content(p, &it->content,
3812                 map_ilist_at(game_map(nlarn, Z(p->pos)), p->pos));
3813     }
3814 
3815     inv_del_element(&p->inventory, it);
3816     item_destroy(it);
3817 
3818     if (count)
3819     {
3820         log_add_entry(nlarn->log, "%s's content%s spill%s onto the floor.",
3821                       desc,
3822                       (count == 1) ? "" : "s",
3823                       (count == 1) ? "s" : "");
3824     }
3825 
3826     g_free(desc);
3827 }
3828 
player_item_drop(player * p,inventory ** inv,item * it)3829 void player_item_drop(player *p, inventory **inv, item *it)
3830 {
3831     gchar *buf;
3832     guint count = 0;
3833     gboolean split = FALSE;
3834 
3835     g_assert(p != NULL && it != NULL && it->type > IT_NONE && it->type < IT_MAX);
3836 
3837     /* Don't use player_movement_possible() here as this would take
3838        the possibility to drop stuff when overstrained. */
3839     if (player_effect(p, ET_PARALYSIS))
3840     {
3841         log_add_entry(nlarn->log, "You can't move!");
3842         return;
3843     }
3844 
3845     if (player_item_is_equipped(p, it))
3846         return;
3847 
3848     if (it->count > 1)
3849     {
3850         /* use the item type plural name except for ammunition */
3851         buf = g_strdup_printf("Drop how many %s%s?",
3852                               (it->type == IT_AMMO ? ammo_name(it) : item_name_pl(it->type)),
3853                               (it->type == IT_AMMO ? "s" : ""));
3854 
3855         count = display_get_count(buf, it->count);
3856         g_free(buf);
3857 
3858         if (!count)
3859             return;
3860     }
3861 
3862     if (count && count < it->count)
3863     {
3864         /* split the item if only a part of it is to be dropped;
3865            otherwise the entire quantity gets dropped */
3866         it = item_split(it, count);
3867 
3868         /* remember the fact */
3869         split = TRUE;
3870     }
3871 
3872     /* show the message before dropping the item, as dropping might remove
3873        the burdened / overloaded effect. If the pre_del callback fails,
3874        it has to display a message which explains why it has failed. */
3875     buf = item_describe(it, player_item_known(p, it), FALSE, FALSE);
3876     log_add_entry(nlarn->log, "You drop %s.", buf);
3877 
3878     /* if the action is aborted or if the callback failed return without dropping the item */
3879     if (!player_make_move(p, 2, TRUE, "dropping %s", buf)
3880             || !inv_del_element(inv, it))
3881     {
3882         /* if the item has been split return it */
3883         if (split)
3884         {
3885             inv_add(&p->inventory, it);
3886         }
3887 
3888         g_free(buf);
3889         return;
3890     }
3891 
3892     g_free(buf);
3893 
3894     if (it->type == IT_GOLD)
3895     {
3896         p->stats.gold_found -= it->count;
3897     }
3898 
3899     inv_add(map_ilist_at(game_map(nlarn, Z(p->pos)), p->pos), it);
3900 
3901     /* reveal if item is cursed or blessed when dropping it on an altar */
3902     sobject_t ms = map_sobject_at(game_map(nlarn, Z(p->pos)), p->pos);
3903 
3904     if (ms == LS_ALTAR
3905             && (!player_effect(p, ET_BLINDNESS) || game_wizardmode(nlarn))
3906             && item_is_blessable(it->type))
3907     {
3908         if (it->cursed || it->blessed)
3909         {
3910             buf = item_describe(it, player_item_known(p, it), FALSE, TRUE);
3911             buf[0] = g_ascii_toupper(buf[0]);
3912 
3913             log_add_entry(nlarn->log, "%s %s surrounded by a %s halo.",
3914                           buf, is_are(it->count),
3915                           it->cursed ? "`darkgray`black`end`" : "`white`white`end`");
3916 
3917             g_free(buf);
3918         }
3919 
3920         it->blessed_known = TRUE;
3921     }
3922 
3923     return;
3924 }
3925 
player_item_notes(player * p,inventory ** inv,item * it)3926 void player_item_notes(player *p, inventory **inv __attribute__((unused)), item *it)
3927 {
3928     gchar *desc = item_describe(it, player_item_known(p, it), FALSE, TRUE);
3929     gchar *caption = g_strdup_printf("Add your description for %s (delete with ESC)", desc);
3930     g_free(desc);
3931 
3932     /* get the new note */
3933     gchar *temp = display_get_string("Edit item notes", caption, it->notes, 60);
3934 
3935     /* free the old note before adding the new note to the item */
3936     g_free(it->notes);
3937     it->notes = temp;
3938 
3939     g_free(caption);
3940 }
3941 
player_read(player * p)3942 void player_read(player *p)
3943 {
3944     g_assert (p != NULL);
3945 
3946     if (inv_length_filtered(p->inventory, item_filter_legible) > 0)
3947     {
3948         item *it = display_inventory("Choose an item to read", p,
3949                 &p->inventory, NULL, FALSE, FALSE, FALSE, item_filter_legible);
3950 
3951         if (it)
3952         {
3953             player_item_use(p, NULL, it);
3954         }
3955     }
3956     else
3957     {
3958         log_add_entry(nlarn->log, "You have nothing to read.");
3959     }
3960 }
3961 
player_quaff(player * p)3962 void player_quaff(player *p)
3963 {
3964     g_assert (p != NULL);
3965 
3966     if (inv_length_filtered(p->inventory, item_filter_potions) > 0)
3967     {
3968         item *it = display_inventory("Choose an item to drink", p,
3969                 &p->inventory, NULL, FALSE, FALSE, FALSE, item_filter_potions);
3970 
3971         if (it)
3972         {
3973             player_item_use(p, NULL, it);
3974         }
3975     }
3976     else
3977     {
3978         log_add_entry(nlarn->log, "You have nothing to drink.");
3979     }
3980 }
3981 
3982 
player_equip(player * p)3983 void player_equip(player *p)
3984 {
3985     g_assert (p != NULL);
3986 
3987     if (inv_length_filtered(p->inventory, item_filter_equippable) > 0)
3988     {
3989         item *it = display_inventory("Choose an item to equip", p,
3990                 &p->inventory, NULL, FALSE, FALSE, FALSE, item_filter_equippable);
3991 
3992         if (it)
3993         {
3994             player_item_equip(p, NULL, it);
3995         }
3996     }
3997     else
3998     {
3999         log_add_entry(nlarn->log, "You have nothing you could equip.");
4000     }
4001 }
4002 
player_take_off(player * p)4003 void player_take_off(player *p)
4004 {
4005     g_assert (p != NULL);
4006 
4007     if (inv_length_filtered(p->inventory, player_item_filter_unequippable) > 0)
4008     {
4009         item *it = display_inventory("Choose an item to take off", p,
4010                 &p->inventory, NULL, FALSE, FALSE, FALSE,
4011                 player_item_filter_unequippable);
4012 
4013         if (it)
4014             player_item_unequip(p, NULL, it, FALSE);
4015     }
4016     else
4017     {
4018         log_add_entry(nlarn->log, "You have nothing you could take off.");
4019     }
4020 }
4021 
player_drop(player * p)4022 void player_drop(player *p)
4023 {
4024     g_assert (p != NULL);
4025 
4026     if (inv_length_filtered(p->inventory, item_filter_dropable) > 0)
4027     {
4028         item *it = display_inventory("Choose an item to drop", p, &p->inventory,
4029                                NULL, FALSE, FALSE, FALSE, item_filter_dropable);
4030 
4031         if (it)
4032             player_item_drop(p, &p->inventory, it);
4033     }
4034     else
4035     {
4036         log_add_entry(nlarn->log, "You have nothing you could drop.");
4037     }
4038 }
4039 
player_get_ac(player * p)4040 guint player_get_ac(player *p)
4041 {
4042     int ac = 0;
4043     g_assert(p != NULL);
4044 
4045     if (p->eq_boots != NULL)
4046         ac += armour_ac(p->eq_boots);
4047 
4048     if (p->eq_cloak != NULL)
4049         ac += armour_ac(p->eq_cloak);
4050 
4051     if (p->eq_gloves != NULL)
4052         ac += armour_ac(p->eq_gloves);
4053 
4054     if (p->eq_helmet != NULL)
4055         ac += armour_ac(p->eq_helmet);
4056 
4057     if (p->eq_shield != NULL)
4058         ac += armour_ac(p->eq_shield);
4059 
4060     if (p->eq_suit != NULL)
4061         ac += armour_ac(p->eq_suit);
4062 
4063     ac += player_effect(p, ET_PROTECTION);
4064     ac += player_effect(p, ET_INVULNERABILITY);
4065 
4066     return ac;
4067 }
4068 
player_get_hp_max(player * p)4069 int player_get_hp_max(player *p)
4070 {
4071     g_assert(p != NULL);
4072     return p->hp_max;
4073 }
4074 
player_get_mp_max(player * p)4075 int player_get_mp_max(player *p)
4076 {
4077     g_assert(p != NULL);
4078     return p->mp_max;
4079 }
4080 
player_get_str(player * p)4081 int player_get_str(player *p)
4082 {
4083     g_assert(p != NULL);
4084     return p->strength
4085            + player_effect(p, ET_INC_STR)
4086            - player_effect(p, ET_DEC_STR)
4087            + player_effect(p, ET_HEROISM)
4088            - player_effect(p, ET_DIZZINESS);
4089 }
4090 
player_get_int(player * p)4091 int player_get_int(player *p)
4092 {
4093     g_assert(p != NULL);
4094     return p->intelligence
4095            + player_effect(p, ET_INC_INT)
4096            - player_effect(p, ET_DEC_INT)
4097            + player_effect(p, ET_HEROISM)
4098            - player_effect(p, ET_DIZZINESS);
4099 }
4100 
player_get_wis(player * p)4101 int player_get_wis(player *p)
4102 {
4103     g_assert(p != NULL);
4104     return p->wisdom
4105            + player_effect(p, ET_INC_WIS)
4106            - player_effect(p, ET_DEC_WIS)
4107            + player_effect(p, ET_HEROISM)
4108            - player_effect(p, ET_DIZZINESS);
4109 }
4110 
player_get_con(player * p)4111 int player_get_con(player *p)
4112 {
4113     g_assert(p != NULL);
4114     return p->constitution
4115            + player_effect(p, ET_INC_CON)
4116            - player_effect(p, ET_DEC_CON)
4117            + player_effect(p, ET_HEROISM)
4118            - player_effect(p, ET_DIZZINESS);
4119 }
4120 
player_get_dex(player * p)4121 int player_get_dex(player *p)
4122 {
4123     g_assert(p != NULL);
4124     return p->dexterity
4125            + player_effect(p, ET_INC_DEX)
4126            - player_effect(p, ET_DEC_DEX)
4127            + player_effect(p, ET_HEROISM)
4128            - player_effect(p, ET_DIZZINESS);
4129 }
4130 
player_get_speed(player * p)4131 int player_get_speed(player *p)
4132 {
4133     g_assert(p != NULL);
4134     return p->speed
4135            + player_effect(p, ET_SPEED)
4136            - player_effect(p, ET_SLOWNESS)
4137            - player_effect(p, ET_BURDENED);
4138 }
4139 
player_get_gold(player * p)4140 guint player_get_gold(player *p)
4141 {
4142     guint gold = 0;
4143 
4144     g_assert(p != NULL);
4145 
4146     /* gold stacks, thus there can only be one item in the inventory */
4147     if (inv_length_filtered(p->inventory, item_filter_gold))
4148     {
4149         item *i = inv_get_filtered(p->inventory, 0, item_filter_gold);
4150         gold += i->count;
4151     }
4152 
4153     /* check content of all containers in player's inventory */
4154     for (guint idx = 0;
4155          idx < inv_length_filtered(p->inventory, item_filter_container);
4156          idx++)
4157     {
4158         item *i = inv_get_filtered(p->inventory, idx, item_filter_container);
4159 
4160         if (inv_length_filtered(i->content, item_filter_gold))
4161         {
4162             item *it = inv_get_filtered(i->content, 0, item_filter_gold);
4163             gold += it->count;
4164         }
4165     }
4166 
4167     return gold;
4168 }
4169 
player_remove_gold(player * p,guint amount)4170 void player_remove_gold(player *p, guint amount)
4171 {
4172     g_assert(p != NULL);
4173 
4174     /* gold stacks, thus there can only be one item in the inventory */
4175     if (inv_length_filtered(p->inventory, item_filter_gold))
4176     {
4177         item *i = inv_get_filtered(p->inventory, 0, item_filter_gold);
4178 
4179         if (amount >= i->count)
4180         {
4181             amount -= i->count;
4182             inv_del_element(&p->inventory, i);
4183         }
4184         else
4185         {
4186             i->count -= amount;
4187             goto done;
4188         }
4189     }
4190 
4191     for (guint idx = 0;
4192          idx < inv_length_filtered(p->inventory, item_filter_container);
4193          idx++)
4194     {
4195         item *i = inv_get_filtered(p->inventory, idx, item_filter_container);
4196 
4197         if (inv_length_filtered(i->content, item_filter_gold))
4198         {
4199             i = inv_get_filtered(i->content, 0, item_filter_gold);
4200 
4201             if (amount >= i->count)
4202             {
4203                 amount -= i->count;
4204                 inv_del_element(&p->inventory, i);
4205             }
4206             else
4207             {
4208                 i->count -= amount;
4209                 goto done;
4210             }
4211         }
4212     }
4213 
4214     /* yes, I _do_ _know_ that goto is evil.. */
4215 done:
4216     /* force recalculation of inventory weight */
4217     player_inv_weight_recalc(p->inventory, NULL);
4218 }
4219 
player_get_level_desc(player * p)4220 const char *player_get_level_desc(player *p)
4221 {
4222     g_assert(p != NULL);
4223     return player_level_desc[p->level - 1];
4224 }
4225 
player_search(player * p)4226 void player_search(player *p)
4227 {
4228     g_assert (p != NULL);
4229 
4230     map *m = game_map(nlarn, Z(p->pos));
4231 
4232     /* look for traps on adjacent tiles */
4233     for (direction dir = GD_NONE + 1; dir < GD_MAX; dir++)
4234     {
4235         position pos = pos_move(p->pos, dir);
4236 
4237         /* skip invalid positions */
4238         if (!pos_valid(pos)) continue;
4239 
4240         /* skip unpassable positions unless the player is blind */
4241         if (!map_pos_passable(m, pos) && !player_effect(p, ET_BLINDNESS))
4242             continue;
4243 
4244         /* consume one turn per tile */
4245         player_make_move(p, 1, FALSE, NULL);
4246 
4247         /* stop searching when the player is under attack */
4248         if (p->attacked || player_adjacent_monster(p, TRUE)) return;
4249 
4250         /* examine the surrounding area if blind */
4251         if (player_effect(p, ET_BLINDNESS))
4252         {
4253             /* examine tile types */
4254             player_memory_of(p, pos).type = map_tile_at(m, pos)->type;
4255 
4256             /* examine stationary objects */
4257             player_memory_of(p, pos).sobject = map_tile_at(m, pos)->sobject;
4258         }
4259 
4260         /* search for traps */
4261         trap_t tt = map_trap_at(m, pos);
4262 
4263         if ((tt != TT_NONE) && (tt != player_memory_of(p, pos).trap))
4264         {
4265             /* found an unknown trap - determine the chance that
4266              * the player can find it */
4267             int prop = (trap_chance(tt) / 2) + player_get_int(p);
4268 
4269             if (chance(prop))
4270             {
4271                 /* discovered the trap */
4272                 log_add_entry(nlarn->log, "`lightmagenta`You find a %s!`end`",
4273                         trap_description(tt));
4274                  player_memory_of(p, pos).trap = tt;
4275             }
4276         }
4277 
4278         /* search for traps on containers */
4279         inventory **ti = map_ilist_at(m, pos);
4280         if (ti != NULL)
4281         {
4282             for (guint idx = 0;
4283                     idx < inv_length_filtered(*ti, item_filter_container);
4284                     idx++)
4285             {
4286                 item *c = inv_get_filtered(*ti, idx, item_filter_container);
4287 
4288                 /* the chance that the player discovers the trap is 3 * int */
4289                 if (c->cursed && !c->blessed_known && chance(player_get_int(p) * 3))
4290                 {
4291                     gchar *idesc = item_describe(c, FALSE, TRUE, TRUE);
4292                     /* the container is cursed */
4293                     c->blessed_known = TRUE;
4294                     log_add_entry(nlarn->log, "`lightmagenta`You discover a trap on %s!`end`",
4295                             idesc);
4296 
4297                     g_free(idesc);
4298                 }
4299             }
4300         }
4301     }
4302 }
4303 
player_list_sobjmem(player * p)4304 void player_list_sobjmem(player *p)
4305 {
4306     GString *sobjlist = g_string_new(NULL);
4307 
4308     if (p->sobjmem == NULL)
4309     {
4310         g_string_append(sobjlist, "You have not discovered any landmarks yet.");
4311 
4312     }
4313     else
4314     {
4315         int prevmap = -1;
4316 
4317         /* sort the array of memorized landmarks */
4318         g_array_sort(p->sobjmem, player_sobjects_sort);
4319 
4320         /* assemble a list of landmarks per map */
4321         for (guint idx = 0; idx < p->sobjmem->len; idx++)
4322         {
4323             player_sobject_memory *som;
4324             som = &g_array_index(p->sobjmem, player_sobject_memory, idx);
4325 
4326             g_string_append_printf(sobjlist, "%-4s %s (%d, %d)\n",
4327                                    (Z(som->pos) > prevmap) ? map_names[Z(som->pos)] : "",
4328                                    so_get_desc(som->sobject),
4329                                    Y(som->pos), X(som->pos));
4330 
4331             if (Z(som->pos) > prevmap) prevmap = Z(som->pos);
4332         }
4333     }
4334 
4335     display_show_message("Discovered landmarks", sobjlist->str, 5);
4336     g_string_free(sobjlist, TRUE);
4337 }
4338 
player_update_fov(player * p)4339 void player_update_fov(player *p)
4340 {
4341     int radius;
4342     position pos = p->pos;
4343     map *pmap;
4344 
4345     int range = (Z(p->pos) == 0 ? 15 : 6);
4346 
4347     /* calculate range */
4348     if (player_effect(nlarn->p, ET_BLINDNESS))
4349         radius = 0;
4350     else
4351     {
4352         radius = range + player_effect(nlarn->p, ET_AWARENESS);
4353         if (player_effect(nlarn->p, ET_TRAPPED))
4354             radius -= 4;
4355     }
4356 
4357     /* get current map */
4358     pmap = game_map(nlarn, Z(p->pos));
4359 
4360     /* determine if the player has infravision */
4361     gboolean infravision = player_effect(p, ET_INFRAVISION);
4362 
4363     /* if player is enlightened, use a circular area around the player */
4364     if (player_effect(p, ET_ENLIGHTENMENT))
4365     {
4366         /* reset FOV manually */
4367         fov_reset(p->fv);
4368 
4369         area *enlight = area_new_circle(p->pos,
4370                 player_effect(p, ET_ENLIGHTENMENT), FALSE);
4371 
4372         /* set visible field according to returned area */
4373         for (int y = 0; y < enlight->size_y; y++)
4374         {
4375             for (int x = 0; x < enlight->size_x; x++)
4376             {
4377                 X(pos) = x + enlight->start_x;
4378                 Y(pos) = y + enlight->start_y;
4379 
4380                 if (pos_valid(pos) && area_point_get(enlight, x, y))
4381                 {
4382                     /* The position if enlightened.
4383                        Now determine if the position has a direct visible connection
4384                        to the players position. This is required to instruct fov_set()
4385                        if a monster at the position shall be added to the list of the
4386                        visible monsters. */
4387                     gboolean mchk = ((pos_distance(p->pos, pos) <= 7)
4388                                     && map_pos_is_visible(pmap, p->pos, pos));
4389 
4390                     fov_set(p->fv, pos, TRUE, infravision, mchk);
4391                 }
4392             }
4393         }
4394 
4395         area_destroy(enlight);
4396     }
4397     else
4398     {
4399         /* otherwise use the fov algorithm */
4400         fov_calculate(p->fv, pmap, p->pos, radius, infravision);
4401     }
4402 
4403     /* update visible fields in player's memory */
4404     for (Y(pos) = 0; Y(pos) < MAP_MAX_Y; Y(pos)++)
4405     {
4406         for (X(pos) = 0; X(pos) < MAP_MAX_X; X(pos)++)
4407         {
4408             if (fov_get(p->fv, pos))
4409             {
4410                 monster *m = map_get_monster_at(pmap, pos);
4411                 inventory **inv = map_ilist_at(pmap, pos);
4412 
4413                 player_memory_of(p,pos).type = map_tiletype_at(pmap, pos);
4414                 player_memory_of(p,pos).sobject = map_sobject_at(pmap, pos);
4415 
4416                 /* remember certain stationary objects */
4417                 switch (map_sobject_at(pmap, pos))
4418                 {
4419                 case LS_ALTAR:
4420                 case LS_BANK2:
4421                 case LS_FOUNTAIN:
4422                 case LS_MIRROR:
4423                 case LS_THRONE:
4424                 case LS_THRONE2:
4425                 case LS_STATUE:
4426                     player_sobject_memorize(p, map_sobject_at(pmap, pos), pos);
4427                     break;
4428 
4429                 default:
4430                     player_sobject_forget(p, pos);
4431                     break;
4432                 }
4433 
4434                 if (m && monster_flags(m, MIMIC) && monster_unknown(m))
4435                 {
4436                     /* remember the undiscovered mimic as an item */
4437                     item *it = get_mimic_item(m);
4438                     if (it != NULL)
4439                     {
4440                         player_memory_of(p,pos).item = it->type;
4441                         player_memory_of(p,pos).item_colour = item_colour(it);
4442                     }
4443                 }
4444                 else if (inv_length(*inv) > 0)
4445                 {
4446                     item *it;
4447 
4448                     /* memorize the most interesting item on the tile */
4449                     if (inv_length_filtered(*inv, item_filter_gems) > 0)
4450                     {
4451                         /* there's a gem in the stack */
4452                         it = inv_get_filtered(*inv, 0, item_filter_gems);
4453                     }
4454                     else if (inv_length_filtered(*inv, item_filter_gold) > 0)
4455                     {
4456                         /* there is gold in the stack */
4457                         it = inv_get_filtered(*inv, 0, item_filter_gold);
4458                     }
4459                     else
4460                     {
4461                         /* memorize the topmost item on the stack */
4462                         it = inv_get(*inv, inv_length(*inv) - 1);
4463                     }
4464 
4465                     player_memory_of(p,pos).item = it->type;
4466                     player_memory_of(p,pos).item_colour = item_colour(it);
4467                 }
4468                 else
4469                 {
4470                     /* no item at that position */
4471                     player_memory_of(p,pos).item = IT_NONE;
4472                     player_memory_of(p,pos).item_colour = 0;
4473                 }
4474             }
4475         }
4476     }
4477 }
4478 
player_item_pickup(player * p,inventory ** inv,item * it,gboolean ask)4479 static guint player_item_pickup(player *p, inventory **inv, item *it, gboolean ask)
4480 {
4481     g_assert(p != NULL && it != NULL && it->type > IT_NONE && it->type < IT_MAX);
4482 
4483     gchar *buf;
4484     gpointer oid = it->oid;
4485     guint gold_amount = 0;
4486 
4487     if (ask && (it->count > 1))
4488     {
4489         /* use the item type plural name except for ammunition */
4490         buf = g_strdup_printf("Pick up how many %s%s?",
4491                               (it->type == IT_AMMO ? ammo_name(it) : item_name_pl(it->type)),
4492                               (it->type == IT_AMMO ? "s" : ""));
4493 
4494         guint count = display_get_count(buf, it->count);
4495         g_free(buf);
4496 
4497         if (count == 0)
4498             return 0;
4499 
4500         if (count < it->count)
4501         {
4502             it = item_split(it, count);
4503             /* set oid to NULL to prevent that the original is remove
4504                from the originating inventory */
4505             oid = NULL;
4506         }
4507     }
4508 
4509     /* Log the attempt to pick up the item */
4510     buf = item_describe(it, player_item_known(p, it), FALSE, FALSE);
4511     log_add_entry(nlarn->log, "You pick up %s.", buf);
4512 
4513     if (it->type == IT_GOLD)
4514     {
4515         /* record the amount of gold as the item might be destroyed */
4516         gold_amount = it->count;
4517     }
4518 
4519     /* Reset the fired flag. This has to be done before adding the item to the
4520        inventory as otherwise the item comparison would fail.
4521        If picking up fails, the item will not be picked up automatically again. */
4522     it->fired = FALSE;
4523 
4524     /* one turn to pick item up, one to stuff it into the pack */
4525     if (!player_make_move(p, 2, TRUE, "picking up %s", buf))
4526     {
4527         /* Adding the item to the player's inventory has failed.
4528            If the item has been split, return it to the originating inventory */
4529         if (oid == NULL) inv_add(inv, it);
4530         g_free(buf);
4531         return 1;
4532     }
4533 
4534     g_free(buf);
4535     if (!inv_add(&p->inventory, it))
4536     {
4537         /* Adding the item to the player's inventory has failed.
4538            If the item has been split, return it to the originating inventory */
4539         if (oid == NULL) inv_add(inv, it);
4540 
4541         return 2;
4542     }
4543 
4544     if (gold_amount > 0)
4545     {
4546         /* if the player has tried to pick up gold and succeeded in doing so,
4547            add the amount picked up to the statistics */
4548         p->stats.gold_found += gold_amount;
4549     }
4550 
4551     /* remove the item from the originating inventory if it has not been split */
4552     if (oid != NULL)
4553         inv_del_oid(inv, oid);
4554 
4555     return 0;
4556 }
4557 
player_sobject_memorize(player * p,sobject_t sobject,position pos)4558 static void player_sobject_memorize(player *p, sobject_t sobject, position pos)
4559 {
4560     player_sobject_memory nsom;
4561 
4562     if (p->sobjmem == NULL)
4563     {
4564         p->sobjmem = g_array_new(FALSE, FALSE, sizeof(player_sobject_memory));
4565     }
4566 
4567     /* check if the sobject has already been memorized */
4568     for (guint idx = 0; idx < p->sobjmem->len; idx++)
4569     {
4570         player_sobject_memory *som;
4571         som = &g_array_index(p->sobjmem, player_sobject_memory, idx);
4572 
4573         /* memory for this position exists */
4574         if (pos_identical(som->pos, pos))
4575         {
4576             /* update remembered sobject */
4577             som->sobject = sobject;
4578 
4579             /* the work is done */
4580             return;
4581         }
4582     }
4583 
4584     /* add a new memory entry */
4585     nsom.pos = pos;
4586     nsom.sobject = sobject;
4587 
4588     g_array_append_val(p->sobjmem, nsom);
4589 }
4590 
player_sobject_forget(player * p,position pos)4591 void player_sobject_forget(player *p, position pos)
4592 {
4593     /* just return if nothing has been memorized yet */
4594     if (p->sobjmem == NULL)
4595     {
4596         return;
4597     }
4598 
4599     for (guint idx = 0; idx < p->sobjmem->len; idx++)
4600     {
4601         player_sobject_memory *som = &g_array_index(p->sobjmem,
4602                 player_sobject_memory, idx);
4603 
4604         /* remove existing entries for this position */
4605         if (pos_identical(som->pos, pos))
4606         {
4607             g_array_remove_index(p->sobjmem, idx);
4608             break;
4609         }
4610     }
4611 
4612     /* free the sobject memory if no entry remains */
4613     if (p->sobjmem->len == 0)
4614     {
4615         g_array_free(p->sobjmem, TRUE);
4616         p->sobjmem = NULL;
4617     }
4618 }
4619 
player_adjacent_monster(player * p,gboolean ignore_harmless)4620 gboolean player_adjacent_monster(player *p, gboolean ignore_harmless)
4621 {
4622     gboolean monster_visible = FALSE;
4623 
4624     /* get the list of all visible monsters */
4625     GList *mlist, *miter;
4626     miter = mlist = fov_get_visible_monsters(p->fv);
4627 
4628     /* no visible monsters? */
4629     if (mlist == NULL)
4630         return monster_visible;
4631 
4632     /* got a list of monsters, check if any are dangerous */
4633     do
4634     {
4635         monster *m = (monster *)miter->data;
4636 
4637         // Ignore the town inhabitants.
4638         if (monster_type(m) == MT_TOWN_PERSON)
4639             continue;
4640 
4641         /* ignore servants */
4642         if (monster_action(m) == MA_SERVE)
4643             continue;
4644 
4645         /* Only ignore floating eye if already paralysed. */
4646         if (ignore_harmless
4647             && (monster_type(m) == MT_FLOATING_EYE)
4648             && player_effect_get(p, ET_PARALYSIS))
4649             continue;
4650 
4651         /* Ignore adjacent umber hulk if already confused. */
4652         if (ignore_harmless
4653             && (monster_type(m) == MT_UMBER_HULK)
4654             && player_effect_get(p, ET_CONFUSION))
4655             continue;
4656 
4657         /* when reaching this point, the monster is a threat */
4658         monster_visible = TRUE;
4659         break;
4660     }
4661     while ((miter = miter->next) != NULL);
4662 
4663     /* free the list returned by fov_get_visible_monsters() */
4664     g_list_free(mlist);
4665 
4666     return monster_visible;
4667 }
4668 
player_sobjects_sort(gconstpointer a,gconstpointer b)4669 static int player_sobjects_sort(gconstpointer a, gconstpointer b)
4670 {
4671     player_sobject_memory *som_a = (player_sobject_memory *)a;
4672     player_sobject_memory *som_b = (player_sobject_memory *)b;
4673 
4674     if (Z(som_a->pos) == Z(som_b->pos))
4675     {
4676         if (som_a->sobject == som_b->sobject)
4677             return 0;
4678         if (som_a->sobject < som_b->sobject)
4679             return -1;
4680         else
4681             return 0;
4682     }
4683     else if (Z(som_a->pos) < Z(som_b->pos))
4684         return -1;
4685     else
4686         return 1;
4687 }
4688 
player_memory_serialize(player * p,position pos)4689 static cJSON *player_memory_serialize(player *p, position pos)
4690 {
4691     cJSON *mser;
4692 
4693     mser = cJSON_CreateObject();
4694     if (player_memory_of(p, pos).type > LT_NONE)
4695         cJSON_AddNumberToObject(mser, "type",
4696                                 player_memory_of(p, pos).type);
4697 
4698     if (player_memory_of(p, pos).sobject > LS_NONE)
4699         cJSON_AddNumberToObject(mser, "sobject",
4700                                 player_memory_of(p, pos).sobject);
4701 
4702     if (player_memory_of(p, pos).item > IT_NONE)
4703         cJSON_AddNumberToObject(mser, "item",
4704                                 player_memory_of(p, pos).item);
4705 
4706     if (player_memory_of(p, pos).item_colour > 0)
4707         cJSON_AddNumberToObject(mser, "item_colour",
4708                                 player_memory_of(p, pos).item_colour);
4709 
4710     if (player_memory_of(p, pos).trap > TT_NONE)
4711         cJSON_AddNumberToObject(mser, "trap",
4712                                 player_memory_of(p, pos).trap);
4713 
4714     return mser;
4715 }
4716 
player_memory_deserialize(player * p,position pos,cJSON * mser)4717 static void player_memory_deserialize(player *p, position pos, cJSON *mser)
4718 {
4719     cJSON *obj;
4720 
4721     obj = cJSON_GetObjectItem(mser, "type");
4722     if (obj != NULL)
4723         player_memory_of(p, pos).type = obj->valueint;
4724 
4725     obj = cJSON_GetObjectItem(mser, "sobject");
4726     if (obj != NULL)
4727         player_memory_of(p, pos).sobject = obj->valueint;
4728 
4729     obj = cJSON_GetObjectItem(mser, "item");
4730     if (obj != NULL)
4731         player_memory_of(p, pos).item = obj->valueint;
4732 
4733     obj = cJSON_GetObjectItem(mser, "item_colour");
4734     if (obj != NULL)
4735         player_memory_of(p, pos).item_colour = obj->valueint;
4736 
4737     obj = cJSON_GetObjectItem(mser, "trap");
4738     if (obj != NULL)
4739         player_memory_of(p, pos).trap = obj->valueint;
4740 }
4741 
calc_fighting_stats(player * p)4742 void calc_fighting_stats(player *p)
4743 {
4744     g_assert(p != NULL);
4745 
4746     gchar *desc = NULL;
4747     GString *text;
4748 
4749     position pos = map_find_space_in(game_map(nlarn, Z(p->pos)),
4750                                      rect_new_sized(p->pos, 2), LE_MONSTER, FALSE);
4751 
4752     if (!pos_valid(pos))
4753     {
4754         log_add_entry(nlarn->log, "Couldn't create a monster.");
4755         return;
4756     }
4757 
4758     text = g_string_new("");
4759 
4760     if (p->eq_weapon)
4761     {
4762         p->eq_weapon->blessed_known = TRUE;
4763         p->eq_weapon->bonus_known   = TRUE;
4764 
4765         desc = item_describe(p->eq_weapon, TRUE, TRUE, FALSE);
4766     }
4767 
4768     const int damage_modifier = player_effect(p, ET_INC_DAMAGE)
4769                                 - player_effect(p, ET_SICKNESS);
4770 
4771     g_string_append_printf(text, "\nPlayer stats\n"
4772                            "------------\n"
4773                            "  wielded weapon : %s\n"
4774                            "  weapon class   : %d\n"
4775                            "  accuracy       : %d\n"
4776                            "  experience     : %d\n"
4777                            "  strength       : %d\n"
4778                            "  dexterity      : %d\n"
4779                            "  damage modifier: %d\n"
4780                            "  speed          : %d\n"
4781                            "  difficulty     : %d\n\n",
4782                            (p->eq_weapon ? desc : "none"),
4783                            (p->eq_weapon ? weapon_damage(p->eq_weapon) : 0),
4784                            (p->eq_weapon ? weapon_acc(p->eq_weapon) : 0),
4785                            p->level,
4786                            player_get_str(p),
4787                            player_get_dex(p),
4788                            damage_modifier,
4789                            player_get_speed(p),
4790                            game_difficulty(nlarn));
4791 
4792     g_string_append_printf(text, "Monsters\n"
4793                            "--------\n\n");
4794 
4795     gboolean mention_instakill = FALSE;
4796 
4797     for (guint32 idx = 0; idx < MT_TOWN_PERSON; idx++)
4798     {
4799         monster *m;
4800         if (!(m = monster_new(idx, pos, NULL)))
4801         {
4802             g_string_append_printf(text, "Monster %s could not be created.\n\n",
4803                                    monster_name(m));
4804             continue;
4805         }
4806         int to_hit = weapon_calc_to_hit(p, m, p->eq_weapon, NULL);
4807         to_hit += ((100 - to_hit) * 5)/100;
4808 
4809         const int instakill_chance = player_instakill_chance(p, m);
4810 
4811         g_string_append_printf(
4812             text,
4813             "%s (ac: %d, max hp: %d, speed: %d)\n"
4814             "     to-hit chance: %d%%\n"
4815             "  instakill chance: %d%%\n",
4816             monster_name(m),
4817             monster_ac(m),
4818             monster_type_hp_max(monster_type(m)),
4819             monster_speed(m),
4820             to_hit, instakill_chance
4821         );
4822 
4823         if (instakill_chance < 100)
4824         {
4825             if (monster_flags(m, REGENERATE))
4826             {
4827                 g_string_append_printf(text, "      regeneration: every %d turns\n",
4828                                        10 - game_difficulty(nlarn));
4829             }
4830 
4831             const min_max_damage mmd = calc_min_max_damage(p, m);
4832             double avg_dam = 0;
4833             int tries = 100;
4834             while (tries-- > 0)
4835                 avg_dam += calc_real_damage(p, m, FALSE);
4836 
4837             avg_dam /= 100;
4838 
4839             int hits_needed = (monster_type_hp_max(monster_type(m)) / avg_dam);
4840             if (((int) (avg_dam * 10)) % 10)
4841                 hits_needed++;
4842 
4843             g_string_append_printf(
4844                 text,
4845                 "       min. damage: %d hp\n"
4846                 "       max. damage: %d hp\n"
4847                 "       avg. damage: %.2f hp\n"
4848                 "  avg. hits needed: %d%s\n\n",
4849                 mmd.min_damage, mmd.max_damage,
4850                 avg_dam, hits_needed,
4851                 hits_needed > 1 && instakill_chance > 0 ? " [*]" : ""
4852             );
4853 
4854             if (!mention_instakill && instakill_chance > 0)
4855                 mention_instakill = TRUE;
4856         }
4857         else
4858             g_string_append_printf(text, "\n");
4859 
4860         monster_destroy(m);
4861     }
4862 
4863     if (mention_instakill)
4864         g_string_append_printf(text, "*) ignoring instakills\n");
4865 
4866     display_show_message("Fighting statistics", text->str, 0);
4867 
4868     if (display_get_yesno("Do you want to save the calculations?",NULL, NULL, NULL))
4869     {
4870         char *filename, *proposal;
4871         GError *error = NULL;
4872 
4873         proposal = g_strconcat(p->name, ".stat", NULL);
4874         filename = display_get_string(NULL, "Enter filename: ", proposal, 40);
4875 
4876         if (filename != NULL)
4877         {
4878             /* file name has been provided. Try to save file */
4879             if (!g_file_set_contents(filename, text->str, -1, &error))
4880             {
4881                 display_show_message("Error", error->message, 0);
4882                 g_error_free(error);
4883             }
4884 
4885             g_free(proposal);
4886         }
4887 
4888         g_free(filename);
4889     }
4890 
4891     g_free(desc);
4892     g_string_free(text, TRUE);
4893 }
4894 
player_equipment_list(player * p)4895 static char *player_equipment_list(player *p)
4896 {
4897     int idx = 0;
4898     GString *el = g_string_new(NULL);
4899 
4900     struct
4901     {
4902         item *slot;
4903         const char *desc;
4904     } slots[] =
4905     {
4906         { p->eq_amulet,  "Necklace:" },
4907         { p->eq_weapon,  "Main weapon:" },
4908         { p->eq_sweapon, "Sec. weapon:" },
4909         { p->eq_quiver,  "In quiver:" },
4910         { p->eq_boots,   "Boots:" },
4911         { p->eq_cloak,   "Cloak:" },
4912         { p->eq_gloves,  "Gloves:" },
4913         { p->eq_helmet,  "Helmet:" },
4914         { p->eq_shield,  "Shield:" },
4915         { p->eq_suit,    "Body armour:" },
4916         { p->eq_ring_l,  "Left ring:" },
4917         { p->eq_ring_r,  "Right ring:" },
4918         { NULL, NULL },
4919     };
4920 
4921     while (slots[idx].desc != NULL)
4922     {
4923         /* skip empty item slots */
4924         if (slots[idx].slot == NULL)
4925         {
4926             idx++;
4927             continue;
4928         }
4929 
4930         char *desc = item_describe(slots[idx].slot, player_item_known(p,
4931                     slots[idx].slot), FALSE, FALSE);
4932 
4933         g_string_append_printf(el, "`white`%-12s`end` %s\n",
4934                     slots[idx].desc, desc);
4935 
4936         g_free(desc);
4937         idx++;
4938     }
4939 
4940     return g_string_free(el, FALSE);
4941 }
4942 
player_create_obituary(player * p,score_t * score,GList * scores)4943 static char *player_create_obituary(player *p, score_t *score, GList *scores)
4944 {
4945     const char *pronoun = (p->sex == PS_MALE) ? "He" : "She";
4946 
4947     /* the obituary */
4948     GString *text;
4949 
4950     gchar *tmp = score_death_description(score, TRUE);
4951     text = g_string_new(tmp);
4952     g_free(tmp);
4953 
4954     /* assemble surrounding scores list */
4955     g_string_append(text, "\n\n");
4956     tmp = scores_to_string(scores, score);
4957     g_string_append(text, tmp);
4958     g_free(tmp);
4959 
4960     /* some statistics */
4961     g_string_append_printf(text, "\n%s %s after searching for the potion for %d mobul%s. ",
4962                            pronoun, score->cod < PD_TOO_LATE ? "died" : "returned",
4963                            gtime2mobuls(nlarn->gtime), plural(gtime2mobuls(nlarn->gtime)));
4964 
4965     g_string_append_printf(text, "%s cast %s spell%s, ", pronoun,
4966                            int2str(p->stats.spells_cast),
4967                            plural(p->stats.spells_cast));
4968     g_string_append_printf(text, "quaffed %s potion%s, ",
4969                            int2str(p->stats.potions_quaffed),
4970                            plural(p->stats.potions_quaffed));
4971     g_string_append_printf(text, "and read %s book%s ",
4972                            int2str(p->stats.books_read),
4973                            plural(p->stats.books_read));
4974     g_string_append_printf(text, "and %s scroll%s. ",
4975                            int2str(p->stats.scrolls_read),
4976                            plural(p->stats.scrolls_read));
4977 
4978     if (p->stats.weapons_wasted > 0)
4979     {
4980         g_string_append_printf(text, "\n%s wasted %s weapon%s in combat. ",
4981                                pronoun, int2str(p->stats.weapons_wasted),
4982                                plural(p->stats.weapons_wasted));
4983     }
4984 
4985     if (p->stats.vandalism > 0)
4986     {
4987         g_string_append_printf(text, "\n%s committed %s act%s of vandalism. ",
4988                                pronoun, int2str(p->stats.vandalism),
4989                                plural(p->stats.vandalism));
4990     }
4991 
4992     if (p->stats.life_protected > 0)
4993     {
4994         g_string_append_printf(text, "\n%s life was protected %s. ",
4995                                (p->sex == PS_MALE) ? "His" : "Her",
4996                                int2time_str(p->stats.life_protected));
4997     }
4998 
4999     g_string_append_printf(text, "\n%s had %s gold on %s bank account "
5000                            "when %s %s.",
5001                            pronoun, int2str(p->bank_account),
5002                            (p->sex == PS_MALE) ? "his" : "her",
5003                            (p->sex == PS_MALE) ? "he"  : "she",
5004                            score->cod < PD_TOO_LATE ? "died"
5005                            : "returned home");
5006 
5007     g_string_append_printf(text, "\n%s found %d gold in the caverns, "
5008                            "sold %s gem%s for %d and %s non-gem "
5009                            "item%s for %d gold, and earned %d gold "
5010                            "as bank interest.",
5011                            pronoun, p->stats.gold_found,
5012                            int2str(p->stats.gems_sold),
5013                            plural(p->stats.gems_sold),
5014                            p->stats.gold_sold_gems,
5015                            int2str(p->stats.items_sold),
5016                            plural(p->stats.items_sold),
5017                            p->stats.gold_sold_items,
5018                            p->stats.gold_bank_interest);
5019 
5020     g_string_append_printf(text, "\n%s bought %s item%s for %d gold, spent "
5021                            "%d on item identification or repair, "
5022                            "donated %d gold to charitable causes, and "
5023                            "invested %d gold in %s personal education.",
5024                            pronoun,
5025                            int2str(p->stats.items_bought),
5026                            plural(p->stats.items_bought),
5027                            p->stats.gold_spent_shop,
5028                            p->stats.gold_spent_id_repair,
5029                            p->stats.gold_spent_donation,
5030                            p->stats.gold_spent_college,
5031                            (p->sex == PS_MALE) ? "his" : "her");
5032 
5033     if (p->outstanding_taxes)
5034         g_string_append_printf(text, " %s owed the tax office %d gold%s",
5035                                pronoun, p->outstanding_taxes,
5036                                p->stats.gold_spent_taxes ? "" : ".");
5037 
5038     if (p->stats.gold_spent_taxes)
5039         g_string_append_printf(text, " %s paid %d gold taxes.",
5040                                p->outstanding_taxes ? "and" : pronoun,
5041                                p->stats.gold_spent_taxes);
5042 
5043     /* append map of current level if the player is not in the town */
5044     if (Z(p->pos) > 0)
5045     {
5046         g_string_append(text, "\n\n-- The current level ------------------\n\n");
5047         tmp = map_dump(game_map(nlarn, Z(p->pos)), p->pos);
5048         g_string_append(text, tmp);
5049         g_free(tmp);
5050     }
5051 
5052     /* player's attributes */
5053     g_string_append(text, "\n\n-- Attributes -------------------------\n\n");
5054     g_string_append_printf(text, "Strength:     %d (%+2d)\n",
5055                            p->strength, p->strength - p->stats.str_orig);
5056     g_string_append_printf(text, "Dexterity:    %d (%+2d)\n",
5057                            p->dexterity, p->dexterity - p->stats.dex_orig);
5058     g_string_append_printf(text, "Constitution: %d (%+2d)\n",
5059                            p->constitution, p->constitution - p->stats.con_orig);
5060     g_string_append_printf(text, "Intelligence: %d (%+2d)\n",
5061                            p->intelligence, p->intelligence - p->stats.int_orig);
5062     g_string_append_printf(text, "Wisdom:       %d (%+2d)\n",
5063                            p->wisdom, p->wisdom - p->stats.wis_orig);
5064 
5065     /* effects */
5066     char **effect_desc = player_effect_text(p);
5067 
5068     if (*effect_desc)
5069     {
5070         g_string_append(text, "\n\n-- Effects ----------------------------\n\n");
5071 
5072         for (guint pos = 0; effect_desc[pos]; pos++)
5073         {
5074             g_string_append_printf(text, "%s\n", effect_desc[pos]);
5075         }
5076     }
5077 
5078     g_strfreev(effect_desc);
5079 
5080     /* append list of known spells */
5081     if (p->known_spells->len > 0)
5082     {
5083         g_string_append(text, "\n\n-- Known Spells -----------------------\n\n");
5084 
5085         for (guint pos = 0; pos < p->known_spells->len; pos++)
5086         {
5087             spell *s = (spell *)g_ptr_array_index(p->known_spells, pos);
5088             tmp = str_capitalize(g_strdup(spell_name(s)));
5089 
5090             g_string_append_printf(text, "%-24s (lvl. %2d): %3d\n",
5091                                    tmp, s->knowledge, s->used);
5092 
5093             g_free(tmp);
5094         }
5095     }
5096 
5097     /* identify entire inventory */
5098     for (guint pos = 0; pos < inv_length(p->inventory); pos++)
5099     {
5100         item *it = inv_get(p->inventory, pos);
5101         it->blessed_known = TRUE;
5102         it->bonus_known = TRUE;
5103     }
5104 
5105     /* equipped items */
5106     gchar *el = player_equipment_list(p);
5107     guint equipment_count = 0;
5108 
5109     if (strlen(el) > 0)
5110     {
5111         g_string_append(text, "\n\n-- Equipment --------------------------\n\n");
5112         g_string_append(text, el);
5113 
5114         for (guint idx = 0; idx < inv_length(p->inventory); idx++)
5115         {
5116             item *it = inv_get(p->inventory, idx);
5117             if (player_item_is_equipped(p, it))
5118                 equipment_count++;
5119         }
5120     }
5121     g_free(el);
5122 
5123     /* inventory */
5124     if (equipment_count < inv_length(p->inventory))
5125     {
5126         g_string_append(text, "\n\n-- Items in pack ----------------------\n\n");
5127         for (guint pos = 0; pos < inv_length(p->inventory); pos++)
5128         {
5129             item *it = inv_get(p->inventory, pos);
5130             if (!player_item_is_equipped(p, it))
5131             {
5132                 gchar *it_desc = item_describe(it, TRUE, FALSE, FALSE);
5133                 g_string_append_printf(text, "%s\n", it_desc);
5134                 g_free(it_desc);
5135             }
5136         }
5137     }
5138 
5139     /* list monsters killed */
5140     guint body_count = 0;
5141     g_string_append(text, "\n\n-- Creatures vanquished ---------------\n\n");
5142 
5143     for (guint mnum = 0; mnum < MT_MAX; mnum++)
5144     {
5145         if (p->stats.monsters_killed[mnum] > 0)
5146         {
5147             guint mcount = p->stats.monsters_killed[mnum];
5148             tmp = str_capitalize(g_strdup(monster_type_plural_name(mnum,
5149                                           mcount)));
5150 
5151             g_string_append_printf(text, "%3d %s\n", mcount, tmp);
5152 
5153             g_free(tmp);
5154             body_count += mcount;
5155         }
5156     }
5157     g_string_append_printf(text, "\n%3d total\n", body_count);
5158 
5159     /* genocided monsters */
5160     gboolean printed_headline = FALSE;
5161     for (guint mnum = 0; mnum < MT_MAX; mnum++)
5162     {
5163         if (!monster_is_genocided(mnum))
5164             continue;
5165 
5166         if (!printed_headline)
5167         {
5168                 g_string_append(text, "\n\n-- Genocided creatures ---------------\n\n");
5169                 printed_headline = TRUE;
5170         }
5171 
5172         tmp = str_capitalize(g_strdup(monster_type_plural_name(mnum, 2)));
5173         g_string_append_printf(text, "%s\n", tmp);
5174     }
5175 
5176      /* messages */
5177     g_string_append(text, "\n\n-- Last messages ----------------------\n\n");
5178     for (guint pos = log_length(nlarn->log) - min(10, log_length(nlarn->log));
5179          pos < log_length(nlarn->log); pos++)
5180     {
5181         message_log_entry *entry = log_get_entry(nlarn->log, pos);
5182         g_string_append_printf(text, "%s\n", entry->message);
5183     }
5184     /* print uncommitted messages */
5185     if (nlarn->log->buffer->len > 0)
5186     {
5187         g_string_append_printf(text, "%s\n", nlarn->log->buffer->str);
5188     }
5189 
5190     return (g_string_free(text, FALSE));
5191 }
5192 
player_memorial_file_save(player * p,const char * text)5193 static void player_memorial_file_save(player *p, const char *text)
5194 {
5195     char *proposal = NULL;
5196     char *filename;
5197     gboolean done = FALSE;
5198 
5199     while (!done)
5200     {
5201         GError *error = NULL;
5202 
5203         if (proposal == NULL)
5204         {
5205             /* When starting, propose a file name. Use the previously
5206                entered file name when trying to find a new name for an
5207                existing file. */
5208             proposal = g_strconcat(p->name, ".txt", NULL);
5209         }
5210 
5211         filename = display_get_string(NULL, "Enter filename: ", proposal, 40);
5212         g_free(proposal);
5213         proposal = NULL;
5214 
5215         if (filename == NULL)
5216         {
5217             /* user pressed ESC, thus display_get_string() returned NULL */
5218             done = TRUE;
5219         } else {
5220             /* file name has been provided, try to save file */
5221             char *fullname = g_build_path(G_DIR_SEPARATOR_S,
5222 #ifdef G_OS_WIN32
5223                     g_get_user_special_dir(G_USER_DIRECTORY_DOCUMENTS),
5224 #else
5225                     g_get_home_dir(),
5226 #endif
5227                     filename, NULL);
5228 
5229             if (g_file_test(fullname, G_FILE_TEST_IS_SYMLINK))
5230             {
5231                 display_show_message("Error", "File is a symlink. I won't " \
5232                         "overwrite those...", 0);
5233                 g_free(fullname);
5234                 continue;
5235             }
5236 
5237             if (g_file_test(fullname, G_FILE_TEST_IS_DIR))
5238             {
5239                 display_show_message("Error", "There is a directory with the " \
5240                         "name you gave. Thus I can't write the file.", 0);
5241                 g_free(fullname);
5242                 continue;
5243             }
5244 
5245             if (g_file_test(fullname, G_FILE_TEST_IS_REGULAR)
5246                     && !display_get_yesno("File exists!\n"
5247                         "Do you want to overwrite it?", NULL, NULL, NULL))
5248             {
5249                 g_free(fullname);
5250                 proposal = filename;
5251                 continue;
5252             }
5253 
5254             /* wrap the text and insert line feed for the platform */
5255             char *wtext = str_prepare_for_saving(text);
5256 
5257             if (g_file_set_contents(fullname, wtext, -1, &error))
5258             {
5259                 /* successfully saved the memorial file */
5260                 done = TRUE;
5261             }
5262             else
5263             {
5264                 display_show_message("Error", error->message, 0);
5265                 g_error_free(error);
5266             }
5267 
5268             g_free(wtext);
5269 
5270             g_free(filename);
5271             g_free(fullname);
5272         }
5273     }
5274 }
5275 
item_filter_equippable(item * it)5276 static int item_filter_equippable(item *it)
5277 {
5278     return player_item_is_equippable(nlarn->p, it);
5279 }
5280 
item_filter_dropable(item * it)5281 static int item_filter_dropable(item *it)
5282 {
5283     return player_item_is_dropable(nlarn->p, it);
5284 }
5285 
player_item_filter_multiple(player * p,item * it)5286 static int player_item_filter_multiple(player *p __attribute__((unused)), item *it)
5287 {
5288     return (it->count > 1);
5289 }
5290