ddnet/src/engine/client/ec_client.c

1205 lines
27 KiB
C
Raw Normal View History

2007-11-25 19:42:40 +00:00
/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */
2007-05-22 15:03:32 +00:00
#include <string.h>
2007-05-24 10:57:18 +00:00
#include <stdarg.h>
#include <stdlib.h>
2007-07-13 13:40:04 +00:00
#include <stdio.h>
2007-05-22 15:03:32 +00:00
#include <math.h>
2007-12-15 10:24:49 +00:00
#include <engine/e_system.h>
#include <engine/e_engine.h>
#include <engine/e_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>
2007-05-22 18:18:41 +00:00
#include <mastersrv/mastersrv.h>
2007-12-17 22:20:43 +00:00
const int prediction_margin = 7; /* magic network prediction value */
2007-09-25 21:53:14 +00:00
/*
Server Time
Client Mirror Time
Client Predicted Time
Snapshot Latency
Downstream latency
Prediction Latency
Upstream latency
*/
2007-10-03 21:32:02 +00:00
/* 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;
2007-12-11 23:00:48 +00:00
static float frametime_low = 1.0f;
static float frametime_high = 0.0f;
2007-12-02 17:55:45 +00:00
static int frames = 0;
static NETADDR4 server_address;
static int window_must_refocus = 0;
static int snaploss = 0;
2007-08-22 21:21:20 +00:00
static int snapcrcerrors = 0;
static int ack_game_tick = -1;
static int current_recv_tick = 0;
2007-10-06 17:01:06 +00:00
/* current time */
static int current_tick = 0;
static float intratick = 0;
static float ticktime = 0;
2007-10-06 17:01:06 +00:00
/* predicted time */
static int current_predtick = 0;
static float intrapredtick = 0;
2007-10-06 17:01:06 +00:00
static struct /* TODO: handle input better */
{
2007-10-06 17:01:06 +00:00
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)
{
2007-10-06 17:01:06 +00:00
float adjust_speed, a;
int64 c = st->current + (now - st->snap);
int64 t = st->target + (now - st->snap);
2007-10-06 17:01:06 +00:00
int64 r;
2007-10-06 17:01:06 +00:00
/* it's faster to adjust upward instead of downward */
/* we might need to adjust these abit */
2007-12-17 22:20:43 +00:00
adjust_speed = 0.2f; /*0.99f;*/
if(t > c)
2007-12-17 22:20:43 +00:00
adjust_speed = 350.0f;
a = ((now-st->snap)/(float)time_freq()) * adjust_speed;
if(a > 1.0f)
a = 1.0f;
2007-10-06 17:01:06 +00:00
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;
2007-10-06 17:01:06 +00:00
/* --- input snapping --- */
static GRAPH input_late_graph;
2007-05-22 15:03:32 +00:00
2007-10-06 17:01:06 +00:00
/* -- snapshot handling --- */
2007-05-22 15:03:32 +00:00
enum
{
2007-10-06 17:01:06 +00:00
NUM_SNAPSHOT_TYPES=2
2007-05-22 15:03:32 +00:00
};
SNAPSTORAGE snapshot_storage;
static SNAPSTORAGE_HOLDER *snapshots[NUM_SNAPSHOT_TYPES];
2007-05-22 15:03:32 +00:00
static int recived_snapshots;
2007-07-26 19:09:31 +00:00
static char snapshot_incomming_data[MAX_SNAPSHOT_SIZE];
2007-05-22 15:03:32 +00:00
2007-10-06 17:01:06 +00:00
/* --- */
2007-05-22 15:03:32 +00:00
const void *snap_get_item(int snapid, int index, SNAP_ITEM *item)
2007-05-22 15:03:32 +00:00
{
2007-10-06 17:01:06 +00:00
SNAPSHOT_ITEM *i;
2007-05-22 15:03:32 +00:00
dbg_assert(snapid >= 0 && snapid < NUM_SNAPSHOT_TYPES, "invalid snapid");
2007-10-06 17:01:06 +00:00
i = snapshot_get_item(snapshots[snapid]->snap, index);
item->type = snapitem_type(i);
item->id = snapitem_id(i);
return (void *)snapitem_data(i);
2007-05-22 15:03:32 +00:00
}
const void *snap_find_item(int snapid, int type, int id)
{
2007-10-06 17:01:06 +00:00
/* 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;
}
2007-05-22 15:03:32 +00:00
int snap_num_items(int snapid)
{
dbg_assert(snapid >= 0 && snapid < NUM_SNAPSHOT_TYPES, "invalid snapid");
2007-07-26 19:09:31 +00:00
return snapshots[snapid]->snap->num_items;
2007-05-22 15:03:32 +00:00
}
2007-10-06 17:01:06 +00:00
/* ------ time functions ------ */
float client_intratick() { return intratick; }
float client_intrapredtick() { return intrapredtick; }
float client_ticktime() { return ticktime; }
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()); }
2007-05-24 10:57:18 +00:00
2007-10-06 17:01:06 +00:00
/* ----- send functions ----- */
2007-07-13 13:40:04 +00:00
int client_send_msg()
2007-05-24 10:57:18 +00:00
{
const MSG_INFO *info = msg_get_info();
2007-07-13 13:40:04 +00:00
NETPACKET packet;
2007-07-21 19:02:47 +00:00
mem_zero(&packet, sizeof(NETPACKET));
2007-07-13 13:40:04 +00:00
packet.client_id = 0;
packet.data = info->data;
packet.data_size = info->size;
if(info->flags&MSGFLAG_VITAL)
packet.flags = PACKETFLAG_VITAL;
2007-05-24 10:57:18 +00:00
netclient_send(net, &packet);
2007-07-13 13:40:04 +00:00
return 0;
2007-05-24 10:57:18 +00:00
}
static void client_send_info()
{
msg_pack_start_system(NETMSG_INFO, MSGFLAG_VITAL);
2007-08-22 21:13:33 +00:00
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();
}
2007-09-23 21:00:57 +00:00
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();
}
2007-12-11 21:55:52 +00:00
int client_connection_problems()
{
return netclient_gotproblems(net);
}
2008-01-12 12:08:26 +00:00
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();
dbg_msg("client", "sent direct input");
}
static void client_send_input()
{
2007-10-06 17:01:06 +00:00
int64 now = time_get();
int i, size;
2007-10-06 17:01:06 +00:00
if(current_predtick <= 0)
return;
2008-01-12 12:08:26 +00:00
/* fetch input */
size = modc_snap_input(inputs[current_input].data);
msg_pack_start_system(NETMSG_INPUT, 0);
msg_pack_int(ack_game_tick);
msg_pack_int(current_predtick);
msg_pack_int(size);
inputs[current_input].tick = current_predtick;
inputs[current_input].game_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();
}
/* 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;
}
2007-10-06 17:01:06 +00:00
/* ------ state handling ----- */
2007-11-08 09:20:55 +00:00
static int state = CLIENTSTATE_OFFLINE;
int client_state() { return state; }
static void client_set_state(int s)
{
2007-10-06 17:01:06 +00:00
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);
}
2007-09-23 22:54:31 +00:00
/* called when the map is loaded and we should init for a new round */
static void client_on_enter_game()
{
2007-10-06 17:01:06 +00:00
/* reset input */
2007-09-23 22:54:31 +00:00
int i;
for(i = 0; i < 200; i++)
inputs[i].tick = -1;
current_input = 0;
2007-10-06 17:01:06 +00:00
/* reset snapshots */
2007-09-23 22:54:31 +00:00
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;
2007-10-06 17:01:06 +00:00
int port = 8303;
client_disconnect();
2007-10-06 17:01:06 +00:00
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);
2007-07-22 11:53:15 +00:00
}
2007-05-22 15:03:32 +00:00
2007-12-03 18:47:29 +00:00
void client_disconnect_with_reason(const char *reason)
{
netclient_disconnect(net, reason);
client_set_state(CLIENTSTATE_OFFLINE);
map_unload();
}
void client_disconnect()
2007-07-22 11:53:15 +00:00
{
client_disconnect_with_reason(0);
2007-07-22 11:53:15 +00:00
}
2007-05-22 15:03:32 +00:00
static int client_load_data()
2007-07-22 11:53:15 +00:00
{
2008-01-11 16:48:34 +00:00
debug_font = gfx_load_texture("data/debug_font.png", IMG_AUTO);
return 1;
2007-07-22 11:53:15 +00:00
}
extern int snapshot_data_rate[0xffff];
extern int snapshot_data_updates[0xffff];
static void client_debug_render()
2007-07-22 11:53:15 +00:00
{
2007-10-06 17:01:06 +00:00
static NETSTATS prev, current;
static int64 last_snap = 0;
static float frametime_avg = 0;
char buffer[512];
2007-11-08 09:11:32 +00:00
2007-07-26 19:09:31 +00:00
if(!config.debug)
return;
2007-11-08 09:11:32 +00:00
2007-07-22 11:53:15 +00:00
gfx_blend_normal();
gfx_texture_set(debug_font);
gfx_mapscreen(0,0,gfx_screenwidth(),gfx_screenheight());
2007-05-22 15:03:32 +00:00
2007-07-22 11:53:15 +00:00
if(time_get()-last_snap > time_freq()/10)
2007-05-22 15:03:32 +00:00
{
2007-07-22 11:53:15 +00:00
last_snap = time_get();
prev = current;
netclient_stats(net, &current);
2007-05-22 15:03:32 +00:00
}
frametime_avg = frametime_avg*0.9f + frametime*0.1f;
2007-10-03 21:32:02 +00:00
sprintf(buffer, "ticks: %8d %8d send: %6d recv: %6d snaploss: %d mem %dk gfxmem: %dk fps: %3d",
current_tick, current_predtick,
2007-07-22 11:53:15 +00:00
(current.send_bytes-prev.send_bytes)*10,
2007-07-26 19:09:31 +00:00
(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);
2007-12-10 19:36:45 +00:00
/* 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);
}
}
}
2007-10-06 17:01:06 +00:00
/* render graphs */
if(config.dbg_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);
}
2007-07-22 11:53:15 +00:00
}
void client_quit()
2007-07-22 11:53:15 +00:00
{
client_set_state(CLIENTSTATE_QUITING);
}
2007-05-22 15:03:32 +00:00
const char *client_error_string()
{
return netclient_error_string(net);
}
2007-05-22 15:03:32 +00:00
static void client_render()
{
gfx_clear(0.0f,0.0f,0.0f);
modc_render();
client_debug_render();
2007-07-22 11:53:15 +00:00
}
2007-12-03 18:47:29 +00:00
static const char *client_load_map(const char *mapname, int wanted_crc)
2007-07-22 11:53:15 +00:00
{
2007-12-03 18:47:29 +00:00
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;
}
2007-12-19 20:19:51 +00:00
recived_snapshots = 0;
2007-12-03 18:47:29 +00:00
map_set(df);
return NULL;
}
2007-05-22 15:03:32 +00:00
static void client_process_packet(NETPACKET *packet)
{
if(packet->client_id == -1)
{
2007-10-06 17:01:06 +00:00
/* 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
2007-12-19 19:39:08 +00:00
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);
2007-12-19 19:39:08 +00:00
client_serverbrowse_set(&addr, 1, &info);
}
}
2007-05-22 15:03:32 +00:00
if(packet->data_size >= (int)sizeof(SERVERBROWSE_INFO) &&
memcmp(packet->data, SERVERBROWSE_INFO, sizeof(SERVERBROWSE_INFO)) == 0)
2007-07-26 19:09:31 +00:00
{
2007-10-06 17:01:06 +00:00
/* we got ze info */
UNPACKER up;
SERVER_INFO info = {0};
2007-10-06 17:01:06 +00:00
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));
2007-07-22 11:53:15 +00:00
}
/* TODO: unpack players aswell */
client_serverbrowse_set(&packet->address, 0, &info);
2007-07-22 11:53:15 +00:00
}
}
else
{
int sys;
int msg = msg_unpack_start(packet->data, packet->data_size, &sys);
2007-10-06 17:01:06 +00:00
2007-07-22 11:53:15 +00:00
if(sys)
{
2007-10-06 17:01:06 +00:00
/* system message */
2007-07-22 11:53:15 +00:00
if(msg == NETMSG_MAP)
{
const char *map = msg_unpack_string();
2007-12-03 18:47:29 +00:00
int map_crc = msg_unpack_int();
const char *error = client_load_map(map, map_crc);
if(!error)
2007-07-22 11:53:15 +00:00
{
dbg_msg("client/network", "loading done");
client_send_ready();
modc_connected();
2007-07-22 11:53:15 +00:00
}
2007-07-22 09:15:34 +00:00
else
{
2007-12-03 18:47:29 +00:00
client_disconnect_with_reason(error);
2007-07-14 13:09:42 +00:00
}
2007-07-13 13:40:04 +00:00
}
else if(msg == NETMSG_SNAP || msg == NETMSG_SNAPSINGLE || msg == NETMSG_SNAPEMPTY)
2007-07-13 13:40:04 +00:00
{
2007-10-06 17:01:06 +00:00
/*dbg_msg("client/network", "got snapshot"); */
int num_parts = 1;
int part = 0;
2007-07-22 11:53:15 +00:00
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();
2007-07-22 11:53:15 +00:00
int part_size = 0;
int crc = 0;
int complete_size = 0;
if(msg == NETMSG_SNAP)
{
num_parts = msg_unpack_int();
part = msg_unpack_int();
}
2007-07-22 11:53:15 +00:00
if(msg != NETMSG_SNAPEMPTY)
2007-08-22 21:21:20 +00:00
{
crc = msg_unpack_int();
2007-07-22 11:53:15 +00:00
part_size = msg_unpack_int();
2007-08-22 21:21:20 +00:00
}
2007-07-22 11:53:15 +00:00
/* 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);
2007-09-25 21:53:14 +00:00
st_update(&predicted_time, target - (int64)(((time_left-prediction_margin)/1000.0f)*time_freq()));
break;
}
}
}
if(snapshot_part == part && game_tick > current_recv_tick)
2007-07-14 13:09:42 +00:00
{
2007-10-06 17:01:06 +00:00
/* TODO: clean this up abit */
2007-07-22 11:53:15 +00:00
const char *d = (const char *)msg_unpack_raw(part_size);
2007-07-26 19:09:31 +00:00
mem_copy((char*)snapshot_incomming_data + part*MAX_SNAPSHOT_PACKSIZE, d, part_size);
2007-07-22 11:53:15 +00:00
snapshot_part++;
if(snapshot_part == num_parts)
{
2007-10-06 17:01:06 +00:00
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;
2007-10-06 17:01:06 +00:00
snapshot_part = 0;
2007-10-06 17:01:06 +00:00
/* find snapshot that we should use as delta */
2007-07-22 11:53:15 +00:00
emptysnap.data_size = 0;
emptysnap.num_items = 0;
2007-10-06 17:01:06 +00:00
/* find delta */
2007-07-22 11:53:15 +00:00
if(delta_tick >= 0)
{
int deltashot_size = snapstorage_get(&snapshot_storage, delta_tick, 0, &deltashot);
if(deltashot_size < 0)
2007-07-14 13:09:42 +00:00
{
2007-10-06 17:01:06 +00:00
/* 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");
2007-10-06 17:01:06 +00:00
/* ack snapshot */
/* TODO: combine this with the input message */
ack_game_tick = -1;
return;
2007-07-14 13:09:42 +00:00
}
2007-07-13 13:40:04 +00:00
}
2007-07-29 22:09:15 +00:00
2007-10-06 17:01:06 +00:00
/* 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;
}
2007-10-06 17:01:06 +00:00
/*dbg_msg("UNPACK", "%d unpacked with %d", game_tick, delta_tick); */
2007-07-26 19:09:31 +00:00
2007-10-06 17:01:06 +00:00
purgetick = delta_tick;
snapsize = snapshot_unpack_delta(deltashot, (SNAPSHOT*)tmpbuffer3, deltadata, deltasize);
if(msg != NETMSG_SNAPEMPTY && snapshot_crc((SNAPSHOT*)tmpbuffer3) != crc)
2007-08-22 21:21:20 +00:00
{
if(config.debug)
2007-09-23 22:54:31 +00:00
dbg_msg("client", "snapshot crc error %d", snapcrcerrors);
2007-08-22 21:21:20 +00:00
snapcrcerrors++;
2007-09-23 22:54:31 +00:00
if(snapcrcerrors > 10)
2007-08-22 21:21:20 +00:00
{
2007-10-06 17:01:06 +00:00
/* to many errors, send reset */
ack_game_tick = -1;
2007-09-23 22:54:31 +00:00
client_send_input();
2007-08-22 21:21:20 +00:00
snapcrcerrors = 0;
}
return;
}
else
{
if(snapcrcerrors)
snapcrcerrors--;
}
2007-07-26 19:09:31 +00:00
2007-10-06 17:01:06 +00:00
/* purge old snapshots */
purgetick = delta_tick;
2007-07-26 19:09:31 +00:00
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);
2007-10-06 17:01:06 +00:00
/*client_snapshot_purge_until(game_tick-50); */
2007-07-22 11:53:15 +00:00
2007-10-06 17:01:06 +00:00
/* add new */
snapstorage_add(&snapshot_storage, game_tick, time_get(), snapsize, (SNAPSHOT*)tmpbuffer3);
2007-07-29 22:09:15 +00:00
2007-10-06 17:01:06 +00:00
/* apply snapshot, cycle pointers */
2007-07-22 11:53:15 +00:00
recived_snapshots++;
if(current_recv_tick > 0)
snaploss += game_tick-current_recv_tick-2;
current_recv_tick = game_tick;
2007-10-06 17:01:06 +00:00
/* we got two snapshots until we see us self as connected */
2007-07-22 11:53:15 +00:00
if(recived_snapshots == 2)
{
2007-10-06 17:01:06 +00:00
/* start at 200ms and work from there */
st_init(&predicted_time, game_tick*time_freq()/50);
2007-09-25 21:53:14 +00:00
st_init(&game_time, (game_tick-1)*time_freq()/50);
snapshots[SNAP_PREV] = snapshot_storage.first;
snapshots[SNAP_CURRENT] = snapshot_storage.last;
2007-07-22 11:53:15 +00:00
local_start_time = time_get();
client_set_state(CLIENTSTATE_ONLINE);
2007-07-22 11:53:15 +00:00
}
{
int64 now = time_get();
graph_add(&predict_graph, (st_get(&predicted_time, now)-st_get(&game_time, now))/(float)time_freq());
}
2007-09-25 21:53:14 +00:00
st_update(&game_time, (game_tick-1)*time_freq()/50);
2007-07-26 19:09:31 +00:00
2007-10-06 17:01:06 +00:00
/* ack snapshot */
ack_game_tick = game_tick;
2007-05-22 15:03:32 +00:00
}
2007-07-14 13:09:42 +00:00
}
2007-07-22 11:53:15 +00:00
else
{
dbg_msg("client", "snapsht reset!");
2007-07-22 11:53:15 +00:00
snapshot_part = 0;
}
}
2007-05-22 15:03:32 +00:00
}
2007-07-22 11:53:15 +00:00
else
2007-05-22 15:03:32 +00:00
{
2007-10-06 17:01:06 +00:00
/* game message */
2007-07-22 11:53:15 +00:00
modc_message(msg);
2007-05-22 15:03:32 +00:00
}
2007-07-22 11:53:15 +00:00
}
}
2007-07-13 13:40:04 +00:00
static void client_pump_network()
2007-07-22 11:53:15 +00:00
{
2007-10-06 17:01:06 +00:00
NETPACKET packet;
netclient_update(net);
2007-07-22 11:53:15 +00:00
2007-10-06 17:01:06 +00:00
/* check for errors */
if(client_state() != CLIENTSTATE_OFFLINE && netclient_state(net) == NETSTATE_OFFLINE)
2007-07-22 11:53:15 +00:00
{
client_set_state(CLIENTSTATE_OFFLINE);
dbg_msg("client", "offline error='%s'", netclient_error_string(net));
2007-07-22 11:53:15 +00:00
}
2007-10-06 17:01:06 +00:00
/* */
if(client_state() == CLIENTSTATE_CONNECTING && netclient_state(net) == NETSTATE_ONLINE)
2007-07-22 11:53:15 +00:00
{
2007-10-06 17:01:06 +00:00
/* we switched to online */
2007-07-22 11:53:15 +00:00
dbg_msg("client", "connected, sending info");
client_set_state(CLIENTSTATE_LOADING);
client_send_info();
2007-07-22 11:53:15 +00:00
}
2007-10-06 17:01:06 +00:00
/* process packets */
while(netclient_recv(net, &packet))
client_process_packet(&packet);
2007-07-22 11:53:15 +00:00
}
2007-05-22 15:03:32 +00:00
2007-12-02 17:55:45 +00:00
static void client_update()
{
/* switch snapshot */
if(client_state() != CLIENTSTATE_OFFLINE && recived_snapshots >= 3)
2007-12-02 17:55:45 +00:00
{
int repredict = 0;
int64 freq = time_freq();
2007-12-02 17:55:45 +00:00
int64 now = st_get(&game_time, time_get());
2007-12-11 23:00:48 +00:00
int64 pred_now = st_get(&predicted_time, time_get());
2007-12-02 17:55:45 +00:00
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;
2007-12-11 23:00:48 +00:00
static float last_intrapred = 0;
2007-12-02 17:55:45 +00:00
intratick = (now - prevtick_start) / (float)(curtick_start-prevtick_start);
ticktime = (now - curtick_start) / (freq/(float)SERVER_TICK_SPEED);
2007-12-02 17:55:45 +00:00
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);
2007-12-11 23:00:48 +00:00
2007-12-02 17:55:45 +00:00
if(new_pred_tick > current_predtick)
{
2007-12-11 23:00:48 +00:00
last_intrapred = intrapredtick;
2007-12-02 17:55:45 +00:00
current_predtick = new_pred_tick;
repredict = 1;
/* send input */
client_send_input();
}
2007-12-11 23:00:48 +00:00
if(intrapredtick < last_intrapred)
dbg_msg("client", "prediction time goes backwards, that can't be good");
last_intrapred = intrapredtick;
2007-12-02 17:55:45 +00:00
}
/* only do sane predictions */
if(repredict)
{
if(current_predtick > current_tick && current_predtick < current_tick+50)
modc_predict();
}
}
/* STRESS TEST: join the server again */
2007-12-11 23:10:07 +00:00
if(client_state() == CLIENTSTATE_OFFLINE && config.dbg_stress && (frames%100) == 0)
client_connect(config.dbg_stress_server);
2007-12-02 17:55:45 +00:00
/* pump the network */
client_pump_network();
/* update the server browser */
client_serverbrowse_update();
}
extern int editor_update_and_render();
extern void editor_init();
2007-11-08 09:11:32 +00:00
static void client_run()
{
2007-10-06 17:01:06 +00:00
NETADDR4 bindaddr;
int64 reporttime = time_get();
int64 reportinterval = time_freq()*1;
2007-12-02 17:55:45 +00:00
int editor_active = 0;
2007-10-06 17:01:06 +00:00
static PERFORMACE_INFO rootscope = {"root", 0};
perf_start(&rootscope);
local_start_time = time_get();
snapshot_part = 0;
2007-10-06 17:01:06 +00:00
/* init graphics and sound */
if(!gfx_init())
return;
2007-12-02 17:55:45 +00:00
/* init the editor */
editor_init();
2007-11-08 09:11:32 +00:00
/* sound is allowed to fail */
snd_init();
2007-10-06 17:01:06 +00:00
/* load data */
if(!client_load_data())
return;
2007-10-06 17:01:06 +00:00
/* init the mod */
modc_init();
2007-08-22 21:13:33 +00:00
dbg_msg("client", "version %s", modc_net_version());
2007-10-06 17:01:06 +00:00
/* open socket */
mem_zero(&bindaddr, sizeof(bindaddr));
net = netclient_open(bindaddr, 0);
2007-10-06 17:01:06 +00:00
/* connect to the server if wanted */
2007-12-11 23:10:07 +00:00
/*
2007-11-08 09:20:55 +00:00
if(config.cl_connect[0] != 0)
2007-11-08 09:11:32 +00:00
client_connect(config.cl_connect);
2007-12-11 23:10:07 +00:00
config.cl_connect[0] = 0;
*/
inp_mouse_mode_relative();
while (1)
{
static PERFORMACE_INFO rootscope = {"root", 0};
int64 frame_start_time = time_get();
2007-11-08 09:11:32 +00:00
frames++;
perf_start(&rootscope);
2007-10-06 17:01:06 +00:00
/* update input */
{
static PERFORMACE_INFO scope = {"inp_update", 0};
perf_start(&scope);
inp_update();
perf_end();
}
2008-01-12 12:08:26 +00:00
/* update sound */
{
static PERFORMACE_INFO scope = {"snd_update", 0};
perf_start(&scope);
snd_update();
perf_end();
}
2007-10-06 17:01:06 +00:00
/* 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;
}
}
2007-10-06 17:01:06 +00:00
/* screenshot button */
2007-08-15 10:18:01 +00:00
if(inp_key_down(config.key_screenshot))
2007-08-04 09:20:59 +00:00
gfx_screenshot();
2007-11-08 09:11:32 +00:00
/* some debug keys */
2007-12-02 17:55:45 +00:00
/*
2007-08-04 22:02:38 +00:00
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))
2007-08-04 22:02:38 +00:00
{
ack_game_tick = -1;
client_send_input();
2007-08-04 22:02:38 +00:00
}
2007-12-02 17:55:45 +00:00
}*/
/* panic quit button */
if(inp_key_pressed(KEY_LCTRL) && inp_key_pressed(KEY_LSHIFT) && inp_key_pressed('Q'))
break;
2007-12-02 17:55:45 +00:00
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;
2007-10-06 17:01:06 +00:00
/* render */
2007-12-02 17:55:45 +00:00
if(editor_active)
{
client_update();
2007-12-02 17:55:45 +00:00
editor_update_and_render();
gfx_swap();
}
else
{
{
static PERFORMACE_INFO scope = {"client_update", 0};
perf_start(&scope);
client_update();
perf_end();
}
2007-12-02 17:55:45 +00:00
2007-12-11 23:10:07 +00:00
if(config.dbg_stress)
2007-12-02 17:55:45 +00:00
{
if((frames%10) == 0)
{
client_render();
gfx_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);
gfx_swap();
perf_end();
}
}
}
perf_end();
2007-10-06 17:01:06 +00:00
/* check conditions */
if(client_state() == CLIENTSTATE_QUITING)
break;
2007-10-06 17:01:06 +00:00
/* be nice */
2007-12-11 23:10:07 +00:00
if(config.cl_cpu_throttle || !gfx_window_active())
thread_sleep(1);
if(config.dbg_hitch)
{
thread_sleep(config.dbg_hitch);
config.dbg_hitch = 0;
}
if(reporttime < time_get())
{
if(config.debug)
{
2007-12-11 23:00:48 +00:00
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));
}
2007-12-11 23:00:48 +00:00
frametime_low = 1;
frametime_high = 0;
frames = 0;
reporttime += reportinterval;
perf_next();
if(config.dbg_pref)
perf_dump(&rootscope);
}
2007-10-06 17:01:06 +00:00
/* update frametime */
frametime = (time_get()-frame_start_time)/(float)time_freq();
2007-12-11 23:00:48 +00:00
if(frametime < frametime_low)
frametime_low = frametime;
if(frametime > frametime_high)
frametime_high = frametime;
}
modc_shutdown();
client_disconnect();
gfx_shutdown();
snd_shutdown();
}
2007-07-15 13:25:10 +00:00
int editor_main(int argc, char **argv);
int main(int argc, char **argv)
2007-05-22 15:03:32 +00:00
{
2007-11-08 09:11:32 +00:00
/* init the engine */
2007-10-06 17:01:06 +00:00
dbg_msg("client", "starting...");
2007-11-08 09:11:32 +00:00
engine_init("Teewars", argc, argv);
2007-10-06 17:01:06 +00:00
2007-12-02 17:55:45 +00:00
client_run();
2007-11-08 09:11:32 +00:00
engine_writeconfig();
2007-05-22 15:03:32 +00:00
return 0;
}