/* * static char *rcsid_item_c = * "$Id: item.c 11578 2009-02-23 22:02:27Z lalo $"; */ /* CrossFire, A Multiplayer game for X-windows Copyright (C) 2002,2006 Mark Wedel & Crossfire Development Team Copyright (C) 1992 Frank Tore Johansen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. The author can be reached via e-mail to crossfire-devel@real-time.com */ /** * \file * Client/server logic. * * \date 2003-12-02 * * This containes item logic for client/server. It doesn't contain * the actual commands that send the data, but does contain * the logic for what items should be sent. */ #include #include /* LOOK_OBJ */ #include #include #include /** This is the maximum number of bytes we expect any one item to take up */ #define MAXITEMLEN 300 /************************************************************************* * * Functions related to sending object data to the client. * ************************************************************************* */ /** * This is a similar to query_name, but returns flags * to be sended to client. */ static unsigned int query_flags(const object *op) { unsigned int flags = 0; if (QUERY_FLAG(op, FLAG_APPLIED)) { switch (op->type) { case BOW: case WAND: case ROD: case HORN: flags = a_readied; break; case WEAPON: flags = a_wielded; break; case SKILL: case ARMOUR: case HELMET: case SHIELD: case RING: case BOOTS: case GLOVES: case AMULET: case GIRDLE: case BRACERS: case CLOAK: flags = a_worn; break; case CONTAINER: flags = a_active; break; default: flags = a_applied; break; } } if (op->type == CONTAINER && ((op->env && op->env->container == op) || (!op->env && QUERY_FLAG(op, FLAG_APPLIED)))) flags |= F_OPEN; if (QUERY_FLAG(op, FLAG_KNOWN_CURSED)) { if (QUERY_FLAG(op, FLAG_DAMNED)) flags |= F_DAMNED; else if (QUERY_FLAG(op, FLAG_CURSED)) flags |= F_CURSED; } if (QUERY_FLAG(op, FLAG_KNOWN_MAGICAL) && !QUERY_FLAG(op, FLAG_IDENTIFIED)) flags |= F_MAGIC; if (QUERY_FLAG(op, FLAG_UNPAID)) flags |= F_UNPAID; if (QUERY_FLAG(op, FLAG_INV_LOCKED)) flags |= F_LOCKED; if (QUERY_FLAG(op, FLAG_KNOWN_BLESSED) && QUERY_FLAG(op, FLAG_BLESSED)) flags |= F_BLESSED; return flags; } /** * Used in the send_look to put object head into SockList * sl for socket ns. Need socket to know if we need to send * animation of face to the client. */ static void add_object_to_socklist(socket_struct *ns, SockList *sl, object *head) { int flags, len, anim_speed; char item_n[MAX_BUF], item_p[MAX_BUF]; flags = query_flags(head); if (QUERY_FLAG(head, FLAG_NO_PICK)) flags |= F_NOPICK; if (!(ns->faces_sent[head->face->number]&NS_FACESENT_FACE)) esrv_send_face(ns, head->face->number, 0); if (QUERY_FLAG(head, FLAG_ANIMATE) && !ns->anims_sent[head->animation_id]) esrv_send_animation(ns, head->animation_id); SockList_AddInt(sl, head->count); SockList_AddInt(sl, flags); SockList_AddInt(sl, QUERY_FLAG(head, FLAG_NO_PICK) ? -1 : WEIGHT(head)); SockList_AddInt(sl, head->face->number); if (!head->custom_name) { query_base_name(head, 0, item_n, 126); item_n[127] = 0; len = strlen(item_n); query_base_name(head, 1, item_p, MAX_BUF); } else { strncpy(item_n, head->custom_name, 127); item_n[127] = 0; len = strlen(item_n); strncpy(item_p, head->custom_name, MAX_BUF); } strncpy(item_n+len+1, item_p, 127); /* This is needed because strncpy may not add a ending \0 if the string is long enough. */ item_n[len+1+127] = 0; len += strlen(item_n+1+len)+1; SockList_AddLen8Data(sl, item_n, len); SockList_AddShort(sl, head->animation_id); anim_speed = 0; if (QUERY_FLAG(head, FLAG_ANIMATE)) { if (head->anim_speed) anim_speed = head->anim_speed; else { if (FABS(head->speed) < 0.001) anim_speed = 255; else if (FABS(head->speed) >= 1.0) anim_speed = 1; else anim_speed = (int)(1.0/FABS(head->speed)); } if (anim_speed > 255) anim_speed = 255; } SockList_AddChar(sl, (char)anim_speed); SockList_AddInt(sl, head->nrof); SockList_AddShort(sl, head->client_type); SET_FLAG(head, FLAG_CLIENT_SENT); } /** * Send the look window. Don't need to do animations here * This sends all the faces to the client, not just updates. This is * because object ordering would otherwise be inconsistent */ void esrv_draw_look(object *pl) { object *tmp, *last; int got_one = 0, start_look = 0, end_look = 0, objects_sent = 0; SockList sl; char buf[MAX_BUF]; if (!pl->contr->socket.update_look) { LOG(llevDebug, "esrv_draw_look called when update_look was not set\n"); return; } else { pl->contr->socket.update_look = 0; } if (QUERY_FLAG(pl, FLAG_REMOVED) || pl->map == NULL || pl->map->in_memory != MAP_IN_MEMORY || out_of_map(pl->map, pl->x, pl->y)) return; if (pl->contr->transport) for (tmp = pl->contr->transport->inv; tmp && tmp->above; tmp = tmp->above) ; else for (tmp = GET_MAP_OB(pl->map, pl->x, pl->y); tmp && tmp->above; tmp = tmp->above) ; SockList_Init(&sl); SockList_AddString(&sl, "delinv 0"); Send_With_Handling(&pl->contr->socket, &sl); SockList_Reset(&sl); SockList_AddPrintf(&sl, "item2 "); SockList_AddInt(&sl, 0); if (!(pl->contr->socket.faces_sent[empty_face->number]&NS_FACESENT_FACE)) esrv_send_face(&pl->contr->socket, empty_face->number, 0); if (pl->contr->socket.look_position) { int overhead = 1+(pl->contr->transport != NULL); int prev_len = pl->contr->socket.num_look_objects-overhead-(pl->contr->socket.look_position > pl->contr->socket.num_look_objects-overhead); SockList_AddInt(&sl, 0x80000000|MAX(0, pl->contr->socket.look_position-prev_len)); SockList_AddInt(&sl, 0); SockList_AddInt(&sl, -1); SockList_AddInt(&sl, empty_face->number); snprintf(buf, sizeof(buf), "Click here to see previous group of items"); SockList_AddLen8Data(&sl, buf, MIN(strlen(buf), 255)); SockList_AddShort(&sl, 0); SockList_AddChar(&sl, 0); SockList_AddInt(&sl, 0); SockList_AddShort(&sl, 0); objects_sent++; got_one++; } if (pl->contr->transport) { add_object_to_socklist(&pl->contr->socket, &sl, pl->contr->transport); objects_sent++; got_one++; } for (last = NULL; tmp != last; tmp = tmp->below) { object *head; if (QUERY_FLAG(tmp, FLAG_IS_FLOOR) && !last) { last = tmp->below; /* assumes double floor mode */ if (last && QUERY_FLAG(last, FLAG_IS_FLOOR)) last = last->below; } if (LOOK_OBJ(tmp)) { if (start_look++ < pl->contr->socket.look_position) continue; end_look++; objects_sent++; if (objects_sent >= pl->contr->socket.num_look_objects) { /* What we basically do is make a 'fake' object - * when the user applies it, we notice the special * tag the object has, and act accordingly. */ SockList_AddInt(&sl, 0x80000000|(pl->contr->socket.look_position+end_look-1)); SockList_AddInt(&sl, 0); SockList_AddInt(&sl, -1); SockList_AddInt(&sl, empty_face->number); snprintf(buf, sizeof(buf), "Click here to see next group of items"); SockList_AddLen8Data(&sl, buf, MIN(strlen(buf), 255)); SockList_AddShort(&sl, 0); SockList_AddChar(&sl, 0); SockList_AddInt(&sl, 0); SockList_AddShort(&sl, 0); break; } if (tmp->head) head = tmp->head; else head = tmp; add_object_to_socklist(&pl->contr->socket, &sl, head); got_one++; if (SockList_Avail(&sl) < MAXITEMLEN) { Send_With_Handling(&pl->contr->socket, &sl); SockList_Reset(&sl); SockList_AddPrintf(&sl, "item2 "); SockList_AddInt(&sl, 0); got_one = 0; } } /* If LOOK_OBJ() */ } if (got_one) Send_With_Handling(&pl->contr->socket, &sl); SockList_Term(&sl); } /** * Sends whole inventory. */ void esrv_send_inventory(object *pl, object *op) { object *tmp; int got_one = 0; SockList sl; SockList_Init(&sl); SockList_AddPrintf(&sl, "delinv %u", op->count); Send_With_Handling(&pl->contr->socket, &sl); SockList_Reset(&sl); SockList_AddString(&sl, "item2 "); SockList_AddInt(&sl, op->count); for (tmp = op->inv; tmp; tmp = tmp->below) { object *head; if (tmp->head) head = tmp->head; else head = tmp; if (LOOK_OBJ(head)) { add_object_to_socklist(&pl->contr->socket, &sl, head); got_one++; /* It is possible for players to accumulate a huge amount of * items (especially with some of the bags out there) to * overflow the buffer. IF so, send multiple item commands. */ if (SockList_Avail(&sl) < MAXITEMLEN) { Send_With_Handling(&pl->contr->socket, &sl); SockList_Reset(&sl); SockList_AddString(&sl, "item2 "); SockList_AddInt(&sl, op->count); got_one = 0; } } /* If LOOK_OBJ() */ } if (got_one) Send_With_Handling(&pl->contr->socket, &sl); SockList_Term(&sl); } /** * Updates object *op for player *pl. * * flags is a list of values to update * to the client (as defined in newclient.h - might as well use the * same value both places. */ void esrv_update_item(int flags, object *pl, object *op) { SockList sl; if (!pl->contr) return; /* If we have a request to send the player item, skip a few checks. */ if (op != pl) { if (!LOOK_OBJ(op)) return; /* we remove the check for op->env, because in theory, the object * is hopefully in the same place, so the client should preserve * order. */ } if (!QUERY_FLAG(op, FLAG_CLIENT_SENT)) { /* FLAG_CLIENT_SENT is debug only. We are using it to see where * this is happening - we can set a breakpoint here in the debugger * and track back the call. */ LOG(llevDebug, "We have not sent item %s (%d)\n", op->name, op->count); } SockList_Init(&sl); SockList_AddString(&sl, "upditem "); SockList_AddChar(&sl, (char)flags); if (op->head) op = op->head; SockList_AddInt(&sl, op->count); if (flags&UPD_LOCATION) SockList_AddInt(&sl, op->env ? op->env->count : 0); if (flags&UPD_FLAGS) SockList_AddInt(&sl, query_flags(op)); if (flags&UPD_WEIGHT) { sint32 weight = WEIGHT(op); /* TRANSPORTS are odd - they sort of look like containers, * yet can't be picked up. So we don't to send the weight, * as it is odd that you see weight sometimes and not other * (the draw_look won't send it for example. */ SockList_AddInt(&sl, QUERY_FLAG(op, FLAG_NO_PICK) ? -1 : weight); if (pl == op) { op->contr->last_weight = weight; } } if (flags&UPD_FACE) { if (!(pl->contr->socket.faces_sent[op->face->number]&NS_FACESENT_FACE)) esrv_send_face(&pl->contr->socket, op->face->number, 0); SockList_AddInt(&sl, op->face->number); } if (flags&UPD_NAME) { int len; char item_p[MAX_BUF]; char item_n[MAX_BUF]; if (!op->custom_name) { query_base_name(op, 0, item_n, MAX_BUF); len = strlen(item_n); query_base_name(op, 1, item_p, MAX_BUF); } else { strncpy(item_n, op->custom_name, MAX_BUF-1); item_n[MAX_BUF-1] = 0; len = strlen(item_n); strncpy(item_p, op->custom_name, MAX_BUF-1); item_p[MAX_BUF-1] = 0; } strncpy(item_n+len+1, item_p, 127); item_n[254] = 0; len += strlen(item_n+1+len)+1; SockList_AddLen8Data(&sl, item_n, len); } if (flags&UPD_ANIM) SockList_AddShort(&sl, op->animation_id); if (flags&UPD_ANIMSPEED) { int anim_speed = 0; if (QUERY_FLAG(op, FLAG_ANIMATE)) { if (op->anim_speed) anim_speed = op->anim_speed; else { if (FABS(op->speed) < 0.001) anim_speed = 255; else if (FABS(op->speed) >= 1.0) anim_speed = 1; else anim_speed = (int)(1.0/FABS(op->speed)); } if (anim_speed > 255) anim_speed = 255; } SockList_AddChar(&sl, (char)anim_speed); } if (flags&UPD_NROF) SockList_AddInt(&sl, op->nrof); Send_With_Handling(&pl->contr->socket, &sl); SockList_Term(&sl); } /** * Sends item's info to player. */ void esrv_send_item(object *pl, object*op) { SockList sl; /* If this is not the player object, do some more checks */ if (op != pl) { /* We only send 'visibile' objects to the client */ if (!LOOK_OBJ(op)) return; /* if the item is on the ground, mark that the look needs to * be updated. */ if (!op->env) { pl->contr->socket.update_look = 1; return; } } SockList_Init(&sl); SockList_AddString(&sl, "item2 "); if (op->head) op = op->head; SockList_AddInt(&sl, op->env ? op->env->count : 0); add_object_to_socklist(&pl->contr->socket, &sl, op); Send_With_Handling(&pl->contr->socket, &sl); SET_FLAG(op, FLAG_CLIENT_SENT); SockList_Term(&sl); } /** * Tells the client to delete an item. Uses the item * command with a -1 location. */ void esrv_del_item(player *pl, int tag) { SockList sl; SockList_Init(&sl); SockList_AddString(&sl, "delitem "); SockList_AddInt(&sl, tag); Send_With_Handling(&pl->socket, &sl); SockList_Term(&sl); } /************************************************************************** * * Client has requested us to do something with an object. * ************************************************************************** */ /** * Takes a player and object count (tag) and returns the actual object * pointer, or null if it can't be found. */ static object *esrv_get_ob_from_count(object *pl, tag_t count) { object *op, *tmp; if (pl->count == count) return pl; for (op = pl->inv; op; op = op->below) if (op->count == count) return op; else if (op->type == CONTAINER && pl->container == op) for (tmp = op->inv; tmp; tmp = tmp->below) if (tmp->count == count) return tmp; for (op = GET_MAP_OB(pl->map, pl->x, pl->y); op; op = op->above) if (op->head != NULL && op->head->count == count) return op; else if (op->count == count) return op; else if (op->type == CONTAINER && pl->container == op) for (tmp = op->inv; tmp; tmp = tmp->below) if (tmp->count == count) return tmp; if (pl->contr->transport) { for (tmp = pl->contr->transport->inv; tmp; tmp = tmp->below) if (tmp->count == count) return tmp; } return NULL; } /** Client wants to examine some object. So lets do so. */ void examine_cmd(char *buf, int len, player *pl) { long tag; object *op; if (len <= 0 || !buf) { LOG(llevDebug, "Player '%s' sent bogus examine_cmd information\n", pl->ob->name); return; } tag = atoi(buf); op = esrv_get_ob_from_count(pl->ob, tag); if (!op) { LOG(llevDebug, "Player '%s' tried to examine the unknown object (%ld)\n", pl->ob->name, tag); return; } examine(pl->ob, op); } /** Client wants to apply some object. Lets do so. */ void apply_cmd(char *buf, int len, player *pl) { uint32 tag; object *op; if (!buf || len <= 0) { LOG(llevDebug, "Player '%s' sent bogus apply_cmd information\n", pl->ob->name); return; } tag = atoi(buf); op = esrv_get_ob_from_count(pl->ob, tag); /* sort of a hack, but if the player saves and the player then * manually applies a savebed (or otherwise tries to do stuff), * we run into trouble. */ if (QUERY_FLAG(pl->ob, FLAG_REMOVED)) return; /* If the high bit is set, player applied a pseudo object. */ if (tag&0x80000000) { pl->socket.look_position = tag&0x7fffffff; pl->socket.update_look = 1; return; } if (!op) { LOG(llevDebug, "Player '%s' tried to apply the unknown object (%d)\n", pl->ob->name, tag); return; } player_apply(pl->ob, op, 0, 0); } /** Client wants to apply some object. Lets do so. */ void lock_item_cmd(uint8 *data, int len, player *pl) { int flag, tag; object *op; object *tmp; if (len != 5) { LOG(llevDebug, "Player '%s' sent bogus lock_item_cmd information\n", pl->ob->name); return; } flag = data[0]; tag = GetInt_String(data+1); op = esrv_get_ob_from_count(pl->ob, tag); if (!op) { draw_ext_info(NDI_UNIQUE, 0, pl->ob, MSG_TYPE_COMMAND, MSG_TYPE_COMMAND_ERROR, "Could not find object to lock/unlock", NULL); return; } if (!flag) CLEAR_FLAG(op, FLAG_INV_LOCKED); else SET_FLAG(op, FLAG_INV_LOCKED); tmp = merge_ob(op, NULL); if (tmp == NULL) { /* object was not merged - if it was, merge_ob sent updates for us. */ esrv_update_item(UPD_FLAGS, pl->ob, op); } } /** * Client wants to mark some object. Lets do so. * * @param data * object tag (4 chars). * @param len * data size. * @param pl * player. */ void mark_item_cmd(uint8 *data, int len, player *pl) { int tag; object *op; char name[MAX_BUF]; if (len != 4) { LOG(llevDebug, "Player '%s' sent bogus mark_item_cmd information\n", pl->ob->name); return; } tag = GetInt_String(data); op = esrv_get_ob_from_count(pl->ob, tag); if (!op) { draw_ext_info(NDI_UNIQUE, 0, pl->ob, MSG_TYPE_COMMAND, MSG_TYPE_COMMAND_ERROR, "Could not find object to mark", NULL); return; } pl->mark = op; pl->mark_count = op->count; query_name(op, name, MAX_BUF); draw_ext_info_format(NDI_UNIQUE, 0, pl->ob, MSG_TYPE_COMMAND, MSG_TYPE_COMMAND_SUCCESS, "Marked item %s", "Marked item %s", name); } /** * Prints items on the specified square. * * [ removed EARTHWALL check and added check for containers inventory. * Tero.Haatanen@lut.fi ] */ void look_at(object *op, int dx, int dy) { object *tmp; int flag = 0; sint16 x, y; mapstruct *m; char name[MAX_BUF]; if (out_of_map(op->map, op->x+dx, op->y+dy)) return; x = op->x+dx; y = op->y+dy; m = get_map_from_coord(op->map, &x, &y); if (!m) return; for (tmp = GET_MAP_OB(m, x, y); tmp != NULL && tmp->above != NULL; tmp = tmp->above) ; for (; tmp != NULL; tmp = tmp->below) { if (tmp->invisible && !QUERY_FLAG(op, FLAG_WIZ)) continue; if (!flag) { if (dx || dy) draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_COMMAND, MSG_TYPE_COMMAND_SUCCESS, "There you see:", NULL); else { draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_COMMAND, MSG_TYPE_COMMAND_SUCCESS, "You see:", NULL); } flag = 1; } query_name(tmp, name, MAX_BUF); if (QUERY_FLAG(op, FLAG_WIZ)) draw_ext_info_format(NDI_UNIQUE, 0, op, MSG_TYPE_COMMAND, MSG_TYPE_COMMAND_EXAMINE, "- %s (%d).", "- %s (%d).", name, tmp->count); else draw_ext_info_format(NDI_UNIQUE, 0, op, MSG_TYPE_COMMAND, MSG_TYPE_COMMAND_EXAMINE, "- %s.", "- %s.", name); if (((tmp->inv != NULL || (tmp->head && tmp->head->inv)) && (tmp->type != CONTAINER && tmp->type != FLESH)) || QUERY_FLAG(op, FLAG_WIZ)) inventory(op, tmp->head == NULL ? tmp : tmp->head); /* don't continue under the floor */ if (QUERY_FLAG(tmp, FLAG_IS_FLOOR) && !QUERY_FLAG(op, FLAG_WIZ)) break; } if (!flag) { if (dx || dy) draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_COMMAND, MSG_TYPE_COMMAND_SUCCESS, "You see nothing there.", NULL); else draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_COMMAND, MSG_TYPE_COMMAND_SUCCESS, "You see nothing.", NULL); } } /** Client wants to look at some object. Lets do so. */ void look_at_cmd(char *buf, int len, player *pl) { int dx, dy; char *cp; dx = atoi(buf); if (!(cp = strchr(buf, ' '))) { return; } dy = atoi(cp); if (FABS(dx) > MAP_CLIENT_X/2 || FABS(dy) > MAP_CLIENT_Y/2) return; if (pl->blocked_los[dx+(pl->socket.mapx/2)][dy+(pl->socket.mapy/2)]) return; look_at(pl->ob, dx, dy); } /** Move an object to a new location */ void esrv_move_object(object *pl, tag_t to, tag_t tag, long nrof) { object *op, *env; op = esrv_get_ob_from_count(pl, tag); if (!op) { LOG(llevDebug, "Player '%s' tried to move an unknown object (%lu)\n", pl->name, (unsigned long)tag); return; } /* If on a transport, you don't drop to the ground - you drop to the * transport. */ if (!to && !pl->contr->transport) { /* drop it to the ground */ /* LOG(llevDebug, "Drop it on the ground.\n");*/ if (op->map && !op->env) { /* LOG(llevDebug, "Dropping object to ground that is already on ground\n");*/ return; } /* If it is an active container, then we should drop all objects * in the container and not the container itself. */ if (op->inv && QUERY_FLAG(op, FLAG_APPLIED)) { object *current, *next; for (current = op->inv; current != NULL; current = next) { next = current->below; drop_object(pl, current, 0); } esrv_update_item(UPD_WEIGHT, pl, op); } else { drop_object(pl, op, nrof); } return; } else if (to == pl->count) { /* pick it up to the inventory */ /* return if player has already picked it up */ if (op->env == pl) return; pl->contr->count = nrof; pick_up(pl, op); return; } /* If not dropped or picked up, we are putting it into a sack */ if (pl->contr->transport) { if (can_pick(pl, op) && transport_can_hold(pl->contr->transport, op, nrof)) { put_object_in_sack(pl, pl->contr->transport, op, nrof); } } else { env = esrv_get_ob_from_count(pl, to); if (!env) { LOG(llevDebug, "Player '%s' tried to move object to the unknown location (%d)\n", pl->name, to); return; } /* put_object_in_sack presumes that necessary sanity checking * has already been done (eg, it can be picked up and fits in * in a sack, so check for those things. We should also check * an make sure env is in fact a container for that matter. */ if (env->type == CONTAINER && can_pick(pl, op) && sack_can_hold(pl, env, op, nrof)) { put_object_in_sack(pl, env, op, nrof); } } } void inscribe_scroll_cmd(char *buf, int len, player *pl) { object *scroll, *spell, *marked, *inscription, *currentspell; tag_t tscroll, tspell, tmarked; char type; if (len < 1) { LOG(llevDebug, "Player %s sent an invalid inscribe command.\n", pl->ob->name); return; } type = buf[0]; inscription = find_skill_by_name(pl->ob, "inscription"); if (!inscription) { draw_ext_info(NDI_UNIQUE, 0, pl->ob, MSG_TYPE_SKILL, MSG_TYPE_SKILL_FAILURE, "You don't know how to write!", NULL); return; } if (type == 0) { if (len != 9) { LOG(llevDebug, "Player %s sent an invalid inscribe command.\n", pl->ob->name); return; } tscroll = GetInt_String((uint8 *)buf+1); tspell = GetInt_String((uint8 *)buf+5); scroll = esrv_get_ob_from_count(pl->ob, tscroll); if (!scroll) { LOG(llevDebug, "Player %s sent an invalid scroll for inscribe command.\n", pl->ob->name); return; } spell = esrv_get_ob_from_count(pl->ob, tspell); if (!spell) { LOG(llevDebug, "Player %s sent an invalid spell for inscribe command.\n", pl->ob->name); return; } tmarked = pl->mark_count; marked = pl->mark; currentspell = pl->ranges[range_magic]; pl->mark_count = tscroll; pl->mark = scroll; pl->ranges[range_magic] = spell; write_on_item(pl->ob, "", inscription); pl->mark_count = tmarked; pl->mark = marked; pl->ranges[range_magic] = currentspell; } else { } }