From a2566b3ebd93e0bbc55a920a7be08054a9377f11 Mon Sep 17 00:00:00 2001 From: Magnus Auvinen Date: Sat, 15 Dec 2007 10:24:49 +0000 Subject: cleaned up code structure a bit --- src/engine/server/es_server.c | 901 ++++++++++++++++++++++++++++++++++++++++++ src/engine/server/server.c | 901 ------------------------------------------ 2 files changed, 901 insertions(+), 901 deletions(-) create mode 100644 src/engine/server/es_server.c delete mode 100644 src/engine/server/server.c (limited to 'src/engine/server') diff --git a/src/engine/server/es_server.c b/src/engine/server/es_server.c new file mode 100644 index 00000000..00100b10 --- /dev/null +++ b/src/engine/server/es_server.c @@ -0,0 +1,901 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include +#include +#include + +#include + +static SNAPBUILD builder; + +static int64 game_start_time; +static int current_tick = 0; + +static int browseinfo_gametype = -1; +static int browseinfo_progression = -1; + +static int64 lastheartbeat; +static NETADDR4 master_server; + +static char current_map[64]; +static int current_map_crc; + +void *snap_new_item(int type, int id, int size) +{ + dbg_assert(type >= 0 && type <=0xffff, "incorrect type"); + dbg_assert(id >= 0 && id <=0xffff, "incorrect id"); + return snapbuild_new_item(&builder, type, id, size); +} + +typedef struct +{ + short next; + short state; /* 0 = free, 1 = alloced, 2 = timed */ + int timeout; +} SNAP_ID; + +static const int MAX_IDS = 16*1024; /* should be lowered */ +static SNAP_ID snap_ids[16*1024]; +static int snap_first_free_id; +static int snap_first_timed_id; +static int snap_last_timed_id; +static int snap_id_usage; +static int snap_id_inusage; +static int snap_id_inited = 0; + + +enum +{ + SRVCLIENT_STATE_EMPTY = 0, + SRVCLIENT_STATE_CONNECTING, + SRVCLIENT_STATE_READY, + SRVCLIENT_STATE_INGAME +}; + +typedef struct +{ + int data[MAX_INPUT_SIZE]; + int pred_tick; /* tick that the client predicted for the input */ + int game_tick; /* the tick that was chosen for the input */ + int64 timeleft; /* how much time in ms there were left before this should be applied */ +} CLIENT_INPUT; + +/* */ +typedef struct +{ + /* connection state info */ + int state; + int latency; + + int last_acked_snapshot; + SNAPSTORAGE snapshots; + + CLIENT_INPUT inputs[200]; /* TODO: handle input better */ + int current_input; + + char name[MAX_NAME_LENGTH]; + char clan[MAX_CLANNAME_LENGTH]; +} CLIENT; + +static CLIENT clients[MAX_CLIENTS]; +static NETSERVER *net; + +static void snap_init_id() +{ + int i; + for(i = 0; i < MAX_IDS; i++) + { + snap_ids[i].next = i+1; + snap_ids[i].state = 0; + } + + snap_ids[MAX_IDS-1].next = -1; + snap_first_free_id = 0; + snap_first_timed_id = -1; + snap_last_timed_id = -1; + snap_id_usage = 0; + snap_id_inusage = 0; + + snap_id_inited = 1; +} + +static void snap_remove_first_timeout() +{ + int next_timed = snap_ids[snap_first_timed_id].next; + + /* add it to the free list */ + snap_ids[snap_first_timed_id].next = snap_first_free_id; + snap_ids[snap_first_timed_id].state = 0; + snap_first_free_id = snap_first_timed_id; + + /* remove it from the timed list */ + snap_first_timed_id = next_timed; + if(snap_first_timed_id == -1) + snap_last_timed_id = -1; + + snap_id_usage--; +} + +int snap_new_id() +{ + int id; + int64 now = time_get(); + dbg_assert(snap_id_inited == 1, "requesting id too soon"); + + + /* process timed ids */ + while(snap_first_timed_id != -1 && snap_ids[snap_first_timed_id].timeout < now) + snap_remove_first_timeout(); + + id = snap_first_free_id; + dbg_assert(id != -1, "id error"); + snap_first_free_id = snap_ids[snap_first_free_id].next; + snap_ids[id].state = 1; + snap_id_usage++; + snap_id_inusage++; + return id; +} + +void snap_timeout_ids() +{ + /* process timed ids */ + while(snap_first_timed_id != -1) + snap_remove_first_timeout(); +} + +void snap_free_id(int id) +{ + dbg_assert(snap_ids[id].state == 1, "id is not alloced"); + + snap_id_inusage--; + snap_ids[id].state = 2; + snap_ids[id].timeout = time_get()+time_freq()*5; + snap_ids[id].next = -1; + + if(snap_last_timed_id != -1) + { + snap_ids[snap_last_timed_id].next = id; + snap_last_timed_id = id; + } + else + { + snap_first_timed_id = id; + snap_last_timed_id = id; + } +} + +const char *server_clientname(int client_id) +{ + if(client_id < 0 || client_id > MAX_CLIENTS || clients[client_id].state < SRVCLIENT_STATE_READY) + return "(invalid client)"; + return clients[client_id].name; +} + +void server_setclientname(int client_id, const char *name) +{ + if(client_id < 0 || client_id > MAX_CLIENTS || clients[client_id].state < SRVCLIENT_STATE_READY) + return; + strncpy(clients[client_id].name, name, MAX_NAME_LENGTH); +} + +void server_setbrowseinfo(int game_type, int progression) +{ + browseinfo_gametype = game_type; + browseinfo_progression = progression; +} + +int server_tick() +{ + return current_tick; +} + +int64 server_tick_start_time(int tick) +{ + return game_start_time + (time_freq()*tick)/SERVER_TICK_SPEED; +} + +int server_tickspeed() +{ + return SERVER_TICK_SPEED; +} + +int server_init() +{ + int i; + for(i = 0; i < MAX_CLIENTS; i++) + { + clients[i].state = SRVCLIENT_STATE_EMPTY; + clients[i].name[0] = 0; + clients[i].clan[0] = 0; + snapstorage_init(&clients[i].snapshots); + } + + current_tick = 0; + + return 0; +} + +int server_getclientinfo(int client_id, CLIENT_INFO *info) +{ + dbg_assert(client_id >= 0 && client_id < MAX_CLIENTS, "client_id is not valid"); + dbg_assert(info != 0, "info can not be null"); + + if(clients[client_id].state == SRVCLIENT_STATE_INGAME) + { + info->name = clients[client_id].name; + info->latency = clients[client_id].latency; + return 1; + } + return 0; +} + + +int server_send_msg(int client_id) +{ + const MSG_INFO *info = msg_get_info(); + NETPACKET packet; + mem_zero(&packet, sizeof(NETPACKET)); + + packet.client_id = client_id; + packet.data = info->data; + packet.data_size = info->size; + + if(info->flags&MSGFLAG_VITAL) + packet.flags = PACKETFLAG_VITAL; + + if(client_id == -1) + { + /* broadcast */ + int i; + for(i = 0; i < MAX_CLIENTS; i++) + if(clients[i].state == SRVCLIENT_STATE_INGAME) + { + packet.client_id = i; + netserver_send(net, &packet); + } + } + else + netserver_send(net, &packet); + return 0; +} + +static void server_do_snap() +{ + int i, k; + mods_presnap(); + + for(i = 0; i < MAX_CLIENTS; i++) + { + if(clients[i].state == SRVCLIENT_STATE_INGAME) + { + char data[MAX_SNAPSHOT_SIZE]; + char deltadata[MAX_SNAPSHOT_SIZE]; + char compdata[MAX_SNAPSHOT_SIZE]; + int snapshot_size; + int crc; + static SNAPSHOT emptysnap; + SNAPSHOT *deltashot = &emptysnap; + int deltashot_size; + int delta_tick = -1; + int input_predtick = -1; + int64 timeleft = 0; + int deltasize; + + snapbuild_init(&builder); + mods_snap(i); + + /* finish snapshot */ + snapshot_size = snapbuild_finish(&builder, data); + crc = snapshot_crc((SNAPSHOT*)data); + + /* remove old snapshos */ + /* keep 1 seconds worth of snapshots */ + snapstorage_purge_until(&clients[i].snapshots, current_tick-SERVER_TICK_SPEED); + + /* save it the snapshot */ + snapstorage_add(&clients[i].snapshots, current_tick, time_get(), snapshot_size, data); + + /* find snapshot that we can preform delta against */ + emptysnap.data_size = 0; + emptysnap.num_items = 0; + + { + deltashot_size = snapstorage_get(&clients[i].snapshots, clients[i].last_acked_snapshot, 0, &deltashot); + if(deltashot_size >= 0) + delta_tick = clients[i].last_acked_snapshot; + } + + for(k = 0; k < 200; k++) /* TODO: do this better */ + { + if(clients[i].inputs[k].game_tick == current_tick) + { + timeleft = clients[i].inputs[k].timeleft; + input_predtick = clients[i].inputs[k].pred_tick; + break; + } + } + + /* create delta */ + deltasize = snapshot_create_delta(deltashot, (SNAPSHOT*)data, deltadata); + + if(deltasize) + { + /* compress it */ + unsigned char intdata[MAX_SNAPSHOT_SIZE]; + int intsize = intpack_compress(deltadata, deltasize, intdata); + int snapshot_size = zerobit_compress(intdata, intsize, compdata); + const int max_size = MAX_SNAPSHOT_PACKSIZE; + int numpackets = (snapshot_size+max_size-1)/max_size; + int n, left; + + for(n = 0, left = snapshot_size; left; n++) + { + int chunk = left < max_size ? left : max_size; + left -= chunk; + + if(numpackets == 1) + msg_pack_start_system(NETMSG_SNAPSINGLE, 0); + else + msg_pack_start_system(NETMSG_SNAP, 0); + + msg_pack_int(current_tick); + msg_pack_int(current_tick-delta_tick); /* compressed with */ + msg_pack_int(input_predtick); + msg_pack_int((timeleft*1000)/time_freq()); + + if(numpackets != 1) + { + msg_pack_int(numpackets); + msg_pack_int(n); + } + + msg_pack_int(crc); + msg_pack_int(chunk); + msg_pack_raw(&compdata[n*max_size], chunk); + msg_pack_end(); + server_send_msg(i); + } + } + else + { + msg_pack_start_system(NETMSG_SNAPEMPTY, 0); + msg_pack_int(current_tick); + msg_pack_int(current_tick-delta_tick); /* compressed with */ + msg_pack_int(input_predtick); + msg_pack_int((timeleft*1000)/time_freq()); + msg_pack_end(); + server_send_msg(i); + } + } + } + + mods_postsnap(); +} + + +static int new_client_callback(int cid, void *user) +{ + int i; + clients[cid].state = SRVCLIENT_STATE_CONNECTING; + clients[cid].name[0] = 0; + clients[cid].clan[0] = 0; + + /* reset input */ + for(i = 0; i < 200; i++) + { + clients[cid].inputs[i].game_tick = -1; + clients[cid].inputs[i].pred_tick = -1; + } + clients[cid].current_input = 0; + + snapstorage_purge_all(&clients[cid].snapshots); + clients[cid].last_acked_snapshot = -1; + return 0; +} + +static int del_client_callback(int cid, void *user) +{ + /* notify the mod about the drop */ + if(clients[cid].state == SRVCLIENT_STATE_READY || + clients[cid].state == SRVCLIENT_STATE_INGAME) + { + mods_client_drop(cid); + } + + clients[cid].state = SRVCLIENT_STATE_EMPTY; + clients[cid].name[0] = 0; + clients[cid].clan[0] = 0; + snapstorage_purge_all(&clients[cid].snapshots); + return 0; +} + + +static void server_send_map(int cid) +{ + msg_pack_start_system(NETMSG_MAP, MSGFLAG_VITAL); + msg_pack_string(config.sv_map, 0); + msg_pack_int(current_map_crc); + msg_pack_end(); + server_send_msg(cid); +} + +static void server_send_heartbeat() +{ + static unsigned char data[sizeof(SERVERBROWSE_HEARTBEAT) + 2]; + unsigned short port = config.sv_port; + NETPACKET packet; + + mem_copy(data, SERVERBROWSE_HEARTBEAT, sizeof(SERVERBROWSE_HEARTBEAT)); + + packet.client_id = -1; + packet.address = master_server; + packet.flags = PACKETFLAG_CONNLESS; + packet.data_size = sizeof(SERVERBROWSE_HEARTBEAT) + 2; + packet.data = &data; + + /* supply the set port that the master can use if it has problems */ + if(config.sv_external_port) + port = config.sv_external_port; + data[sizeof(SERVERBROWSE_HEARTBEAT)] = port >> 8; + data[sizeof(SERVERBROWSE_HEARTBEAT)+1] = port&0xff; + + netserver_send(net, &packet); +} + +static void server_process_client_packet(NETPACKET *packet) +{ + int cid = packet->client_id; + int sys; + int msg = msg_unpack_start(packet->data, packet->data_size, &sys); + if(sys) + { + /* system message */ + if(msg == NETMSG_INFO) + { + char version[64]; + const char *password; + strncpy(version, msg_unpack_string(), 64); + if(strcmp(version, mods_net_version()) != 0) + { + /* OH FUCK! wrong version, drop him */ + char reason[256]; + sprintf(reason, "wrong version. server is running %s.", mods_net_version()); + netserver_drop(net, cid, reason); + return; + } + + strncpy(clients[cid].name, msg_unpack_string(), MAX_NAME_LENGTH); + strncpy(clients[cid].clan, msg_unpack_string(), MAX_CLANNAME_LENGTH); + password = msg_unpack_string(); + + if(config.password[0] != 0 && strcmp(config.password, password) != 0) + { + /* wrong password */ + netserver_drop(net, cid, "wrong password"); + return; + } + + server_send_map(cid); + } + else if(msg == NETMSG_READY) + { + if(clients[cid].state == SRVCLIENT_STATE_CONNECTING) + { + dbg_msg("server", "player is ready. cid=%x", cid); + clients[cid].state = SRVCLIENT_STATE_READY; + mods_connected(cid); + } + } + else if(msg == NETMSG_ENTERGAME) + { + if(clients[cid].state != SRVCLIENT_STATE_INGAME) + { + dbg_msg("server", "player as entered the game. cid=%x", cid); + clients[cid].state = SRVCLIENT_STATE_INGAME; + mods_client_enter(cid); + } + } + else if(msg == NETMSG_INPUT) + { + int tick, size, i; + CLIENT_INPUT *input; + int64 tagtime; + + clients[cid].last_acked_snapshot = msg_unpack_int(); + if(snapstorage_get(&clients[cid].snapshots, clients[cid].last_acked_snapshot, &tagtime, 0) >= 0) + clients[cid].latency = (int)(((time_get()-tagtime)*1000)/time_freq()); + + tick = msg_unpack_int(); + size = msg_unpack_int(); + + input = &clients[cid].inputs[clients[cid].current_input]; + input->timeleft = server_tick_start_time(tick)-time_get(); + input->pred_tick = tick; + + if(tick <= server_tick()) + { + /* TODO: how should we handle this */ + dbg_msg("server", "input got in late for=%d cur=%d", tick, server_tick()); + tick = server_tick()+1; + } + + input->game_tick = tick; + + for(i = 0; i < size/4; i++) + input->data[i] = msg_unpack_int(); + + clients[cid].current_input++; + clients[cid].current_input %= 200; + } + else if(msg == NETMSG_CMD) + { + const char *pw = msg_unpack_string(); + const char *cmd = msg_unpack_string(); + if(config.rcon_password[0] != 0 && strcmp(pw, config.rcon_password) == 0) + { + dbg_msg("server", "cid=%d rcon='%s'", cid, cmd); + config_set(cmd); + } + } + else + { + dbg_msg("server", "strange message cid=%d msg=%d data_size=%d", cid, msg, packet->data_size); + } + } + else + { + /* game message */ + mods_message(msg, cid); + } +} + +static void server_send_serverinfo(NETADDR4 *addr) +{ + NETPACKET packet; + PACKER p; + char buf[128]; + + /* count the players */ + int c = 0; + int i; + for(i = 0; i < MAX_CLIENTS; i++) + { + if(clients[i].state != SRVCLIENT_STATE_EMPTY) + c++; + } + + packer_reset(&p); + packer_add_raw(&p, SERVERBROWSE_INFO, sizeof(SERVERBROWSE_INFO)); + packer_add_string(&p, mods_net_version(), 32); + packer_add_string(&p, config.sv_name, 64); + packer_add_string(&p, config.sv_map, 32); + + /* gametype */ + sprintf(buf, "%d", browseinfo_gametype); + packer_add_string(&p, buf, 2); + + /* flags */ + i = 0; + if(strlen(config.password)) + i |= 1; + sprintf(buf, "%d", i); + packer_add_string(&p, buf, 2); + + /* progression */ + sprintf(buf, "%d", browseinfo_progression); + packer_add_string(&p, buf, 4); + + sprintf(buf, "%d", c); packer_add_string(&p, buf, 3); /* num players */ + sprintf(buf, "%d", netserver_max_clients(net)); packer_add_string(&p, buf, 3); /* max players */ + + for(i = 0; i < MAX_CLIENTS; i++) + { + if(clients[i].state != SRVCLIENT_STATE_EMPTY) + { + packer_add_string(&p, clients[i].name, 48); /* player name */ + packer_add_string(&p, "0", 6); /* score */ + } + } + + + packet.client_id = -1; + packet.address = *addr; + packet.flags = PACKETFLAG_CONNLESS; + packet.data_size = packer_size(&p); + packet.data = packer_data(&p); + netserver_send(net, &packet); +} + + +static void server_send_fwcheckresponse(NETADDR4 *addr) +{ + NETPACKET packet; + packet.client_id = -1; + packet.address = *addr; + packet.flags = PACKETFLAG_CONNLESS; + packet.data_size = sizeof(SERVERBROWSE_FWRESPONSE); + packet.data = SERVERBROWSE_FWRESPONSE; + netserver_send(net, &packet); +} + +static void server_pump_network() +{ + NETPACKET packet; + + netserver_update(net); + + /* process packets */ + while(netserver_recv(net, &packet)) + { + if(packet.client_id == -1) + { + /* stateless */ + if(packet.data_size == sizeof(SERVERBROWSE_GETINFO) && + memcmp(packet.data, SERVERBROWSE_GETINFO, sizeof(SERVERBROWSE_GETINFO)) == 0) + { + server_send_serverinfo(&packet.address); + } + else if(packet.data_size == sizeof(SERVERBROWSE_FWCHECK) && + memcmp(packet.data, SERVERBROWSE_FWCHECK, sizeof(SERVERBROWSE_FWCHECK)) == 0) + { + server_send_fwcheckresponse(&packet.address); + } + else if(packet.data_size == sizeof(SERVERBROWSE_FWOK) && + memcmp(packet.data, SERVERBROWSE_FWOK, sizeof(SERVERBROWSE_FWOK)) == 0) + { + if(config.debug) + dbg_msg("server", "no firewall/nat problems detected"); + } + else if(packet.data_size == sizeof(SERVERBROWSE_FWERROR) && + memcmp(packet.data, SERVERBROWSE_FWERROR, sizeof(SERVERBROWSE_FWERROR)) == 0) + { + dbg_msg("server", "ERROR: the master server reports that clients can not connect to this server."); + dbg_msg("server", "ERROR: configure your firewall/nat to let trough udp on port %d.", config.sv_port); + } + } + else + server_process_client_packet(&packet); + } +} + +static int server_load_map(const char *mapname) +{ + DATAFILE *df; + char buf[512]; + sprintf(buf, "data/maps/%s.map", mapname); + df = datafile_load(buf); + if(!df) + return 0; + + /* reinit snapshot ids */ + snap_timeout_ids(); + + /* get the crc of the map */ + current_map_crc = datafile_crc(buf); + dbg_msg("server", "%s crc is %08x", buf, current_map_crc); + + strcpy(current_map, mapname); + map_set(df); + return 1; +} + + +static int server_run() +{ + NETADDR4 bindaddr; + + net_init(); + + snap_init_id(); + + /* load map */ + if(!server_load_map(config.sv_map)) + { + dbg_msg("server", "failed to load map. mapname='%s'", config.sv_map); + return -1; + } + + /* start server */ + if(strlen(config.sv_bindaddr) && net_host_lookup(config.sv_bindaddr, config.sv_port, &bindaddr) != 0) + { + /* sweet! */ + } + else + { + mem_zero(&bindaddr, sizeof(bindaddr)); + bindaddr.port = config.sv_port; + } + + net = netserver_open(bindaddr, config.sv_max_clients, 0); + if(!net) + { + dbg_msg("server", "couldn't open socket. port might already be in use"); + return -1; + } + + netserver_set_callbacks(net, new_client_callback, del_client_callback, 0); + + dbg_msg("server", "server name is '%s'", config.sv_name); + dbg_msg("server", "masterserver is '%s'", config.masterserver); + if(net_host_lookup(config.masterserver, MASTERSERVER_PORT, &master_server) != 0) + { + /* TODO: fix me */ + /*master_server = netaddr4(0, 0, 0, 0, 0); */ + } + + mods_init(); + dbg_msg("server", "version %s", mods_net_version()); + + /* start game */ + { + int64 time_per_heartbeat = time_freq() * 30; + int64 reporttime = time_get(); + int reportinterval = 3; + + int64 simulationtime = 0; + int64 snaptime = 0; + int64 networktime = 0; + int64 totaltime = 0; + + lastheartbeat = 0; + game_start_time = time_get(); + + if(config.debug) + dbg_msg("server", "baseline memory usage %dk", mem_allocated()/1024); + + while(1) + { + int64 t = time_get(); + /* load new map TODO: don't poll this */ + if(strcmp(config.sv_map, current_map) != 0 || config.sv_map_reload) + { + config.sv_map_reload = 0; + + /* load map */ + if(server_load_map(config.sv_map)) + { + int c; + + /* new map loaded */ + mods_shutdown(); + + for(c = 0; c < MAX_CLIENTS; c++) + { + if(clients[c].state == SRVCLIENT_STATE_EMPTY) + continue; + + server_send_map(c); + clients[c].state = SRVCLIENT_STATE_CONNECTING; + clients[c].last_acked_snapshot = -1; + snapstorage_purge_all(&clients[c].snapshots); + } + + game_start_time = time_get(); + current_tick = 0; + mods_init(); + } + else + { + dbg_msg("server", "failed to load map. mapname='%s'", config.sv_map); + config_set_sv_map(&config, current_map); + } + } + + if(t > server_tick_start_time(current_tick+1)) + { + current_tick++; + + /* apply new input */ + { + int c, i; + for(c = 0; c < MAX_CLIENTS; c++) + { + if(clients[c].state == SRVCLIENT_STATE_EMPTY) + continue; + for(i = 0; i < 200; i++) + { + if(clients[c].inputs[i].game_tick == server_tick()) + { + mods_client_input(c, clients[c].inputs[i].data); + break; + } + } + } + } + + /* progress game */ + { + int64 start = time_get(); + mods_tick(); + simulationtime += time_get()-start; + } + + /* snap game */ + if(config.sv_bandwidth_mode == 0 || + (config.sv_bandwidth_mode == 1 && current_tick%2) || + (config.sv_bandwidth_mode == 2 && (current_tick%3) == 0 )) + /* if(current_tick&1) */ + { + int64 start = time_get(); + server_do_snap(); + snaptime += time_get()-start; + } + } + + if(config.sv_sendheartbeats) + { + if (t > lastheartbeat+time_per_heartbeat) + { + server_send_heartbeat(); + lastheartbeat = t+time_per_heartbeat; + } + } + + { + int64 start = time_get(); + server_pump_network(); + networktime += time_get()-start; + } + + if(reporttime < time_get()) + { + if(config.debug) + { + static NETSTATS prev_stats; + NETSTATS stats; + netserver_stats(net, &stats); + dbg_msg("server", "sim=%.02fms snap=%.02fms net=%.02fms tot=%.02fms load=%.02f%%", + (simulationtime/reportinterval)/(double)time_freq()*1000, + (snaptime/reportinterval)/(double)time_freq()*1000, + (networktime/reportinterval)/(double)time_freq()*1000, + (totaltime/reportinterval)/(double)time_freq()*1000, + (totaltime)/reportinterval/(double)time_freq()*100.0f); + + dbg_msg("server", "send=%8d recv=%8d", + (stats.send_bytes - prev_stats.send_bytes)/reportinterval, + (stats.recv_bytes - prev_stats.recv_bytes)/reportinterval); + + prev_stats = stats; + } + + simulationtime = 0; + snaptime = 0; + networktime = 0; + totaltime = 0; + + reporttime += time_freq()*reportinterval; + } + totaltime += time_get()-t; + thread_sleep(1); + } + } + + mods_shutdown(); + map_unload(); + + return 0; +} + + +int main(int argc, char **argv) +{ + dbg_msg("server", "starting..."); + engine_init("Teewars", argc, argv); + server_run(); + return 0; +} + diff --git a/src/engine/server/server.c b/src/engine/server/server.c deleted file mode 100644 index d2ef39ed..00000000 --- a/src/engine/server/server.c +++ /dev/null @@ -1,901 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#include -#include -#include - -#include -#include -#include -#include - -#include -#include - -#include - -#include -#include -#include -#include - -#include - -static SNAPBUILD builder; - -static int64 game_start_time; -static int current_tick = 0; - -static int browseinfo_gametype = -1; -static int browseinfo_progression = -1; - -static int64 lastheartbeat; -static NETADDR4 master_server; - -static char current_map[64]; -static int current_map_crc; - -void *snap_new_item(int type, int id, int size) -{ - dbg_assert(type >= 0 && type <=0xffff, "incorrect type"); - dbg_assert(id >= 0 && id <=0xffff, "incorrect id"); - return snapbuild_new_item(&builder, type, id, size); -} - -typedef struct -{ - short next; - short state; /* 0 = free, 1 = alloced, 2 = timed */ - int timeout; -} SNAP_ID; - -static const int MAX_IDS = 16*1024; /* should be lowered */ -static SNAP_ID snap_ids[16*1024]; -static int snap_first_free_id; -static int snap_first_timed_id; -static int snap_last_timed_id; -static int snap_id_usage; -static int snap_id_inusage; -static int snap_id_inited = 0; - - -enum -{ - SRVCLIENT_STATE_EMPTY = 0, - SRVCLIENT_STATE_CONNECTING, - SRVCLIENT_STATE_READY, - SRVCLIENT_STATE_INGAME -}; - -typedef struct -{ - int data[MAX_INPUT_SIZE]; - int pred_tick; /* tick that the client predicted for the input */ - int game_tick; /* the tick that was chosen for the input */ - int64 timeleft; /* how much time in ms there were left before this should be applied */ -} CLIENT_INPUT; - -/* */ -typedef struct -{ - /* connection state info */ - int state; - int latency; - - int last_acked_snapshot; - SNAPSTORAGE snapshots; - - CLIENT_INPUT inputs[200]; /* TODO: handle input better */ - int current_input; - - char name[MAX_NAME_LENGTH]; - char clan[MAX_CLANNAME_LENGTH]; -} CLIENT; - -static CLIENT clients[MAX_CLIENTS]; -static NETSERVER *net; - -static void snap_init_id() -{ - int i; - for(i = 0; i < MAX_IDS; i++) - { - snap_ids[i].next = i+1; - snap_ids[i].state = 0; - } - - snap_ids[MAX_IDS-1].next = -1; - snap_first_free_id = 0; - snap_first_timed_id = -1; - snap_last_timed_id = -1; - snap_id_usage = 0; - snap_id_inusage = 0; - - snap_id_inited = 1; -} - -static void snap_remove_first_timeout() -{ - int next_timed = snap_ids[snap_first_timed_id].next; - - /* add it to the free list */ - snap_ids[snap_first_timed_id].next = snap_first_free_id; - snap_ids[snap_first_timed_id].state = 0; - snap_first_free_id = snap_first_timed_id; - - /* remove it from the timed list */ - snap_first_timed_id = next_timed; - if(snap_first_timed_id == -1) - snap_last_timed_id = -1; - - snap_id_usage--; -} - -int snap_new_id() -{ - int id; - int64 now = time_get(); - dbg_assert(snap_id_inited == 1, "requesting id too soon"); - - - /* process timed ids */ - while(snap_first_timed_id != -1 && snap_ids[snap_first_timed_id].timeout < now) - snap_remove_first_timeout(); - - id = snap_first_free_id; - dbg_assert(id != -1, "id error"); - snap_first_free_id = snap_ids[snap_first_free_id].next; - snap_ids[id].state = 1; - snap_id_usage++; - snap_id_inusage++; - return id; -} - -void snap_timeout_ids() -{ - /* process timed ids */ - while(snap_first_timed_id != -1) - snap_remove_first_timeout(); -} - -void snap_free_id(int id) -{ - dbg_assert(snap_ids[id].state == 1, "id is not alloced"); - - snap_id_inusage--; - snap_ids[id].state = 2; - snap_ids[id].timeout = time_get()+time_freq()*5; - snap_ids[id].next = -1; - - if(snap_last_timed_id != -1) - { - snap_ids[snap_last_timed_id].next = id; - snap_last_timed_id = id; - } - else - { - snap_first_timed_id = id; - snap_last_timed_id = id; - } -} - -const char *server_clientname(int client_id) -{ - if(client_id < 0 || client_id > MAX_CLIENTS || clients[client_id].state < SRVCLIENT_STATE_READY) - return "(invalid client)"; - return clients[client_id].name; -} - -void server_setclientname(int client_id, const char *name) -{ - if(client_id < 0 || client_id > MAX_CLIENTS || clients[client_id].state < SRVCLIENT_STATE_READY) - return; - strncpy(clients[client_id].name, name, MAX_NAME_LENGTH); -} - -void server_setbrowseinfo(int game_type, int progression) -{ - browseinfo_gametype = game_type; - browseinfo_progression = progression; -} - -int server_tick() -{ - return current_tick; -} - -int64 server_tick_start_time(int tick) -{ - return game_start_time + (time_freq()*tick)/SERVER_TICK_SPEED; -} - -int server_tickspeed() -{ - return SERVER_TICK_SPEED; -} - -int server_init() -{ - int i; - for(i = 0; i < MAX_CLIENTS; i++) - { - clients[i].state = SRVCLIENT_STATE_EMPTY; - clients[i].name[0] = 0; - clients[i].clan[0] = 0; - snapstorage_init(&clients[i].snapshots); - } - - current_tick = 0; - - return 0; -} - -int server_getclientinfo(int client_id, CLIENT_INFO *info) -{ - dbg_assert(client_id >= 0 && client_id < MAX_CLIENTS, "client_id is not valid"); - dbg_assert(info != 0, "info can not be null"); - - if(clients[client_id].state == SRVCLIENT_STATE_INGAME) - { - info->name = clients[client_id].name; - info->latency = clients[client_id].latency; - return 1; - } - return 0; -} - - -int server_send_msg(int client_id) -{ - const MSG_INFO *info = msg_get_info(); - NETPACKET packet; - mem_zero(&packet, sizeof(NETPACKET)); - - packet.client_id = client_id; - packet.data = info->data; - packet.data_size = info->size; - - if(info->flags&MSGFLAG_VITAL) - packet.flags = PACKETFLAG_VITAL; - - if(client_id == -1) - { - /* broadcast */ - int i; - for(i = 0; i < MAX_CLIENTS; i++) - if(clients[i].state == SRVCLIENT_STATE_INGAME) - { - packet.client_id = i; - netserver_send(net, &packet); - } - } - else - netserver_send(net, &packet); - return 0; -} - -static void server_do_snap() -{ - int i, k; - mods_presnap(); - - for(i = 0; i < MAX_CLIENTS; i++) - { - if(clients[i].state == SRVCLIENT_STATE_INGAME) - { - char data[MAX_SNAPSHOT_SIZE]; - char deltadata[MAX_SNAPSHOT_SIZE]; - char compdata[MAX_SNAPSHOT_SIZE]; - int snapshot_size; - int crc; - static SNAPSHOT emptysnap; - SNAPSHOT *deltashot = &emptysnap; - int deltashot_size; - int delta_tick = -1; - int input_predtick = -1; - int64 timeleft = 0; - int deltasize; - - snapbuild_init(&builder); - mods_snap(i); - - /* finish snapshot */ - snapshot_size = snapbuild_finish(&builder, data); - crc = snapshot_crc((SNAPSHOT*)data); - - /* remove old snapshos */ - /* keep 1 seconds worth of snapshots */ - snapstorage_purge_until(&clients[i].snapshots, current_tick-SERVER_TICK_SPEED); - - /* save it the snapshot */ - snapstorage_add(&clients[i].snapshots, current_tick, time_get(), snapshot_size, data); - - /* find snapshot that we can preform delta against */ - emptysnap.data_size = 0; - emptysnap.num_items = 0; - - { - deltashot_size = snapstorage_get(&clients[i].snapshots, clients[i].last_acked_snapshot, 0, &deltashot); - if(deltashot_size >= 0) - delta_tick = clients[i].last_acked_snapshot; - } - - for(k = 0; k < 200; k++) /* TODO: do this better */ - { - if(clients[i].inputs[k].game_tick == current_tick) - { - timeleft = clients[i].inputs[k].timeleft; - input_predtick = clients[i].inputs[k].pred_tick; - break; - } - } - - /* create delta */ - deltasize = snapshot_create_delta(deltashot, (SNAPSHOT*)data, deltadata); - - if(deltasize) - { - /* compress it */ - unsigned char intdata[MAX_SNAPSHOT_SIZE]; - int intsize = intpack_compress(deltadata, deltasize, intdata); - int snapshot_size = zerobit_compress(intdata, intsize, compdata); - const int max_size = MAX_SNAPSHOT_PACKSIZE; - int numpackets = (snapshot_size+max_size-1)/max_size; - int n, left; - - for(n = 0, left = snapshot_size; left; n++) - { - int chunk = left < max_size ? left : max_size; - left -= chunk; - - if(numpackets == 1) - msg_pack_start_system(NETMSG_SNAPSINGLE, 0); - else - msg_pack_start_system(NETMSG_SNAP, 0); - - msg_pack_int(current_tick); - msg_pack_int(current_tick-delta_tick); /* compressed with */ - msg_pack_int(input_predtick); - msg_pack_int((timeleft*1000)/time_freq()); - - if(numpackets != 1) - { - msg_pack_int(numpackets); - msg_pack_int(n); - } - - msg_pack_int(crc); - msg_pack_int(chunk); - msg_pack_raw(&compdata[n*max_size], chunk); - msg_pack_end(); - server_send_msg(i); - } - } - else - { - msg_pack_start_system(NETMSG_SNAPEMPTY, 0); - msg_pack_int(current_tick); - msg_pack_int(current_tick-delta_tick); /* compressed with */ - msg_pack_int(input_predtick); - msg_pack_int((timeleft*1000)/time_freq()); - msg_pack_end(); - server_send_msg(i); - } - } - } - - mods_postsnap(); -} - - -static int new_client_callback(int cid, void *user) -{ - int i; - clients[cid].state = SRVCLIENT_STATE_CONNECTING; - clients[cid].name[0] = 0; - clients[cid].clan[0] = 0; - - /* reset input */ - for(i = 0; i < 200; i++) - { - clients[cid].inputs[i].game_tick = -1; - clients[cid].inputs[i].pred_tick = -1; - } - clients[cid].current_input = 0; - - snapstorage_purge_all(&clients[cid].snapshots); - clients[cid].last_acked_snapshot = -1; - return 0; -} - -static int del_client_callback(int cid, void *user) -{ - /* notify the mod about the drop */ - if(clients[cid].state == SRVCLIENT_STATE_READY || - clients[cid].state == SRVCLIENT_STATE_INGAME) - { - mods_client_drop(cid); - } - - clients[cid].state = SRVCLIENT_STATE_EMPTY; - clients[cid].name[0] = 0; - clients[cid].clan[0] = 0; - snapstorage_purge_all(&clients[cid].snapshots); - return 0; -} - - -static void server_send_map(int cid) -{ - msg_pack_start_system(NETMSG_MAP, MSGFLAG_VITAL); - msg_pack_string(config.sv_map, 0); - msg_pack_int(current_map_crc); - msg_pack_end(); - server_send_msg(cid); -} - -static void server_send_heartbeat() -{ - static unsigned char data[sizeof(SERVERBROWSE_HEARTBEAT) + 2]; - unsigned short port = config.sv_port; - NETPACKET packet; - - mem_copy(data, SERVERBROWSE_HEARTBEAT, sizeof(SERVERBROWSE_HEARTBEAT)); - - packet.client_id = -1; - packet.address = master_server; - packet.flags = PACKETFLAG_CONNLESS; - packet.data_size = sizeof(SERVERBROWSE_HEARTBEAT) + 2; - packet.data = &data; - - /* supply the set port that the master can use if it has problems */ - if(config.sv_external_port) - port = config.sv_external_port; - data[sizeof(SERVERBROWSE_HEARTBEAT)] = port >> 8; - data[sizeof(SERVERBROWSE_HEARTBEAT)+1] = port&0xff; - - netserver_send(net, &packet); -} - -static void server_process_client_packet(NETPACKET *packet) -{ - int cid = packet->client_id; - int sys; - int msg = msg_unpack_start(packet->data, packet->data_size, &sys); - if(sys) - { - /* system message */ - if(msg == NETMSG_INFO) - { - char version[64]; - const char *password; - strncpy(version, msg_unpack_string(), 64); - if(strcmp(version, mods_net_version()) != 0) - { - /* OH FUCK! wrong version, drop him */ - char reason[256]; - sprintf(reason, "wrong version. server is running %s.", mods_net_version()); - netserver_drop(net, cid, reason); - return; - } - - strncpy(clients[cid].name, msg_unpack_string(), MAX_NAME_LENGTH); - strncpy(clients[cid].clan, msg_unpack_string(), MAX_CLANNAME_LENGTH); - password = msg_unpack_string(); - - if(config.password[0] != 0 && strcmp(config.password, password) != 0) - { - /* wrong password */ - netserver_drop(net, cid, "wrong password"); - return; - } - - server_send_map(cid); - } - else if(msg == NETMSG_READY) - { - if(clients[cid].state == SRVCLIENT_STATE_CONNECTING) - { - dbg_msg("server", "player is ready. cid=%x", cid); - clients[cid].state = SRVCLIENT_STATE_READY; - mods_connected(cid); - } - } - else if(msg == NETMSG_ENTERGAME) - { - if(clients[cid].state != SRVCLIENT_STATE_INGAME) - { - dbg_msg("server", "player as entered the game. cid=%x", cid); - clients[cid].state = SRVCLIENT_STATE_INGAME; - mods_client_enter(cid); - } - } - else if(msg == NETMSG_INPUT) - { - int tick, size, i; - CLIENT_INPUT *input; - int64 tagtime; - - clients[cid].last_acked_snapshot = msg_unpack_int(); - if(snapstorage_get(&clients[cid].snapshots, clients[cid].last_acked_snapshot, &tagtime, 0) >= 0) - clients[cid].latency = (int)(((time_get()-tagtime)*1000)/time_freq()); - - tick = msg_unpack_int(); - size = msg_unpack_int(); - - input = &clients[cid].inputs[clients[cid].current_input]; - input->timeleft = server_tick_start_time(tick)-time_get(); - input->pred_tick = tick; - - if(tick <= server_tick()) - { - /* TODO: how should we handle this */ - dbg_msg("server", "input got in late for=%d cur=%d", tick, server_tick()); - tick = server_tick()+1; - } - - input->game_tick = tick; - - for(i = 0; i < size/4; i++) - input->data[i] = msg_unpack_int(); - - clients[cid].current_input++; - clients[cid].current_input %= 200; - } - else if(msg == NETMSG_CMD) - { - const char *pw = msg_unpack_string(); - const char *cmd = msg_unpack_string(); - if(config.rcon_password[0] != 0 && strcmp(pw, config.rcon_password) == 0) - { - dbg_msg("server", "cid=%d rcon='%s'", cid, cmd); - config_set(cmd); - } - } - else - { - dbg_msg("server", "strange message cid=%d msg=%d data_size=%d", cid, msg, packet->data_size); - } - } - else - { - /* game message */ - mods_message(msg, cid); - } -} - -static void server_send_serverinfo(NETADDR4 *addr) -{ - NETPACKET packet; - PACKER p; - char buf[128]; - - /* count the players */ - int c = 0; - int i; - for(i = 0; i < MAX_CLIENTS; i++) - { - if(clients[i].state != SRVCLIENT_STATE_EMPTY) - c++; - } - - packer_reset(&p); - packer_add_raw(&p, SERVERBROWSE_INFO, sizeof(SERVERBROWSE_INFO)); - packer_add_string(&p, mods_net_version(), 32); - packer_add_string(&p, config.sv_name, 64); - packer_add_string(&p, config.sv_map, 32); - - /* gametype */ - sprintf(buf, "%d", browseinfo_gametype); - packer_add_string(&p, buf, 2); - - /* flags */ - i = 0; - if(strlen(config.password)) - i |= 1; - sprintf(buf, "%d", i); - packer_add_string(&p, buf, 2); - - /* progression */ - sprintf(buf, "%d", browseinfo_progression); - packer_add_string(&p, buf, 4); - - sprintf(buf, "%d", c); packer_add_string(&p, buf, 3); /* num players */ - sprintf(buf, "%d", netserver_max_clients(net)); packer_add_string(&p, buf, 3); /* max players */ - - for(i = 0; i < MAX_CLIENTS; i++) - { - if(clients[i].state != SRVCLIENT_STATE_EMPTY) - { - packer_add_string(&p, clients[i].name, 48); /* player name */ - packer_add_string(&p, "0", 6); /* score */ - } - } - - - packet.client_id = -1; - packet.address = *addr; - packet.flags = PACKETFLAG_CONNLESS; - packet.data_size = packer_size(&p); - packet.data = packer_data(&p); - netserver_send(net, &packet); -} - - -static void server_send_fwcheckresponse(NETADDR4 *addr) -{ - NETPACKET packet; - packet.client_id = -1; - packet.address = *addr; - packet.flags = PACKETFLAG_CONNLESS; - packet.data_size = sizeof(SERVERBROWSE_FWRESPONSE); - packet.data = SERVERBROWSE_FWRESPONSE; - netserver_send(net, &packet); -} - -static void server_pump_network() -{ - NETPACKET packet; - - netserver_update(net); - - /* process packets */ - while(netserver_recv(net, &packet)) - { - if(packet.client_id == -1) - { - /* stateless */ - if(packet.data_size == sizeof(SERVERBROWSE_GETINFO) && - memcmp(packet.data, SERVERBROWSE_GETINFO, sizeof(SERVERBROWSE_GETINFO)) == 0) - { - server_send_serverinfo(&packet.address); - } - else if(packet.data_size == sizeof(SERVERBROWSE_FWCHECK) && - memcmp(packet.data, SERVERBROWSE_FWCHECK, sizeof(SERVERBROWSE_FWCHECK)) == 0) - { - server_send_fwcheckresponse(&packet.address); - } - else if(packet.data_size == sizeof(SERVERBROWSE_FWOK) && - memcmp(packet.data, SERVERBROWSE_FWOK, sizeof(SERVERBROWSE_FWOK)) == 0) - { - if(config.debug) - dbg_msg("server", "no firewall/nat problems detected"); - } - else if(packet.data_size == sizeof(SERVERBROWSE_FWERROR) && - memcmp(packet.data, SERVERBROWSE_FWERROR, sizeof(SERVERBROWSE_FWERROR)) == 0) - { - dbg_msg("server", "ERROR: the master server reports that clients can not connect to this server."); - dbg_msg("server", "ERROR: configure your firewall/nat to let trough udp on port %d.", config.sv_port); - } - } - else - server_process_client_packet(&packet); - } -} - -static int server_load_map(const char *mapname) -{ - DATAFILE *df; - char buf[512]; - sprintf(buf, "data/maps/%s.map", mapname); - df = datafile_load(buf); - if(!df) - return 0; - - /* reinit snapshot ids */ - snap_timeout_ids(); - - /* get the crc of the map */ - current_map_crc = datafile_crc(buf); - dbg_msg("server", "%s crc is %08x", buf, current_map_crc); - - strcpy(current_map, mapname); - map_set(df); - return 1; -} - - -static int server_run() -{ - NETADDR4 bindaddr; - - net_init(); - - snap_init_id(); - - /* load map */ - if(!server_load_map(config.sv_map)) - { - dbg_msg("server", "failed to load map. mapname='%s'", config.sv_map); - return -1; - } - - /* start server */ - if(strlen(config.sv_bindaddr) && net_host_lookup(config.sv_bindaddr, config.sv_port, &bindaddr) != 0) - { - /* sweet! */ - } - else - { - mem_zero(&bindaddr, sizeof(bindaddr)); - bindaddr.port = config.sv_port; - } - - net = netserver_open(bindaddr, config.sv_max_clients, 0); - if(!net) - { - dbg_msg("server", "couldn't open socket. port might already be in use"); - return -1; - } - - netserver_set_callbacks(net, new_client_callback, del_client_callback, 0); - - dbg_msg("server", "server name is '%s'", config.sv_name); - dbg_msg("server", "masterserver is '%s'", config.masterserver); - if(net_host_lookup(config.masterserver, MASTERSERVER_PORT, &master_server) != 0) - { - /* TODO: fix me */ - /*master_server = netaddr4(0, 0, 0, 0, 0); */ - } - - mods_init(); - dbg_msg("server", "version %s", mods_net_version()); - - /* start game */ - { - int64 time_per_heartbeat = time_freq() * 30; - int64 reporttime = time_get(); - int reportinterval = 3; - - int64 simulationtime = 0; - int64 snaptime = 0; - int64 networktime = 0; - int64 totaltime = 0; - - lastheartbeat = 0; - game_start_time = time_get(); - - if(config.debug) - dbg_msg("server", "baseline memory usage %dk", mem_allocated()/1024); - - while(1) - { - int64 t = time_get(); - /* load new map TODO: don't poll this */ - if(strcmp(config.sv_map, current_map) != 0 || config.sv_map_reload) - { - config.sv_map_reload = 0; - - /* load map */ - if(server_load_map(config.sv_map)) - { - int c; - - /* new map loaded */ - mods_shutdown(); - - for(c = 0; c < MAX_CLIENTS; c++) - { - if(clients[c].state == SRVCLIENT_STATE_EMPTY) - continue; - - server_send_map(c); - clients[c].state = SRVCLIENT_STATE_CONNECTING; - clients[c].last_acked_snapshot = -1; - snapstorage_purge_all(&clients[c].snapshots); - } - - game_start_time = time_get(); - current_tick = 0; - mods_init(); - } - else - { - dbg_msg("server", "failed to load map. mapname='%s'", config.sv_map); - config_set_sv_map(&config, current_map); - } - } - - if(t > server_tick_start_time(current_tick+1)) - { - current_tick++; - - /* apply new input */ - { - int c, i; - for(c = 0; c < MAX_CLIENTS; c++) - { - if(clients[c].state == SRVCLIENT_STATE_EMPTY) - continue; - for(i = 0; i < 200; i++) - { - if(clients[c].inputs[i].game_tick == server_tick()) - { - mods_client_input(c, clients[c].inputs[i].data); - break; - } - } - } - } - - /* progress game */ - { - int64 start = time_get(); - mods_tick(); - simulationtime += time_get()-start; - } - - /* snap game */ - if(config.sv_bandwidth_mode == 0 || - (config.sv_bandwidth_mode == 1 && current_tick%2) || - (config.sv_bandwidth_mode == 2 && (current_tick%3) == 0 )) - /* if(current_tick&1) */ - { - int64 start = time_get(); - server_do_snap(); - snaptime += time_get()-start; - } - } - - if(config.sv_sendheartbeats) - { - if (t > lastheartbeat+time_per_heartbeat) - { - server_send_heartbeat(); - lastheartbeat = t+time_per_heartbeat; - } - } - - { - int64 start = time_get(); - server_pump_network(); - networktime += time_get()-start; - } - - if(reporttime < time_get()) - { - if(config.debug) - { - static NETSTATS prev_stats; - NETSTATS stats; - netserver_stats(net, &stats); - dbg_msg("server", "sim=%.02fms snap=%.02fms net=%.02fms tot=%.02fms load=%.02f%%", - (simulationtime/reportinterval)/(double)time_freq()*1000, - (snaptime/reportinterval)/(double)time_freq()*1000, - (networktime/reportinterval)/(double)time_freq()*1000, - (totaltime/reportinterval)/(double)time_freq()*1000, - (totaltime)/reportinterval/(double)time_freq()*100.0f); - - dbg_msg("server", "send=%8d recv=%8d", - (stats.send_bytes - prev_stats.send_bytes)/reportinterval, - (stats.recv_bytes - prev_stats.recv_bytes)/reportinterval); - - prev_stats = stats; - } - - simulationtime = 0; - snaptime = 0; - networktime = 0; - totaltime = 0; - - reporttime += time_freq()*reportinterval; - } - totaltime += time_get()-t; - thread_sleep(1); - } - } - - mods_shutdown(); - map_unload(); - - return 0; -} - - -int main(int argc, char **argv) -{ - dbg_msg("server", "starting..."); - engine_init("Teewars", argc, argv); - server_run(); - return 0; -} - -- cgit 1.4.1