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