diff options
Diffstat (limited to 'src/engine/client/ec_client.cpp')
| -rw-r--r-- | src/engine/client/ec_client.cpp | 2087 |
1 files changed, 2087 insertions, 0 deletions
diff --git a/src/engine/client/ec_client.cpp b/src/engine/client/ec_client.cpp new file mode 100644 index 00000000..7ba0a2bb --- /dev/null +++ b/src/engine/client/ec_client.cpp @@ -0,0 +1,2087 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ + +#include <string.h> +#include <stdarg.h> +#include <stdlib.h> +#include <stdio.h> +#include <math.h> + +#include <base/system.h> +#include <engine/e_engine.h> +#include <engine/e_client_interface.h> + +#include <engine/e_protocol.h> +#include <engine/e_snapshot.h> +#include <engine/e_compression.h> +#include <engine/e_network.h> +#include <engine/e_config.h> +#include <engine/e_packer.h> +#include <engine/e_memheap.h> +#include <engine/e_datafile.h> +#include <engine/e_console.h> +#include <engine/e_ringbuffer.h> + +#include <engine/e_huffman.h> + +#include <engine/e_demorec.h> + +#include <mastersrv/mastersrv.h> +#include <versionsrv/versionsrv.h> + +#include "editor.h" +#include "graphics.h" +#include "client.h" + +static IEditor *m_pEditor = 0; +static IEngineGraphics *m_pGraphics = 0; +IEngineGraphics *Graphics() { return m_pGraphics; } + +static IGameClient *m_pGameClient = 0; + + +class CClient : public IEngine +{ +public: + virtual class IGraphics *Graphics() + { + return m_pGraphics; + } +}; + +static CClient m_Client; + +const int prediction_margin = 1000/50/2; /* magic network prediction value */ + +/* + Server Time + Client Mirror Time + Client Predicted Time + + Snapshot Latency + Downstream latency + + Prediction Latency + Upstream latency +*/ + +/* network client, must be accessible from other parts like the server browser */ +CNetClient m_NetClient; + +/* TODO: ugly, fix me */ +extern void client_serverbrowse_set(NETADDR *addr, int request, int token, SERVER_INFO *info); +extern void client_serverbrowse_save(); + +static unsigned snapshot_parts; +static int64 local_start_time; + +static int debug_font; +static float frametime = 0.0001f; +static float frametime_low = 1.0f; +static float frametime_high = 0.0f; +static int frames = 0; +static NETADDR server_address; +static int window_must_refocus = 0; +static int snapcrcerrors = 0; + +static int ack_game_tick = -1; +static int current_recv_tick = 0; +static int rcon_authed = 0; + +/* version-checking */ +static char versionstr[10] = "0"; + +/* pinging */ +static int64 ping_start_time = 0; + +/* */ +static char current_map[256] = {0}; +static int current_map_crc = 0; + +/* */ +static char cmd_connect[256] = {0}; + +/* map download */ +static char mapdownload_filename[256] = {0}; +static char mapdownload_name[256] = {0}; +static IOHANDLE mapdownload_file = 0; +static int mapdownload_chunk = 0; +static int mapdownload_crc = 0; +static int mapdownload_amount = -1; +static int mapdownload_totalsize = -1; + +/* */ +static SERVER_INFO current_server_info = {0}; +static int64 current_server_info_requesttime = -1; /* >= 0 should request, == -1 got info */ + +/* current time */ +static int current_tick = 0; +static float intratick = 0; +static float ticktime = 0; +static int prev_tick = 0; + +/* */ +/*static int predictiontime_pingspikecounter = 0; +static int gametime_pingspikecounter = 0;*/ + +/* predicted time */ +static int current_predtick = 0; +static float predintratick = 0; +static int last_input_timeleft = 0; + +static struct /* TODO: handle input better */ +{ + int data[MAX_INPUT_SIZE]; /* the input data */ + int tick; /* the tick that the input is for */ + int64 predicted_time; /* prediction latency when we sent this input */ + int64 time; +} inputs[200]; +static int current_input = 0; + +enum +{ + GRAPH_MAX=128 +}; + +typedef struct +{ + float min, max; + float values[GRAPH_MAX]; + float colors[GRAPH_MAX][3]; + int index; +} GRAPH; + +static void graph_init(GRAPH *g, float min, float max) +{ + g->min = min; + g->max = max; + g->index = 0; +} + +static void graph_scale_max(GRAPH *g) +{ + int i = 0; + g->max = 0; + for(i = 0; i < GRAPH_MAX; i++) + { + if(g->values[i] > g->max) + g->max = g->values[i]; + } +} + +static void graph_scale_min(GRAPH *g) +{ + int i = 0; + g->min = g->max; + for(i = 0; i < GRAPH_MAX; i++) + { + if(g->values[i] < g->min) + g->min = g->values[i]; + } +} + +static void graph_add(GRAPH *graph, float v, float r, float g, float b) +{ + graph->index = (graph->index+1)&(GRAPH_MAX-1); + graph->values[graph->index] = v; + graph->colors[graph->index][0] = r; + graph->colors[graph->index][1] = g; + graph->colors[graph->index][2] = b; +} + +static void graph_render(GRAPH *g, float x, float y, float w, float h, const char *description) +{ + char buf[32]; + int i; + + //m_pGraphics->BlendNormal(); + + + Graphics()->TextureSet(-1); + + m_pGraphics->QuadsBegin(); + Graphics()->SetColor(0, 0, 0, 0.75f); + Graphics()->QuadsDrawTL(x, y, w, h); + m_pGraphics->QuadsEnd(); + + Graphics()->LinesBegin(); + Graphics()->SetColor(0.95f, 0.95f, 0.95f, 1.00f); + Graphics()->LinesDraw(x, y+h/2, x+w, y+h/2); + Graphics()->SetColor(0.5f, 0.5f, 0.5f, 0.75f); + Graphics()->LinesDraw(x, y+(h*3)/4, x+w, y+(h*3)/4); + Graphics()->LinesDraw(x, y+h/4, x+w, y+h/4); + for(i = 1; i < GRAPH_MAX; i++) + { + float a0 = (i-1)/(float)GRAPH_MAX; + float a1 = i/(float)GRAPH_MAX; + int i0 = (g->index+i-1)&(GRAPH_MAX-1); + int i1 = (g->index+i)&(GRAPH_MAX-1); + + float v0 = (g->values[i0]-g->min) / (g->max-g->min); + float v1 = (g->values[i1]-g->min) / (g->max-g->min); + + Graphics()->SetColorVertex(0, g->colors[i0][0], g->colors[i0][1], g->colors[i0][2], 0.75f); + Graphics()->SetColorVertex(1, g->colors[i1][0], g->colors[i1][1], g->colors[i1][2], 0.75f); + Graphics()->LinesDraw(x+a0*w, y+h-v0*h, x+a1*w, y+h-v1*h); + + } + Graphics()->LinesEnd(); + + + Graphics()->TextureSet(debug_font); + Graphics()->QuadsText(x+2, y+h-16, 16, 1,1,1,1, description); + + str_format(buf, sizeof(buf), "%.2f", g->max); + Graphics()->QuadsText(x+w-8*strlen(buf)-8, y+2, 16, 1,1,1,1, buf); + + str_format(buf, sizeof(buf), "%.2f", g->min); + Graphics()->QuadsText(x+w-8*strlen(buf)-8, y+h-16, 16, 1,1,1,1, buf); + +} + +typedef struct +{ + int64 snap; + int64 current; + int64 target; + + int64 rlast; + int64 tlast; + GRAPH graph; + + int spikecounter; + + float adjustspeed[2]; /* 0 = down, 1 = up */ +} SMOOTHTIME; + +static void st_init(SMOOTHTIME *st, int64 target) +{ + st->snap = time_get(); + st->current = target; + st->target = target; + st->adjustspeed[0] = 0.3f; + st->adjustspeed[1] = 0.3f; + graph_init(&st->graph, 0.0f, 0.5f); +} + +static int64 st_get(SMOOTHTIME *st, int64 now) +{ + float adjust_speed, a; + int64 c = st->current + (now - st->snap); + int64 t = st->target + (now - st->snap); + int64 r; + + /* it's faster to adjust upward instead of downward */ + /* we might need to adjust these abit */ + + adjust_speed = st->adjustspeed[0]; + if(t > c) + adjust_speed = st->adjustspeed[1]; + + a = ((now-st->snap)/(float)time_freq()) * adjust_speed; + if(a > 1.0f) + a = 1.0f; + + r = c + (int64)((t-c)*a); + + graph_add(&st->graph, a+0.5f,1,1,1); + + return r; +} + +static void st_update_int(SMOOTHTIME *st, int64 target) +{ + int64 now = time_get(); + st->current = st_get(st, now); + st->snap = now; + st->target = target; +} + +static void st_update(SMOOTHTIME *st, GRAPH *graph, int64 target, int time_left, int adjust_direction) +{ + int update_timer = 1; + + if(time_left < 0) + { + int is_spike = 0; + if(time_left < -50) + { + is_spike = 1; + + st->spikecounter += 5; + if(st->spikecounter > 50) + st->spikecounter = 50; + } + + if(is_spike && st->spikecounter < 15) + { + /* ignore this ping spike */ + update_timer = 0; + graph_add(graph, time_left, 1,1,0); + } + else + { + graph_add(graph, time_left, 1,0,0); + if(st->adjustspeed[adjust_direction] < 30.0f) + st->adjustspeed[adjust_direction] *= 2.0f; + } + } + else + { + if(st->spikecounter) + st->spikecounter--; + + graph_add(graph, time_left, 0,1,0); + + st->adjustspeed[adjust_direction] *= 0.95f; + if(st->adjustspeed[adjust_direction] < 2.0f) + st->adjustspeed[adjust_direction] = 2.0f; + } + + last_input_timeleft = time_left; + + if(update_timer) + st_update_int(st, target); +} + +static SMOOTHTIME game_time; +static SMOOTHTIME predicted_time; + +/* graphs */ +static GRAPH inputtime_margin_graph; +static GRAPH gametime_margin_graph; +static GRAPH fps_graph; + +/* -- snapshot handling --- */ +enum +{ + NUM_SNAPSHOT_TYPES=2 +}; + +/* the game snapshots are modifiable by the game */ +CSnapshotStorage snapshot_storage; +static CSnapshotStorage::CHolder *snapshots[NUM_SNAPSHOT_TYPES] = {0, 0}; + +static int recived_snapshots = 0; +static char snapshot_incomming_data[CSnapshot::MAX_SIZE]; + +static CSnapshotStorage::CHolder demorec_snapshotholders[NUM_SNAPSHOT_TYPES]; +static char *demorec_snapshotdata[NUM_SNAPSHOT_TYPES][2][CSnapshot::MAX_SIZE]; + +/* --- */ + +void *snap_get_item(int snapid, int index, SNAP_ITEM *item) +{ + CSnapshotItem *i; + dbg_assert(snapid >= 0 && snapid < NUM_SNAPSHOT_TYPES, "invalid snapid"); + i = snapshots[snapid]->m_pAltSnap->GetItem(index); + item->datasize = snapshots[snapid]->m_pAltSnap->GetItemSize(index); + item->type = i->Type(); + item->id = i->ID(); + return (void *)i->Data(); +} + +void snap_invalidate_item(int snapid, int index) +{ + CSnapshotItem *i; + dbg_assert(snapid >= 0 && snapid < NUM_SNAPSHOT_TYPES, "invalid snapid"); + i = snapshots[snapid]->m_pAltSnap->GetItem(index); + if(i) + { + if((char *)i < (char *)snapshots[snapid]->m_pAltSnap || (char *)i > (char *)snapshots[snapid]->m_pAltSnap + snapshots[snapid]->m_SnapSize) + dbg_msg("ASDFASDFASdf", "ASDFASDFASDF"); + if((char *)i >= (char *)snapshots[snapid]->m_pSnap && (char *)i < (char *)snapshots[snapid]->m_pSnap + snapshots[snapid]->m_SnapSize) + dbg_msg("ASDFASDFASdf", "ASDFASDFASDF"); + i->m_TypeAndID = -1; + } +} + +void *snap_find_item(int snapid, int type, int id) +{ + /* TODO: linear search. should be fixed. */ + int i; + + if(!snapshots[snapid]) + return 0x0; + + for(i = 0; i < snapshots[snapid]->m_pSnap->m_NumItems; i++) + { + CSnapshotItem *itm = snapshots[snapid]->m_pAltSnap->GetItem(i); + if(itm->Type() == type && itm->ID() == id) + return (void *)itm->Data(); + } + return 0x0; +} + +int snap_num_items(int snapid) +{ + dbg_assert(snapid >= 0 && snapid < NUM_SNAPSHOT_TYPES, "invalid snapid"); + if(!snapshots[snapid]) + return 0; + return snapshots[snapid]->m_pSnap->m_NumItems; +} + +/* ------ time functions ------ */ +float client_intratick() { return intratick; } +float client_predintratick() { return predintratick; } +float client_ticktime() { return ticktime; } +int client_tick() { return current_tick; } +int client_prevtick() { return prev_tick; } +int client_predtick() { return current_predtick; } +int client_tickspeed() { return SERVER_TICK_SPEED; } +float client_frametime() { return frametime; } +float client_localtime() { return (time_get()-local_start_time)/(float)(time_freq()); } + +/* ----- send functions ----- */ +int client_send_msg() +{ + const MSG_INFO *pInfo = msg_get_info(); + CNetChunk Packet; + + if(!pInfo) + return -1; + + if(client_state() == CLIENTSTATE_OFFLINE) + return 0; + + mem_zero(&Packet, sizeof(CNetChunk)); + + Packet.m_ClientID = 0; + Packet.m_pData = pInfo->data; + Packet.m_DataSize = pInfo->size; + + if(pInfo->flags&MSGFLAG_VITAL) + Packet.m_Flags |= NETSENDFLAG_VITAL; + if(pInfo->flags&MSGFLAG_FLUSH) + Packet.m_Flags |= NETSENDFLAG_FLUSH; + + if(pInfo->flags&MSGFLAG_RECORD) + { + if(demorec_isrecording()) + demorec_record_message(Packet.m_pData, Packet.m_DataSize); + } + + if(!(pInfo->flags&MSGFLAG_NOSEND)) + m_NetClient.Send(&Packet); + return 0; +} + +static void client_send_info() +{ + msg_pack_start_system(NETMSG_INFO, MSGFLAG_VITAL|MSGFLAG_FLUSH); + msg_pack_string(modc_net_version(), 128); + msg_pack_string(config.player_name, 128); + msg_pack_string(config.clan_name, 128); + msg_pack_string(config.password, 128); + msg_pack_end(); + client_send_msg(); +} + + +static void client_send_entergame() +{ + msg_pack_start_system(NETMSG_ENTERGAME, MSGFLAG_VITAL|MSGFLAG_FLUSH); + msg_pack_end(); + client_send_msg(); +} + +static void client_send_ready() +{ + msg_pack_start_system(NETMSG_READY, MSGFLAG_VITAL|MSGFLAG_FLUSH); + msg_pack_end(); + client_send_msg(); +} + +int client_rcon_authed() +{ + return rcon_authed; +} + +void client_rcon_auth(const char *name, const char *password) +{ + msg_pack_start_system(NETMSG_RCON_AUTH, MSGFLAG_VITAL); + msg_pack_string(name, 32); + msg_pack_string(password, 32); + msg_pack_end(); + client_send_msg(); +} + +void client_rcon(const char *cmd) +{ + msg_pack_start_system(NETMSG_RCON_CMD, MSGFLAG_VITAL); + msg_pack_string(cmd, 256); + msg_pack_end(); + client_send_msg(); +} + +int client_connection_problems() +{ + return m_NetClient.GotProblems(); +} + +void client_direct_input(int *input, int size) +{ + int i; + msg_pack_start_system(NETMSG_INPUT, 0); + msg_pack_int(ack_game_tick); + msg_pack_int(current_predtick); + msg_pack_int(size); + + for(i = 0; i < size/4; i++) + msg_pack_int(input[i]); + + msg_pack_end(); + client_send_msg(); +} + + +static void client_send_input() +{ + int64 now = time_get(); + int i, size; + + if(current_predtick <= 0) + return; + + /* fetch input */ + size = modc_snap_input(inputs[current_input].data); + + if(!size) + return; + + /* pack input */ + msg_pack_start_system(NETMSG_INPUT, MSGFLAG_FLUSH); + msg_pack_int(ack_game_tick); + msg_pack_int(current_predtick); + msg_pack_int(size); + + inputs[current_input].tick = current_predtick; + inputs[current_input].predicted_time = st_get(&predicted_time, now); + inputs[current_input].time = now; + + /* pack it */ + for(i = 0; i < size/4; i++) + msg_pack_int(inputs[current_input].data[i]); + + current_input++; + current_input%=200; + + msg_pack_end(); + client_send_msg(); +} + +const char *client_latestversion() +{ + return versionstr; +} + +/* TODO: OPT: do this alot smarter! */ +int *client_get_input(int tick) +{ + int i; + int best = -1; + for(i = 0; i < 200; i++) + { + if(inputs[i].tick <= tick && (best == -1 || inputs[best].tick < inputs[i].tick)) + best = i; + } + + if(best != -1) + return (int *)inputs[best].data; + return 0; +} + +/* ------ state handling ----- */ +static int state = CLIENTSTATE_OFFLINE; +int client_state() { return state; } +static void client_set_state(int s) +{ + int old = state; + if(config.debug) + dbg_msg("client", "state change. last=%d current=%d", state, s); + state = s; + if(old != s) + modc_statechange(state, old); +} + +/* called when the map is loaded and we should init for a new round */ +static void client_on_enter_game() +{ + /* reset input */ + int i; + for(i = 0; i < 200; i++) + inputs[i].tick = -1; + current_input = 0; + + /* reset snapshots */ + snapshots[SNAP_CURRENT] = 0; + snapshots[SNAP_PREV] = 0; + snapshot_storage.PurgeAll(); + recived_snapshots = 0; + snapshot_parts = 0; + current_predtick = 0; + current_recv_tick = 0; +} + +void client_entergame() +{ + if(state == CLIENTSTATE_DEMOPLAYBACK) + return; + + /* now we will wait for two snapshots */ + /* to finish the connection */ + client_send_entergame(); + client_on_enter_game(); +} + +void client_connect(const char *server_address_str) +{ + char buf[512]; + const char *port_str = 0; + int k; + int port = 8303; + + client_disconnect(); + + dbg_msg("client", "connecting to '%s'", server_address_str); + + //client_serverinfo_request(); + str_copy(buf, server_address_str, sizeof(buf)); + + for(k = 0; buf[k]; k++) + { + if(buf[k] == ':') + { + port_str = &(buf[k+1]); + buf[k] = 0; + break; + } + } + + if(port_str) + port = atoi(port_str); + + /* TODO: IPv6 support */ + if(net_host_lookup(buf, &server_address, NETTYPE_IPV4) != 0) + dbg_msg("client", "could not find the address of %s, connecting to localhost", buf); + + rcon_authed = 0; + server_address.port = port; + m_NetClient.Connect(&server_address); + client_set_state(CLIENTSTATE_CONNECTING); + + graph_init(&inputtime_margin_graph, -150.0f, 150.0f); + graph_init(&gametime_margin_graph, -150.0f, 150.0f); +} + +void client_disconnect_with_reason(const char *reason) +{ + /* stop demo playback */ + demorec_playback_stop(); + + /* */ + rcon_authed = 0; + m_NetClient.Disconnect(reason); + client_set_state(CLIENTSTATE_OFFLINE); + map_unload(); + + /* disable all downloads */ + mapdownload_chunk = 0; + if(mapdownload_file) + io_close(mapdownload_file); + mapdownload_file = 0; + mapdownload_crc = 0; + mapdownload_totalsize = -1; + mapdownload_amount = 0; + + /* clear the current server info */ + mem_zero(¤t_server_info, sizeof(current_server_info)); + mem_zero(&server_address, sizeof(server_address)); + + /* clear snapshots */ + snapshots[SNAP_CURRENT] = 0; + snapshots[SNAP_PREV] = 0; + recived_snapshots = 0; +} + +void client_disconnect() +{ + client_disconnect_with_reason(0); +} + + +void client_serverinfo(SERVER_INFO *serverinfo) +{ + mem_copy(serverinfo, ¤t_server_info, sizeof(current_server_info)); +} + +void client_serverinfo_request() +{ + mem_zero(¤t_server_info, sizeof(current_server_info)); + current_server_info_requesttime = 0; +} + +static int client_load_data() +{ + debug_font = Graphics()->LoadTexture("debug_font.png", IMG_AUTO, TEXLOAD_NORESAMPLE); + return 1; +} + +extern int snapshot_data_rate[0xffff]; +extern int snapshot_data_updates[0xffff]; + +const char *modc_getitemname(int type); + +static void client_debug_render() +{ + static NETSTATS prev, current; + static int64 last_snap = 0; + static float frametime_avg = 0; + int64 now = time_get(); + char buffer[512]; + + if(!config.debug) + return; + + //m_pGraphics->BlendNormal(); + Graphics()->TextureSet(debug_font); + Graphics()->MapScreen(0,0,Graphics()->ScreenWidth(),Graphics()->ScreenHeight()); + + if(time_get()-last_snap > time_freq()) + { + last_snap = time_get(); + prev = current; + net_stats(¤t); + } + + /* + eth = 14 + ip = 20 + udp = 8 + total = 42 + */ + frametime_avg = frametime_avg*0.9f + frametime*0.1f; + str_format(buffer, sizeof(buffer), "ticks: %8d %8d mem %dk %d gfxmem: N/A fps: %3d", + current_tick, current_predtick, + mem_stats()->allocated/1024, + mem_stats()->total_allocations, + /*gfx_memory_usage()/1024, */ // TODO: Refactor: Reenable this + (int)(1.0f/frametime_avg)); + Graphics()->QuadsText(2, 2, 16, 1,1,1,1, buffer); + + + { + int send_packets = (current.sent_packets-prev.sent_packets); + int send_bytes = (current.sent_bytes-prev.sent_bytes); + int send_total = send_bytes + send_packets*42; + int recv_packets = (current.recv_packets-prev.recv_packets); + int recv_bytes = (current.recv_bytes-prev.recv_bytes); + int recv_total = recv_bytes + recv_packets*42; + + if(!send_packets) send_packets++; + if(!recv_packets) recv_packets++; + str_format(buffer, sizeof(buffer), "send: %3d %5d+%4d=%5d (%3d kbps) avg: %5d\nrecv: %3d %5d+%4d=%5d (%3d kbps) avg: %5d", + send_packets, send_bytes, send_packets*42, send_total, (send_total*8)/1024, send_bytes/send_packets, + recv_packets, recv_bytes, recv_packets*42, recv_total, (recv_total*8)/1024, recv_bytes/recv_packets); + Graphics()->QuadsText(2, 14, 16, 1,1,1,1, buffer); + } + + /* render rates */ + { + int y = 0; + int i; + for(i = 0; i < 256; i++) + { + if(snapshot_data_rate[i]) + { + str_format(buffer, sizeof(buffer), "%4d %20s: %8d %8d %8d", i, modc_getitemname(i), snapshot_data_rate[i]/8, snapshot_data_updates[i], + (snapshot_data_rate[i]/snapshot_data_updates[i])/8); + Graphics()->QuadsText(2, 100+y*12, 16, 1,1,1,1, buffer); + y++; + } + } + } + + str_format(buffer, sizeof(buffer), "pred: %d ms %3.2f", + (int)((st_get(&predicted_time, now)-st_get(&game_time, now))*1000/(float)time_freq()), + predicted_time.adjustspeed[1]); + Graphics()->QuadsText(2, 70, 16, 1,1,1,1, buffer); + + /* render graphs */ + if(config.dbg_graphs) + { + //Graphics()->MapScreen(0,0,400.0f,300.0f); + float w = Graphics()->ScreenWidth()/4.0f; + float h = Graphics()->ScreenHeight()/6.0f; + float sp = Graphics()->ScreenWidth()/100.0f; + float x = Graphics()->ScreenWidth()-w-sp; + + graph_scale_max(&fps_graph); + graph_scale_min(&fps_graph); + graph_render(&fps_graph, x, sp*5, w, h, "FPS"); + graph_render(&inputtime_margin_graph, x, sp*5+h+sp, w, h, "Prediction Margin"); + graph_render(&gametime_margin_graph, x, sp*5+h+sp+h+sp, w, h, "Gametime Margin"); + } +} + +void client_quit() +{ + client_set_state(CLIENTSTATE_QUITING); +} + +const char *client_error_string() +{ + return m_NetClient.ErrorString(); +} + +static void client_render() +{ + if(config.gfx_clear) + Graphics()->Clear(1,1,0); + + modc_render(); + client_debug_render(); +} + +static const char *client_load_map(const char *name, const char *filename, int wanted_crc) +{ + static char errormsg[128]; + DATAFILE *df; + int crc; + + client_set_state(CLIENTSTATE_LOADING); + + df = datafile_load(filename); + if(!df) + { + str_format(errormsg, sizeof(errormsg), "map '%s' not found", filename); + return errormsg; + } + + /* get the crc of the map */ + crc = datafile_crc(filename); + if(crc != wanted_crc) + { + datafile_unload(df); + str_format(errormsg, sizeof(errormsg), "map differs from the server. %08x != %08x", crc, wanted_crc); + return errormsg; + } + + // stop demo recording if we loaded a new map + demorec_record_stop(); + + dbg_msg("client", "loaded map '%s'", filename); + recived_snapshots = 0; + map_set(df); + + str_copy(current_map, name, sizeof(current_map)); + current_map_crc = crc; + + return NULL; +} + +static const char *client_load_map_search(const char *mapname, int wanted_crc) +{ + const char *error = 0; + char buf[512]; + dbg_msg("client", "loading map, map=%s wanted crc=%08x", mapname, wanted_crc); + client_set_state(CLIENTSTATE_LOADING); + + /* try the normal maps folder */ + str_format(buf, sizeof(buf), "maps/%s.map", mapname); + error = client_load_map(mapname, buf, wanted_crc); + if(!error) + return error; + + /* try the downloaded maps */ + str_format(buf, sizeof(buf), "downloadedmaps/%s_%8x.map", mapname, wanted_crc); + error = client_load_map(mapname, buf, wanted_crc); + return error; +} + +static int player_score_comp(const void *a, const void *b) +{ + SERVER_INFO_PLAYER *p0 = (SERVER_INFO_PLAYER *)a; + SERVER_INFO_PLAYER *p1 = (SERVER_INFO_PLAYER *)b; + if(p0->score == p1->score) + return 0; + if(p0->score < p1->score) + return 1; + return -1; +} + +static void client_process_packet(CNetChunk *pPacket) +{ + if(pPacket->m_ClientID == -1) + { + /* connectionlesss */ + if(pPacket->m_DataSize == (int)(sizeof(VERSIONSRV_VERSION) + sizeof(VERSION_DATA)) && + memcmp(pPacket->m_pData, VERSIONSRV_VERSION, sizeof(VERSIONSRV_VERSION)) == 0) + { + unsigned char *versiondata = (unsigned char*)pPacket->m_pData + sizeof(VERSIONSRV_VERSION); + int version_match = !memcmp(versiondata, VERSION_DATA, sizeof(VERSION_DATA)); + + dbg_msg("client/version", "version does %s (%d.%d.%d)", + version_match ? "match" : "NOT match", + versiondata[1], versiondata[2], versiondata[3]); + + /* assume version is out of date when version-data doesn't match */ + if (!version_match) + { + sprintf(versionstr, "%d.%d.%d", versiondata[1], versiondata[2], versiondata[3]); + } + } + + if(pPacket->m_DataSize >= (int)sizeof(SERVERBROWSE_LIST) && + memcmp(pPacket->m_pData, SERVERBROWSE_LIST, sizeof(SERVERBROWSE_LIST)) == 0) + { + int size = pPacket->m_DataSize-sizeof(SERVERBROWSE_LIST); + int num = size/sizeof(MASTERSRV_ADDR); + MASTERSRV_ADDR *addrs = (MASTERSRV_ADDR *)((char*)pPacket->m_pData+sizeof(SERVERBROWSE_LIST)); + int i; + + for(i = 0; i < num; i++) + { + NETADDR addr; + + /* convert address */ + mem_zero(&addr, sizeof(addr)); + addr.type = NETTYPE_IPV4; + addr.ip[0] = addrs[i].ip[0]; + addr.ip[1] = addrs[i].ip[1]; + addr.ip[2] = addrs[i].ip[2]; + addr.ip[3] = addrs[i].ip[3]; + addr.port = (addrs[i].port[1]<<8) | addrs[i].port[0]; + + client_serverbrowse_set(&addr, BROWSESET_MASTER_ADD, -1, NULL); + } + } + + { + int packet_type = 0; + if(pPacket->m_DataSize >= (int)sizeof(SERVERBROWSE_INFO) && memcmp(pPacket->m_pData, SERVERBROWSE_INFO, sizeof(SERVERBROWSE_INFO)) == 0) + packet_type = 2; + + if(pPacket->m_DataSize >= (int)sizeof(SERVERBROWSE_OLD_INFO) && memcmp(pPacket->m_pData, SERVERBROWSE_OLD_INFO, sizeof(SERVERBROWSE_OLD_INFO)) == 0) + packet_type = 1; + + if(packet_type) + { + /* we got ze info */ + CUnpacker up; + SERVER_INFO info = {0}; + int i; + int token = -1; + + up.Reset((unsigned char*)pPacket->m_pData+sizeof(SERVERBROWSE_INFO), pPacket->m_DataSize-sizeof(SERVERBROWSE_INFO)); + if(packet_type >= 2) + token = atol(up.GetString()); + str_copy(info.version, up.GetString(), sizeof(info.version)); + str_copy(info.name, up.GetString(), sizeof(info.name)); + str_copy(info.map, up.GetString(), sizeof(info.map)); + str_copy(info.gametype, up.GetString(), sizeof(info.gametype)); + info.flags = atol(up.GetString()); + info.progression = atol(up.GetString()); + info.num_players = atol(up.GetString()); + info.max_players = atol(up.GetString()); + str_format(info.address, sizeof(info.address), "%d.%d.%d.%d:%d", + pPacket->m_Address.ip[0], pPacket->m_Address.ip[1], pPacket->m_Address.ip[2], + pPacket->m_Address.ip[3], pPacket->m_Address.port); + + for(i = 0; i < info.num_players; i++) + { + str_copy(info.players[i].name, up.GetString(), sizeof(info.players[i].name)); + info.players[i].score = atol(up.GetString()); + } + + if(!up.Error()) + { + /* sort players */ + qsort(info.players, info.num_players, sizeof(*info.players), player_score_comp); + + if(net_addr_comp(&server_address, &pPacket->m_Address) == 0) + { + mem_copy(¤t_server_info, &info, sizeof(current_server_info)); + current_server_info.netaddr = server_address; + current_server_info_requesttime = -1; + } + else + { + if(packet_type == 2) + client_serverbrowse_set(&pPacket->m_Address, BROWSESET_TOKEN, token, &info); + else + client_serverbrowse_set(&pPacket->m_Address, BROWSESET_OLD_INTERNET, -1, &info); + } + } + } + } + } + else + { + int sys; + int msg = msg_unpack_start(pPacket->m_pData, pPacket->m_DataSize, &sys); + + if(sys) + { + /* system message */ + if(msg == NETMSG_MAP_CHANGE) + { + const char *map = msg_unpack_string(); + int map_crc = msg_unpack_int(); + const char *error = 0; + int i; + + if(msg_unpack_error()) + return; + + for(i = 0; map[i]; i++) /* protect the player from nasty map names */ + { + if(map[i] == '/' || map[i] == '\\') + error = "strange character in map name"; + } + + if(error) + client_disconnect_with_reason(error); + else + { + error = client_load_map_search(map, map_crc); + + if(!error) + { + dbg_msg("client/network", "loading done"); + client_send_ready(); + modc_connected(); + } + else + { + str_format(mapdownload_filename, sizeof(mapdownload_filename), "downloadedmaps/%s_%08x.map", map, map_crc); + + dbg_msg("client/network", "starting to download map to '%s'", mapdownload_filename); + + mapdownload_chunk = 0; + str_copy(mapdownload_name, map, sizeof(mapdownload_name)); + mapdownload_file = engine_openfile(mapdownload_filename, IOFLAG_WRITE); + mapdownload_crc = map_crc; + mapdownload_totalsize = -1; + mapdownload_amount = 0; + + msg_pack_start_system(NETMSG_REQUEST_MAP_DATA, MSGFLAG_VITAL|MSGFLAG_FLUSH); + msg_pack_int(mapdownload_chunk); + msg_pack_end(); + client_send_msg(); + + if(config.debug) + dbg_msg("client/network", "requested chunk %d", mapdownload_chunk); + } + } + } + else if(msg == NETMSG_MAP_DATA) + { + int last = msg_unpack_int(); + int total_size = msg_unpack_int(); + int size = msg_unpack_int(); + const unsigned char *data = msg_unpack_raw(size); + + /* check fior errors */ + if(msg_unpack_error() || size <= 0 || total_size <= 0 || !mapdownload_file) + return; + + io_write(mapdownload_file, data, size); + + mapdownload_totalsize = total_size; + mapdownload_amount += size; + + if(last) + { + const char *error; + dbg_msg("client/network", "download complete, loading map"); + + io_close(mapdownload_file); + mapdownload_file = 0; + mapdownload_amount = 0; + mapdownload_totalsize = -1; + + /* load map */ + error = client_load_map(mapdownload_name, mapdownload_filename, mapdownload_crc); + if(!error) + { + dbg_msg("client/network", "loading done"); + client_send_ready(); + modc_connected(); + } + else + client_disconnect_with_reason(error); + } + else + { + /* request new chunk */ + mapdownload_chunk++; + msg_pack_start_system(NETMSG_REQUEST_MAP_DATA, MSGFLAG_VITAL|MSGFLAG_FLUSH); + msg_pack_int(mapdownload_chunk); + msg_pack_end(); + client_send_msg(); + + if(config.debug) + dbg_msg("client/network", "requested chunk %d", mapdownload_chunk); + } + } + else if(msg == NETMSG_PING) + { + msg_pack_start_system(NETMSG_PING_REPLY, 0); + msg_pack_end(); + client_send_msg(); + } + else if(msg == NETMSG_RCON_AUTH_STATUS) + { + int result = msg_unpack_int(); + if(msg_unpack_error() == 0) + rcon_authed = result; + } + else if(msg == NETMSG_RCON_LINE) + { + const char *line = msg_unpack_string(); + if(msg_unpack_error() == 0) + { + /*dbg_msg("remote", "%s", line);*/ + modc_rcon_line(line); + } + } + else if(msg == NETMSG_PING_REPLY) + dbg_msg("client/network", "latency %.2f", (time_get() - ping_start_time)*1000 / (float)time_freq()); + else if(msg == NETMSG_INPUTTIMING) + { + int input_predtick = msg_unpack_int(); + int time_left = msg_unpack_int(); + + /* adjust our prediction time */ + int k; + int64 target = 0; + for(k = 0; k < 200; k++) + { + if(inputs[k].tick == input_predtick) + { + target = inputs[k].predicted_time + (time_get() - inputs[k].time); + target = target - (int64)(((time_left-prediction_margin)/1000.0f)*time_freq()); + //st_update(&predicted_time, ); + break; + } + } + + if(target) + st_update(&predicted_time, &inputtime_margin_graph, target, time_left, 1); + } + else if(msg == NETMSG_SNAP || msg == NETMSG_SNAPSINGLE || msg == NETMSG_SNAPEMPTY) + { + /*dbg_msg("client/network", "got snapshot"); */ + int num_parts = 1; + int part = 0; + int game_tick = msg_unpack_int(); + int delta_tick = game_tick-msg_unpack_int(); + int part_size = 0; + int crc = 0; + int complete_size = 0; + const char *data = 0; + + /* we are not allowed to process snapshot yet */ + if(client_state() < CLIENTSTATE_LOADING) + return; + + if(msg == NETMSG_SNAP) + { + num_parts = msg_unpack_int(); + part = msg_unpack_int(); + } + + if(msg != NETMSG_SNAPEMPTY) + { + crc = msg_unpack_int(); + part_size = msg_unpack_int(); + } + + data = (const char *)msg_unpack_raw(part_size); + + if(msg_unpack_error()) + return; + + if(game_tick >= current_recv_tick) + { + if(game_tick != current_recv_tick) + { + snapshot_parts = 0; + current_recv_tick = game_tick; + } + + /* TODO: clean this up abit */ + mem_copy((char*)snapshot_incomming_data + part*MAX_SNAPSHOT_PACKSIZE, data, part_size); + snapshot_parts |= 1<<part; + + if(snapshot_parts == (unsigned)((1<<num_parts)-1)) + { + static CSnapshot emptysnap; + CSnapshot *deltashot = &emptysnap; + int purgetick; + void *deltadata; + int deltasize; + unsigned char tmpbuffer2[CSnapshot::MAX_SIZE]; + unsigned char tmpbuffer3[CSnapshot::MAX_SIZE]; + int snapsize; + + complete_size = (num_parts-1) * MAX_SNAPSHOT_PACKSIZE + part_size; + + /* reset snapshoting */ + snapshot_parts = 0; + + /* find snapshot that we should use as delta */ + emptysnap.m_DataSize = 0; + emptysnap.m_NumItems = 0; + + /* find delta */ + if(delta_tick >= 0) + { + int deltashot_size = snapshot_storage.Get(delta_tick, 0, &deltashot, 0); + + if(deltashot_size < 0) + { + /* couldn't find the delta snapshots that the server used */ + /* to compress this snapshot. force the server to resync */ + if(config.debug) + dbg_msg("client", "error, couldn't find the delta snapshot"); + + /* ack snapshot */ + /* TODO: combine this with the input message */ + ack_game_tick = -1; + return; + } + } + + /* decompress snapshot */ + deltadata = CSnapshot::EmptyDelta(); + deltasize = sizeof(int)*3; + + if(complete_size) + { + int intsize = intpack_decompress(snapshot_incomming_data, complete_size, tmpbuffer2); + + if(intsize < 0) /* failure during decompression, bail */ + return; + + deltadata = tmpbuffer2; + deltasize = intsize; + } + + /* unpack delta */ + purgetick = delta_tick; + snapsize = CSnapshot::UnpackDelta(deltashot, (CSnapshot*)tmpbuffer3, deltadata, deltasize); + if(snapsize < 0) + { + dbg_msg("client", "delta unpack failed!"); + return; + } + + if(msg != NETMSG_SNAPEMPTY && ((CSnapshot*)tmpbuffer3)->Crc() != crc) + { + if(config.debug) + { + dbg_msg("client", "snapshot crc error #%d - tick=%d wantedcrc=%d gotcrc=%d compressed_size=%d delta_tick=%d", + snapcrcerrors, game_tick, crc, ((CSnapshot*)tmpbuffer3)->Crc(), complete_size, delta_tick); + } + + snapcrcerrors++; + if(snapcrcerrors > 10) + { + /* to many errors, send reset */ + ack_game_tick = -1; + client_send_input(); + snapcrcerrors = 0; + } + return; + } + else + { + if(snapcrcerrors) + snapcrcerrors--; + } + + /* purge old snapshots */ + purgetick = delta_tick; + if(snapshots[SNAP_PREV] && snapshots[SNAP_PREV]->m_Tick < purgetick) + purgetick = snapshots[SNAP_PREV]->m_Tick; + if(snapshots[SNAP_CURRENT] && snapshots[SNAP_CURRENT]->m_Tick < purgetick) + purgetick = snapshots[SNAP_PREV]->m_Tick; + snapshot_storage.PurgeUntil(purgetick); + + /* add new */ + snapshot_storage.Add(game_tick, time_get(), snapsize, (CSnapshot*)tmpbuffer3, 1); + + /* add snapshot to demo */ + if(demorec_isrecording()) + { + + /* write tick marker */ + /* + DEMOREC_TICKMARKER marker; + marker.tick = game_tick; + swap_endian(&marker, sizeof(int), sizeof(marker)/sizeof(int)); + demorec_record_write("TICK", sizeof(marker), &marker); + demorec_record_write("SNAP", snapsize, tmpbuffer3); + */ + + /* write snapshot */ + demorec_record_snapshot(game_tick, tmpbuffer3, snapsize); + } + + /* apply snapshot, cycle pointers */ + recived_snapshots++; + + current_recv_tick = game_tick; + + /* we got two snapshots until we see us self as connected */ + if(recived_snapshots == 2) + { + /* start at 200ms and work from there */ + st_init(&predicted_time, game_tick*time_freq()/50); + predicted_time.adjustspeed[1] = 1000.0f; + st_init(&game_time, (game_tick-1)*time_freq()/50); + snapshots[SNAP_PREV] = snapshot_storage.m_pFirst; + snapshots[SNAP_CURRENT] = snapshot_storage.m_pLast; + local_start_time = time_get(); + client_set_state(CLIENTSTATE_ONLINE); + } + + /* adjust game time */ + { + int64 now = st_get(&game_time, time_get()); + int64 tickstart = game_tick*time_freq()/50; + int64 time_left = (tickstart-now)*1000 / time_freq(); + /*st_update(&game_time, (game_tick-1)*time_freq()/50);*/ + st_update(&game_time, &gametime_margin_graph, (game_tick-1)*time_freq()/50, time_left, 0); + } + + /* ack snapshot */ + ack_game_tick = game_tick; + } + } + } + } + else + { + /* game message */ + if(demorec_isrecording()) + demorec_record_message(pPacket->m_pData, pPacket->m_DataSize); + /* demorec_record_write("MESG", pPacket->data_size, ); */ + + modc_message(msg); + } + } +} + +int client_mapdownload_amount() { return mapdownload_amount; } +int client_mapdownload_totalsize() { return mapdownload_totalsize; } + +static void client_pump_network() +{ + + m_NetClient.Update(); + + if(client_state() != CLIENTSTATE_DEMOPLAYBACK) + { + /* check for errors */ + if(client_state() != CLIENTSTATE_OFFLINE && m_NetClient.State() == NETSTATE_OFFLINE) + { + client_set_state(CLIENTSTATE_OFFLINE); + client_disconnect(); + dbg_msg("client", "offline error='%s'", m_NetClient.ErrorString()); + } + + /* */ + if(client_state() == CLIENTSTATE_CONNECTING && m_NetClient.State() == NETSTATE_ONLINE) + { + /* we switched to online */ + dbg_msg("client", "connected, sending info"); + client_set_state(CLIENTSTATE_LOADING); + client_send_info(); + } + } + + /* process packets */ + CNetChunk Packet; + while(m_NetClient.Recv(&Packet)) + client_process_packet(&Packet); +} + +static void client_democallback_snapshot(void *pData, int Size) +{ + /* update ticks, they could have changed */ + const DEMOREC_PLAYBACKINFO *info = demorec_playback_info(); + CSnapshotStorage::CHolder *temp; + current_tick = info->current_tick; + prev_tick = info->previous_tick; + + /* handle snapshots */ + temp = snapshots[SNAP_PREV]; + snapshots[SNAP_PREV] = snapshots[SNAP_CURRENT]; + snapshots[SNAP_CURRENT] = temp; + + mem_copy(snapshots[SNAP_CURRENT]->m_pSnap, pData, Size); + mem_copy(snapshots[SNAP_CURRENT]->m_pAltSnap, pData, Size); + + modc_newsnapshot(); + /*modc_predict();*/ +} + +static void client_democallback_message(void *data, int size) +{ + int sys = 0; + int msg = msg_unpack_start(data, size, &sys); + if(!sys) + modc_message(msg); +} + + +const DEMOPLAYBACK_INFO *client_demoplayer_getinfo() +{ + static DEMOPLAYBACK_INFO ret; + const DEMOREC_PLAYBACKINFO *info = demorec_playback_info(); + ret.first_tick = info->first_tick; + ret.last_tick = info->last_tick; + ret.current_tick = info->current_tick; + ret.paused = info->paused; + ret.speed = info->speed; + return &ret; +} + +void client_demoplayer_setpos(float percent) +{ + demorec_playback_set(percent); +} + +void client_demoplayer_setspeed(float speed) +{ + demorec_playback_setspeed(speed); +} + +void client_demoplayer_setpause(int paused) +{ + if(paused) + demorec_playback_pause(); + else + demorec_playback_unpause(); +} + +static void client_update() +{ + if(client_state() == CLIENTSTATE_DEMOPLAYBACK) + { + demorec_playback_update(); + if(demorec_isplaying()) + { + /* update timers */ + const DEMOREC_PLAYBACKINFO *info = demorec_playback_info(); + current_tick = info->current_tick; + prev_tick = info->previous_tick; + intratick = info->intratick; + ticktime = info->ticktime; + } + else + { + /* disconnect on error */ + client_disconnect(); + } + } + else if(client_state() != CLIENTSTATE_OFFLINE && recived_snapshots >= 3) + { + /* switch snapshot */ + int repredict = 0; + int64 freq = time_freq(); + int64 now = st_get(&game_time, time_get()); + int64 pred_now = st_get(&predicted_time, time_get()); + + while(1) + { + CSnapshotStorage::CHolder *cur = snapshots[SNAP_CURRENT]; + int64 tickstart = (cur->m_Tick)*time_freq()/50; + + if(tickstart < now) + { + CSnapshotStorage::CHolder *next = snapshots[SNAP_CURRENT]->m_pNext; + if(next) + { + snapshots[SNAP_PREV] = snapshots[SNAP_CURRENT]; + snapshots[SNAP_CURRENT] = next; + + /* set ticks */ + current_tick = snapshots[SNAP_CURRENT]->m_Tick; + prev_tick = snapshots[SNAP_PREV]->m_Tick; + + if(snapshots[SNAP_CURRENT] && snapshots[SNAP_PREV]) + { + modc_newsnapshot(); + repredict = 1; + } + } + else + break; + } + else + break; + } + + if(snapshots[SNAP_CURRENT] && snapshots[SNAP_PREV]) + { + int64 curtick_start = (snapshots[SNAP_CURRENT]->m_Tick)*time_freq()/50; + int64 prevtick_start = (snapshots[SNAP_PREV]->m_Tick)*time_freq()/50; + /*tg_add(&predicted_time_graph, pred_now, 0); */ + int prev_pred_tick = (int)(pred_now*50/time_freq()); + int new_pred_tick = prev_pred_tick+1; + static float last_predintra = 0; + + intratick = (now - prevtick_start) / (float)(curtick_start-prevtick_start); + ticktime = (now - prevtick_start) / (float)freq; /*(float)SERVER_TICK_SPEED);*/ + + curtick_start = new_pred_tick*time_freq()/50; + prevtick_start = prev_pred_tick*time_freq()/50; + predintratick = (pred_now - prevtick_start) / (float)(curtick_start-prevtick_start); + + if(new_pred_tick < snapshots[SNAP_PREV]->m_Tick-SERVER_TICK_SPEED || new_pred_tick > snapshots[SNAP_PREV]->m_Tick+SERVER_TICK_SPEED) + { + dbg_msg("client", "prediction time reset!"); + st_init(&predicted_time, snapshots[SNAP_CURRENT]->m_Tick*time_freq()/50); + } + + if(new_pred_tick > current_predtick) + { + last_predintra = predintratick; + current_predtick = new_pred_tick; + repredict = 1; + + /* send input */ + client_send_input(); + } + + last_predintra = predintratick; + } + + /* only do sane predictions */ + if(repredict) + { + if(current_predtick > current_tick && current_predtick < current_tick+50) + modc_predict(); + } + + /* fetch server info if we don't have it */ + if(client_state() >= CLIENTSTATE_LOADING && + current_server_info_requesttime >= 0 && + time_get() > current_server_info_requesttime) + { + client_serverbrowse_request(&server_address); + current_server_info_requesttime = time_get()+time_freq()*2; + } + } + + /* STRESS TEST: join the server again */ + if(config.dbg_stress) + { + static int64 action_taken = 0; + int64 now = time_get(); + if(client_state() == CLIENTSTATE_OFFLINE) + { + if(now > action_taken+time_freq()*2) + { + dbg_msg("stress", "reconnecting!"); + client_connect(config.dbg_stress_server); + action_taken = now; + } + } + else + { + /*if(now > action_taken+time_freq()*(10+config.dbg_stress)) + { + dbg_msg("stress", "disconnecting!"); + client_disconnect(); + action_taken = now; + }*/ + } + } + + /* pump the network */ + client_pump_network(); + + /* update the maser server registry */ + mastersrv_update(); + + /* update the server browser */ + client_serverbrowse_update(); +} + + +static void client_versionupdate() +{ + static int state = 0; + static HOSTLOOKUP version_serveraddr; + + if(state == 0) + { + engine_hostlookup(&version_serveraddr, config.cl_version_server); + state++; + } + else if(state == 1) + { + if(jobs_status(&version_serveraddr.job) == JOBSTATUS_DONE) + { + CNetChunk Packet; + + mem_zero(&Packet, sizeof(Packet)); + + version_serveraddr.addr.port = VERSIONSRV_PORT; + + Packet.m_ClientID = -1; + Packet.m_Address = version_serveraddr.addr; + Packet.m_pData = VERSIONSRV_GETVERSION; + Packet.m_DataSize = sizeof(VERSIONSRV_GETVERSION); + Packet.m_Flags = NETSENDFLAG_CONNLESS; + + m_NetClient.Send(&Packet); + state++; + } + } +} + +static void client_run() +{ + NETADDR bindaddr; + int64 reporttime = time_get(); + int64 reportinterval = time_freq()*1; + + static PERFORMACE_INFO rootscope = {"root", 0}; + perf_start(&rootscope); + + local_start_time = time_get(); + snapshot_parts = 0; + + /* init graphics and sound */ + m_pGraphics = CreateEngineGraphics(); + if(m_pGraphics->Init() != 0) + return; + + /* start refreshing addresses while we load */ + mastersrv_refresh_addresses(); + + /* init the editor */ + m_pEditor = CreateEditor(); + m_pEditor->Init(m_pGraphics); + + /* sound is allowed to fail */ + snd_init(); + + /* load data */ + if(!client_load_data()) + return; + + /* init the mod */ + m_pGameClient = CreateGameClient(&m_Client); + modc_init(); + dbg_msg("client", "version %s", modc_net_version()); + + /* open socket */ + mem_zero(&bindaddr, sizeof(bindaddr)); + m_NetClient.Open(bindaddr, 0); + + /* connect to the server if wanted */ + /* + if(config.cl_connect[0] != 0) + client_connect(config.cl_connect); + config.cl_connect[0] = 0; + */ + + /* */ + graph_init(&fps_graph, 0.0f, 200.0f); + + /* never start with the editor */ + config.cl_editor = 0; + + inp_mouse_mode_relative(); + + while (1) + { + static PERFORMACE_INFO rootscope = {"root", 0}; + int64 frame_start_time = time_get(); + frames++; + + perf_start(&rootscope); + + /* */ + client_versionupdate(); + + /* handle pending connects */ + if(cmd_connect[0]) + { + client_connect(cmd_connect); + cmd_connect[0] = 0; + } + + /* update input */ + { + static PERFORMACE_INFO scope = {"inp_update", 0}; + perf_start(&scope); + inp_update(); + perf_end(); + } + + /* update sound */ + { + static PERFORMACE_INFO scope = {"snd_update", 0}; + perf_start(&scope); + snd_update(); + perf_end(); + } + + /* release focus */ + if(!Graphics()->WindowActive()) + { + if(window_must_refocus == 0) + inp_mouse_mode_absolute(); + window_must_refocus = 1; + } + else if (config.dbg_focus && inp_key_pressed(KEY_ESCAPE)) + { + inp_mouse_mode_absolute(); + window_must_refocus = 1; + } + + /* refocus */ + if(window_must_refocus && Graphics()->WindowActive()) + { + if(window_must_refocus < 3) + { + inp_mouse_mode_absolute(); + window_must_refocus++; + } + + if(inp_key_pressed(KEY_MOUSE_1)) + { + inp_mouse_mode_relative(); + window_must_refocus = 0; + } + } + + /* panic quit button */ + if(inp_key_pressed(KEY_LCTRL) && inp_key_pressed(KEY_LSHIFT) && inp_key_pressed('q')) + break; + + if(inp_key_pressed(KEY_LCTRL) && inp_key_pressed(KEY_LSHIFT) && inp_key_down('d')) + config.debug ^= 1; + + if(inp_key_pressed(KEY_LCTRL) && inp_key_pressed(KEY_LSHIFT) && inp_key_down('g')) + config.dbg_graphs ^= 1; + + if(inp_key_pressed(KEY_LCTRL) && inp_key_pressed(KEY_LSHIFT) && inp_key_down('e')) + { + config.cl_editor = config.cl_editor^1; + inp_mouse_mode_relative(); + } + + /* + if(!gfx_window_open()) + break; + */ + + /* render */ + if(config.cl_editor) + { + client_update(); + m_pEditor->UpdateAndRender(); + m_pGraphics->Swap(); + } + else + { + { + static PERFORMACE_INFO scope = {"client_update", 0}; + perf_start(&scope); + client_update(); + perf_end(); + } + + if(config.dbg_stress) + { + if((frames%10) == 0) + { + client_render(); + m_pGraphics->Swap(); + } + } + else + { + { + static PERFORMACE_INFO scope = {"client_render", 0}; + perf_start(&scope); + client_render(); + perf_end(); + } + + { + static PERFORMACE_INFO scope = {"gfx_swap", 0}; + perf_start(&scope); + m_pGraphics->Swap(); + perf_end(); + } + } + } + + perf_end(); + + + /* check conditions */ + if(client_state() == CLIENTSTATE_QUITING) + break; + + /* be nice */ + if(config.dbg_stress) + thread_sleep(5); + else if(config.cl_cpu_throttle || !Graphics()->WindowActive()) + thread_sleep(1); + + if(config.dbg_hitch) + { + thread_sleep(config.dbg_hitch); + config.dbg_hitch = 0; + } + + if(reporttime < time_get()) + { + if(0 && config.debug) + { + dbg_msg("client/report", "fps=%.02f (%.02f %.02f) netstate=%d", + frames/(float)(reportinterval/time_freq()), + 1.0f/frametime_high, + 1.0f/frametime_low, + m_NetClient.State()); + } + frametime_low = 1; + frametime_high = 0; + frames = 0; + reporttime += reportinterval; + perf_next(); + + if(config.dbg_pref) + perf_dump(&rootscope); + } + + /* update frametime */ + frametime = (time_get()-frame_start_time)/(float)time_freq(); + if(frametime < frametime_low) + frametime_low = frametime; + if(frametime > frametime_high) + frametime_high = frametime; + + graph_add(&fps_graph, 1.0f/frametime, 1,1,1); + } + + modc_shutdown(); + client_disconnect(); + + m_pGraphics->Shutdown(); + snd_shutdown(); +} + +void gfx_swap() +{ + m_pGraphics->Swap(); +} + +static void con_connect(void *result, void *user_data) +{ + str_copy(cmd_connect, console_arg_string(result, 0), sizeof(cmd_connect)); +} + +static void con_disconnect(void *result, void *user_data) +{ + client_disconnect(); +} + +static void con_quit(void *result, void *user_data) +{ + client_quit(); +} + +static void con_ping(void *result, void *user_data) +{ + msg_pack_start_system(NETMSG_PING, 0); + msg_pack_end(); + client_send_msg(); + ping_start_time = time_get(); +} + +static void con_screenshot(void *result, void *user_data) +{ + Graphics()->TakeScreenshot(); +} + +static void con_rcon(void *result, void *user_data) +{ + client_rcon(console_arg_string(result, 0)); +} + +static void con_rcon_auth(void *result, void *user_data) +{ + client_rcon_auth("", console_arg_string(result, 0)); +} + +static void con_addfavorite(void *result, void *user_data) +{ + NETADDR addr; + if(net_addr_from_str(&addr, console_arg_string(result, 0)) == 0) + client_serverbrowse_addfavorite(addr); +} + +const char *client_demoplayer_play(const char *filename) +{ + int crc; + const char *error; + client_disconnect(); + m_NetClient.ResetErrorString(); + + /* try to start playback */ + demorec_playback_registercallbacks(client_democallback_snapshot, client_democallback_message); + + if(demorec_playback_load(filename)) + return "error loading demo"; + + /* load map */ + crc = (demorec_playback_info()->header.crc[0]<<24)| + (demorec_playback_info()->header.crc[1]<<16)| + (demorec_playback_info()->header.crc[2]<<8)| + (demorec_playback_info()->header.crc[3]); + error = client_load_map_search(demorec_playback_info()->header.map, crc); + if(error) + { + client_disconnect_with_reason(error); + return error; + } + + modc_connected(); + + /* setup buffers */ + mem_zero(demorec_snapshotdata, sizeof(demorec_snapshotdata)); + + snapshots[SNAP_CURRENT] = &demorec_snapshotholders[SNAP_CURRENT]; + snapshots[SNAP_PREV] = &demorec_snapshotholders[SNAP_PREV]; + + snapshots[SNAP_CURRENT]->m_pSnap = (CSnapshot *)demorec_snapshotdata[SNAP_CURRENT][0]; + snapshots[SNAP_CURRENT]->m_pAltSnap = (CSnapshot *)demorec_snapshotdata[SNAP_CURRENT][1]; + snapshots[SNAP_CURRENT]->m_SnapSize = 0; + snapshots[SNAP_CURRENT]->m_Tick = -1; + + snapshots[SNAP_PREV]->m_pSnap = (CSnapshot *)demorec_snapshotdata[SNAP_PREV][0]; + snapshots[SNAP_PREV]->m_pAltSnap = (CSnapshot *)demorec_snapshotdata[SNAP_PREV][1]; + snapshots[SNAP_PREV]->m_SnapSize = 0; + snapshots[SNAP_PREV]->m_Tick = -1; + + /* enter demo playback state */ + client_set_state(CLIENTSTATE_DEMOPLAYBACK); + + demorec_playback_play(); + modc_entergame(); + + return 0; +} + +static void con_play(void *result, void *user_data) +{ + client_demoplayer_play(console_arg_string(result, 0)); +} + +static void con_record(void *result, void *user_data) +{ + if(state != CLIENTSTATE_ONLINE) + dbg_msg("demorec/record", "client is not online"); + else + { + char filename[512]; + str_format(filename, sizeof(filename), "demos/%s.demo", console_arg_string(result, 0)); + demorec_record_start(filename, modc_net_version(), current_map, current_map_crc, "client"); + } +} + +static void con_stoprecord(void *result, void *user_data) +{ + demorec_record_stop(); +} + +static void con_serverdummy(void *result, void *user_data) +{ + dbg_msg("client", "this command is not available on the client"); +} + +static void client_register_commands() +{ + MACRO_REGISTER_COMMAND("quit", "", CFGFLAG_CLIENT, con_quit, 0x0, "Quit Teeworlds"); + MACRO_REGISTER_COMMAND("exit", "", CFGFLAG_CLIENT, con_quit, 0x0, "Quit Teeworlds"); + MACRO_REGISTER_COMMAND("connect", "s", CFGFLAG_CLIENT, con_connect, 0x0, "Connect to the specified host/ip"); + MACRO_REGISTER_COMMAND("disconnect", "", CFGFLAG_CLIENT, con_disconnect, 0x0, "Disconnect from the server"); + MACRO_REGISTER_COMMAND("ping", "", CFGFLAG_CLIENT, con_ping, 0x0, "Ping the current server"); + MACRO_REGISTER_COMMAND("screenshot", "", CFGFLAG_CLIENT, con_screenshot, 0x0, "Take a screenshot"); + MACRO_REGISTER_COMMAND("rcon", "r", CFGFLAG_CLIENT, con_rcon, 0x0, "Send specified command to rcon"); + MACRO_REGISTER_COMMAND("rcon_auth", "s", CFGFLAG_CLIENT, con_rcon_auth, 0x0, "Authenticate to rcon"); + + MACRO_REGISTER_COMMAND("play", "r", CFGFLAG_CLIENT, con_play, 0x0, "Play the file specified"); + MACRO_REGISTER_COMMAND("record", "s", CFGFLAG_CLIENT, con_record, 0, "Record to the file"); + MACRO_REGISTER_COMMAND("stoprecord", "", CFGFLAG_CLIENT, con_stoprecord, 0, "Stop recording"); + + MACRO_REGISTER_COMMAND("add_favorite", "s", CFGFLAG_CLIENT, con_addfavorite, 0x0, "Add a server as a favorite"); + + /* register server dummy commands for tab completion */ + MACRO_REGISTER_COMMAND("kick", "i", CFGFLAG_SERVER, con_serverdummy, 0, "Kick player with specified id"); + MACRO_REGISTER_COMMAND("ban", "s?i", CFGFLAG_SERVER, con_serverdummy, 0, "Ban player with ip/id for x minutes"); + MACRO_REGISTER_COMMAND("unban", "s", CFGFLAG_SERVER, con_serverdummy, 0, "Unban ip"); + MACRO_REGISTER_COMMAND("bans", "", CFGFLAG_SERVER, con_serverdummy, 0, "Show banlist"); + MACRO_REGISTER_COMMAND("status", "", CFGFLAG_SERVER, con_serverdummy, 0, "List players"); + MACRO_REGISTER_COMMAND("shutdown", "", CFGFLAG_SERVER, con_serverdummy, 0, "Shut down"); + /*MACRO_REGISTER_COMMAND("record", "", CFGFLAG_SERVER, con_serverdummy, 0); + MACRO_REGISTER_COMMAND("stoprecord", "", CFGFLAG_SERVER, con_serverdummy, 0);*/ +} + +void client_save_line(const char *line) +{ + engine_config_write_line(line); +} + +const char *client_user_directory() +{ + static char path[1024] = {0}; + fs_storage_path("Teeworlds", path, sizeof(path)); + return path; +} + +#if defined(CONF_PLATFORM_MACOSX) +int SDL_main(int argc, char **argv) +#else +int main(int argc, char **argv) +#endif +{ + /* init the engine */ + dbg_msg("client", "starting..."); + engine_init("Teeworlds"); + + /* register all console commands */ + client_register_commands(); + modc_console_init(); + + /* parse the command line arguments */ + engine_parse_arguments(argc, argv); + + /* execute config file */ + console_execute_file("settings.cfg"); + + /* run the client*/ + client_run(); + + /* write down the config and quit */ + if(engine_config_write_start() == 0) + { + config_save(); + client_serverbrowse_save(); + modc_save_config(); + engine_config_write_stop(); + } + + return 0; +} |