Crossfire Client, Branch  R11627
item.c
Go to the documentation of this file.
00001 const char * const rcsid_common_item_c =
00002     "$Id: item.c 9201 2008-06-01 17:32:45Z anmaster $";
00003 /*
00004     Crossfire client, a client program for the crossfire program.
00005 
00006     Copyright (C) 2001 Mark Wedel & Crossfire Development Team
00007 
00008     This program is free software; you can redistribute it and/or modify
00009     it under the terms of the GNU General Public License as published by
00010     the Free Software Foundation; either version 2 of the License, or
00011     (at your option) any later version.
00012 
00013     This program is distributed in the hope that it will be useful,
00014     but WITHOUT ANY WARRANTY; without even the implied warranty of
00015     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00016     GNU General Public License for more details.
00017 
00018     You should have received a copy of the GNU General Public License
00019     along with this program; if not, write to the Free Software
00020     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
00021 
00022     The author can be reached via e-mail to crossfire-devel@real-time.com
00023 */
00024 
00025 
00026 #include <ctype.h>      /* needed for isdigit */
00027 #include <client.h>
00028 #include <item.h>
00029 #include <newclient.h>
00030 #include <external.h>
00031 #include <script.h>
00032 
00033 static item *free_items;        /* the list of free (unused) items */
00034 static item *player, *map;      /* these lists contains rest of items */
00035                                 /* player = pl->ob, map = pl->below */
00036 
00037 #define NROF_ITEMS 50           /* how many items are reserved initially */
00038                                 /* for the item spool */
00039 
00040 #include <item-types.h>
00041 
00042 
00043 /* This uses the item_types table above.  We try to figure out if
00044  * name has a match above.  Matching is done pretty loosely - however
00045  * we try to match the start of the name because that is more reliable.
00046  * We return the 'type' (matching array element above), 255 if no match
00047  * (so unknown objects put at the end)
00048  */
00049 uint8 get_type_from_name(const char *name) {
00050     int type, pos;
00051 
00052     for (type = 0; type < NUM_ITEM_TYPES; type++) {
00053         pos = 0;
00054         while (item_types[type][pos] != NULL) {
00055             /* Only search at start of line */
00056             if (item_types[type][pos][0] == '^') {
00057                 if (!strncasecmp(name, item_types[type][pos]+1, strlen(item_types[type][pos]+1))) {
00058                     return type;
00059                 }
00060             }
00061             /* String anywhere in name */
00062             else if (strstr(name, item_types[type][pos]) != NULL) {
00063 #if 0
00064                 fprintf(stderr, "Returning type %d for %s\n", type, name);
00065 #endif
00066                 return type;
00067             }
00068             pos++;
00069         }
00070     }
00071     LOG(LOG_WARNING, "common::get_type_from_name", "Could not find match for %s", name);
00072     return 255;
00073 }
00074 
00075 /* Does what is says - inserts newitem before the object.
00076  * the parameters can not be null
00077  */
00078 static void insert_item_before_item(item *newitem, item *before) {
00079     if (before->prev) {
00080         before->prev->next = newitem;
00081     } else {
00082         newitem->env->inv = newitem;
00083     }
00084 
00085     newitem->prev = before->prev;
00086 
00087     before->prev = newitem;
00088     newitem->next = before;
00089 
00090     if (newitem->env) {
00091         newitem->env->inv_updated = 1;
00092     }
00093 }
00094 
00095 /* Item it has gotten an item type, so we need to resort its location */
00096 void update_item_sort(item *it) {
00097     item *itmp, *last = NULL;
00098 
00099     /* If not in some environment or the map, return */
00100     /* Sorting on the map doesn't work.  In theory, it would be nice,
00101      * but the server really must know the map order for things to
00102      * work.
00103      */
00104     if (!it->env || it->env == it || it->env == map) {
00105         return;
00106     }
00107 
00108     /* If we are already sorted properly, don't do anything further.
00109      * this is prevents the order of the inventory from changing around
00110      * if you just equip something.
00111      */
00112     if (it->prev && it->prev->type == it->type &&
00113         it->prev->locked == it->locked &&
00114         !strcasecmp(it->prev->s_name, it->s_name)) {
00115         return;
00116     }
00117 
00118     if (it->next && it->next->type == it->type &&
00119         it->next->locked == it->locked &&
00120         !strcasecmp(it->next->s_name, it->s_name)) {
00121         return;
00122     }
00123 
00124     /* Remove this item from the list */
00125     if (it->prev) it->prev->next = it->next;
00126     if (it->next) it->next->prev = it->prev;
00127     if (it->env->inv == it) it->env->inv = it->next;
00128 
00129     for (itmp = it->env->inv; itmp != NULL; itmp = itmp->next) {
00130         last = itmp;
00131 
00132         /* If the next item is higher in the order, insert here */
00133         if (itmp->type > it->type) {
00134             insert_item_before_item(it, itmp);
00135             return;
00136         } else if (itmp->type == it->type) {
00137 #if 0
00138             /* This could be a nice idea, but doesn't work very well if you
00139              * have a few unidentified wands, as the position of a wand
00140              * which you know the effect will move around as you equip others.
00141              */
00142             /* Hmm.  We can actually use the tag value of the items to reduce
00143              * this a bit - do this by grouping, but if name is equal, then
00144              * sort by tag.  Needs further investigation.
00145              */
00146 
00147             /* applied items go first */
00148             if (itmp->applied) continue;
00149             /* put locked items before others */
00150             if (itmp->locked && !it->locked) continue;
00151 #endif
00152 
00153             /* Now alphabetise */
00154             if (strcasecmp(itmp->s_name, it->s_name) < 0) continue;
00155 
00156             /* IF we got here, it means it passed all our sorting tests */
00157             insert_item_before_item(it, itmp);
00158             return;
00159         }
00160     }
00161     /* No match - put it at the end */
00162 
00163     /* If there was a previous item, update pointer.  IF no previous
00164      * item, we need to update the environment to point to us */
00165     if (last) {
00166         last->next = it;
00167     } else {
00168         it->env->inv = it;
00169     }
00170 
00171     it->prev = last;
00172     it->next = NULL;
00173 }
00174 
00175 /* Stolen from common/item.c */
00176 /*
00177  * get_number(integer) returns the text-representation of the given number
00178  * in a static buffer.  The buffer might be overwritten at the next
00179  * call to get_number().
00180  * It is currently only used by the query_name() function.
00181  */
00182 const char *get_number(uint32 i) {
00183     static const char numbers[21][20] = {
00184         "no", "a", "two", "three", "four",
00185         "five", "six", "seven", "eight", "nine",
00186         "ten", "eleven", "twelve", "thirteen", "fourteen",
00187         "fifteen", "sixteen", "seventeen", "eighteen", "nineteen",
00188         "twenty",
00189     };
00190     static char buf[MAX_BUF];
00191 
00192     if (i < 0) {
00193         snprintf(buf, sizeof(buf), "negative");
00194         return buf;
00195     }
00196 
00197     if (i <= 20) {
00198         return numbers[i];
00199     } else {
00200         snprintf(buf, sizeof(buf), "%u", i);
00201         return buf;
00202     }
00203 }
00204 
00205 /*
00206  *  new_item() returns pointer to new item which
00207  *  is allocated and initialized correctly
00208  */
00209 static item *new_item(void) {
00210     item *op = malloc(sizeof(item));
00211 
00212     if (!op) {
00213         exit(0);
00214     }
00215 
00216     op->next = op->prev = NULL;
00217     copy_name(op->d_name, "");
00218     copy_name(op->s_name, "");
00219     copy_name(op->p_name, "");
00220     op->inv = NULL;
00221     op->env = NULL;
00222     op->tag = 0;
00223     op->face = 0;
00224     op->weight = 0;
00225     op->magical = op->cursed = op->damned = 0;
00226     op->unpaid = op->locked = op->applied = 0;
00227     op->flagsval = 0;
00228     op->animation_id = 0;
00229     op->last_anim = 0;
00230     op->anim_state = 0;
00231     op->nrof = 0;
00232     op->open = 0;
00233     op->type = NO_ITEM_TYPE;
00234     op->inv_updated = 0;
00235     return op;
00236 }
00237 
00238 /*
00239  *  alloc_items() returns pointer to list of allocated objects
00240  */
00241 static item *alloc_items(int nrof) {
00242     item *op, *list;
00243     int i;
00244 
00245     list = op = new_item();
00246 
00247     for (i = 1; i < nrof; i++) {
00248         op->next = new_item();
00249         op->next->prev = op;
00250         op = op->next;
00251     }
00252     return list;
00253 }
00254 
00255 /*
00256  *  free_items() frees all allocated items from list
00257  */
00258 void free_all_items(item *op) {
00259     item *tmp;
00260 
00261     while (op) {
00262         if (op->inv) {
00263             free_all_items(op->inv);
00264         }
00265         tmp = op->next;
00266         free(op);
00267         op = tmp;
00268     }
00269 }
00270 
00271 /*
00272  *  Recursive function, used by locate_item()
00273  */
00274 static item *locate_item_from_item(item *op, sint32 tag) {
00275     item *tmp;
00276 
00277     for (; op; op = op->next) {
00278         if (op->tag == tag) {
00279             return op;
00280         } else if (op->inv && (tmp = locate_item_from_item(op->inv, tag))) {
00281             return tmp;
00282         }
00283     }
00284 
00285     return NULL;
00286 }
00287 
00288 /*
00289  *  locate_item() returns pointer to the item which tag is given
00290  *  as parameter or if item is not found returns NULL
00291  */
00292 item *locate_item(sint32 tag) {
00293     item *op;
00294 
00295     if (tag == 0) {
00296         return map;
00297     }
00298 
00299     if ((op = locate_item_from_item(map->inv, tag)) != NULL) {
00300         return op;
00301     }
00302 
00303     if ((op = locate_item_from_item(player, tag)) != NULL) {
00304         return op;
00305     }
00306 
00307     if (cpl.container && cpl.container->tag == tag) {
00308         return cpl.container;
00309     }
00310 
00311     if (cpl.container && (op = locate_item_from_item(cpl.container->inv, tag)) != NULL)
00312         return op;
00313 
00314     return NULL;
00315 }
00316 
00317 /*
00318  *  remove_item() inserts op the the list of free items
00319  *  Note that it don't clear all fields in item
00320  */
00321 void remove_item(item *op) {
00322     /* IF no op, or it is the player */
00323     if (!op || op == player || op == map) {
00324         return;
00325     }
00326 
00327     item_event_item_deleting(op);
00328 
00329     op->env->inv_updated = 1;
00330 
00331     /* Do we really want to do this? */
00332     if (op->inv && op != cpl.container) {
00333         remove_item_inventory(op);
00334     }
00335 
00336     if (op->prev) {
00337         op->prev->next = op->next;
00338     } else {
00339         op->env->inv = op->next;
00340     }
00341     if (op->next) {
00342         op->next->prev = op->prev;
00343     }
00344 
00345     if (cpl.container == op) {
00346         return; /* Don't free this! */
00347     }
00348 
00349     /* add object to a list of free objects */
00350     op->next = free_items;
00351     if (op->next != NULL) {
00352         op->next->prev = op;
00353     }
00354     free_items = op;
00355 
00356     /* Clear all these values, since this item will get re-used */
00357     op->prev = NULL;
00358     op->env = NULL;
00359     op->tag = 0;
00360     copy_name(op->d_name, "");
00361     copy_name(op->s_name, "");
00362     copy_name(op->p_name, "");
00363     op->inv = NULL;
00364     op->env = NULL;
00365     op->tag = 0;
00366     op->face = 0;
00367     op->weight = 0;
00368     op->magical = op->cursed = op->damned = 0;
00369     op->unpaid = op->locked = op->applied = 0;
00370     op->flagsval = 0;
00371     op->animation_id = 0;
00372     op->last_anim = 0;
00373     op->anim_state = 0;
00374     op->nrof = 0;
00375     op->open = 0;
00376     op->type = NO_ITEM_TYPE;
00377 }
00378 
00379 /*
00380  *  remove_item_inventory() recursive frees items inventory
00381  */
00382 void remove_item_inventory(item *op) {
00383     if (!op) {
00384         return;
00385     }
00386 
00387     item_event_container_clearing(op);
00388 
00389     op->inv_updated = 1;
00390     while (op->inv) {
00391         remove_item(op->inv);
00392     }
00393 }
00394 
00395 /*
00396  *  add_item() adds item op to end of the inventory of item env
00397  */
00398 static void add_item(item *env, item *op) {
00399     item *tmp;
00400 
00401     for (tmp = env->inv; tmp && tmp->next; tmp = tmp->next)
00402         ;
00403 
00404     op->next = NULL;
00405     op->prev = tmp;
00406     op->env = env;
00407     if (!tmp) {
00408         env->inv = op;
00409     } else {
00410         if (tmp->next) {
00411             tmp->next->prev = op;
00412         }
00413         tmp->next = op;
00414     }
00415 }
00416 
00417 /*
00418  *  create_new_item() returns pointer to a new item, inserts it to env
00419  *  and sets its tag field and clears locked flag (all other fields
00420  *  are unitialized and may contain random values)
00421  */
00422 item *create_new_item(item *env, sint32 tag) {
00423     item *op;
00424 
00425     if (!free_items) {
00426         free_items = alloc_items(NROF_ITEMS);
00427     }
00428 
00429     op = free_items;
00430     free_items = free_items->next;
00431     if (free_items) {
00432         free_items->prev = NULL;
00433     }
00434 
00435     op->tag = tag;
00436     op->locked = 0;
00437     if (env) {
00438         add_item(env, op);
00439     }
00440 
00441     return op;
00442 }
00443 
00444 int num_free_items(void) {
00445     item *tmp;
00446     int count = 0;
00447 
00448     for (tmp = free_items; tmp; tmp = tmp->next) {
00449         count++;
00450     }
00451 
00452     return count;
00453 }
00454 
00455 /*
00456  *  Hardcoded now, server could send these at initiation phase.
00457  */
00458 static const char *const apply_string[] = {
00459     "", " (readied)", " (wielded)", " (worn)", " (active)", " (applied)",
00460 };
00461 
00462 static void set_flag_string(item *op) {
00463     op->flags[0] = 0;
00464 
00465     if (op->locked) {
00466         strcat(op->flags, " *");
00467     }
00468     if (op->apply_type) {
00469         if (op->apply_type < sizeof(apply_string)/sizeof(apply_string[0])) {
00470             strcat(op->flags, apply_string[op->apply_type]);
00471         } else {
00472             strcat(op->flags, " (undefined)");
00473         }
00474     }
00475     if (op->open) {
00476         strcat(op->flags, " (open)");
00477     }
00478     if (op->damned) {
00479         strcat(op->flags, " (damned)");
00480     }
00481     if (op->cursed) {
00482         strcat(op->flags, " (cursed)");
00483     }
00484     if (op->magical) {
00485         strcat(op->flags, " (magic)");
00486     }
00487     if (op->unpaid) {
00488         strcat(op->flags, " (unpaid)");
00489     }
00490 }
00491 
00492 static void get_flags(item *op, uint16 flags) {
00493     op->was_open = op->open;
00494     op->open     = flags&F_OPEN    ? 1 : 0;
00495     op->damned   = flags&F_DAMNED  ? 1 : 0;
00496     op->cursed   = flags&F_CURSED  ? 1 : 0;
00497     op->magical  = flags&F_MAGIC   ? 1 : 0;
00498     op->unpaid   = flags&F_UNPAID  ? 1 : 0;
00499     op->applied  = flags&F_APPLIED ? 1 : 0;
00500     op->locked   = flags&F_LOCKED  ? 1 : 0;
00501     op->flagsval = flags;
00502     op->apply_type = flags&F_APPLIED;
00503     set_flag_string(op);
00504 }
00505 
00506 void set_item_values(item *op, char *name, sint32 weight, uint16 face,
00507                      uint16 flags, uint16 anim, uint16 animspeed,
00508                      uint32 nrof, uint16 type) {
00509     int resort = 1;
00510 
00511     if (!op) {
00512         printf("Error in set_item_values(): item pointer is NULL.\n");
00513         return;
00514     }
00515 
00516     /* Program always expect at least 1 object internall */
00517     if (nrof == 0) {
00518         nrof = 1;
00519     }
00520 
00521     if (*name != '\0') {
00522         copy_name(op->s_name, name);
00523 
00524         /* Unfortunately, we don't get a length parameter, so we just have
00525          * to assume that if it is a new server, it is giving us two piece
00526          * names.
00527          */
00528         if (csocket.sc_version >= 1024) {
00529             copy_name(op->p_name, name+strlen(name)+1);
00530         } else { /* If not new version, just use same for both */
00531             copy_name(op->p_name, name);
00532         }
00533 
00534         /* Necessary so that d_name is updated below */
00535         op->nrof = nrof+1;
00536     } else {
00537         resort = 0;             /* no name - don't resort */
00538     }
00539 
00540     if (op->nrof != nrof) {
00541         if (nrof != 1 ) {
00542             sprintf(op->d_name, "%s %s", get_number(nrof), op->p_name);
00543         } else {
00544             strcpy(op->d_name, op->s_name);
00545         }
00546         op->nrof = nrof;
00547     }
00548 
00549     if (op->env) {
00550         op->env->inv_updated = 1;
00551     }
00552     op->weight = (float)weight/1000;
00553     op->face = face;
00554     op->animation_id = anim;
00555     op->anim_speed = animspeed;
00556     op->type = type;
00557     get_flags(op, flags);
00558 
00559     /* We don't sort the map, so lets not bother figuring out the
00560      * type.  Likewiwse, only figure out item type if this
00561      * doesn't have a type (item2 provides us with a type
00562      */
00563     if (op->env != map && op->type == NO_ITEM_TYPE) {
00564         op->type = get_type_from_name(op->s_name);
00565     }
00566     if (resort) {
00567         update_item_sort(op);
00568     }
00569 
00570     item_event_item_changed(op);
00571 }
00572 
00573 void toggle_locked(item *op) {
00574     SockList sl;
00575     uint8 buf[MAX_BUF];
00576 
00577     if (op->env->tag == 0) {
00578         return; /* if item is on the ground, don't lock it */
00579     }
00580 
00581     snprintf((char*)buf, sizeof(buf), "lock %c %d", !op->locked, op->tag);
00582     script_monitor_str((char*)buf);
00583     SockList_Init(&sl, buf);
00584     SockList_AddString(&sl, "lock ");
00585     SockList_AddChar(&sl, !op->locked);
00586     SockList_AddInt(&sl, op->tag);
00587     SockList_Send(&sl, csocket.fd);
00588 }
00589 
00590 void send_mark_obj(item *op) {
00591     SockList sl;
00592     uint8 buf[MAX_BUF];
00593 
00594     if (op->env->tag == 0) {
00595         return; /* if item is on the ground, don't mark it */
00596     }
00597 
00598     snprintf((char*)buf, sizeof(buf), "mark %d", op->tag);
00599     script_monitor_str((char*)buf);
00600     SockList_Init(&sl, buf);
00601     SockList_AddString(&sl, "mark ");
00602     SockList_AddInt(&sl, op->tag);
00603     SockList_Send(&sl, csocket.fd);
00604 }
00605 
00606 item *player_item (void) {
00607     player = new_item();
00608     return player;
00609 }
00610 
00611 item *map_item (void) {
00612     map = new_item();
00613     map->weight = -1;
00614     return map;
00615 }
00616 
00617 /* Upates an item with new attributes. */
00618 void update_item(int tag, int loc, char *name, int weight, int face, int flags,
00619                  int anim, int animspeed, uint32 nrof, int type) {
00620     item *ip = locate_item(tag), *env = locate_item(loc);
00621 
00622     /* Need to do some special handling if this is the player that is
00623      * being updated.
00624      */
00625     if (player->tag == tag) {
00626         copy_name(player->d_name, name);
00627         /* I don't think this makes sense, as you can have
00628          * two players merged together, so nrof should always be one
00629          */
00630         player->nrof = nrof;
00631         player->weight = (float)weight/1000;
00632         player->face = face;
00633         get_flags(player, flags);
00634         if (player->inv) {
00635             player->inv->inv_updated = 1;
00636         }
00637         player->animation_id = anim;
00638         player->anim_speed = animspeed;
00639         player->nrof = nrof;
00640     } else {
00641         if (ip && ip->env != env) {
00642             remove_item(ip);
00643             ip = NULL;
00644         }
00645         set_item_values(ip ? ip : create_new_item(env, tag), name, weight, face, flags,
00646                         anim, animspeed, nrof, type);
00647     }
00648 }
00649 
00650 /*
00651  *  Prints players inventory, contain extra information for debugging purposes
00652  * This isn't pretty, but is only used for debugging, so it doesn't need to be.
00653  */
00654 void print_inventory(item *op) {
00655     char buf[MAX_BUF];
00656     char buf2[MAX_BUF];
00657     item *tmp;
00658     static int l = 0;
00659 #if 0
00660     int info_width = get_info_width();
00661 #else
00662     /* A callback for a debugging command seems pretty pointless.  If anything,
00663      * it may be more useful to dump this out to stderr
00664      */
00665     int info_width = 40;
00666 #endif
00667 
00668     if (l == 0) {
00669         snprintf(buf, sizeof(buf), "%s's inventory (%d):", op->d_name, op->tag);
00670         snprintf(buf2, sizeof(buf2), "%-*s%6.1f kg", info_width-10, buf, op->weight);
00671         draw_info(buf2,NDI_BLACK);
00672     }
00673 
00674     l += 2;
00675     for (tmp = op->inv; tmp; tmp = tmp->next) {
00676         snprintf(buf, sizeof(buf), "%*s- %d %s%s (%d)", l-2, "", tmp->nrof, tmp->d_name, tmp->flags, tmp->tag);
00677         snprintf(buf2, sizeof(buf2), "%-*s%6.1f kg", info_width-8-l, buf, tmp->nrof*tmp->weight);
00678         draw_info(buf2,NDI_BLACK);
00679         if (tmp->inv) {
00680             print_inventory(tmp);
00681         }
00682     }
00683     l -= 2;
00684 }
00685 
00686 /* Check the objects, animate the ones as necessary */
00687 void animate_objects(void) {
00688     item *ip;
00689     int got_one = 0;
00690 
00691     /* Animate players inventory */
00692     for (ip = player->inv; ip; ip = ip->next) {
00693         if (ip->animation_id > 0 && ip->anim_speed) {
00694             ip->last_anim++;
00695             if (ip->last_anim >= ip->anim_speed) {
00696                 ip->anim_state++;
00697                 if (ip->anim_state >= animations[ip->animation_id].num_animations) {
00698                     ip->anim_state = 0;
00699                 }
00700                 ip->face = animations[ip->animation_id].faces[ip->anim_state];
00701                 ip->last_anim = 0;
00702                 got_one = 1;
00703             }
00704         }
00705     }
00706 #ifndef GTK_CLIENT
00707     if (got_one) {
00708         player->inv_updated = 1;
00709     }
00710 #endif
00711     if (cpl.container) {
00712         /* Now do a container if one is active */
00713         for (ip = cpl.container->inv; ip; ip = ip->next) {
00714             if (ip->animation_id > 0 && ip->anim_speed) {
00715                 ip->last_anim++;
00716                 if (ip->last_anim >= ip->anim_speed) {
00717                     ip->anim_state++;
00718                     if (ip->anim_state >= animations[ip->animation_id].num_animations) {
00719                         ip->anim_state = 0;
00720                     }
00721                     ip->face = animations[ip->animation_id].faces[ip->anim_state];
00722                     ip->last_anim = 0;
00723                     got_one = 1;
00724                 }
00725             }
00726         }
00727         if (got_one) {
00728             cpl.container->inv_updated = 1;
00729         }
00730     } else {
00731         /* Now do the map (look window) */
00732         for (ip = cpl.below->inv; ip; ip = ip->next) {
00733             if (ip->animation_id > 0 && ip->anim_speed) {
00734                 ip->last_anim++;
00735                 if (ip->last_anim >= ip->anim_speed) {
00736                     ip->anim_state++;
00737                     if (ip->anim_state >= animations[ip->animation_id].num_animations) {
00738                         ip->anim_state = 0;
00739                     }
00740                     ip->face = animations[ip->animation_id].faces[ip->anim_state];
00741                     ip->last_anim = 0;
00742                     got_one = 1;
00743                 }
00744             }
00745         }
00746         if (got_one) {
00747             cpl.below->inv_updated = 1;
00748         }
00749     }
00750 }
00751 
00752 int can_write_spell_on(item* it) {
00753     return (it->type == 661);
00754 }
00755 
00756 void inscribe_magical_scroll(item *scroll, Spell *spell) {
00757     SockList sl;
00758     uint8 buf[MAX_BUF];
00759 
00760     if (command_inscribe == 0) {
00761         LOG(LOG_WARNING, "common::inscribe_magical_scroll", "Called when server doesn't handle inscribe command.");
00762         return;
00763     }
00764     snprintf((char*)buf, sizeof(buf), "inscribe 0 %d %d", scroll->tag, spell->tag);
00765     script_monitor_str((char*)buf);
00766     SockList_Init(&sl, buf);
00767     SockList_AddString(&sl, "inscribe ");
00768     SockList_AddChar(&sl, 0);
00769     SockList_AddInt(&sl, scroll->tag);
00770     SockList_AddInt(&sl, spell->tag);
00771     SockList_Send(&sl, csocket.fd);
00772 }