diff options
Diffstat (limited to 'src/engine/client/ec_client.c')
| -rw-r--r-- | src/engine/client/ec_client.c | 1146 |
1 files changed, 1146 insertions, 0 deletions
diff --git a/src/engine/client/ec_client.c b/src/engine/client/ec_client.c new file mode 100644 index 00000000..78e7877c --- /dev/null +++ b/src/engine/client/ec_client.c @@ -0,0 +1,1146 @@ +/* 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 <engine/e_system.h> +#include <engine/e_engine.h> +#include <engine/e_interface.h> +#include "ec_ui.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 <mastersrv/mastersrv.h> + +const int prediction_margin = 10; /* 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 */ +NETCLIENT *net; + +/* TODO: ugly, fix me */ +extern void client_serverbrowse_set(NETADDR4 *addr, int request, SERVER_INFO *info); + + +static int snapshot_part; +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 NETADDR4 server_address; +static int window_must_refocus = 0; +static int snaploss = 0; +static int snapcrcerrors = 0; + +static int ack_game_tick = -1; +static int current_recv_tick = 0; + +/* current time */ +static int current_tick = 0; +static float intratick = 0; + +/* predicted time */ +static int current_predtick = 0; +static float intrapredtick = 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 game_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]; + 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_add(GRAPH *g, float v) +{ + g->index = (g->index+1)&(GRAPH_MAX-1); + g->values[g->index] = v; +} + +static void graph_render(GRAPH *g, float x, float y, float w, float h) +{ + int i; + gfx_texture_set(-1); + + gfx_quads_begin(); + gfx_setcolor(0, 0, 0, 1); + gfx_quads_drawTL(x, y, w, h); + gfx_quads_end(); + + gfx_lines_begin(); + gfx_setcolor(0.95f, 0.95f, 0.95f, 1); + gfx_lines_draw(x, y+h/2, x+w, y+h/2); + gfx_setcolor(0.5f, 0.5f, 0.5f, 1); + gfx_lines_draw(x, y+(h*3)/4, x+w, y+(h*3)/4); + gfx_lines_draw(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]; + float v1 = g->values[i1]; + + gfx_setcolor(0, 1, 0, 1); + gfx_lines_draw(x+a0*w, y+h-v0*h, x+a1*w, y+h-v1*h); + } + gfx_lines_end(); +} + +typedef struct +{ + int64 snap; + int64 current; + int64 target; + + int64 rlast; + int64 tlast; + GRAPH graph; +} SMOOTHTIME; + +static void st_init(SMOOTHTIME *st, int64 target) +{ + st->snap = time_get(); + st->current = target; + st->target = target; + graph_init(&st->graph, 0.0f, 1.0f); +} + +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 = 0.25f; /*0.99f;*/ + if(t > c) + adjust_speed = 500.0f; + + 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); + + return r; +} + +static void st_update(SMOOTHTIME *st, int64 target) +{ + int64 now = time_get(); + st->current = st_get(st, now); + st->snap = now; + st->target = target; +} + +SMOOTHTIME game_time; +SMOOTHTIME predicted_time; + +GRAPH intra_graph; +GRAPH predict_graph; + +/* --- input snapping --- */ +static int input_data[MAX_INPUT_SIZE] = {0}; +static int input_data_size; +static int input_is_changed = 1; +static GRAPH input_late_graph; +void snap_input(void *data, int size) +{ + if(input_data_size != size || memcmp(input_data, data, size)) + input_is_changed = 1; + mem_copy(input_data, data, size); + input_data_size = size; +} + +/* -- snapshot handling --- */ +enum +{ + NUM_SNAPSHOT_TYPES=2 +}; + +SNAPSTORAGE snapshot_storage; +static SNAPSTORAGE_HOLDER *snapshots[NUM_SNAPSHOT_TYPES]; +static int recived_snapshots; +static char snapshot_incomming_data[MAX_SNAPSHOT_SIZE]; + +/* --- */ + +const void *snap_get_item(int snapid, int index, SNAP_ITEM *item) +{ + SNAPSHOT_ITEM *i; + dbg_assert(snapid >= 0 && snapid < NUM_SNAPSHOT_TYPES, "invalid snapid"); + i = snapshot_get_item(snapshots[snapid]->snap, index); + item->type = snapitem_type(i); + item->id = snapitem_id(i); + return (void *)snapitem_data(i); +} + +const void *snap_find_item(int snapid, int type, int id) +{ + /* TODO: linear search. should be fixed. */ + int i; + for(i = 0; i < snapshots[snapid]->snap->num_items; i++) + { + SNAPSHOT_ITEM *itm = snapshot_get_item(snapshots[snapid]->snap, i); + if(snapitem_type(itm) == type && snapitem_id(itm) == id) + return (void *)snapitem_data(itm); + } + return 0x0; +} + +int snap_num_items(int snapid) +{ + dbg_assert(snapid >= 0 && snapid < NUM_SNAPSHOT_TYPES, "invalid snapid"); + return snapshots[snapid]->snap->num_items; +} + +/* ------ time functions ------ */ +float client_intratick() { return intratick; } +float client_intrapredtick() { return intrapredtick; } +int client_tick() { return current_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 *info = msg_get_info(); + NETPACKET packet; + mem_zero(&packet, sizeof(NETPACKET)); + + packet.client_id = 0; + packet.data = info->data; + packet.data_size = info->size; + + if(info->flags&MSGFLAG_VITAL) + packet.flags = PACKETFLAG_VITAL; + + netclient_send(net, &packet); + return 0; +} + +static void client_send_info() +{ + msg_pack_start_system(NETMSG_INFO, MSGFLAG_VITAL); + 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); + msg_pack_end(); + client_send_msg(); +} + +static void client_send_ready() +{ + msg_pack_start_system(NETMSG_READY, MSGFLAG_VITAL); + msg_pack_end(); + client_send_msg(); +} + +void client_rcon(const char *cmd) +{ + msg_pack_start_system(NETMSG_CMD, MSGFLAG_VITAL); + msg_pack_string(config.rcon_password, 32); + msg_pack_string(cmd, 256); + msg_pack_end(); + client_send_msg(); +} + +int client_connection_problems() +{ + return netclient_gotproblems(net); +} + +static void client_send_input() +{ + int64 now = time_get(); + int i; + + if(current_predtick <= 0) + return; + + msg_pack_start_system(NETMSG_INPUT, 0); + msg_pack_int(ack_game_tick); + msg_pack_int(current_predtick); + msg_pack_int(input_data_size); + + inputs[current_input].tick = current_predtick; + inputs[current_input].game_time = st_get(&predicted_time, now); + inputs[current_input].time = now; + + for(i = 0; i < input_data_size/4; i++) + { + inputs[current_input].data[i] = input_data[i]; + msg_pack_int(input_data[i]); + } + + current_input++; + current_input%=200; + + msg_pack_end(); + client_send_msg(); +} + +/* 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; + snapstorage_purge_all(&snapshot_storage); + recived_snapshots = 0; + current_predtick = 0; + current_recv_tick = 0; +} + +void client_entergame() +{ + /* 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; + + dbg_msg("client", "connecting to '%s'", server_address_str); + + strncpy(buf, server_address_str, 512); + + 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); + + if(net_host_lookup(buf, port, &server_address) != 0) + dbg_msg("client", "could not find the address of %s, connecting to localhost", buf); + + netclient_connect(net, &server_address); + client_set_state(CLIENTSTATE_CONNECTING); + + graph_init(&intra_graph, 0.0f, 1.0f); + graph_init(&input_late_graph, 0.0f, 1.0f); + graph_init(&predict_graph, 0.0f, 200.0f); + +} + +void client_disconnect_with_reason(const char *reason) +{ + netclient_disconnect(net, reason); + client_set_state(CLIENTSTATE_OFFLINE); + map_unload(); +} + +void client_disconnect() +{ + netclient_disconnect(net, 0); + client_set_state(CLIENTSTATE_OFFLINE); + map_unload(); +} + +static int client_load_data() +{ + debug_font = gfx_load_texture("data/debug_font.png"); + return 1; +} + +extern int snapshot_data_rate[0xffff]; +extern int snapshot_data_updates[0xffff]; + +static void client_debug_render() +{ + static NETSTATS prev, current; + static int64 last_snap = 0; + static float frametime_avg = 0; + char buffer[512]; + + if(!config.debug) + return; + + gfx_blend_normal(); + gfx_texture_set(debug_font); + gfx_mapscreen(0,0,gfx_screenwidth(),gfx_screenheight()); + + if(time_get()-last_snap > time_freq()/10) + { + last_snap = time_get(); + prev = current; + netclient_stats(net, ¤t); + } + + frametime_avg = frametime_avg*0.9f + frametime*0.1f; + sprintf(buffer, "ticks: %8d %8d send: %6d recv: %6d snaploss: %d mem %dk gfxmem: %dk fps: %3d", + current_tick, current_predtick, + (current.send_bytes-prev.send_bytes)*10, + (current.recv_bytes-prev.recv_bytes)*10, + snaploss, + mem_allocated()/1024, + gfx_memory_usage()/1024, + (int)(1.0f/frametime_avg)); + gfx_quads_text(2, 2, 16, buffer); + + sprintf(buffer, "ui: %p %p", ui_hot_item(), ui_active_item()); + gfx_quads_text(2, 16, 16, buffer); + + + /* render rates */ + { + int i; + for(i = 0; i < 256; i++) + { + if(snapshot_data_rate[i]) + { + sprintf(buffer, "%4d : %8d %8d %8d", i, snapshot_data_rate[i]/8, snapshot_data_updates[i], + (snapshot_data_rate[i]/snapshot_data_updates[i])/8); + gfx_quads_text(2, 100+i*8, 16, buffer); + } + } + } + + /* render graphs */ + gfx_mapscreen(0,0,400.0f,300.0f); + graph_render(&predict_graph, 300, 10, 90, 50); + graph_render(&predicted_time.graph, 300, 10+50+10, 90, 50); + + graph_render(&intra_graph, 300, 10+50+10+50+10, 90, 50); + graph_render(&input_late_graph, 300, 10+50+10+50+10+50+10, 90, 50); + +} + +void client_quit() +{ + client_set_state(CLIENTSTATE_QUITING); +} + +const char *client_error_string() +{ + return netclient_error_string(net); +} + +static void client_render() +{ + gfx_clear(0.0f,0.0f,0.0f); + modc_render(); + client_debug_render(); +} + +static const char *client_load_map(const char *mapname, int wanted_crc) +{ + static char errormsg[128]; + DATAFILE *df; + char buf[512]; + int crc; + + dbg_msg("client", "loading map, map=%s wanted crc=%08x", mapname, wanted_crc); + client_set_state(CLIENTSTATE_LOADING); + + sprintf(buf, "data/maps/%s.map", mapname); + df = datafile_load(buf); + if(!df) + { + sprintf(errormsg, "map '%s' not found", mapname); + return errormsg; + } + + /* get the crc of the map */ + crc = datafile_crc(buf); + if(crc != wanted_crc) + { + datafile_unload(df); + sprintf(errormsg, "map differs from the server. %08x != %08x", crc, wanted_crc); + return errormsg; + } + + map_set(df); + return NULL; +} + +static void client_process_packet(NETPACKET *packet) +{ + if(packet->client_id == -1) + { + /* connectionlesss */ + if(packet->data_size >= (int)sizeof(SERVERBROWSE_LIST) && + memcmp(packet->data, SERVERBROWSE_LIST, sizeof(SERVERBROWSE_LIST)) == 0) + { + int size = packet->data_size-sizeof(SERVERBROWSE_LIST); + int num = size/sizeof(NETADDR4); + NETADDR4 *addrs = (NETADDR4 *)((char*)packet->data+sizeof(SERVERBROWSE_LIST)); + int i; + + for(i = 0; i < num; i++) + { + NETADDR4 addr = addrs[i]; + SERVER_INFO info = {0}; + +#if defined(CONF_ARCH_ENDIAN_BIG) + const char *tmp = (const char *)&addr.port; + addr.port = (tmp[1]<<8) | tmp[0]; +#endif + + info.latency = 999; + sprintf(info.address, "%d.%d.%d.%d:%d", + addr.ip[0], addr.ip[1], addr.ip[2], + addr.ip[3], addr.port); + sprintf(info.name, "\255%d.%d.%d.%d:%d", /* the \255 is to make sure that it's sorted last */ + addr.ip[0], addr.ip[1], addr.ip[2], + addr.ip[3], addr.port); + + client_serverbrowse_set(addrs+i, 1, &info); + } + } + + if(packet->data_size >= (int)sizeof(SERVERBROWSE_INFO) && + memcmp(packet->data, SERVERBROWSE_INFO, sizeof(SERVERBROWSE_INFO)) == 0) + { + /* we got ze info */ + UNPACKER up; + SERVER_INFO info = {0}; + int i; + + unpacker_reset(&up, (unsigned char*)packet->data+sizeof(SERVERBROWSE_INFO), packet->data_size-sizeof(SERVERBROWSE_INFO)); + + strncpy(info.version, unpacker_get_string(&up), 32); + strncpy(info.name, unpacker_get_string(&up), 64); + strncpy(info.map, unpacker_get_string(&up), 32); + info.game_type = atol(unpacker_get_string(&up)); + info.flags = atol(unpacker_get_string(&up)); + info.progression = atol(unpacker_get_string(&up)); + info.num_players = atol(unpacker_get_string(&up)); + info.max_players = atol(unpacker_get_string(&up)); + sprintf(info.address, "%d.%d.%d.%d:%d", + packet->address.ip[0], packet->address.ip[1], packet->address.ip[2], + packet->address.ip[3], packet->address.port); + + for(i = 0; i < info.num_players; i++) + { + strncpy(info.player_names[i], unpacker_get_string(&up), 48); + info.player_scores[i] = atol(unpacker_get_string(&up)); + } + + /* TODO: unpack players aswell */ + client_serverbrowse_set(&packet->address, 0, &info); + } + } + else + { + int sys; + int msg = msg_unpack_start(packet->data, packet->data_size, &sys); + + if(sys) + { + /* system message */ + if(msg == NETMSG_MAP) + { + const char *map = msg_unpack_string(); + int map_crc = msg_unpack_int(); + const char *error = client_load_map(map, map_crc); + + if(!error) + { + dbg_msg("client/network", "loading done"); + client_send_ready(); + modc_connected(); + } + else + { + client_disconnect_with_reason(error); + } + } + 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 input_predtick = msg_unpack_int(); + int time_left = msg_unpack_int(); + int part_size = 0; + int crc = 0; + int complete_size = 0; + + 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(); + } + + /* TODO: adjust our prediction time */ + if(time_left) + { + int k; + + graph_add(&input_late_graph, time_left/100.0f+0.5f); + + if(time_left < 0) + dbg_msg("client", "input was late with %d ms", time_left); + + for(k = 0; k < 200; k++) /* TODO: do this better */ + { + if(inputs[k].tick == input_predtick) + { + /*-1000/50 prediction_margin */ + int64 target = inputs[k].game_time + (time_get() - inputs[k].time); + st_update(&predicted_time, target - (int64)(((time_left-prediction_margin)/1000.0f)*time_freq())); + break; + } + } + } + + if(snapshot_part == part && game_tick > current_recv_tick) + { + /* TODO: clean this up abit */ + const char *d = (const char *)msg_unpack_raw(part_size); + mem_copy((char*)snapshot_incomming_data + part*MAX_SNAPSHOT_PACKSIZE, d, part_size); + snapshot_part++; + + if(snapshot_part == num_parts) + { + static SNAPSHOT emptysnap; + SNAPSHOT *deltashot = &emptysnap; + int purgetick; + void *deltadata; + int deltasize; + unsigned char tmpbuffer[MAX_SNAPSHOT_SIZE]; + unsigned char tmpbuffer2[MAX_SNAPSHOT_SIZE]; + unsigned char tmpbuffer3[MAX_SNAPSHOT_SIZE]; + int snapsize; + + complete_size = (num_parts-1) * MAX_SNAPSHOT_PACKSIZE + part_size; + + snapshot_part = 0; + + /* find snapshot that we should use as delta */ + emptysnap.data_size = 0; + emptysnap.num_items = 0; + + /* find delta */ + if(delta_tick >= 0) + { + int deltashot_size = snapstorage_get(&snapshot_storage, delta_tick, 0, &deltashot); + + 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 = snapshot_empty_delta(); + deltasize = sizeof(int)*3; + + if(complete_size) + { + int compsize = zerobit_decompress(snapshot_incomming_data, complete_size, tmpbuffer); + int intsize = intpack_decompress(tmpbuffer, compsize, tmpbuffer2); + deltadata = tmpbuffer2; + deltasize = intsize; + } + + /*dbg_msg("UNPACK", "%d unpacked with %d", game_tick, delta_tick); */ + + purgetick = delta_tick; + snapsize = snapshot_unpack_delta(deltashot, (SNAPSHOT*)tmpbuffer3, deltadata, deltasize); + if(msg != NETMSG_SNAPEMPTY && snapshot_crc((SNAPSHOT*)tmpbuffer3) != crc) + { + if(config.debug) + dbg_msg("client", "snapshot crc error %d", snapcrcerrors); + 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]->tick < purgetick) + purgetick = snapshots[SNAP_PREV]->tick; + if(snapshots[SNAP_CURRENT] && snapshots[SNAP_CURRENT]->tick < purgetick) + purgetick = snapshots[SNAP_PREV]->tick; + snapstorage_purge_until(&snapshot_storage, purgetick); + /*client_snapshot_purge_until(game_tick-50); */ + + /* add new */ + snapstorage_add(&snapshot_storage, game_tick, time_get(), snapsize, (SNAPSHOT*)tmpbuffer3); + + /* apply snapshot, cycle pointers */ + recived_snapshots++; + + + if(current_recv_tick > 0) + snaploss += game_tick-current_recv_tick-1; + 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); + st_init(&game_time, (game_tick-1)*time_freq()/50); + snapshots[SNAP_PREV] = snapshot_storage.first; + snapshots[SNAP_CURRENT] = snapshot_storage.last; + local_start_time = time_get(); + client_set_state(CLIENTSTATE_ONLINE); + } + + { + int64 now = time_get(); + graph_add(&predict_graph, (st_get(&predicted_time, now)-st_get(&game_time, now))/(float)time_freq()); + } + + st_update(&game_time, (game_tick-1)*time_freq()/50); + + /* ack snapshot */ + ack_game_tick = game_tick; + } + } + else + { + dbg_msg("client", "snapsht reset!"); + snapshot_part = 0; + } + } + } + else + { + /* game message */ + modc_message(msg); + } + } +} + +static void client_pump_network() +{ + NETPACKET packet; + + netclient_update(net); + + /* check for errors */ + if(client_state() != CLIENTSTATE_OFFLINE && netclient_state(net) == NETSTATE_OFFLINE) + { + client_set_state(CLIENTSTATE_OFFLINE); + dbg_msg("client", "offline error='%s'", netclient_error_string(net)); + } + + /* */ + if(client_state() == CLIENTSTATE_CONNECTING && netclient_state(net) == NETSTATE_ONLINE) + { + /* we switched to online */ + dbg_msg("client", "connected, sending info"); + client_set_state(CLIENTSTATE_LOADING); + client_send_info(); + } + + /* process packets */ + while(netclient_recv(net, &packet)) + client_process_packet(&packet); +} + +static void client_update() +{ + /* switch snapshot */ + if(client_state() != CLIENTSTATE_OFFLINE && recived_snapshots >= 3) + { + int repredict = 0; + int64 now = st_get(&game_time, time_get()); + int64 pred_now = st_get(&predicted_time, time_get()); + + while(1) + { + SNAPSTORAGE_HOLDER *cur = snapshots[SNAP_CURRENT]; + int64 tickstart = (cur->tick)*time_freq()/50; + + if(tickstart < now) + { + SNAPSTORAGE_HOLDER *next = snapshots[SNAP_CURRENT]->next; + if(next) + { + snapshots[SNAP_PREV] = snapshots[SNAP_CURRENT]; + snapshots[SNAP_CURRENT] = next; + + /* set tick */ + current_tick = snapshots[SNAP_CURRENT]->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]->tick)*time_freq()/50; + int64 prevtick_start = (snapshots[SNAP_PREV]->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_intrapred = 0; + + intratick = (now - prevtick_start) / (float)(curtick_start-prevtick_start); + + graph_add(&intra_graph, intratick*0.25f); + + curtick_start = new_pred_tick*time_freq()/50; + prevtick_start = prev_pred_tick*time_freq()/50; + intrapredtick = (pred_now - prevtick_start) / (float)(curtick_start-prevtick_start); + + + if(new_pred_tick > current_predtick) + { + last_intrapred = intrapredtick; + current_predtick = new_pred_tick; + repredict = 1; + + /* send input */ + client_send_input(); + } + + if(intrapredtick < last_intrapred) + dbg_msg("client", "prediction time goes backwards, that can't be good"); + last_intrapred = intrapredtick; + } + + /* only do sane predictions */ + if(repredict) + { + if(current_predtick > current_tick && current_predtick < current_tick+50) + modc_predict(); + } + } + + /* STRESS TEST: join the server again */ + if(client_state() == CLIENTSTATE_OFFLINE && config.dbg_stress && (frames%100) == 0) + client_connect(config.dbg_stress_server); + + /* pump the network */ + client_pump_network(); + + /* update the server browser */ + client_serverbrowse_update(); +} + +extern int editor_update_and_render(); +extern void editor_init(); + +static void client_run() +{ + NETADDR4 bindaddr; + int64 reporttime = time_get(); + int64 reportinterval = time_freq()*1; + int editor_active = 0; + + local_start_time = time_get(); + snapshot_part = 0; + + /* init graphics and sound */ + if(!gfx_init()) + return; + + /* init the editor */ + editor_init(); + + /* sound is allowed to fail */ + snd_init(); + + /* load data */ + if(!client_load_data()) + return; + + /* init the mod */ + modc_init(); + dbg_msg("client", "version %s", modc_net_version()); + + /* open socket */ + mem_zero(&bindaddr, sizeof(bindaddr)); + net = 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; + */ + + inp_mouse_mode_relative(); + + while (1) + { + int64 frame_start_time = time_get(); + frames++; + + /* update input */ + inp_update(); + + /* refocus */ + if(!gfx_window_active()) + { + if(window_must_refocus == 0) + inp_mouse_mode_absolute(); + window_must_refocus = 1; + } + + if(window_must_refocus && gfx_window_active()) + { + 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; + } + } + + /* screenshot button */ + if(inp_key_down(config.key_screenshot)) + gfx_screenshot(); + + /* some debug keys */ + /* + if(config.debug) + { + if(inp_key_pressed(KEY_F1)) + inp_mouse_mode_absolute(); + if(inp_key_pressed(KEY_F2)) + inp_mouse_mode_relative(); + + if(inp_key_pressed(KEY_F5)) + { + ack_game_tick = -1; + client_send_input(); + } + }*/ + + /* 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('E')) + editor_active = editor_active^1; + + if(!gfx_window_open()) + break; + + /* render */ + if(editor_active) + { + client_update(); + editor_update_and_render(); + gfx_swap(); + } + else + { + client_update(); + + if(config.dbg_stress) + { + if((frames%10) == 0) + { + client_render(); + gfx_swap(); + } + } + else + { + client_render(); + gfx_swap(); + } + } + + /* check conditions */ + if(client_state() == CLIENTSTATE_QUITING) + break; + + /* be nice */ + if(config.cl_cpu_throttle || !gfx_window_active()) + thread_sleep(1); + + if(reporttime < time_get()) + { + if(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, + netclient_state(net)); + } + frametime_low = 1; + frametime_high = 0; + frames = 0; + reporttime += reportinterval; + } + + /* 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; + } + + modc_shutdown(); + client_disconnect(); + + gfx_shutdown(); + snd_shutdown(); +} + + +int editor_main(int argc, char **argv); + +int main(int argc, char **argv) +{ + /* init the engine */ + dbg_msg("client", "starting..."); + engine_init("Teewars", argc, argv); + + client_run(); + + engine_writeconfig(); + return 0; +} |