/* * 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 #include #include #include #include #include #include /* 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 #include #include #include #include #include #endif /* win32 */ #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_SYS_TIME_H #include #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 ... * * 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<supported_readables&(1<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<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); }