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