895 lines
27 KiB
C
895 lines
27 KiB
C
/*
|
|
* 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 <global.h>
|
|
#include <object.h> /* LOOK_OBJ */
|
|
#include <newclient.h>
|
|
#include <newserver.h>
|
|
#include <sproto.h>
|
|
|
|
/** 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 {
|
|
}
|
|
}
|