1 /*
2 * Crossfire -- cooperative multi-player graphical RPG and adventure game
3 *
4 * Copyright (c) 1999-2013 Mark Wedel and the Crossfire Development Team
5 * Copyright (c) 1992 Frank Tore Johansen
6 *
7 * Crossfire is free software and comes with ABSOLUTELY NO WARRANTY. You are
8 * welcome to redistribute it under certain conditions. For details, please
9 * see COPYING and LICENSE.
10 *
11 * The authors can be reached via e-mail at <crossfire@metalforge.org>.
12 */
13
14 /**
15 * @file
16 * Provides functions that process items in various ways.
17 */
18
19 #include "client.h"
20
21 #include <ctype.h> /* needed for isdigit */
22
23 #include "external.h"
24 #include "item.h"
25 #include "script.h"
26
27 static item *player, *map; /* these lists contains rest of items */
28 /* player = pl->ob, map = pl->below */
29
30 #include "item-types.h"
31
32 /* This uses the item_types table above. We try to figure out if
33 * name has a match above. Matching is done pretty loosely - however
34 * we try to match the start of the name because that is more reliable.
35 * We return the 'type' (matching array element above), 255 if no match
36 * (so unknown objects put at the end)
37 */
get_type_from_name(const char * name)38 guint8 get_type_from_name(const char *name)
39 {
40 int type, pos;
41
42 for (type = 0; type < NUM_ITEM_TYPES; type++) {
43 pos = 0;
44 while (item_types[type][pos] != NULL) {
45 /* Only search at start of line */
46 if (item_types[type][pos][0] == '^') {
47 if (!g_ascii_strncasecmp(name, item_types[type][pos]+1, strlen(item_types[type][pos]+1))) {
48 return type;
49 }
50 }
51 /* String anywhere in name */
52 else if (strstr(name, item_types[type][pos]) != NULL) {
53 #if 0
54 fprintf(stderr, "Returning type %d for %s\n", type, name);
55 #endif
56 return type;
57 }
58 pos++;
59 }
60 }
61 LOG(LOG_WARNING, "common::get_type_from_name", "Could not find match for %s", name);
62 return 255;
63 }
64
65 /* Does what is says - inserts newitem before the object.
66 * the parameters can not be null
67 */
insert_item_before_item(item * newitem,item * before)68 static void insert_item_before_item(item *newitem, item *before)
69 {
70 if (before->prev) {
71 before->prev->next = newitem;
72 } else {
73 newitem->env->inv = newitem;
74 }
75
76 newitem->prev = before->prev;
77
78 before->prev = newitem;
79 newitem->next = before;
80
81 if (newitem->env) {
82 newitem->env->inv_updated = 1;
83 }
84 }
85
86 /* Item it has gotten an item type, so we need to resort its location */
update_item_sort(item * it)87 void update_item_sort(item *it)
88 {
89 item *itmp, *last = NULL;
90
91 /* If not in some environment or the map, return */
92 /* Sorting on the map doesn't work. In theory, it would be nice,
93 * but the server really must know the map order for things to
94 * work.
95 */
96 if (!it->env || it->env == it || it->env == map) {
97 return;
98 }
99
100 /* If we are already sorted properly, don't do anything further.
101 * this is prevents the order of the inventory from changing around
102 * if you just equip something.
103 */
104 if (it->prev && it->prev->type == it->type &&
105 it->prev->locked == it->locked &&
106 !g_ascii_strcasecmp(it->prev->s_name, it->s_name)) {
107 return;
108 }
109
110 if (it->next && it->next->type == it->type &&
111 it->next->locked == it->locked &&
112 !g_ascii_strcasecmp(it->next->s_name, it->s_name)) {
113 return;
114 }
115
116 /* Remove this item from the list */
117 if (it->prev) {
118 it->prev->next = it->next;
119 }
120 if (it->next) {
121 it->next->prev = it->prev;
122 }
123 if (it->env->inv == it) {
124 it->env->inv = it->next;
125 }
126
127 for (itmp = it->env->inv; itmp != NULL; itmp = itmp->next) {
128 last = itmp;
129
130 /* If the next item is higher in the order, insert here */
131 if (itmp->type > it->type) {
132 insert_item_before_item(it, itmp);
133 return;
134 } else if (itmp->type == it->type) {
135 #if 0
136 /* This could be a nice idea, but doesn't work very well if you
137 * have a few unidentified wands, as the position of a wand
138 * which you know the effect will move around as you equip others.
139 */
140 /* Hmm. We can actually use the tag value of the items to reduce
141 * this a bit - do this by grouping, but if name is equal, then
142 * sort by tag. Needs further investigation.
143 */
144
145 /* applied items go first */
146 if (itmp->applied) {
147 continue;
148 }
149 /* put locked items before others */
150 if (itmp->locked && !it->locked) {
151 continue;
152 }
153 #endif
154
155 /* Now alphabetise */
156 if (g_ascii_strcasecmp(itmp->s_name, it->s_name) < 0) {
157 continue;
158 }
159
160 /* IF we got here, it means it passed all our sorting tests */
161 insert_item_before_item(it, itmp);
162 return;
163 }
164 }
165 /* No match - put it at the end */
166
167 /* If there was a previous item, update pointer. IF no previous
168 * item, we need to update the environment to point to us */
169 if (last) {
170 last->next = it;
171 } else {
172 it->env->inv = it;
173 }
174
175 it->prev = last;
176 it->next = NULL;
177 }
178
179 /* Stolen from common/item.c */
180 /*
181 * get_number(integer) returns the text-representation of the given number
182 * in a static buffer. The buffer might be overwritten at the next
183 * call to get_number().
184 * It is currently only used by the query_name() function.
185 */
get_number(guint32 i)186 const char *get_number(guint32 i)
187 {
188 static const char *numbers[] = {
189 "no", "a", "two", "three", "four",
190 "five", "six", "seven", "eight", "nine",
191 "ten", "eleven", "twelve", "thirteen", "fourteen",
192 "fifteen", "sixteen", "seventeen", "eighteen", "nineteen",
193 "twenty",
194 };
195 static char buf[MAX_BUF];
196
197 if (i <= 20) {
198 return numbers[i];
199 } else {
200 snprintf(buf, sizeof(buf), "%u", i);
201 return buf;
202 }
203 }
204
205 /*
206 * new_item() returns pointer to new item which
207 * is allocated and initialized correctly
208 */
new_item(void)209 static item *new_item(void)
210 {
211 item *op = g_malloc(sizeof(item));
212
213 if (!op) {
214 exit(0);
215 }
216
217 op->next = op->prev = NULL;
218 copy_name(op->d_name, "");
219 copy_name(op->s_name, "");
220 copy_name(op->p_name, "");
221 op->inv = NULL;
222 op->env = NULL;
223 op->tag = 0;
224 op->face = 0;
225 op->weight = 0;
226 op->magical = op->cursed = op->damned = op->blessed = 0;
227 op->unpaid = op->locked = op->applied = 0;
228 op->flagsval = 0;
229 op->animation_id = 0;
230 op->last_anim = 0;
231 op->anim_state = 0;
232 op->nrof = 0;
233 op->open = 0;
234 op->type = NO_ITEM_TYPE;
235 op->inv_updated = 0;
236 return op;
237 }
238
239 /*
240 * free_items() frees all allocated items from list
241 */
free_all_items(item * op)242 void free_all_items(item *op)
243 {
244 item *tmp;
245
246 while (op) {
247 if (op->inv) {
248 free_all_items(op->inv);
249 }
250 tmp = op->next;
251 free(op);
252 op = tmp;
253 }
254 }
255
256 /*
257 * Recursive function, used by locate_item()
258 */
locate_item_from_item(item * op,gint32 tag)259 static item *locate_item_from_item(item *op, gint32 tag)
260 {
261 item *tmp;
262
263 for (; op; op = op->next) {
264 if (op->tag == tag) {
265 return op;
266 } else if (op->inv && (tmp = locate_item_from_item(op->inv, tag))) {
267 return tmp;
268 }
269 }
270
271 return NULL;
272 }
273
274 /*
275 * locate_item() returns pointer to the item which tag is given
276 * as parameter or if item is not found returns NULL
277 */
locate_item(gint32 tag)278 item *locate_item(gint32 tag)
279 {
280 item *op;
281
282 if (tag == 0) {
283 return map;
284 }
285
286 if ((op = locate_item_from_item(map->inv, tag)) != NULL) {
287 return op;
288 }
289
290 if ((op = locate_item_from_item(player, tag)) != NULL) {
291 return op;
292 }
293
294 if (cpl.container && cpl.container->tag == tag) {
295 return cpl.container;
296 }
297
298 if (cpl.container && (op = locate_item_from_item(cpl.container->inv, tag)) != NULL) {
299 return op;
300 }
301
302 return NULL;
303 }
304
305 /*
306 * remove_item() inserts op the the list of free items
307 * Note that it don't clear all fields in item
308 */
remove_item(item * op)309 void remove_item(item *op)
310 {
311 /* IF no op, or it is the player */
312 if (!op || op == player || op == map) {
313 return;
314 }
315
316 item_event_item_deleting(op);
317
318 op->env->inv_updated = 1;
319
320 /* Do we really want to do this? */
321 if (op->inv && op != cpl.container) {
322 remove_item_inventory(op);
323 }
324
325 if (op->prev) {
326 op->prev->next = op->next;
327 } else {
328 op->env->inv = op->next;
329 }
330 if (op->next) {
331 op->next->prev = op->prev;
332 }
333
334 if (cpl.container == op) {
335 return; /* Don't free this! */
336 }
337
338 g_free(op);
339 }
340
341 /*
342 * remove_item_inventory() recursive frees items inventory
343 */
remove_item_inventory(item * op)344 void remove_item_inventory(item *op)
345 {
346 if (!op) {
347 return;
348 }
349
350 item_event_container_clearing(op);
351
352 op->inv_updated = 1;
353 while (op->inv) {
354 remove_item(op->inv);
355 }
356 }
357
358 /*
359 * add_item() adds item op to end of the inventory of item env
360 */
add_item(item * env,item * op)361 static void add_item(item *env, item *op)
362 {
363 item *tmp;
364
365 for (tmp = env->inv; tmp && tmp->next; tmp = tmp->next)
366 ;
367
368 op->next = NULL;
369 op->prev = tmp;
370 op->env = env;
371 if (!tmp) {
372 env->inv = op;
373 } else {
374 if (tmp->next) {
375 tmp->next->prev = op;
376 }
377 tmp->next = op;
378 }
379 }
380
381 /*
382 * create_new_item() returns pointer to a new item, inserts it to env
383 * and sets its tag field and clears locked flag (all other fields
384 * are unitialized and may contain random values)
385 */
create_new_item(item * env,gint32 tag)386 static item *create_new_item(item *env, gint32 tag)
387 {
388 item *op;
389 op = new_item();
390
391 op->tag = tag;
392 op->locked = 0;
393 if (env) {
394 add_item(env, op);
395 }
396
397 return op;
398 }
399
400 /*
401 * Hardcoded now, server could send these at initiation phase.
402 */
403 static const char *const apply_string[] = {
404 "", " (readied)", " (wielded)", " (worn)", " (active)", " (applied)",
405 };
406
set_flag_string(item * op)407 static void set_flag_string(item *op)
408 {
409 op->flags[0] = 0;
410
411 if (op->locked) {
412 strcat(op->flags, " *");
413 }
414 if (op->apply_type) {
415 if (op->apply_type < sizeof(apply_string)/sizeof(apply_string[0])) {
416 strcat(op->flags, apply_string[op->apply_type]);
417 } else {
418 strcat(op->flags, " (undefined)");
419 }
420 }
421 if (op->open) {
422 strcat(op->flags, " (open)");
423 }
424 if (op->damned) {
425 strcat(op->flags, " (damned)");
426 }
427 if (op->cursed) {
428 strcat(op->flags, " (cursed)");
429 }
430 if (op->blessed) {
431 strcat(op->flags, " (blessed)");
432 }
433 if (op->magical) {
434 strcat(op->flags, " (magic)");
435 }
436 if (op->unpaid) {
437 strcat(op->flags, " (unpaid)");
438 }
439 if (op->read) {
440 strcat(op->flags, " (read)");
441 }
442 }
443
get_flags(item * op,guint16 flags)444 static void get_flags(item *op, guint16 flags)
445 {
446 op->was_open = op->open;
447 op->open = flags&F_OPEN ? 1 : 0;
448 op->damned = flags&F_DAMNED ? 1 : 0;
449 op->cursed = flags&F_CURSED ? 1 : 0;
450 op->blessed = flags&F_BLESSED ? 1 : 0;
451 op->magical = flags&F_MAGIC ? 1 : 0;
452 op->unpaid = flags&F_UNPAID ? 1 : 0;
453 op->applied = flags&F_APPLIED ? 1 : 0;
454 op->locked = flags&F_LOCKED ? 1 : 0;
455 op->read = flags&F_READ ? 1 : 0;
456 op->flagsval = flags;
457 op->apply_type = flags&F_APPLIED;
458 set_flag_string(op);
459 }
460
set_item_values(item * op,char * name,gint32 weight,guint16 face,guint16 flags,guint16 anim,guint16 animspeed,guint32 nrof,guint16 type)461 void set_item_values(item *op, char *name, gint32 weight, guint16 face,
462 guint16 flags, guint16 anim, guint16 animspeed,
463 guint32 nrof, guint16 type)
464 {
465 int resort = 1;
466
467 if (!op) {
468 printf("Error in set_item_values(): item pointer is NULL.\n");
469 return;
470 }
471
472 /* Program always expect at least 1 object internall */
473 if (nrof == 0) {
474 nrof = 1;
475 }
476
477 if (*name != '\0') {
478 copy_name(op->s_name, name);
479
480 /* Unfortunately, we don't get a length parameter, so we just have
481 * to assume that if it is a new server, it is giving us two piece
482 * names.
483 */
484 if (csocket.sc_version >= 1024) {
485 copy_name(op->p_name, name+strlen(name)+1);
486 } else { /* If not new version, just use same for both */
487 copy_name(op->p_name, name);
488 }
489
490 /* Necessary so that d_name is updated below */
491 op->nrof = nrof+1;
492 } else {
493 resort = 0; /* no name - don't resort */
494 }
495
496 if (op->nrof != nrof) {
497 if (nrof != 1 ) {
498 snprintf(op->d_name, sizeof(op->d_name), "%s %s", get_number(nrof),
499 op->p_name);
500 } else {
501 strcpy(op->d_name, op->s_name);
502 }
503 op->nrof = nrof;
504 }
505
506 if (op->env) {
507 op->env->inv_updated = 1;
508 }
509 op->weight = (float)weight/1000;
510 op->face = face;
511 op->animation_id = anim;
512 op->anim_speed = animspeed;
513 op->type = type;
514 get_flags(op, flags);
515
516 /* We don't sort the map, so lets not bother figuring out the
517 * type. Likewiwse, only figure out item type if this
518 * doesn't have a type (item2 provides us with a type
519 */
520 if (op->env != map && op->type == NO_ITEM_TYPE) {
521 op->type = get_type_from_name(op->s_name);
522 }
523 if (resort) {
524 update_item_sort(op);
525 }
526
527 item_event_item_changed(op);
528 }
529
toggle_locked(item * op)530 void toggle_locked(item *op)
531 {
532 SockList sl;
533 guint8 buf[MAX_BUF];
534
535 if (op->env->tag == 0) {
536 return; /* if item is on the ground, don't lock it */
537 }
538
539 snprintf((char*)buf, sizeof(buf), "lock %d %d", !op->locked, op->tag);
540 script_monitor_str((char*)buf);
541 SockList_Init(&sl, buf);
542 SockList_AddString(&sl, "lock ");
543 SockList_AddChar(&sl, !op->locked);
544 SockList_AddInt(&sl, op->tag);
545 SockList_Send(&sl, csocket.fd);
546 }
547
send_mark_obj(item * op)548 void send_mark_obj(item *op)
549 {
550 SockList sl;
551 guint8 buf[MAX_BUF];
552
553 if (op->env->tag == 0) {
554 return; /* if item is on the ground, don't mark it */
555 }
556
557 snprintf((char*)buf, sizeof(buf), "mark %d", op->tag);
558 script_monitor_str((char*)buf);
559 SockList_Init(&sl, buf);
560 SockList_AddString(&sl, "mark ");
561 SockList_AddInt(&sl, op->tag);
562 SockList_Send(&sl, csocket.fd);
563 }
564
player_item(void)565 item *player_item (void)
566 {
567 player = new_item();
568 return player;
569 }
570
map_item(void)571 item *map_item (void)
572 {
573 map = new_item();
574 map->weight = -1;
575 return map;
576 }
577
578 /* Upates an item with new attributes. */
update_item(int tag,int loc,char * name,int weight,int face,int flags,int anim,int animspeed,guint32 nrof,int type)579 void update_item(int tag, int loc, char *name, int weight, int face, int flags,
580 int anim, int animspeed, guint32 nrof, int type)
581 {
582 /* Need to do some special handling if this is the player that is
583 * being updated.
584 */
585 if (player->tag == tag) {
586 copy_name(player->d_name, name);
587 /* I don't think this makes sense, as you can have
588 * two players merged together, so nrof should always be one
589 */
590 player->nrof = nrof;
591 player->weight = (float)weight/1000;
592 player->face = face;
593 get_flags(player, flags);
594 if (player->inv) {
595 player->inv->inv_updated = 1;
596 }
597 player->animation_id = anim;
598 player->anim_speed = animspeed;
599 player->nrof = nrof;
600 } else {
601 item *ip = locate_item(tag), *env = locate_item(loc);
602 if (ip && ip->env != env) {
603 // If item moved, it's easier to remove and re-add than to update
604 // everything that needs updating.
605 remove_item(ip);
606 ip = NULL;
607 }
608 if (ip == NULL) {
609 ip = create_new_item(env, tag);
610 }
611 set_item_values(ip, name, weight, face, flags,
612 anim, animspeed, nrof, type);
613 }
614 }
615
616 /*
617 * Prints players inventory, contain extra information for debugging purposes
618 * This isn't pretty, but is only used for debugging, so it doesn't need to be.
619 */
print_inventory(item * op)620 void print_inventory(item *op)
621 {
622 char buf[MAX_BUF];
623 char buf2[MAX_BUF];
624 item *tmp;
625 static int l = 0;
626 #if 0
627 int info_width = get_info_width();
628 #else
629 /* A callback for a debugging command seems pretty pointless. If anything,
630 * it may be more useful to dump this out to stderr
631 */
632 int info_width = 40;
633 #endif
634
635 if (l == 0) {
636 snprintf(buf, sizeof(buf), "%s's inventory (%d):", op->d_name, op->tag);
637 snprintf(buf2, sizeof(buf2), "%-*s%6.1f kg", info_width-10, buf, op->weight);
638 draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_DEBUG, buf2);
639 }
640
641 l += 2;
642 for (tmp = op->inv; tmp; tmp = tmp->next) {
643 snprintf(buf, sizeof(buf), "%*s- %d %s%s (%d)", l-2, "", tmp->nrof, tmp->d_name, tmp->flags, tmp->tag);
644 snprintf(buf2, sizeof(buf2), "%-*s%6.1f kg", info_width-8-l, buf, tmp->nrof*tmp->weight);
645 draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_DEBUG, buf2);
646 if (tmp->inv) {
647 print_inventory(tmp);
648 }
649 }
650 l -= 2;
651 }
652
653 /* Check the objects, animate the ones as necessary */
animate_objects(void)654 void animate_objects(void)
655 {
656 item *ip;
657 int got_one = 0;
658
659 /* Animate players inventory */
660 for (ip = player->inv; ip; ip = ip->next) {
661 if (ip->animation_id > 0 && ip->anim_speed) {
662 ip->last_anim++;
663 if (ip->last_anim >= ip->anim_speed) {
664 ip->anim_state++;
665 if (ip->anim_state >= animations[ip->animation_id].num_animations) {
666 ip->anim_state = 0;
667 }
668 ip->face = animations[ip->animation_id].faces[ip->anim_state];
669 ip->last_anim = 0;
670 got_one = 1;
671 }
672 }
673 }
674 #ifndef GTK_CLIENT
675 if (got_one) {
676 player->inv_updated = 1;
677 }
678 #endif
679 if (cpl.container) {
680 /* Now do a container if one is active */
681 for (ip = cpl.container->inv; ip; ip = ip->next) {
682 if (ip->animation_id > 0 && ip->anim_speed) {
683 ip->last_anim++;
684 if (ip->last_anim >= ip->anim_speed) {
685 ip->anim_state++;
686 if (ip->anim_state >= animations[ip->animation_id].num_animations) {
687 ip->anim_state = 0;
688 }
689 ip->face = animations[ip->animation_id].faces[ip->anim_state];
690 ip->last_anim = 0;
691 got_one = 1;
692 }
693 }
694 }
695 if (got_one) {
696 cpl.container->inv_updated = 1;
697 }
698 } else {
699 /* Now do the map (look window) */
700 for (ip = cpl.below->inv; ip; ip = ip->next) {
701 if (ip->animation_id > 0 && ip->anim_speed) {
702 ip->last_anim++;
703 if (ip->last_anim >= ip->anim_speed) {
704 ip->anim_state++;
705 if (ip->anim_state >= animations[ip->animation_id].num_animations) {
706 ip->anim_state = 0;
707 }
708 ip->face = animations[ip->animation_id].faces[ip->anim_state];
709 ip->last_anim = 0;
710 got_one = 1;
711 }
712 }
713 }
714 if (got_one) {
715 cpl.below->inv_updated = 1;
716 }
717 }
718 }
719
can_write_spell_on(item * it)720 int can_write_spell_on(item* it)
721 {
722 return (it->type == 661);
723 }
724
inscribe_magical_scroll(item * scroll,Spell * spell)725 void inscribe_magical_scroll(item *scroll, Spell *spell)
726 {
727 SockList sl;
728 guint8 buf[MAX_BUF];
729
730 snprintf((char*)buf, sizeof(buf), "inscribe 0 %d %d", scroll->tag, spell->tag);
731 script_monitor_str((char*)buf);
732 SockList_Init(&sl, buf);
733 SockList_AddString(&sl, "inscribe ");
734 SockList_AddChar(&sl, 0);
735 SockList_AddInt(&sl, scroll->tag);
736 SockList_AddInt(&sl, spell->tag);
737 SockList_Send(&sl, csocket.fd);
738 }
739