Crossfire Client, Branch
R11627
|
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 }