611 lines
22 KiB
C
611 lines
22 KiB
C
|
|
/*
|
|
* static char *rcsid_loop_c =
|
|
* "$Id: loop.c 11578 2009-02-23 22:02:27Z lalo $";
|
|
*/
|
|
|
|
/*
|
|
CrossFire, A Multiplayer game for X-windows
|
|
|
|
Copyright (C) 2006 Mark Wedel & The Crossfire Development Team
|
|
Copyright (C) 1992 Frank Tore Johansen
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
|
|
The author can be reached via e-mail to crossfire-devel@real-time.com
|
|
*/
|
|
|
|
/**
|
|
* \file
|
|
* Main client/server loops.
|
|
*
|
|
* \date 2003-12-02
|
|
*
|
|
* Mainly deals with initialization and higher level socket
|
|
* maintenance (checking for lost connections and if data has arrived.)
|
|
* The reading of data is handled in lowlevel.c
|
|
*/
|
|
|
|
#include <global.h>
|
|
#ifndef __CEXTRACT__
|
|
#include <sproto.h>
|
|
#include <sockproto.h>
|
|
#endif
|
|
|
|
#ifndef WIN32 /* ---win32 exclude unix headers */
|
|
#include <sys/types.h>
|
|
#include <sys/time.h>
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
#include <netdb.h>
|
|
#endif /* end win32 */
|
|
|
|
#ifdef HAVE_UNISTD_H
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_ARPA_INET_H
|
|
#include <arpa/inet.h>
|
|
#endif
|
|
|
|
#include <loader.h>
|
|
#include <newserver.h>
|
|
|
|
/*****************************************************************************
|
|
* Start of command dispatch area.
|
|
* The commands here are protocol commands.
|
|
****************************************************************************/
|
|
|
|
/* Either keep this near the start or end of the file so it is
|
|
* at least reasonablye easy to find.
|
|
* There are really 2 commands - those which are sent/received
|
|
* before player joins, and those happen after the player has joined.
|
|
* As such, we have function types that might be called, so
|
|
* we end up having 2 tables.
|
|
*/
|
|
|
|
/** Prototype for functions the client sends without player interaction. */
|
|
typedef void (*func_uint8_int_ns)(char *, int, socket_struct *);
|
|
|
|
/** Definition of a function the client sends without player interaction. */
|
|
struct client_cmd_mapping {
|
|
const char *cmdname; /**< Command name. */
|
|
const func_uint8_int_ns cmdproc; /**< Function to call. */
|
|
};
|
|
|
|
/** Prototype for functions used to handle player actions. */
|
|
typedef void (*func_uint8_int_pl)(char *, int, player *);
|
|
/** Definition of a function called in reaction to player's action. */
|
|
struct player_cmd_mapping {
|
|
const char *cmdname; /**< Command name. */
|
|
const func_uint8_int_pl cmdproc; /**< Function to call. */
|
|
const uint8 flag; /**< If set, the player must be in the ST_PLAYING state for this command to be available. */
|
|
};
|
|
|
|
/**
|
|
* Dispatch tables for the server.
|
|
*
|
|
* CmdMapping is the dispatch table for the server, used in handle_client,
|
|
* which gets called when the client has input. All commands called here
|
|
* use the same parameter form (char *data, int len, int clientnum.
|
|
* We do implicit casts, because the data that is being passed is
|
|
* unsigned (pretty much needs to be for binary data), however, most
|
|
* of these treat it only as strings, so it makes things easier
|
|
* to cast it here instead of a bunch of times in the function itself.
|
|
* flag is 1 if the player must be in the playing state to issue the
|
|
* command, 0 if they can issue it at any time.
|
|
*/
|
|
|
|
/** Commands sent by the client reacting to player's actions. */
|
|
static const struct player_cmd_mapping player_commands[] = {
|
|
{ "examine", examine_cmd, 1 },
|
|
{ "apply", apply_cmd, 1 },
|
|
{ "move", move_cmd, 1 },
|
|
{ "reply", reply_cmd, 0 },
|
|
{ "ncom", (func_uint8_int_pl)new_player_cmd, 1 },
|
|
{ "lookat", look_at_cmd, 1 },
|
|
{ "lock", (func_uint8_int_pl)lock_item_cmd, 1 },
|
|
{ "mark", (func_uint8_int_pl)mark_item_cmd, 1 },
|
|
{ "mapredraw", map_redraw_cmd, 0 }, /* Added: phil */
|
|
{ "inscribe", inscribe_scroll_cmd, 0 },
|
|
{ NULL, NULL, 0 } /* terminator */
|
|
};
|
|
|
|
/** Commands sent directly by client, when connecting or when needed. */
|
|
static const struct client_cmd_mapping client_commands[] = {
|
|
{ "addme", add_me_cmd },
|
|
{ "askface", send_face_cmd }, /* Added: phil */
|
|
{ "requestinfo", request_info_cmd },
|
|
{ "setfacemode", set_face_mode_cmd },
|
|
{ "setsound", set_sound_cmd },
|
|
{ "setup", set_up_cmd },
|
|
{ "version", version_cmd },
|
|
{ "toggleextendedinfos", toggle_extended_infos_cmd }, /*Added: tchize*/
|
|
{ "toggleextendedtext", toggle_extended_text_cmd }, /*Added: tchize*/
|
|
{ "asksmooth", ask_smooth_cmd }, /*Added: tchize (smoothing technologies)*/
|
|
{ NULL, NULL } /* terminator (I, II & III)*/
|
|
};
|
|
|
|
/**
|
|
* request_info_cmd is sort of a meta command. There is some specific
|
|
* request of information, but we call other functions to provide
|
|
* that information.
|
|
*/
|
|
void request_info_cmd(char *buf, int len, socket_struct *ns) {
|
|
char *params = NULL, *cp;
|
|
/* No match */
|
|
SockList sl;
|
|
|
|
/* Set up replyinfo before we modify any of the buffers - this is used
|
|
* if we don't find a match.
|
|
*/
|
|
SockList_Init(&sl);
|
|
SockList_AddString(&sl, "replyinfo ");
|
|
SockList_AddString(&sl, buf);
|
|
|
|
/* find the first space, make it null, and update the
|
|
* params pointer.
|
|
*/
|
|
for (cp = buf; *cp != '\0'; cp++)
|
|
if (*cp == ' ') {
|
|
*cp = '\0';
|
|
params = cp+1;
|
|
break;
|
|
}
|
|
if (!strcmp(buf, "image_info"))
|
|
send_image_info(ns, params);
|
|
else if (!strcmp(buf, "image_sums"))
|
|
send_image_sums(ns, params);
|
|
else if (!strcmp(buf, "skill_info"))
|
|
send_skill_info(ns, params);
|
|
else if (!strcmp(buf, "spell_paths"))
|
|
send_spell_paths(ns, params);
|
|
else if (!strcmp(buf, "exp_table"))
|
|
send_exp_table(ns, params);
|
|
else if (!strcmp(buf, "race_list"))
|
|
send_race_list(ns, params);
|
|
else if (!strcmp(buf, "race_info"))
|
|
send_race_info(ns, params);
|
|
else if (!strcmp(buf, "class_list"))
|
|
send_class_list(ns, params);
|
|
else if (!strcmp(buf, "class_info"))
|
|
send_class_info(ns, params);
|
|
else
|
|
Send_With_Handling(ns, &sl);
|
|
SockList_Term(&sl);
|
|
}
|
|
|
|
/**
|
|
* Handle client commands.
|
|
*
|
|
* We only get here once there is input, and only do basic connection checking.
|
|
*
|
|
* @param ns
|
|
* socket sending the command. Will be set to Ns_Dead if read error.
|
|
* @param pl
|
|
* player associated to the socket. If NULL, only commands in client_cmd_mapping will be checked.
|
|
*/
|
|
void handle_client(socket_struct *ns, player *pl) {
|
|
int len, i;
|
|
unsigned char *data;
|
|
|
|
/* Loop through this - maybe we have several complete packets here. */
|
|
while (1) {
|
|
/* If it is a player, and they don't have any speed left, we
|
|
* return, and will read in the data when they do have time.
|
|
*/
|
|
if (pl && pl->state == ST_PLAYING && pl->ob != NULL && pl->ob->speed_left < 0) {
|
|
return;
|
|
}
|
|
|
|
i = SockList_ReadPacket(ns->fd, &ns->inbuf, sizeof(ns->inbuf.buf)-1);
|
|
if (i < 0) {
|
|
#ifdef ESRV_DEBUG
|
|
LOG(llevDebug, "handle_client: Read error on connection player %s\n", (pl ? pl->ob->name : "None"));
|
|
#endif
|
|
/* Caller will take care of cleaning this up */
|
|
ns->status = Ns_Dead;
|
|
return;
|
|
}
|
|
/* Still dont have a full packet */
|
|
if (i == 0)
|
|
return;
|
|
|
|
SockList_NullTerminate(&ns->inbuf); /* Terminate buffer - useful for string data */
|
|
|
|
/* First, break out beginning word. There are at least
|
|
* a few commands that do not have any paremeters. If
|
|
* we get such a command, don't worry about trying
|
|
* to break it up.
|
|
*/
|
|
data = (unsigned char *)strchr((char *)ns->inbuf.buf+2, ' ');
|
|
if (data) {
|
|
*data = '\0';
|
|
data++;
|
|
len = ns->inbuf.len-(data-ns->inbuf.buf);
|
|
} else
|
|
len = 0;
|
|
|
|
for (i = 0; client_commands[i].cmdname != NULL; i++) {
|
|
if (strcmp((char *)ns->inbuf.buf+2, client_commands[i].cmdname) == 0) {
|
|
client_commands[i].cmdproc((char *)data, len, ns);
|
|
SockList_ResetRead(&ns->inbuf);
|
|
return;
|
|
}
|
|
}
|
|
/* Player must be in the playing state or the flag on the
|
|
* the command must be zero for the user to use the command -
|
|
* otherwise, a player cam save, be in the play_again state, and
|
|
* the map they were on getsswapped out, yet things that try to look
|
|
* at the map causes a crash. If the command is valid, but
|
|
* one they can't use, we still swallow it up.
|
|
*/
|
|
if (pl)
|
|
for (i = 0; player_commands[i].cmdname != NULL; i++) {
|
|
if (strcmp((char *)ns->inbuf.buf+2, player_commands[i].cmdname) == 0) {
|
|
if (pl->state == ST_PLAYING || player_commands[i].flag == 0)
|
|
player_commands[i].cmdproc((char *)data, len, pl);
|
|
SockList_ResetRead(&ns->inbuf);
|
|
return;
|
|
}
|
|
}
|
|
/* If we get here, we didn't find a valid command. Logging
|
|
* this might be questionable, because a broken client/malicious
|
|
* user could certainly send a whole bunch of invalid commands.
|
|
*/
|
|
LOG(llevDebug, "Bad command from client (%s)\n", ns->inbuf.buf+2);
|
|
SockList_ResetRead(&ns->inbuf);
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* Low level socket looping - select calls and watchdog udp packet
|
|
* sending.
|
|
*
|
|
******************************************************************************/
|
|
|
|
#ifdef WATCHDOG
|
|
/**
|
|
* Tell watchdog that we are still alive
|
|
*
|
|
* I put the function here since we should hopefully already be getting
|
|
* all the needed include files for socket support
|
|
*/
|
|
void watchdog(void) {
|
|
static int fd = -1;
|
|
static struct sockaddr_in insock;
|
|
|
|
if (fd == -1) {
|
|
struct protoent *protoent;
|
|
|
|
if ((protoent = getprotobyname("udp")) == NULL
|
|
|| (fd = socket(PF_INET, SOCK_DGRAM, protoent->p_proto)) == -1) {
|
|
return;
|
|
}
|
|
insock.sin_family = AF_INET;
|
|
insock.sin_port = htons((unsigned short)13325);
|
|
insock.sin_addr.s_addr = inet_addr("127.0.0.1");
|
|
}
|
|
sendto(fd, (void *)&fd, 1, 0, (struct sockaddr *)&insock, sizeof(insock));
|
|
}
|
|
#endif
|
|
|
|
extern unsigned long todtick;
|
|
|
|
/** Waits for new connection */
|
|
static void block_until_new_connection(void) {
|
|
struct timeval Timeout;
|
|
fd_set readfs;
|
|
int cycles;
|
|
|
|
LOG(llevInfo, "Waiting for connections...\n");
|
|
|
|
cycles = 1;
|
|
do {
|
|
/* Every minutes is a bit often for updates - especially if nothing is going
|
|
* on. This slows it down to every 6 minutes.
|
|
*/
|
|
cycles++;
|
|
if (cycles%2 == 0)
|
|
tick_the_clock();
|
|
|
|
FD_ZERO(&readfs);
|
|
FD_SET((uint32)init_sockets[0].fd, &readfs);
|
|
|
|
/* If fastclock is set, we need to seriously slow down the updates
|
|
* to the metaserver as well as watchdog. Do same for flush_old_maps() -
|
|
* that is time sensitive, so there is no good reason to call it 2000 times
|
|
* a second.
|
|
*/
|
|
if (settings.fastclock > 0) {
|
|
#ifdef WATCHDOG
|
|
if (cycles%120000 == 0) {
|
|
watchdog();
|
|
flush_old_maps();
|
|
}
|
|
#endif
|
|
if (cycles == 720000) {
|
|
metaserver_update();
|
|
cycles = 1;
|
|
}
|
|
Timeout.tv_sec = 0;
|
|
Timeout.tv_usec = 50;
|
|
} else {
|
|
Timeout.tv_sec = 60;
|
|
Timeout.tv_usec = 0;
|
|
if (cycles == 7) {
|
|
metaserver_update();
|
|
cycles = 1;
|
|
}
|
|
flush_old_maps();
|
|
}
|
|
} while (select(socket_info.max_filedescriptor, &readfs, NULL, NULL, &Timeout) == 0);
|
|
|
|
reset_sleep(); /* Or the game would go too fast */
|
|
}
|
|
|
|
/**
|
|
* Checks if file descriptor is valid.
|
|
*
|
|
* @param fd
|
|
* file descriptor to check.
|
|
* @return
|
|
* 1 if fd is valid, 0 else.
|
|
*/
|
|
static int is_fd_valid(int fd) {
|
|
#ifndef WIN32
|
|
return fcntl(fd, F_GETFL) != -1 || errno != EBADF;
|
|
#else
|
|
return 1;
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* This checks the sockets for input and exceptions, does the right thing.
|
|
*
|
|
* A bit of this code is grabbed out of socket.c
|
|
* There are 2 lists we need to look through - init_sockets is a list
|
|
*
|
|
*/
|
|
void do_server(void) {
|
|
int i, pollret;
|
|
fd_set tmp_read, tmp_exceptions, tmp_write;
|
|
struct sockaddr_in addr;
|
|
socklen_t addrlen = sizeof(struct sockaddr);
|
|
player *pl, *next;
|
|
char err[MAX_BUF];
|
|
|
|
#ifdef CS_LOGSTATS
|
|
if ((time(NULL)-cst_lst.time_start) >= CS_LOGTIME)
|
|
write_cs_stats();
|
|
#endif
|
|
|
|
FD_ZERO(&tmp_read);
|
|
FD_ZERO(&tmp_write);
|
|
FD_ZERO(&tmp_exceptions);
|
|
|
|
for (i = 0; i < socket_info.allocated_sockets; i++) {
|
|
if (init_sockets[i].status == Ns_Add && !is_fd_valid(init_sockets[i].fd)) {
|
|
LOG(llevError, "do_server: invalid waiting fd %d\n", i);
|
|
init_sockets[i].status = Ns_Dead;
|
|
}
|
|
if (init_sockets[i].status == Ns_Dead) {
|
|
free_newsocket(&init_sockets[i]);
|
|
init_sockets[i].status = Ns_Avail;
|
|
socket_info.nconns--;
|
|
} else if (init_sockets[i].status != Ns_Avail) {
|
|
FD_SET((uint32)init_sockets[i].fd, &tmp_read);
|
|
FD_SET((uint32)init_sockets[i].fd, &tmp_write);
|
|
FD_SET((uint32)init_sockets[i].fd, &tmp_exceptions);
|
|
}
|
|
}
|
|
|
|
/* Go through the players. Let the loop set the next pl value,
|
|
* since we may remove some
|
|
*/
|
|
for (pl = first_player; pl != NULL; ) {
|
|
if (pl->socket.status != Ns_Dead && !is_fd_valid(pl->socket.fd)) {
|
|
LOG(llevError, "do_server: invalid file descriptor for player %s [%s]: %d\n", (pl->ob && pl->ob->name) ? pl->ob->name : "(unnamed player?)", (pl->socket.host) ? pl->socket.host : "(unknown ip?)", pl->socket.fd);
|
|
pl->socket.status = Ns_Dead;
|
|
}
|
|
|
|
if (pl->socket.status == Ns_Dead) {
|
|
player *npl = pl->next;
|
|
|
|
save_player(pl->ob, 0);
|
|
if (!QUERY_FLAG(pl->ob, FLAG_REMOVED)) {
|
|
terminate_all_pets(pl->ob);
|
|
remove_ob(pl->ob);
|
|
}
|
|
leave(pl, 1);
|
|
final_free_player(pl);
|
|
pl = npl;
|
|
} else {
|
|
FD_SET((uint32)pl->socket.fd, &tmp_read);
|
|
FD_SET((uint32)pl->socket.fd, &tmp_write);
|
|
FD_SET((uint32)pl->socket.fd, &tmp_exceptions);
|
|
pl = pl->next;
|
|
}
|
|
}
|
|
|
|
if (socket_info.nconns == 1 && first_player == NULL)
|
|
block_until_new_connection();
|
|
|
|
/* Reset timeout each time, since some OS's will change the values on
|
|
* the return from select.
|
|
*/
|
|
socket_info.timeout.tv_sec = 0;
|
|
socket_info.timeout.tv_usec = 0;
|
|
|
|
pollret = select(socket_info.max_filedescriptor, &tmp_read, &tmp_write, &tmp_exceptions, &socket_info.timeout);
|
|
|
|
if (pollret == -1) {
|
|
LOG(llevError, "select failed: %s\n", strerror_local(errno, err, sizeof(err)));
|
|
return;
|
|
}
|
|
|
|
/* We need to do some of the processing below regardless */
|
|
/* if (!pollret) return;*/
|
|
|
|
/* Following adds a new connection */
|
|
if (pollret && FD_ISSET(init_sockets[0].fd, &tmp_read)) {
|
|
int newsocknum = 0;
|
|
|
|
#ifdef ESRV_DEBUG
|
|
LOG(llevDebug, "do_server: New Connection\n");
|
|
#endif
|
|
/* If this is the case, all sockets currently in used */
|
|
if (socket_info.allocated_sockets <= socket_info.nconns) {
|
|
init_sockets = realloc(init_sockets, sizeof(socket_struct)*(socket_info.nconns+1));
|
|
if (!init_sockets)
|
|
fatal(OUT_OF_MEMORY);
|
|
newsocknum = socket_info.allocated_sockets;
|
|
socket_info.allocated_sockets++;
|
|
init_sockets[newsocknum].faces_sent_len = nrofpixmaps;
|
|
init_sockets[newsocknum].faces_sent = calloc(1, nrofpixmaps*sizeof(*init_sockets[newsocknum].faces_sent));
|
|
if (!init_sockets[newsocknum].faces_sent)
|
|
fatal(OUT_OF_MEMORY);
|
|
init_sockets[newsocknum].status = Ns_Avail;
|
|
} else {
|
|
int j;
|
|
|
|
for (j = 1; j < socket_info.allocated_sockets; j++)
|
|
if (init_sockets[j].status == Ns_Avail) {
|
|
newsocknum = j;
|
|
break;
|
|
}
|
|
}
|
|
init_sockets[newsocknum].fd = accept(init_sockets[0].fd, (struct sockaddr *)&addr, &addrlen);
|
|
if (init_sockets[newsocknum].fd == -1) {
|
|
LOG(llevError, "accept failed: %s\n", strerror_local(errno, err, sizeof(err)));
|
|
} else {
|
|
char buf[MAX_BUF];
|
|
long ip;
|
|
socket_struct *ns;
|
|
|
|
ns = &init_sockets[newsocknum];
|
|
|
|
ip = ntohl(addr.sin_addr.s_addr);
|
|
snprintf(buf, sizeof(buf), "%ld.%ld.%ld.%ld", (ip>>24)&255, (ip>>16)&255, (ip>>8)&255, ip&255);
|
|
|
|
if (checkbanned(NULL, buf)) {
|
|
LOG(llevInfo, "Banned host tried to connect: [%s]\n", buf);
|
|
close(init_sockets[newsocknum].fd);
|
|
init_sockets[newsocknum].fd = -1;
|
|
} else {
|
|
init_connection(ns, buf);
|
|
socket_info.nconns++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Check for any exceptions/input on the sockets */
|
|
if (pollret)
|
|
for (i = 1; i < socket_info.allocated_sockets; i++) {
|
|
if (init_sockets[i].status == Ns_Avail)
|
|
continue;
|
|
if (FD_ISSET(init_sockets[i].fd, &tmp_exceptions)) {
|
|
free_newsocket(&init_sockets[i]);
|
|
init_sockets[i].status = Ns_Avail;
|
|
socket_info.nconns--;
|
|
continue;
|
|
}
|
|
if (FD_ISSET(init_sockets[i].fd, &tmp_read)) {
|
|
handle_client(&init_sockets[i], NULL);
|
|
}
|
|
if (FD_ISSET(init_sockets[i].fd, &tmp_write)) {
|
|
init_sockets[i].can_write = 1;
|
|
}
|
|
}
|
|
|
|
/* This does roughly the same thing, but for the players now */
|
|
for (pl = first_player; pl != NULL; pl = next) {
|
|
next = pl->next;
|
|
if (pl->socket.status == Ns_Dead)
|
|
continue;
|
|
|
|
if (FD_ISSET(pl->socket.fd, &tmp_write)) {
|
|
if (!pl->socket.can_write) {
|
|
pl->socket.can_write = 1;
|
|
write_socket_buffer(&pl->socket);
|
|
}
|
|
/* if we get an error on the write_socket buffer, no reason to
|
|
* continue on this socket.
|
|
*/
|
|
if (pl->socket.status == Ns_Dead)
|
|
continue;
|
|
} else
|
|
pl->socket.can_write = 0;
|
|
|
|
if (FD_ISSET(pl->socket.fd, &tmp_exceptions)) {
|
|
save_player(pl->ob, 0);
|
|
if (!QUERY_FLAG(pl->ob, FLAG_REMOVED)) {
|
|
terminate_all_pets(pl->ob);
|
|
remove_ob(pl->ob);
|
|
}
|
|
leave(pl, 1);
|
|
final_free_player(pl);
|
|
} else {
|
|
handle_client(&pl->socket, pl);
|
|
|
|
/* There seems to be rare cases where next points to a removed/freed player.
|
|
* My belief is that this player does something (shout, move, whatever)
|
|
* that causes data to be sent to the next player on the list, but
|
|
* that player is defunct, so the socket codes removes that player.
|
|
* End result is that next now points at the removed player, and
|
|
* that has garbage data so we crash. So update the next pointer
|
|
* while pl is still valid. MSW 2007-04-21
|
|
*/
|
|
next = pl->next;
|
|
|
|
|
|
/* If the player has left the game, then the socket status
|
|
* will be set to this be the leave function. We don't
|
|
* need to call leave again, as it has already been called
|
|
* once.
|
|
*/
|
|
if (pl->socket.status == Ns_Dead) {
|
|
save_player(pl->ob, 0);
|
|
if (!QUERY_FLAG(pl->ob, FLAG_REMOVED)) {
|
|
terminate_all_pets(pl->ob);
|
|
remove_ob(pl->ob);
|
|
}
|
|
leave(pl, 1);
|
|
final_free_player(pl);
|
|
} else {
|
|
/* Update the players stats once per tick. More efficient than
|
|
* sending them whenever they change, and probably just as useful
|
|
*/
|
|
esrv_update_stats(pl);
|
|
if (pl->last_weight != -1 && pl->last_weight != WEIGHT(pl->ob)) {
|
|
esrv_update_item(UPD_WEIGHT, pl->ob, pl->ob);
|
|
if (pl->last_weight != WEIGHT(pl->ob))
|
|
LOG(llevError, "esrv_update_item(UPD_WEIGHT) did not set player weight: is %lu, should be %lu\n", (unsigned long)pl->last_weight, (unsigned long)WEIGHT(pl->ob));
|
|
}
|
|
/* draw_client_map does sanity checking that map is
|
|
* valid, so don't do it here.
|
|
*/
|
|
draw_client_map(pl->ob);
|
|
if (pl->socket.update_look)
|
|
esrv_draw_look(pl->ob);
|
|
if (pl->socket.tick)
|
|
send_tick(pl);
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|