server-1.12/socket/request.c

1880 lines
63 KiB
C

/*
* static char *rcsid_request_c =
* "$Id: request.c 17660 2012-03-23 19:08:26Z akirschbaum $";
*/
/*
CrossFire, A Multiplayer game for X-windows
Copyright (C) 2001-2006 Mark Wedel
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 handling.
*
* \date 2003-12-02
*
* This file implements all of the goo on the server side for handling
* clients. It's got a bunch of global variables for keeping track of
* each of the clients.
*
* Note: All functions that are used to process data from the client
* have the prototype of (char *data, int datalen, int client_num). This
* way, we can use one dispatch table.
*
* esrv_map_new starts updating the map
*
* esrv_map_setbelow allows filling in all of the faces for the map.
* if a face has not already been sent to the client, it is sent now.
*
* compactstack, perform the map compressing
* operations
*
* esrv_map_scroll tells the client to scroll the map, and does similarily
* for the locally cached copy.
*
* @todo
* smoothing should be automatic for latest clients. Remove some stuff we can assume is always on.
* fix comments for this file.
*/
#include <assert.h>
#include <global.h>
#include <sproto.h>
#include <newclient.h>
#include <newserver.h>
#include <living.h>
#include <commands.h>
/* This block is basically taken from socket.c - I assume if it works there,
* it should work here.
*/
#ifndef WIN32 /* ---win32 exclude unix headers */
#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <netdb.h>
#endif /* win32 */
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#include "sounds.h"
/**
* This table translates the attack numbers as used within the
* program to the value we use when sending STATS command to the
* client. If a value is -1, then we don't send that to the
* client.
*/
static const short atnr_cs_stat[NROFATTACKS] = {
CS_STAT_RES_PHYS, CS_STAT_RES_MAG,
CS_STAT_RES_FIRE, CS_STAT_RES_ELEC,
CS_STAT_RES_COLD, CS_STAT_RES_CONF,
CS_STAT_RES_ACID,
CS_STAT_RES_DRAIN, -1 /* weaponmagic */,
CS_STAT_RES_GHOSTHIT, CS_STAT_RES_POISON,
CS_STAT_RES_SLOW, CS_STAT_RES_PARA,
CS_STAT_TURN_UNDEAD,
CS_STAT_RES_FEAR, -1 /* Cancellation */,
CS_STAT_RES_DEPLETE, CS_STAT_RES_DEATH,
-1 /* Chaos */, -1 /* Counterspell */,
-1 /* Godpower */, CS_STAT_RES_HOLYWORD,
CS_STAT_RES_BLIND,
-1, /* Internal */
-1, /* life stealing */
-1 /* Disease - not fully done yet */
};
/** This is the Setup cmd - easy first implementation */
void set_up_cmd(char *buf, int len, socket_struct *ns) {
int s;
char *cmd, *param;
SockList sl;
/* run through the cmds of setup
* syntax is setup <cmdname1> <parameter> <cmdname2> <parameter> ...
*
* we send the status of the cmd back, or a FALSE is the cmd
* is the server unknown
* The client then must sort this out
*/
LOG(llevInfo, "Get SetupCmd:: %s\n", buf);
SockList_Init(&sl);
SockList_AddString(&sl, "setup");
for (s = 0; s < len; ) {
cmd = &buf[s];
/* find the next space, and put a null there */
for (; buf[s] && buf[s] != ' '; s++)
;
if (s >= len)
break;
buf[s++] = 0;
while (buf[s] == ' ')
s++;
if (s >= len)
break;
param = &buf[s];
for (; buf[s] && buf[s] != ' '; s++)
;
buf[s++] = 0;
while (s < len && buf[s] == ' ')
s++;
SockList_AddPrintf(&sl, " %s ", cmd);
if (!strcmp(cmd, "sound")) {
/* this is the old sound command, which means the client doesn't understand our sound => mute. */
ns->sound = 0;
SockList_AddString(&sl, "FALSE");
} else if (!strcmp(cmd, "sound2")) {
ns->sound = atoi(param)&(SND_EFFECTS|SND_MUSIC|SND_MUTE);
SockList_AddString(&sl, param);
} else if (!strcmp(cmd, "exp64")) {
/* for compatibility, return 1 since older clients can be confused else. */
SockList_AddString(&sl, "1");
} else if (!strcmp(cmd, "spellmon")) {
int monitor_spells;
monitor_spells = atoi(param);
if (monitor_spells != 0 && monitor_spells != 1) {
SockList_AddString(&sl, "FALSE");
} else {
ns->monitor_spells = monitor_spells;
SockList_AddPrintf(&sl, "%d", monitor_spells);
}
} else if (!strcmp(cmd, "darkness")) {
int darkness;
darkness = atoi(param);
if (darkness != 0 && darkness != 1) {
SockList_AddString(&sl, "FALSE");
} else {
ns->darkness = darkness;
SockList_AddPrintf(&sl, "%d", darkness);
}
} else if (!strcmp(cmd, "map2cmd")) {
int map2cmd;
map2cmd = atoi(param);
if (map2cmd != 1) {
SockList_AddString(&sl, "FALSE");
} else {
SockList_AddString(&sl, "1");
}
} else if (!strcmp(cmd, "newmapcmd")) {
int newmapcmd;
newmapcmd = atoi(param);
if (newmapcmd != 0 && newmapcmd != 1) {
SockList_AddString(&sl, "FALSE");
} else {
ns->newmapcmd = newmapcmd;
SockList_AddPrintf(&sl, "%d", newmapcmd);
}
} else if (!strcmp(cmd, "facecache")) {
int facecache;
facecache = atoi(param);
if (facecache != 0 && facecache != 1) {
SockList_AddString(&sl, "FALSE");
} else {
ns->facecache = facecache;
SockList_AddPrintf(&sl, "%d", facecache);
}
} else if (!strcmp(cmd, "faceset")) {
int q = atoi(param);
if (is_valid_faceset(q))
ns->faceset = q;
SockList_AddPrintf(&sl, "%d", ns->faceset);
} else if (!strcmp(cmd, "itemcmd")) {
/* client ignore the value anyway. */
SockList_AddString(&sl, "2");
} else if (!strcmp(cmd, "mapsize")) {
int x, y, n;
if (sscanf(param, "%dx%d%n", &x, &y, &n) != 2 || n != (int)strlen(param)) {
x = 0;
y = 0;
}
if (x < 9 || y < 9 || x > MAP_CLIENT_X || y > MAP_CLIENT_Y) {
SockList_AddPrintf(&sl, "%dx%d", MAP_CLIENT_X, MAP_CLIENT_Y);
} else {
ns->mapx = x;
ns->mapy = y;
/* better to send back what we are really using and not the
* param as given to us in case it gets parsed differently.
*/
SockList_AddPrintf(&sl, "%dx%d", x, y);
/* Client and server need to resynchronize on data - treating it as
* a new map is best way to go.
*/
map_newmap_cmd(ns);
}
} else if (!strcmp(cmd, "extendedMapInfos")) {
SockList_AddString(&sl, "1");
} else if (!strcmp(cmd, "extendedTextInfos")) {
int has_readable_type;
has_readable_type = atoi(param);
if (has_readable_type != 0 && has_readable_type != 1) {
SockList_AddString(&sl, "FALSE");
} else {
ns->has_readable_type = has_readable_type;
SockList_AddPrintf(&sl, "%d", has_readable_type);
}
} else if (!strcmp(cmd, "tick")) {
int tick;
tick = atoi(param);
if (tick != 0 && tick != 1) {
SockList_AddString(&sl, "FALSE");
} else {
ns->tick = tick;
SockList_AddPrintf(&sl, "%d", tick);
}
} else if (!strcmp(cmd, "bot")) {
int is_bot;
is_bot = atoi(param);
if (is_bot != 0 && is_bot != 1) {
SockList_AddString(&sl, "FALSE");
} else {
ns->is_bot = is_bot;
SockList_AddPrintf(&sl, "%d", is_bot);
}
} else if (!strcmp(cmd, "want_pickup")) {
int want_pickup;
want_pickup = atoi(param);
if (want_pickup != 0 && want_pickup != 1) {
SockList_AddString(&sl, "FALSE");
} else {
ns->want_pickup = want_pickup;
SockList_AddPrintf(&sl, "%d", want_pickup);
}
} else if (!strcmp(cmd, "inscribe")) {
SockList_AddString(&sl, "1");
} else if (!strcmp(cmd, "num_look_objects")) {
int tmp;
tmp = atoi(param);
if (tmp < MIN_NUM_LOOK_OBJECTS) {
tmp = MIN_NUM_LOOK_OBJECTS;
} else if (tmp > MAX_NUM_LOOK_OBJECTS) {
tmp = MAX_NUM_LOOK_OBJECTS;
}
ns->num_look_objects = (uint8)tmp;
SockList_AddPrintf(&sl, "%d", tmp);
} else {
/* Didn't get a setup command we understood -
* report a failure to the client.
*/
SockList_AddString(&sl, "FALSE");
}
} /* for processing all the setup commands */
Send_With_Handling(ns, &sl);
SockList_Term(&sl);
}
/**
* The client has requested to be added to the game.
* This is what takes care of it. We tell the client how things worked out.
* I am not sure if this file is the best place for this function. However,
* it either has to be here or init_sockets needs to be exported.
*
* @todo can ns->status not be Ns_Add?
*/
void add_me_cmd(char *buf, int len, socket_struct *ns) {
Settings oldsettings;
SockList sl;
oldsettings = settings;
if (ns->status != Ns_Add) {
SockList_Init(&sl);
SockList_AddString(&sl, "addme_failed");
Send_With_Handling(ns, &sl);
SockList_Term(&sl);
} else {
add_player(ns);
/* Basically, the add_player copies the socket structure into
* the player structure, so this one (which is from init_sockets)
* is not needed anymore. The write below should still work,
* as the stuff in ns is still relevant.
*/
SockList_Init(&sl);
SockList_AddString(&sl, "addme_success");
Send_With_Handling(ns, &sl);
SockList_Term(&sl);
if (ns->sc_version < 1027 || ns->cs_version < 1023) {
/* The space in the link isn't correct, but in my
* quick test with client 1.1.0, it didn't print it
* out correctly when done as a single line.
*/
SockList_Init(&sl);
SockList_AddString(&sl, "drawinfo 3 Warning: Your client is too old to receive map data. Please update to a new client at http://sourceforge.net/project/showfiles.php ?group_id=13833");
Send_With_Handling(ns, &sl);
SockList_Term(&sl);
}
socket_info.nconns--;
ns->status = Ns_Avail;
}
settings = oldsettings;
}
/** Reply to ExtendedInfos command */
void toggle_extended_infos_cmd(char *buf, int len, socket_struct *ns) {
SockList sl;
char command[50];
int info, nextinfo, smooth = 0;
nextinfo = 0;
while (1) {
/* 1. Extract an info*/
info = nextinfo;
while (info < len && buf[info] == ' ')
info++;
if (info >= len)
break;
nextinfo = info+1;
while (nextinfo < len && buf[nextinfo] != ' ')
nextinfo++;
if (nextinfo-info >= 49) /*Erroneous info asked*/
continue;
strncpy(command, &buf[info], nextinfo-info);
command[nextinfo-info] = '\0';
/* 2. Interpret info*/
if (!strcmp("smooth", command)) {
/* Toggle smoothing*/
smooth = 1;
} else {
/*bad value*/
}
/*3. Next info*/
}
SockList_Init(&sl);
SockList_AddString(&sl, "ExtendedInfoSet");
if (smooth) {
SockList_AddString(&sl, " smoothing");
}
Send_With_Handling(ns, &sl);
SockList_Term(&sl);
}
/** Reply to ExtendedInfos command */
void toggle_extended_text_cmd(char *buf, int len, socket_struct *ns) {
SockList sl;
char command[50];
int info, nextinfo, i, flag;
nextinfo = 0;
while (1) {
/* 1. Extract an info*/
info = nextinfo;
while (info < len && buf[info] == ' ')
info++;
if (info >= len)
break;
nextinfo = info+1;
while (nextinfo < len && buf[nextinfo] != ' ')
nextinfo++;
if (nextinfo-info >= 49) /*Erroneous info asked*/
continue;
strncpy(command, &buf[info], nextinfo-info);
command[nextinfo-info] = '\0';
/* 2. Interpret info*/
i = sscanf(command, "%d", &flag);
if (i == 1 && flag > 0 && flag <= MSG_TYPE_LAST)
ns->supported_readables |= (1<<flag);
/*3. Next info*/
}
/* Send resulting state */
SockList_Init(&sl);
SockList_AddString(&sl, "ExtendedTextSet");
for (i = 0; i <= MSG_TYPE_LAST; i++)
if (ns->supported_readables&(1<<i)) {
SockList_AddPrintf(&sl, " %d", i);
}
Send_With_Handling(ns, &sl);
SockList_Term(&sl);
}
/**
* A lot like the old AskSmooth (in fact, now called by AskSmooth).
* Basically, it makes no sense to wait for the client to request a
* a piece of data from us that we know the client wants. So
* if we know the client wants it, might as well push it to the
* client.
*/
static void send_smooth(socket_struct *ns, uint16 face) {
uint16 smoothface;
SockList sl;
/* If we can't find a face, return and set it so we won't
* try to send this again.
*/
if (!find_smooth(face, &smoothface)
&& !find_smooth(smooth_face->number, &smoothface)) {
LOG(llevError, "could not findsmooth for %d. Neither default (%s)\n", face, smooth_face->name);
ns->faces_sent[face] |= NS_FACESENT_SMOOTH;
return;
}
if (!(ns->faces_sent[smoothface]&NS_FACESENT_FACE))
esrv_send_face(ns, smoothface, 0);
ns->faces_sent[face] |= NS_FACESENT_SMOOTH;
SockList_Init(&sl);
SockList_AddString(&sl, "smooth ");
SockList_AddShort(&sl, face);
SockList_AddShort(&sl, smoothface);
Send_With_Handling(ns, &sl);
SockList_Term(&sl);
}
/**
* Tells client the picture it has to use
* to smooth a picture number given as argument.
*/
void ask_smooth_cmd(char *buf, int len, socket_struct *ns) {
uint16 facenbr;
facenbr = atoi(buf);
send_smooth(ns, facenbr);
}
/**
* This handles the commands issued by the player (ie, north, fire, cast,
* etc.). This is called with the 'ncom' method which gives more information back
* to the client so it can throttle.
*
* @param buf
* data received.
* @param len
* length of buf.
* @param pl
* player who issued the command. Mustn't be NULL.
*/
void new_player_cmd(uint8 *buf, int len, player *pl) {
int time, repeat;
short packet;
char command[MAX_BUF];
SockList sl;
if (len < 7) {
LOG(llevDebug, "Corrupt ncom command - not long enough - discarding\n");
return;
}
packet = GetShort_String(buf);
repeat = GetInt_String(buf+2);
/* -1 is special - no repeat, but don't update */
if (repeat != -1) {
pl->count = repeat;
}
if (len-4 >= MAX_BUF)
len = MAX_BUF-5;
strncpy(command, (char *)buf+6, len-4);
command[len-4] = '\0';
/* The following should never happen with a proper or honest client.
* Therefore, the error message doesn't have to be too clear - if
* someone is playing with a hacked/non working client, this gives them
* an idea of the problem, but they deserve what they get
*/
if (pl->state != ST_PLAYING) {
draw_ext_info_format(NDI_UNIQUE, 0, pl->ob, MSG_TYPE_COMMAND, MSG_TYPE_COMMAND_ERROR,
"You can not issue commands - state is not ST_PLAYING (%s)",
"You can not issue commands - state is not ST_PLAYING (%s)",
buf);
return;
}
/* This should not happen anymore. */
if (pl->ob->speed_left < -1.0) {
LOG(llevError, "Player has negative time - shouldn't do command.\n");
}
/* In c_new.c */
execute_newserver_command(pl->ob, command);
/* Perhaps something better should be done with a left over count.
* Cleaning up the input should probably be done first - all actions
* for the command that issued the count should be done before
* any other commands.
*/
pl->count = 0;
/* Send confirmation of command execution now */
SockList_Init(&sl);
SockList_AddString(&sl, "comc ");
SockList_AddShort(&sl, packet);
if (FABS(pl->ob->speed) < 0.001)
time = MAX_TIME*100;
else
time = (int)(MAX_TIME/FABS(pl->ob->speed));
SockList_AddInt(&sl, time);
Send_With_Handling(&pl->socket, &sl);
SockList_Term(&sl);
}
/** This is a reply to a previous query. */
void reply_cmd(char *buf, int len, player *pl) {
/* This is to synthesize how the data would be stored if it
* was normally entered. A bit of a hack, and should be cleaned up
* once all the X11 code is removed from the server.
*
* We pass 13 to many of the functions because this way they
* think it was the carriage return that was entered, and the
* function then does not try to do additional input.
*/
snprintf(pl->write_buf, sizeof(pl->write_buf), ":%s", buf);
/* this avoids any hacking here */
switch (pl->state) {
case ST_PLAYING:
LOG(llevError, "Got reply message with ST_PLAYING input state\n");
break;
case ST_PLAY_AGAIN:
/* We can check this for return value (2==quit). Maybe we
* should, and do something appropriate?
*/
receive_play_again(pl->ob, buf[0]);
break;
case ST_ROLL_STAT:
key_roll_stat(pl->ob, buf[0]);
break;
case ST_CHANGE_CLASS:
key_change_class(pl->ob, buf[0]);
break;
case ST_CONFIRM_QUIT:
key_confirm_quit(pl->ob, buf[0]);
break;
case ST_GET_NAME:
receive_player_name(pl->ob);
break;
case ST_GET_PASSWORD:
case ST_CONFIRM_PASSWORD:
case ST_CHANGE_PASSWORD_OLD:
case ST_CHANGE_PASSWORD_NEW:
case ST_CHANGE_PASSWORD_CONFIRM:
receive_player_password(pl->ob);
break;
case ST_GET_PARTY_PASSWORD: /* Get password for party */
receive_party_password(pl->ob);
break;
default:
LOG(llevError, "Unknown input state: %d\n", pl->state);
}
}
/**
* Client tells its version. If there is a mismatch, we close the
* socket. In real life, all we should care about is the client having
* something older than the server. If we assume the client will be
* backwards compatible, having it be a later version should not be a
* problem.
*/
void version_cmd(char *buf, int len, socket_struct *ns) {
char *cp;
if (!buf) {
LOG(llevError, "CS: received corrupted version command\n");
return;
}
ns->cs_version = atoi(buf);
ns->sc_version = ns->cs_version;
if (VERSION_CS != ns->cs_version) {
#ifdef ESRV_DEBUG
LOG(llevDebug, "CS: csversion mismatch (%d,%d)\n", VERSION_CS, ns->cs_version);
#endif
}
cp = strchr(buf+1, ' ');
if (!cp)
return;
ns->sc_version = atoi(cp);
if (VERSION_SC != ns->sc_version) {
#ifdef ESRV_DEBUG
LOG(llevDebug, "CS: scversion mismatch (%d,%d)\n", VERSION_SC, ns->sc_version);
#endif
}
cp = strchr(cp+1, ' ');
if (cp) {
LOG(llevDebug, "CS: connection from client of type <%s>, ip %s\n", cp, ns->host);
}
}
/**
* Sound related function.
* @todo remove once clients don't try to use this - server closes connection on invalid client.
*/
void set_sound_cmd(char *buf, int len, socket_struct *ns) {
}
/** client wants the map resent
* @todo remove
*/
void map_redraw_cmd(char *buf, int len, player *pl) {
/* This function is currently disabled; just clearing the
* map state results in display errors. It should clear the
* cache and send a newmap command. Unfortunately this
* solution does not work because some client versions send
* a mapredraw command after receiving a newmap command.
*/
}
/** Newmap command */
void map_newmap_cmd(socket_struct *ns) {
/* If getting a newmap command, this scroll information
* is no longer relevant.
*/
ns->map_scroll_x = 0;
ns->map_scroll_y = 0;
if (ns->newmapcmd == 1) {
SockList sl;
memset(&ns->lastmap, 0, sizeof(ns->lastmap));
SockList_Init(&sl);
SockList_AddString(&sl, "newmap");
Send_With_Handling(ns, &sl);
SockList_Term(&sl);
}
}
/**
* Moves an object (typically, container to inventory).
* syntax is: move (to) (tag) (nrof)
*/
void move_cmd(char *buf, int len, player *pl) {
int vals[3], i;
/* A little funky here. We only cycle for 2 records, because
* we obviously am not going to find a space after the third
* record. Perhaps we should just replace this with a
* sscanf?
*/
for (i = 0; i < 2; i++) {
vals[i] = atoi(buf);
if (!(buf = strchr(buf, ' '))) {
LOG(llevError, "Incomplete move command: %s\n", buf);
return;
}
buf++;
}
vals[2] = atoi(buf);
/* LOG(llevDebug, "Move item %d (nrof=%d) to %d.\n", vals[1], vals[2], vals[0]);*/
esrv_move_object(pl->ob, vals[0], vals[1], vals[2]);
}
/***************************************************************************
*
* Start of commands the server sends to the client.
*
***************************************************************************
*/
/**
* Asks the client to query the user. This way, the client knows
* it needs to send something back (vs just printing out a message)
*/
void send_query(socket_struct *ns, uint8 flags, const char *text) {
SockList sl;
SockList_Init(&sl);
SockList_AddPrintf(&sl, "query %d %s", flags, text ? text : "");
Send_With_Handling(ns, &sl);
SockList_Term(&sl);
}
#define AddIfInt64(Old, New, Type) \
if (Old != New) { \
Old = New; \
SockList_AddChar(&sl, Type); \
SockList_AddInt64(&sl, New); \
}
#define AddIfInt(Old, New, Type) \
if (Old != New) { \
Old = New; \
SockList_AddChar(&sl, Type); \
SockList_AddInt(&sl, New); \
}
#define AddIfShort(Old, New, Type) \
if (Old != New) { \
Old = New; \
SockList_AddChar(&sl, Type); \
SockList_AddShort(&sl, New); \
}
#define AddIfFloat(Old, New, Type) \
if (Old != New) { \
Old = New; \
SockList_AddChar(&sl, Type); \
SockList_AddInt(&sl, (long)(New*FLOAT_MULTI));\
}
#define AddIfString(Old, New, Type) \
if (Old == NULL || strcmp(Old, New)) { \
if (Old) \
free(Old); \
Old = strdup_local(New); \
SockList_AddChar(&sl, Type); \
SockList_AddLen8Data(&sl, New, strlen(New));\
}
/**
* Sends a statistics update. We look at the old values,
* and only send what has changed. Stat mapping values are in newclient.h
* Since this gets sent a lot, this is actually one of the few binary
* commands for now.
*/
void esrv_update_stats(player *pl) {
SockList sl;
char buf[MAX_BUF];
uint16 flags;
uint8 s;
SockList_Init(&sl);
SockList_AddString(&sl, "stats ");
if (pl->ob != NULL) {
AddIfShort(pl->last_stats.hp, pl->ob->stats.hp, CS_STAT_HP);
AddIfShort(pl->last_stats.maxhp, pl->ob->stats.maxhp, CS_STAT_MAXHP);
AddIfShort(pl->last_stats.sp, pl->ob->stats.sp, CS_STAT_SP);
AddIfShort(pl->last_stats.maxsp, pl->ob->stats.maxsp, CS_STAT_MAXSP);
AddIfShort(pl->last_stats.grace, pl->ob->stats.grace, CS_STAT_GRACE);
AddIfShort(pl->last_stats.maxgrace, pl->ob->stats.maxgrace, CS_STAT_MAXGRACE);
AddIfShort(pl->last_stats.Str, pl->ob->stats.Str, CS_STAT_STR);
AddIfShort(pl->last_stats.Int, pl->ob->stats.Int, CS_STAT_INT);
AddIfShort(pl->last_stats.Pow, pl->ob->stats.Pow, CS_STAT_POW);
AddIfShort(pl->last_stats.Wis, pl->ob->stats.Wis, CS_STAT_WIS);
AddIfShort(pl->last_stats.Dex, pl->ob->stats.Dex, CS_STAT_DEX);
AddIfShort(pl->last_stats.Con, pl->ob->stats.Con, CS_STAT_CON);
AddIfShort(pl->last_stats.Cha, pl->ob->stats.Cha, CS_STAT_CHA);
}
for (s = 0; s < NUM_SKILLS; s++) {
if (pl->last_skill_ob[s]
&& pl->last_skill_exp[s] != pl->last_skill_ob[s]->stats.exp) {
/* Always send along the level if exp changes. This
* is only 1 extra byte, but keeps processing simpler.
*/
SockList_AddChar(&sl, (char)(s+CS_STAT_SKILLINFO));
SockList_AddChar(&sl, (char)pl->last_skill_ob[s]->level);
SockList_AddInt64(&sl, pl->last_skill_ob[s]->stats.exp);
pl->last_skill_exp[s] = pl->last_skill_ob[s]->stats.exp;
}
}
AddIfInt64(pl->last_stats.exp, pl->ob->stats.exp, CS_STAT_EXP64);
AddIfShort(pl->last_level, (char)pl->ob->level, CS_STAT_LEVEL);
AddIfShort(pl->last_stats.wc, pl->ob->stats.wc, CS_STAT_WC);
AddIfShort(pl->last_stats.ac, pl->ob->stats.ac, CS_STAT_AC);
AddIfShort(pl->last_stats.dam, pl->ob->stats.dam, CS_STAT_DAM);
AddIfFloat(pl->last_speed, pl->ob->speed, CS_STAT_SPEED);
AddIfShort(pl->last_stats.food, pl->ob->stats.food, CS_STAT_FOOD);
AddIfFloat(pl->last_weapon_sp, pl->weapon_sp, CS_STAT_WEAP_SP);
AddIfInt(pl->last_weight_limit, (sint32)weight_limit[pl->ob->stats.Str], CS_STAT_WEIGHT_LIM);
flags = 0;
if (pl->fire_on)
flags |= SF_FIREON;
if (pl->run_on)
flags |= SF_RUNON;
AddIfShort(pl->last_flags, flags, CS_STAT_FLAGS);
if (pl->socket.sc_version < 1025) {
AddIfShort(pl->last_resist[ATNR_PHYSICAL], pl->ob->resist[ATNR_PHYSICAL], CS_STAT_ARMOUR);
} else {
int i;
for (i = 0; i < NROFATTACKS; i++) {
/* Skip ones we won't send */
if (atnr_cs_stat[i] == -1)
continue;
AddIfShort(pl->last_resist[i], pl->ob->resist[i], (char)atnr_cs_stat[i]);
}
}
if (pl->socket.monitor_spells) {
AddIfInt(pl->last_path_attuned, pl->ob->path_attuned, CS_STAT_SPELL_ATTUNE);
AddIfInt(pl->last_path_repelled, pl->ob->path_repelled, CS_STAT_SPELL_REPEL);
AddIfInt(pl->last_path_denied, pl->ob->path_denied, CS_STAT_SPELL_DENY);
}
/* we want to use the new fire & run system in new client */
rangetostring(pl->ob, buf, sizeof(buf));
AddIfString(pl->socket.stats.range, buf, CS_STAT_RANGE);
set_title(pl->ob, buf, sizeof(buf));
AddIfString(pl->socket.stats.title, buf, CS_STAT_TITLE);
/* Only send it away if we have some actual data - 2 bytes for length, 6 for "stats ". */
if (sl.len > 8) {
#ifdef ESRV_DEBUG
LOG(llevDebug, "Sending stats command, %d bytes long.\n", sl.len);
#endif
Send_With_Handling(&pl->socket, &sl);
}
SockList_Term(&sl);
}
/**
* Tells the client that here is a player it should start using.
*/
void esrv_new_player(player *pl, uint32 weight) {
SockList sl;
pl->last_weight = weight;
if (!(pl->socket.faces_sent[pl->ob->face->number]&NS_FACESENT_FACE))
esrv_send_face(&pl->socket, pl->ob->face->number, 0);
SockList_Init(&sl);
SockList_AddString(&sl, "player ");
SockList_AddInt(&sl, pl->ob->count);
SockList_AddInt(&sl, weight);
SockList_AddInt(&sl, pl->ob->face->number);
SockList_AddLen8Data(&sl, pl->ob->name, strlen(pl->ob->name));
Send_With_Handling(&pl->socket, &sl);
SockList_Term(&sl);
SET_FLAG(pl->ob, FLAG_CLIENT_SENT);
}
/**
* Need to send an animation sequence to the client.
* We will send appropriate face commands to the client if we haven't
* sent them the face yet (this can become quite costly in terms of
* how much we are sending - on the other hand, this should only happen
* when the player logs in and picks stuff up.
*/
void esrv_send_animation(socket_struct *ns, short anim_num) {
SockList sl;
int i;
/* Do some checking on the anim_num we got. Note that the animations
* are added in contigous order, so if the number is in the valid
* range, it must be a valid animation.
*/
if (anim_num < 0 || anim_num > num_animations) {
LOG(llevError, "esrv_send_anim (%d) out of bounds??\n", anim_num);
return;
}
SockList_Init(&sl);
SockList_AddString(&sl, "anim ");
SockList_AddShort(&sl, anim_num);
SockList_AddShort(&sl, 0); /* flags - not used right now */
/* Build up the list of faces. Also, send any information (ie, the
* the face itself) down to the client.
*/
for (i = 0; i < animations[anim_num].num_animations; i++) {
if (!(ns->faces_sent[animations[anim_num].faces[i]]&NS_FACESENT_FACE))
esrv_send_face(ns, animations[anim_num].faces[i], 0);
/* flags - not used right now */
SockList_AddShort(&sl, animations[anim_num].faces[i]);
}
Send_With_Handling(ns, &sl);
SockList_Term(&sl);
ns->anims_sent[anim_num] = 1;
}
/****************************************************************************
*
* Start of map related commands.
*
****************************************************************************/
/** Clears a map cell */
static void map_clearcell(struct map_cell_struct *cell, int face, int count) {
cell->darkness = count;
memset(cell->faces, face, sizeof(cell->faces));
}
#define MAX_HEAD_POS MAX(MAX_CLIENT_X, MAX_CLIENT_Y)
/** Using a global really isn't a good approach, but saves the over head of
* allocating and deallocating such a block of data each time run through,
* and saves the space of allocating this in the socket object when we only
* need it for this cycle. If the serve is ever threaded, this needs to be
* re-examined.
*/
static object *heads[MAX_HEAD_POS][MAX_HEAD_POS][MAP_LAYERS];
/****************************************************************************
* This block is for map2 drawing related commands.
* Note that the map2 still uses other functions.
*
***************************************************************************/
/**
* object 'ob' at 'ax,ay' on 'layer' is visible to the client.
* This function does the following things:
* If is_head head is set, this means this is from the heads[] array,
* so don't try to store it away again - just send it and update
* our look faces.
*
* 1) If a multipart object and we are not at the lower right corner,
* store this info away for later use.
* 2) Check to see if this face has been sent to the client. If not,
* we add data to the socklist, update the last map, and send any
* other data the client will need (smoothing table, image data, etc)
* 3) Return 1 if function increased socket.
* 4) has_obj is increased by one if there are visible objects on this
* this space, whether or not we sent them. Basically, if has_obj
* is 0, we can clear info about this space. It could be set to 1
* with the function returning zero - this means there are objects
* on the space we have already sent to the client.
*/
static int map2_add_ob(int ax, int ay, int layer, object *ob, SockList *sl, socket_struct *ns, int *has_obj, int is_head) {
uint16 face_num;
uint8 nlayer, smoothlevel = 0;
object *head;
assert(ob != NULL);
head = ob->head ? ob->head : ob;
face_num = ob->face->number;
/* This is a multipart object, and we are not at the lower
* right corner. So we need to store away the lower right corner.
*/
if (!is_head && (head->arch->tail_x || head->arch->tail_y)
&& (head->arch->tail_x != ob->arch->clone.x || head->arch->tail_y != ob->arch->clone.y)) {
int bx, by, l;
/* Basically figure out where the offset is from where we
* are right now. the ob->arch->clone.{x,y} values hold
* the offset that this current piece is from the head,
* and the tail is where the tail is from the head.
* Note that bx and by will equal sx and sy if we are
* already working on the bottom right corner. If ob is
* the head, the clone values will be zero, so the right
* thing will still happen.
*/
bx = ax+head->arch->tail_x-ob->arch->clone.x;
by = ay+head->arch->tail_y-ob->arch->clone.y;
/* I don't think this can ever happen, but better to check
* for it just in case.
*/
if (bx < ax || by < ay) {
LOG(llevError, "map2_add_ob: bx (%d) or by (%d) is less than ax (%d) or ay (%d)\n", bx, by, ax, ay);
face_num = 0;
}
/* the target position must be within +/-1 of our current
* layer as the layers are defined. We are basically checking
* to see if we have already stored this object away.
*/
for (l = layer-1; l <= layer+1; l++) {
if (l < 0 || l >= MAP_LAYERS)
continue;
if (heads[by][bx][l] == head)
break;
}
/* Didn't find it. So we need to store it away. Try to store it
* on our original layer, and then move up a layer.
*/
if (l == layer+2) {
if (!heads[by][bx][layer])
heads[by][bx][layer] = head;
else if (layer+1 < MAP_LAYERS && !heads[by][bx][layer+1])
heads[by][bx][layer+1] = head;
}
return 0;
/* Ok - All done storing away the head for future use */
} else {
(*has_obj)++;
if (QUERY_FLAG(ob, FLAG_CLIENT_ANIM_SYNC)
|| QUERY_FLAG(ob, FLAG_CLIENT_ANIM_RANDOM)) {
face_num = ob->animation_id|(1<<15);
if (QUERY_FLAG(ob, FLAG_CLIENT_ANIM_SYNC))
face_num |= ANIM_SYNC;
else if (QUERY_FLAG(ob, FLAG_CLIENT_ANIM_RANDOM))
face_num |= ANIM_RANDOM;
}
/* Since face_num includes the bits for the animation tag,
* and we will store that away in the faces[] array, below
* check works fine _except_ for the case where animation
* speed chances.
*/
if (ns->lastmap.cells[ax][ay].faces[layer] != face_num) {
uint8 len, anim_speed = 0, i;
/* This block takes care of sending the actual face
* to the client. */
ns->lastmap.cells[ax][ay].faces[layer] = face_num;
/* Now form the data packet */
nlayer = 0x10+layer;
len = 2;
if (!MAP_NOSMOOTH(ob->map)) {
smoothlevel = ob->smoothlevel;
if (smoothlevel)
len++;
}
if (QUERY_FLAG(ob, FLAG_CLIENT_ANIM_SYNC)
|| QUERY_FLAG(ob, FLAG_CLIENT_ANIM_RANDOM)) {
len++;
/* 1/0.004 == 250, so this is a good cap for an
* upper limit */
if (ob->anim_speed)
anim_speed = ob->anim_speed;
else if (FABS(ob->speed) < 0.004)
anim_speed = 255;
else if (FABS(ob->speed) >= 1.0)
anim_speed = 1;
else
anim_speed = (int)(1.0/FABS(ob->speed));
if (!ns->anims_sent[ob->animation_id])
esrv_send_animation(ns, ob->animation_id);
/* If smoothing, need to send smoothing information
* for all faces in the animation sequence. Since
* smoothlevel is an object attribute,
* it applies to all faces.
*/
if (smoothlevel) {
for (i = 0; i < NUM_ANIMATIONS(ob); i++) {
if (!(ns->faces_sent[animations[ob->animation_id].faces[i]]&NS_FACESENT_SMOOTH))
send_smooth(ns, animations[ob->animation_id].faces[i]);
}
}
} else if (!(ns->faces_sent[face_num]&NS_FACESENT_FACE)) {
esrv_send_face(ns, face_num, 0);
}
if (smoothlevel
&& !(ns->faces_sent[ob->face->number]&NS_FACESENT_SMOOTH))
send_smooth(ns, ob->face->number);
/* Length of packet */
nlayer |= len<<5;
SockList_AddChar(sl, nlayer);
SockList_AddShort(sl, face_num);
if (anim_speed)
SockList_AddChar(sl, anim_speed);
if (smoothlevel)
SockList_AddChar(sl, smoothlevel);
return 1;
} /* Face is different */
}
return 0;
}
/* This function is used see if a layer needs to be cleared.
* It updates the socklist, and returns 1 if the update is
* needed, 0 otherwise.
*/
static int map2_delete_layer(int ax, int ay, int layer, SockList *sl, socket_struct *ns) {
int nlayer;
if (ns->lastmap.cells[ax][ay].faces[layer] != 0) {
/* Now form the data packet */
nlayer = 0x10+layer+(2<<5);
SockList_AddChar(sl, nlayer);
SockList_AddShort(sl, 0);
ns->lastmap.cells[ax][ay].faces[layer] = 0;
return 1;
}
return 0;
}
/*
* This function is used to check a space (ax, ay) whose only
* data we may care about are any heads. Basically, this
* space is out of direct view. This is only used with the
* Map2 protocol.
*
* @param ax
* viewport relative x-coordinate
* @param ay
* viewport relative y-coordinate
* @param sl
* the reply to append to
* @param ns
* the client socket
*/
static void check_space_for_heads(int ax, int ay, SockList *sl, socket_struct *ns) {
int layer, got_one = 0, del_one = 0, oldlen, has_obj = 0;
uint16 coord;
coord = MAP2_COORD_ENCODE(ax, ay, 0);
oldlen = sl->len;
SockList_AddShort(sl, coord);
for (layer = 0; layer < MAP_LAYERS; layer++) {
object *head;
head = heads[ay][ax][layer];
if (head) {
/* in this context, got_one should always increase
* because heads should always point to data to really send.
*/
got_one += map2_add_ob(ax, ay, layer, head, sl, ns, &has_obj, 1);
} else {
del_one += map2_delete_layer(ax, ay, layer, sl, ns);
}
}
/* Note - if/when lighting information is added, some code is
* needed here - lighting sources that are out of sight may still
* extend into the viewable area.
*/
/* If nothing to do for this space, we
* can erase the coordinate bytes
*/
if (!del_one && !got_one) {
sl->len = oldlen;
} else if (del_one && !has_obj) {
/* If we're only deleting faces and not adding, and there
* are not any faces on the space we care about,
* more efficient
* to send 0 as the type/len field.
*/
sl->len = oldlen+2; /* 2 bytes for coordinate */
SockList_AddChar(sl, 0); /* Clear byte */
SockList_AddChar(sl, 255); /* Termination byte */
map_clearcell(&ns->lastmap.cells[ax][ay], 0, 0);
} else {
SockList_AddChar(sl, 255); /* Termination byte */
}
}
void draw_client_map2(object *pl) {
int x, y, ax, ay, d, min_x, max_x, min_y, max_y, oldlen, layer;
size_t startlen;
sint16 nx, ny;
SockList sl;
uint16 coord;
mapstruct *m;
object *ob;
SockList_Init(&sl);
SockList_AddString(&sl, "map2 ");
startlen = sl.len;
/* Handle map scroll */
if (pl->contr->socket.map_scroll_x || pl->contr->socket.map_scroll_y) {
coord = MAP2_COORD_ENCODE(pl->contr->socket.map_scroll_x, pl->contr->socket.map_scroll_y, 1);
pl->contr->socket.map_scroll_x = 0;
pl->contr->socket.map_scroll_y = 0;
SockList_AddShort(&sl, coord);
}
/* Init data to zero */
memset(heads, 0, sizeof(heads));
/* We could do this logic as conditionals in the if statement,
* but that started to get a bit messy to look at.
*/
min_x = pl->x-pl->contr->socket.mapx/2;
min_y = pl->y-pl->contr->socket.mapy/2;
max_x = pl->x+(pl->contr->socket.mapx+1)/2+MAX_HEAD_OFFSET;
max_y = pl->y+(pl->contr->socket.mapy+1)/2+MAX_HEAD_OFFSET;
/* x, y are the real map locations. ax, ay are viewport relative
* locations.
*/
ay = 0;
for (y = min_y; y < max_y; y++, ay++) {
ax = 0;
for (x = min_x; x < max_x; x++, ax++) {
/* If this space is out of the normal viewable area,
* we only check the heads value. This is used to
* handle big images - if they extend to a viewable
* portion, we need to send just the lower right corner.
*/
if (ax >= pl->contr->socket.mapx || ay >= pl->contr->socket.mapy) {
check_space_for_heads(ax, ay, &sl, &pl->contr->socket);
} else {
/* This space is within the viewport of the client. Due
* to walls or darkness, it may still not be visible.
*/
/* Meaning of d:
* 0 - object is in plain sight, full brightness.
* 1 - MAX_DARKNESS - how dark the space is - higher
* value is darker space. If level is at max darkness,
* you can't see the space (too dark)
* 100 - space is blocked from sight.
*/
d = pl->contr->blocked_los[ax][ay];
/* If the coordinates are not valid, or it is too
* dark to see, we tell the client as such
*/
nx = x;
ny = y;
m = get_map_from_coord(pl->map, &nx, &ny);
coord = MAP2_COORD_ENCODE(ax, ay, 0);
if (!m) {
/* space is out of map. Update space and clear
* values if this hasn't already been done.
* If the space is out of the map, it shouldn't
* have a head.
*/
if (pl->contr->socket.lastmap.cells[ax][ay].darkness != 0) {
SockList_AddShort(&sl, coord);
SockList_AddChar(&sl, 0);
SockList_AddChar(&sl, 255); /* Termination byte */
map_clearcell(&pl->contr->socket.lastmap.cells[ax][ay], 0, 0);
}
} else if (d >= MAX_LIGHT_RADII) {
/* This block deals with spaces that are not
* visible due to darkness or walls. Still
* need to send the heads for this space.
*/
check_space_for_heads(ax, ay, &sl, &pl->contr->socket);
} else {
int have_darkness = 0, has_obj = 0, got_one = 0, del_one = 0, g1;
/* In this block, the space is visible. */
/* Rather than try to figure out what everything
* that we might need to send is, then form the
* packet after that, we presume that we will in
* fact form a packet, and update the bits by what
* we do actually send. If we send nothing, we
* just back out sl.len to the old value, and no
* harm is done.
* I think this is simpler than doing a bunch of
* checks to see what if anything we need to send,
* setting the bits, then doing those checks again
* to add the real data.
*/
oldlen = sl.len;
SockList_AddShort(&sl, coord);
/* Darkness changed */
if (pl->contr->socket.lastmap.cells[ax][ay].darkness != d
&& pl->contr->socket.darkness) {
pl->contr->socket.lastmap.cells[ax][ay].darkness = d;
/* Darkness tag & length*/
SockList_AddChar(&sl, 0x1|1<<5);
SockList_AddChar(&sl, 255-d*(256/MAX_LIGHT_RADII));
have_darkness = 1;
}
for (layer = 0; layer < MAP_LAYERS; layer++) {
ob = GET_MAP_FACE_OBJ(m, nx, ny, layer);
/* Special case: send player itself if invisible */
if (!ob
&& x == pl->x
&& y == pl->y
&& (pl->invisible&(pl->invisible < 50 ? 4 : 1))
&& (layer == MAP_LAYER_LIVING1 || layer == MAP_LAYER_LIVING2))
ob = pl;
if (ob) {
g1 = has_obj;
got_one += map2_add_ob(ax, ay, layer, ob, &sl, &pl->contr->socket, &has_obj, 0);
/* If we are just storing away the head
* for future use, then effectively this
* space/layer is blank, and we should clear
* it if needed.
*/
if (g1 == has_obj)
del_one += map2_delete_layer(ax, ay, layer, &sl, &pl->contr->socket);
} else {
del_one += map2_delete_layer(ax, ay, layer, &sl, &pl->contr->socket);
}
}
/* If nothing to do for this space, we
* can erase the coordinate bytes
*/
if (!del_one && !got_one && !have_darkness) {
sl.len = oldlen;
} else if (del_one && !has_obj) {
/* If we're only deleting faces and don't
* have any objs we care about, just clear
* space. Note it is possible we may have
* darkness, but if there is nothing on the
* space, darkness isn't all that interesting
* - we can send it when an object shows up.
*/
sl.len = oldlen+2; /* 2 bytes for coordinate */
SockList_AddChar(&sl, 0); /* Clear byte */
SockList_AddChar(&sl, 255); /* Termination byte */
map_clearcell(&pl->contr->socket.lastmap.cells[ax][ay], 0, 0);
} else {
SockList_AddChar(&sl, 255); /* Termination byte */
}
}
} /* else this is a viewable space */
} /* for x loop */
} /* for y loop */
/* Only send this if there are in fact some differences. */
if (sl.len > startlen) {
Send_With_Handling(&pl->contr->socket, &sl);
}
SockList_Term(&sl);
}
/**
* Draws client map.
*/
void draw_client_map(object *pl) {
int i, j;
sint16 ax, ay;
int mflags;
mapstruct *m, *pm;
int min_x, min_y, max_x, max_y;
if (pl->type != PLAYER) {
LOG(llevError, "draw_client_map called with non player/non eric-server\n");
return;
}
if (pl->contr->transport) {
pm = pl->contr->transport->map;
} else
pm = pl->map;
/* If player is just joining the game, he isn't here yet, so
* the map can get swapped out. If so, don't try to send them
* a map. All will be OK once they really log in.
*/
if (pm == NULL || pm->in_memory != MAP_IN_MEMORY)
return;
/*
* This block just makes sure all the spaces are properly
* updated in terms of what they look like.
*/
min_x = pl->x-pl->contr->socket.mapx/2;
min_y = pl->y-pl->contr->socket.mapy/2;
max_x = pl->x+(pl->contr->socket.mapx+1)/2;
max_y = pl->y+(pl->contr->socket.mapy+1)/2;
for (j = min_y; j < max_y; j++) {
for (i = min_x; i < max_x; i++) {
ax = i;
ay = j;
m = pm;
mflags = get_map_flags(m, &m, ax, ay, &ax, &ay);
if (mflags&P_OUT_OF_MAP)
continue;
if (mflags&P_NEED_UPDATE)
update_position(m, ax, ay);
/* If a map is visible to the player, we don't want
* to swap it out just to reload it. This should
* really call something like swap_map, but this is
* much more efficient and 'good enough'
*/
if (mflags&P_NEW_MAP)
m->timeout = 50;
}
}
/* do LOS after calls to update_position */
if (pl->contr->do_los) {
update_los(pl);
pl->contr->do_los = 0;
}
draw_client_map2(pl);
}
void esrv_map_scroll(socket_struct *ns, int dx, int dy) {
struct Map newmap;
int x, y, mx, my;
ns->map_scroll_x += dx;
ns->map_scroll_y += dy;
mx = ns->mapx+MAX_HEAD_OFFSET;
my = ns->mapy+MAX_HEAD_OFFSET;
/* the x and y here are coordinates for the new map, i.e. if we moved
* (dx,dy), newmap[x][y] = oldmap[x-dx][y-dy]. For this reason,
* if the destination x or y coordinate is outside the viewable
* area, we clear the values - otherwise, the old values
* are preserved, and the check_head thinks it needs to clear them.
*/
for (x = 0; x < mx; x++) {
for (y = 0; y < my; y++) {
if (x >= ns->mapx || y >= ns->mapy) {
/* clear cells outside the viewable area */
memset(&newmap.cells[x][y], 0, sizeof(newmap.cells[x][y]));
} else if (x+dx < 0 || x+dx >= ns->mapx || y+dy < 0 || y+dy >= ns->mapy) {
/* clear newly visible tiles within the viewable area */
memset(&newmap.cells[x][y], 0, sizeof(newmap.cells[x][y]));
} else {
memcpy(&newmap.cells[x][y], &ns->lastmap.cells[x+dx][y+dy], sizeof(newmap.cells[x][y]));
}
}
}
memcpy(&ns->lastmap, &newmap, sizeof(ns->lastmap));
}
/**
* GROS: The following one is used to allow a plugin to send a generic cmd to
* a player. Of course, the client need to know the command to be able to
* manage it !
*/
void send_plugin_custom_message(object *pl, char *buf) {
SockList sl;
SockList_Init(&sl);
SockList_AddString(&sl, buf);
Send_With_Handling(&pl->contr->socket, &sl);
SockList_Term(&sl);
}
/**
* This sends the experience table the sever is using
*/
void send_exp_table(socket_struct *ns, char *params) {
SockList sl;
int i;
extern sint64 *levels;
SockList_Init(&sl);
SockList_AddString(&sl, "replyinfo exp_table\n");
SockList_AddShort(&sl, settings.max_level+1);
for (i = 1; i <= settings.max_level; i++) {
if (SockList_Avail(&sl) < 8) {
LOG(llevError, "Buffer overflow in send_exp_table, not sending all information\n");
break;
}
SockList_AddInt64(&sl, levels[i]);
}
Send_With_Handling(ns, &sl);
SockList_Term(&sl);
}
/**
* This sends the skill number to name mapping. We ignore
* the params - we always send the same info no matter what.
*/
void send_skill_info(socket_struct *ns, char *params) {
SockList sl;
int i;
SockList_Init(&sl);
SockList_AddString(&sl, "replyinfo skill_info\n");
for (i = 1; i < NUM_SKILLS; i++) {
size_t len;
len = 16+strlen(skill_names[i]); /* upper bound for length */
if (SockList_Avail(&sl) < len) {
LOG(llevError, "Buffer overflow in send_skill_info, not sending all skill information\n");
break;
}
SockList_AddPrintf(&sl, "%d:%s\n", i+CS_STAT_SKILLINFO, skill_names[i]);
}
Send_With_Handling(ns, &sl);
SockList_Term(&sl);
}
/**
* This sends the spell path to name mapping. We ignore
* the params - we always send the same info no matter what.
*/
void send_spell_paths(socket_struct *ns, char *params) {
SockList sl;
int i;
SockList_Init(&sl);
SockList_AddString(&sl, "replyinfo spell_paths\n");
for (i = 0; i < NRSPELLPATHS; i++) {
size_t len;
len = 16+strlen(spellpathnames[i]); /* upper bound for length */
if (SockList_Avail(&sl) < len) {
LOG(llevError, "Buffer overflow in send_spell_paths, not sending all spell information\n");
break;
}
SockList_AddPrintf(&sl, "%d:%s\n", 1<<i, spellpathnames[i]);
}
Send_With_Handling(ns, &sl);
SockList_Term(&sl);
}
/**
* Creates the appropriate reply to the 'race_list' request info.
*
* @param sl
* suitable reply.
*/
static void build_race_list_reply(SockList *sl) {
archetype *race;
SockList_AddString(sl, "replyinfo race_list ");
for (race = first_archetype; race; race = race->next) {
if (race->clone.type == PLAYER) {
SockList_AddPrintf(sl, "|%s", race->name);
}
}
}
/**
* Send the list of player races to the client.
* The reply is kept in a static buffer, as it won't change during server run.
*
* @param ns
* where to send.
* @param params
* ignored.
*/
void send_race_list(socket_struct *ns, char *params) {
static SockList sl;
static int sl_initialized = 0;
if (!sl_initialized) {
sl_initialized = 1;
SockList_Init(&sl);
build_race_list_reply(&sl);
}
Send_With_Handling(ns, &sl);
}
/**
* Sends information on specified race to the client.
*
* @param ns
* where to send.
* @param params
* race name to send.
* @todo finish writing
*/
void send_race_info(socket_struct *ns, char *params) {
archetype *race = try_find_archetype(params);
SockList sl;
SockList_Init(&sl);
SockList_AddPrintf(&sl, "replyinfo race_info %s", params);
if (race) {
}
Send_With_Handling(ns, &sl);
SockList_Term(&sl);
}
/**
* Creates the appropriate reply to the 'class_list' request info.
*
* @param sl
* reply.
*/
static void build_class_list_reply(SockList *sl) {
archetype *cl;
SockList_Reset(sl);
SockList_AddString(sl, "replyinfo class_list ");
for (cl = first_archetype; cl; cl = cl->next) {
if (cl->clone.type == CLASS) {
SockList_AddPrintf(sl, "|%s", cl->name);
}
}
}
/**
* Sends the list of classes to the client.
* The reply is kept in a static buffer, as it won't change during server run.
*
* @param ns
* client to send to.
* @param params
* ignored.
*/
void send_class_list(socket_struct *ns, char *params) {
static SockList sl;
static int sl_initialized = 0;
if (!sl_initialized) {
sl_initialized = 1;
SockList_Init(&sl);
build_class_list_reply(&sl);
}
Send_With_Handling(ns, &sl);
}
/**
* Send information on the specified class.
*
* @param ns
* where to send.
* @param params
* class name to send.
* @todo finish writing
*/
void send_class_info(socket_struct *ns, char *params) {
}
/**
* This looks for any spells the player may have that have changed
* their stats. It then sends an updspell packet for each spell that
* has changed in this way.
*/
void esrv_update_spells(player *pl) {
SockList sl;
int flags = 0;
object *spell;
client_spell *spell_info;
if (!pl->socket.monitor_spells)
return;
/* Handles problem at login, where this is called from fix_object
* before we have had a chance to send spells to the player. It does seem
* to me that there should never be a case where update_spells is called
* before add_spells has been called. Add_spells() will update the
* spell_state to non null.
*/
if (!pl->spell_state)
return;
for (spell = pl->ob->inv; spell != NULL; spell = spell->below) {
if (spell->type == SPELL) {
spell_info = get_client_spell_state(pl, spell);
/* check if we need to update it*/
if (spell_info->last_sp != SP_level_spellpoint_cost(pl->ob, spell, SPELL_MANA)) {
spell_info->last_sp = SP_level_spellpoint_cost(pl->ob, spell, SPELL_MANA);
flags |= UPD_SP_MANA;
}
if (spell_info->last_grace != SP_level_spellpoint_cost(pl->ob, spell, SPELL_GRACE)) {
spell_info->last_grace = SP_level_spellpoint_cost(pl->ob, spell, SPELL_GRACE);
flags |= UPD_SP_GRACE;
}
if (spell_info->last_dam != spell->stats.dam+SP_level_dam_adjust(pl->ob, spell)) {
spell_info->last_dam = spell->stats.dam+SP_level_dam_adjust(pl->ob, spell);
flags |= UPD_SP_DAMAGE;
}
if (flags != 0) {
SockList_Init(&sl);
SockList_AddString(&sl, "updspell ");
SockList_AddChar(&sl, flags);
SockList_AddInt(&sl, spell->count);
if (flags&UPD_SP_MANA)
SockList_AddShort(&sl, spell_info->last_sp);
if (flags&UPD_SP_GRACE)
SockList_AddShort(&sl, spell_info->last_grace);
if (flags&UPD_SP_DAMAGE)
SockList_AddShort(&sl, spell_info->last_dam);
flags = 0;
Send_With_Handling(&pl->socket, &sl);
SockList_Term(&sl);
}
}
}
}
void esrv_remove_spell(player *pl, object *spell) {
SockList sl;
if (!pl->socket.monitor_spells)
return;
if (!pl || !spell || spell->env != pl->ob) {
LOG(llevError, "Invalid call to esrv_remove_spell\n");
return;
}
SockList_Init(&sl);
SockList_AddString(&sl, "delspell ");
SockList_AddInt(&sl, spell->count);
Send_With_Handling(&pl->socket, &sl);
SockList_Term(&sl);
}
/**
* Sends the "pickup" state to pl if client wants it requested.
*
* @param pl
* player that just logged in.
*/
void esrv_send_pickup(player *pl) {
SockList sl;
if (!pl->socket.want_pickup)
return;
SockList_Init(&sl);
SockList_AddString(&sl, "pickup ");
SockList_AddInt(&sl, pl->mode);
Send_With_Handling(&pl->socket, &sl);
SockList_Term(&sl);
}
/** appends the spell *spell to the Socklist we will send the data to. */
static void append_spell(player *pl, SockList *sl, object *spell) {
client_spell *spell_info;
int len, i, skill = 0;
if (!spell->name) {
LOG(llevError, "item number %d is a spell with no name.\n", spell->count);
return;
}
if (spell->face && !(pl->socket.faces_sent[spell->face->number]&NS_FACESENT_FACE))
esrv_send_face(&pl->socket, spell->face->number, 0);
spell_info = get_client_spell_state(pl, spell);
SockList_AddInt(sl, spell->count);
SockList_AddShort(sl, spell->level);
SockList_AddShort(sl, spell->casting_time);
/* store costs and damage in the object struct, to compare to later */
spell_info->last_sp = SP_level_spellpoint_cost(pl->ob, spell, SPELL_MANA);
spell_info->last_grace = SP_level_spellpoint_cost(pl->ob, spell, SPELL_GRACE);
spell_info->last_dam = spell->stats.dam+SP_level_dam_adjust(pl->ob, spell);
/* send the current values */
SockList_AddShort(sl, spell_info->last_sp);
SockList_AddShort(sl, spell_info->last_grace);
SockList_AddShort(sl, spell_info->last_dam);
/* figure out which skill it uses, if it uses one */
if (spell->skill) {
for (i = 1; i < NUM_SKILLS; i++)
if (!strcmp(spell->skill, skill_names[i])) {
skill = i+CS_STAT_SKILLINFO;
break;
}
}
SockList_AddChar(sl, skill);
SockList_AddInt(sl, spell->path_attuned);
SockList_AddInt(sl, spell->face ? spell->face->number : 0);
SockList_AddLen8Data(sl, spell->name, strlen(spell->name));
if (!spell->msg) {
SockList_AddShort(sl, 0);
} else {
len = strlen(spell->msg);
SockList_AddShort(sl, len);
SockList_AddData(sl, spell->msg, len);
}
}
/**
* This tells the client to add the spell *spell, if spell is NULL, then add
* all spells in the player's inventory.
*/
void esrv_add_spells(player *pl, object *spell) {
SockList sl;
if (!pl) {
LOG(llevError, "esrv_add_spells, tried to add a spell to a NULL player\n");
return;
}
if (!pl->socket.monitor_spells)
return;
SockList_Init(&sl);
SockList_AddString(&sl, "addspell ");
if (!spell) {
for (spell = pl->ob->inv; spell != NULL; spell = spell->below) {
if (spell->type != SPELL)
continue;
/* Were we to simply keep appending data here, we could
* exceed the SockList buffer if the player has enough spells
* to add. We know that append_spell will always append
* 23 data bytes, plus 3 length bytes and 2 strings
* (because that is the spec) so we need to check that
* the length of those 2 strings, plus the 26 bytes,
* won't take us over the length limit for the socket.
* If it does, we need to send what we already have,
* and restart packet formation.
*/
if (SockList_Avail(&sl) < 26+strlen(spell->name)+(spell->msg ? strlen(spell->msg) : 0)) {
Send_With_Handling(&pl->socket, &sl);
SockList_Reset(&sl);
SockList_AddString(&sl, "addspell ");
}
append_spell(pl, &sl, spell);
}
} else if (spell->type != SPELL) {
LOG(llevError, "Asked to send a non-spell object as a spell\n");
return;
} else
append_spell(pl, &sl, spell);
/* finally, we can send the packet */
Send_With_Handling(&pl->socket, &sl);
SockList_Term(&sl);
}
/* sends a 'tick' information to the client.
* We also take the opportunity to toggle TCP_NODELAY -
* this forces the data in the socket to be flushed sooner to the
* client - otherwise, the OS tries to wait for full packets
* and will this hold sending the data for some amount of time,
* which thus adds some additional latency.
*/
void send_tick(player *pl) {
SockList sl;
int tmp;
SockList_Init(&sl);
SockList_AddString(&sl, "tick ");
SockList_AddInt(&sl, pticks);
tmp = 1;
if (setsockopt(pl->socket.fd, IPPROTO_TCP, TCP_NODELAY, &tmp, sizeof(tmp)))
LOG(llevError, "send_tick: Unable to turn on TCP_NODELAY\n");
Send_With_Handling(&pl->socket, &sl);
tmp = 0;
if (setsockopt(pl->socket.fd, IPPROTO_TCP, TCP_NODELAY, &tmp, sizeof(tmp)))
LOG(llevError, "send_tick: Unable to turn off TCP_NODELAY\n");
SockList_Term(&sl);
}