diff --git a/data/char_teefault.png b/data/char_teefault.png index 7f1ea09d4..5305abd1b 100644 Binary files a/data/char_teefault.png and b/data/char_teefault.png differ diff --git a/default.bam b/default.bam index 537edaf62..3eb185b8e 100644 --- a/default.bam +++ b/default.bam @@ -201,7 +201,7 @@ function build(settings) engine_settings = settings:copy() if family == "windows" then - engine_settings.cc.flags = "/wd4244 /TP" + engine_settings.cc.flags = "/wd4244" else engine_settings.cc.flags = "-Wall" engine_settings.linker.flags = "" @@ -231,12 +231,12 @@ function build(settings) objs = Compile(settings, tools_src) tools = {} for i,v in objs do - toolname = PathFilename(file_base(v)) + toolname = PathFilename(PathBase(v)) tools[i] = Link(settings, toolname, v, engine) end -- build client, server and master server - client_exe = Link(settings, "teewars", game_shared, game_client, engine, client, editor, glfw, pa, client_link_other) + client_exe = Link(settings, "teewars", game_shared, game_client, engine, client, editor, glfw, pa, client_libs, client_link_other) server_exe = Link(server_settings, "teewars_srv", engine, server, game_shared, game_server) masterserver_exe = Link(server_settings, "mastersrv", masterserver, engine) diff --git a/src/engine/client/client.c b/src/engine/client/client.c index 72f73741c..d07c3aca2 100644 --- a/src/engine/client/client.c +++ b/src/engine/client/client.c @@ -18,12 +18,26 @@ #include +/* + Server Time + Client Mirror Time + Client Predicted Time + + Snapshot Latency + Downstream latency + + Prediction Latency + Upstream latency +*/ static int info_request_begin; static int info_request_end; static int snapshot_part; static int64 local_start_time; static int64 game_start_time; -static float latency = 0; + +static float snapshot_latency = 0; +static float prediction_latency = 0; + static int extra_polating = 0; static int debug_font; static float frametime = 0.0001f; @@ -34,9 +48,25 @@ static int window_must_refocus = 0; static int snaploss = 0; static int snapcrcerrors = 0; +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 + float latency; // prediction latency when we sent this input +} inputs[200]; +static int current_input = 0; + + // --- input snapping --- static int input_data[MAX_INPUT_SIZE] = {0}; static int input_data_size; @@ -100,32 +130,13 @@ static void snap_init() } // ------ time functions ------ -float client_intratick() -{ - return intratick; -} - -int client_tick() -{ - return current_tick; -} - -int client_tickspeed() -{ - return SERVER_TICK_SPEED; -} - -float client_frametime() -{ - return frametime; -} - -float client_localtime() -{ - return (time_get()-local_start_time)/(float)(time_freq()); -} - -int menu_loop(); // TODO: what is this? +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() @@ -176,19 +187,46 @@ static void client_send_error(const char *error) //send_packet(&p); //send_packet(&p); */ -} +} static void client_send_input() { msg_pack_start_system(NETMSG_INPUT, 0); + msg_pack_int(current_predtick); msg_pack_int(input_data_size); + + inputs[current_input].tick = current_predtick; + inputs[current_input].latency = prediction_latency; + int i; 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; +} // ------ server browse ---- static struct @@ -337,7 +375,17 @@ void client_connect(const char *server_address_str) netclient_connect(net, &server_address); client_set_state(CLIENTSTATE_CONNECTING); - current_tick = 0; + + current_recv_tick = 0; + + // reset input + int i; + for(i = 0; i < 200; i++) + inputs[i].tick = -1; + current_input = 0; + + snapshot_latency = 0; + prediction_latency = 0; } void client_disconnect() @@ -375,11 +423,12 @@ static void client_debug_render() static float frametime_avg = 0; frametime_avg = frametime_avg*0.9f + frametime*0.1f; char buffer[512]; - sprintf(buffer, "send: %6d recv: %6d snaploss: %d latency: %4.0f %c mem %dk gfxmem: %dk fps: %3d", + sprintf(buffer, "send: %6d recv: %6d snaploss: %d snaplatency: %4.2f %c predlatency: %4.2f mem %dk gfxmem: %dk fps: %3d", (current.send_bytes-prev.send_bytes)*10, (current.recv_bytes-prev.recv_bytes)*10, snaploss, - latency*1000.0f, extra_polating?'E':' ', + snapshot_latency*1000.0f, extra_polating?'E':' ', + prediction_latency*1000.0f, mem_allocated()/1024, gfx_memory_usage()/1024, (int)(1.0f/frametime_avg)); @@ -526,10 +575,12 @@ static void client_process_packet(NETPACKET *packet) //dbg_msg("client/network", "got snapshot"); 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 num_parts = 1; int part = 0; int part_size = 0; - int crc; + int crc = 0; if(msg != NETMSG_SNAPEMPTY) { @@ -537,7 +588,25 @@ static void client_process_packet(NETPACKET *packet) part_size = msg_unpack_int(); } - if(snapshot_part == part && game_tick > current_tick) + /* TODO: adjust our prediction time */ + if(time_left) + { + int k; + for(k = 0; k < 200; k++) /* TODO: do this better */ + { + if(inputs[k].tick == input_predtick) + { + float wanted_latency = inputs[k].latency - time_left/1000.0f + 0.01f; + prediction_latency = prediction_latency*0.95f + wanted_latency*0.05f; + //dbg_msg("DEBUG", "predlatency=%f", prediction_latency); + break; + } + } + } + + //dbg_msg("DEBUG", "new predlatency=%f", prediction_latency); + + if(snapshot_part == part && game_tick > current_recv_tick) { // TODO: clean this up abit const char *d = (const char *)msg_unpack_raw(part_size); @@ -568,6 +637,7 @@ static void client_process_packet(NETPACKET *packet) dbg_msg("client", "error, couldn't find the delta snapshot"); // ack snapshot + // TODO: combine this with the input message msg_pack_start_system(NETMSG_SNAPACK, 0); msg_pack_int(-1); msg_pack_end(); @@ -637,9 +707,9 @@ static void client_process_packet(NETPACKET *packet) recived_snapshots++; - if(current_tick > 0) - snaploss += game_tick-current_tick-1; - current_tick = game_tick; + 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) @@ -661,7 +731,7 @@ static void client_process_packet(NETPACKET *packet) int64 wanted = game_start_time+(game_tick*time_freq())/50; float current_latency = (now-wanted)/(float)time_freq(); - latency = latency*0.95f+current_latency*0.05f; + snapshot_latency = snapshot_latency*0.95f+current_latency*0.05f; // ack snapshot msg_pack_start_system(NETMSG_SNAPACK, 0); @@ -693,7 +763,6 @@ static void client_pump_network() // check for errors if(client_state() != CLIENTSTATE_OFFLINE && netclient_state(net) == NETSTATE_OFFLINE) { - // TODO: add message to the user there client_set_state(CLIENTSTATE_OFFLINE); dbg_msg("client", "offline error='%s'", netclient_error_string(net)); } @@ -754,9 +823,6 @@ static void client_run(const char *direct_connect_server) if(direct_connect_server) client_connect(direct_connect_server); - int64 game_starttime = time_get(); - int64 last_input = game_starttime; - int64 reporttime = time_get(); int64 reportinterval = time_freq()*1; int frames = 0; @@ -771,14 +837,15 @@ static void client_run(const char *direct_connect_server) // switch snapshot if(recived_snapshots >= 3) { + int repredict = 0; int64 now = time_get(); while(1) { SNAPSTORAGE_HOLDER *cur = snapshots[SNAP_CURRENT]; int64 tickstart = game_start_time + (cur->tick+1)*time_freq()/50; int64 t = tickstart; - if(latency > 0) - t += (int64)(time_freq()*(latency*1.1f)); + if(snapshot_latency > 0) + t += (int64)(time_freq()*(snapshot_latency*1.1f)); if(t < now) { @@ -787,8 +854,15 @@ static void client_run(const char *direct_connect_server) { 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 { @@ -806,31 +880,33 @@ static void client_run(const char *direct_connect_server) if(snapshots[SNAP_CURRENT] && snapshots[SNAP_PREV]) { int64 curtick_start = game_start_time + (snapshots[SNAP_CURRENT]->tick+1)*time_freq()/50; - if(latency > 0) - curtick_start += (int64)(time_freq()*(latency*1.1f)); + if(snapshot_latency > 0) + curtick_start += (int64)(time_freq()*(snapshot_latency*1.1f)); int64 prevtick_start = game_start_time + (snapshots[SNAP_PREV]->tick+1)*time_freq()/50; - if(latency > 0) - prevtick_start += (int64)(time_freq()*(latency*1.1f)); + if(snapshot_latency > 0) + prevtick_start += (int64)(time_freq()*(snapshot_latency*1.1f)); intratick = (now - prevtick_start) / (float)(curtick_start-prevtick_start); + + // 25 frames ahead + int new_predtick = current_tick+prediction_latency*SERVER_TICK_SPEED; + if(new_predtick > current_predtick) + { + //dbg_msg("") + current_predtick = new_predtick; + repredict = 1; + + // send input + client_send_input(); + } } + + if(repredict) + modc_predict(); } - // send input - if(client_state() == CLIENTSTATE_ONLINE) - { - if(config.stress&1 && client_localtime() > 10.0f) - client_disconnect(); - - if(input_is_changed || time_get() > last_input+time_freq()) - { - client_send_input(); - input_is_changed = 0; - last_input = time_get(); - } - } - + // STRESS TEST: join the server again if(client_state() == CLIENTSTATE_OFFLINE && config.stress && (frames%100) == 0) client_connect(config.cl_stress_server); @@ -838,8 +914,6 @@ static void client_run(const char *direct_connect_server) inp_update(); // refocus - // TODO: fixme - if(!gfx_window_active()) { if(window_must_refocus == 0) diff --git a/src/engine/compression.c b/src/engine/compression.c index 2f3e37cab..e89cdadfc 100644 --- a/src/engine/compression.c +++ b/src/engine/compression.c @@ -1,23 +1,23 @@ #include "system.h" #include -// Format: ESDDDDDD EDDDDDDD EDD... Extended, Data, Sign +/* Format: ESDDDDDD EDDDDDDD EDD... Extended, Data, Sign */ unsigned char *vint_pack(unsigned char *dst, int i) { - *dst = (i>>25)&0x40; // set sign bit if i<0 - i = i^(i>>31); // if(i<0) i = ~i + *dst = (i>>25)&0x40; /* set sign bit if i<0 */ + i = i^(i>>31); /* if(i<0) i = ~i */ - *dst |= i&0x3F; // pack 6bit into dst - i >>= 6; // discard 6 bits + *dst |= i&0x3F; /* pack 6bit into dst */ + i >>= 6; /* discard 6 bits */ if(i) { - *dst |= 0x80; // set extend bit + *dst |= 0x80; /* set extend bit */ while(1) { dst++; - *dst = i&(0x7F); // pack 7bit - i >>= 7; // discard 7 bits - *dst |= (i!=0)<<7; // set extend bit (may branch) + *dst = i&(0x7F); /* pack 7bit */ + i >>= 7; /* discard 7 bits */ + *dst |= (i!=0)<<7; /* set extend bit (may branch) */ if(!i) break; } @@ -52,7 +52,7 @@ const unsigned char *vint_unpack(const unsigned char *src, int *i) } while(0); src++; - *i ^= -sign; // if(sign) *i = ~(*i) + *i ^= -sign; /* if(sign) *i = ~(*i) */ return src; } @@ -85,7 +85,7 @@ long intpack_compress(const void *src_, int size, void *dst_) } -// +/* */ long zerobit_compress(const void *src_, int size, void *dst_) { unsigned char *src = (unsigned char *)src_; diff --git a/src/engine/config.c b/src/engine/config.c index 59c07f3e5..ba46d0735 100644 --- a/src/engine/config.c +++ b/src/engine/config.c @@ -7,7 +7,7 @@ #include "config.h" -// buffered stream for reading lines, should perhaps be something smaller +/* buffered stream for reading lines, should perhaps be something smaller */ typedef struct { char buffer[4*1024]; @@ -33,9 +33,9 @@ char *linereader_get(LINEREADER *lr) { if(lr->buffer_pos >= lr->buffer_size) { - // fetch more + /* fetch more */ - // move the remaining part to the front + /* move the remaining part to the front */ unsigned left = lr->buffer_size - line_start; if(line_start > lr->buffer_size) left = 0; @@ -43,7 +43,7 @@ char *linereader_get(LINEREADER *lr) mem_move(lr->buffer, &lr->buffer[line_start], left); lr->buffer_pos = left; - // fill the buffer + /* fill the buffer */ unsigned read = io_read(lr->io, &lr->buffer[lr->buffer_pos], lr->buffer_max_size-lr->buffer_pos); lr->buffer_size = left + read; line_start = 0; @@ -52,20 +52,20 @@ char *linereader_get(LINEREADER *lr) { if(left) { - lr->buffer[left] = 0; // return the last line + lr->buffer[left] = 0; /* return the last line */ lr->buffer_pos = left; lr->buffer_size = left; return lr->buffer; } else - return 0x0; // we are done! + return 0x0; /* we are done! */ } } else { if(lr->buffer[lr->buffer_pos] == '\n' || lr->buffer[lr->buffer_pos] == '\r') { - // line found + /* line found */ lr->buffer[lr->buffer_pos] = 0; lr->buffer_pos++; return &lr->buffer[line_start]; @@ -176,7 +176,6 @@ void config_save(const char *filename) dbg_msg("config/save", "saving config to %s", filename); - //file_stream file; IOHANDLE file = io_open(filename, IOFLAG_WRITE); if(file) diff --git a/src/engine/datafile.c b/src/engine/datafile.c index ce835b46d..fddd522db 100644 --- a/src/engine/datafile.c +++ b/src/engine/datafile.c @@ -100,9 +100,6 @@ DATAFILE *datafile_load(const char *filename) return 0; } - //if(DEBUG) - //dbg_msg("datafile", "loading. size=%d", datafile.size); - /* read in the rest except the data */ int size = 0; size += header.num_item_types*sizeof(DATAFILE_ITEM_TYPE); @@ -112,8 +109,8 @@ DATAFILE *datafile_load(const char *filename) size += header.item_size; int allocsize = size; - allocsize += sizeof(DATAFILE); // add space for info structure - allocsize += header.num_raw_data*sizeof(void*); // add space for data pointers + allocsize += sizeof(DATAFILE); /* add space for info structure */ + allocsize += header.num_raw_data*sizeof(void*); /* add space for data pointers */ df = (DATAFILE*)mem_alloc(allocsize, 1); df->header = header; @@ -207,9 +204,6 @@ int datafile_num_data(DATAFILE *df) /* always returns the size in the file */ int datafile_get_datasize(DATAFILE *df, int index) { - //if(df->header.version == 4) - // return df->info.data_sizes[index]; - if(index == df->header.num_raw_data-1) return df->header.data_size-df->info.data_offsets[index]; return df->info.data_offsets[index+1]-df->info.data_offsets[index]; @@ -217,7 +211,7 @@ int datafile_get_datasize(DATAFILE *df, int index) void *datafile_get_data(DATAFILE *df, int index) { - // load it if needed + /* load it if needed */ if(!df->data_ptrs[index]) { /* fetch the data size */ diff --git a/src/engine/datafile.h b/src/engine/datafile.h index e0f5380e6..e83aa3c5c 100644 --- a/src/engine/datafile.h +++ b/src/engine/datafile.h @@ -5,7 +5,7 @@ typedef struct DATAFILE_t DATAFILE; /* read access */ DATAFILE *datafile_load(const char *filename); DATAFILE *datafile_load_old(const char *filename); -void *datafile_get_data(DATAFILE *df, int index); // automaticly load the data for the item +void *datafile_get_data(DATAFILE *df, int index); int datafile_get_datasize(DATAFILE *df, int index); void datafile_unload_data(DATAFILE *df, int index); void *datafile_get_item(DATAFILE *df, int index, int *type, int *id); diff --git a/src/engine/interface.h b/src/engine/interface.h index a00fb81d2..e1608cffc 100644 --- a/src/engine/interface.h +++ b/src/engine/interface.h @@ -706,15 +706,13 @@ int modmenu_render(int ingame); /* undocumented callbacks */ void modc_message(int msg); +void modc_predict(); void mods_message(int msg, int client_id); + const char *modc_net_version(); const char *mods_net_version(); -// unused -// const char *modc_version(); -// const char *mods_version(); - /* server */ int server_getclientinfo(int client_id, CLIENT_INFO *info); int server_tick(); @@ -769,14 +767,16 @@ int client_send_msg(); /* client */ int client_tick(); +int client_predtick(); float client_intratick(); +float client_intrapredtick(); int client_tickspeed(); float client_frametime(); float client_localtime(); int client_state(); const char *client_error_string(); - +int *client_get_input(int tick); void client_connect(const char *address); void client_disconnect(); diff --git a/src/engine/server/server.c b/src/engine/server/server.c index ddc51c1d5..edb3f6446 100644 --- a/src/engine/server/server.c +++ b/src/engine/server/server.c @@ -20,7 +20,9 @@ static SNAPBUILD builder; -static int64 lasttick; +static int64 game_start_time; +static int current_tick = 0; + static int64 lastheartbeat; static NETADDR4 master_server; @@ -36,11 +38,11 @@ void *snap_new_item(int type, int id, int size) typedef struct { short next; - short state; // 0 = free, 1 = alloced, 2 = timed + short state; /* 0 = free, 1 = alloced, 2 = timed */ int timeout_tick; } SNAP_ID; -static const int MAX_IDS = 8*1024; // should be lowered +static const int MAX_IDS = 8*1024; /* should be lowered */ static SNAP_ID snap_ids[8*1024]; static int snap_first_free_id; static int snap_first_timed_id; @@ -49,6 +51,42 @@ static int snap_id_usage; static int snap_id_inusage; static int snap_id_inited = 0; + +enum +{ + SRVCLIENT_STATE_EMPTY = 0, + SRVCLIENT_STATE_CONNECTING = 1, + SRVCLIENT_STATE_INGAME = 2, +}; + +typedef struct +{ + int data[MAX_INPUT_SIZE]; + int pred_tick; /* tick that the client predicted for the input */ + int game_tick; /* the tick that was chosen for the input */ + int64 timeleft; /* how much time in ms there were left before this should be applied */ +} CLIENT_INPUT; + +/* */ +typedef struct +{ + /* connection state info */ + int state; + int latency; + + int last_acked_snapshot; + SNAPSTORAGE snapshots; + + CLIENT_INPUT inputs[200]; /* TODO: handle input better */ + int current_input; + + char name[MAX_NAME_LENGTH]; + char clan[MAX_CLANNAME_LENGTH]; +} CLIENT; + +static CLIENT clients[MAX_CLIENTS]; +static NETSERVER *net; + static void snap_init_id() { int i; @@ -72,17 +110,17 @@ int snap_new_id() { dbg_assert(snap_id_inited == 1, "requesting id too soon"); - // process timed ids + /* process timed ids */ while(snap_first_timed_id != -1 && snap_ids[snap_first_timed_id].timeout_tick < server_tick()) { int next_timed = snap_ids[snap_first_timed_id].next; - // add it to the free list + /* add it to the free list */ snap_ids[snap_first_timed_id].next = snap_first_free_id; snap_ids[snap_first_timed_id].state = 0; snap_first_free_id = snap_first_timed_id; - // remove it from the timed list + /* remove it from the timed list */ snap_first_timed_id = next_timed; if(snap_first_timed_id == -1) snap_last_timed_id = -1; @@ -120,36 +158,16 @@ void snap_free_id(int id) } } -enum -{ - SRVCLIENT_STATE_EMPTY = 0, - SRVCLIENT_STATE_CONNECTING = 1, - SRVCLIENT_STATE_INGAME = 2, -}; - -// -typedef struct -{ - // connection state info - int state; - int latency; - - int last_acked_snapshot; - SNAPSTORAGE snapshots; - - char name[MAX_NAME_LENGTH]; - char clan[MAX_CLANNAME_LENGTH]; -} CLIENT; - -static CLIENT clients[MAX_CLIENTS]; -static int current_tick = 0; -static NETSERVER *net; - int server_tick() { return current_tick; } +int64 server_tick_start_time(int tick) +{ + return game_start_time + (time_freq()*tick)/SERVER_TICK_SPEED; +} + int server_tickspeed() { return SERVER_TICK_SPEED; @@ -201,7 +219,7 @@ int server_send_msg(int client_id) if(client_id == -1) { - // broadcast + /* broadcast */ int i; for(i = 0; i < MAX_CLIENTS; i++) if(clients[i].state == SRVCLIENT_STATE_INGAME) @@ -224,10 +242,10 @@ static void server_do_tick() static void server_do_snap() { + int i, k; mods_presnap(); - int i; - for( i = 0; i < MAX_CLIENTS; i++) + for(i = 0; i < MAX_CLIENTS; i++) { if(clients[i].state == SRVCLIENT_STATE_INGAME) { @@ -237,18 +255,18 @@ static void server_do_snap() snapbuild_init(&builder); mods_snap(i); - // finish snapshot + /* finish snapshot */ int snapshot_size = snapbuild_finish(&builder, data); int crc = snapshot_crc((SNAPSHOT*)data); - // remove old snapshos - // keep 1 seconds worth of snapshots + /* remove old snapshos */ + /* keep 1 seconds worth of snapshots */ snapstorage_purge_until(&clients[i].snapshots, current_tick-SERVER_TICK_SPEED); - // save it the snapshot + /* save it the snapshot */ snapstorage_add(&clients[i].snapshots, current_tick, time_get(), snapshot_size, data); - // find snapshot that we can preform delta against + /* find snapshot that we can preform delta against */ static SNAPSHOT emptysnap; emptysnap.data_size = 0; emptysnap.num_items = 0; @@ -262,12 +280,24 @@ static void server_do_snap() delta_tick = clients[i].last_acked_snapshot; } - // create delta + int input_predtick = -1; + int64 timeleft = 0; + for(k = 0; k < 200; k++) // TODO: do this better + { + if(clients[i].inputs[k].game_tick == current_tick) + { + timeleft = clients[i].inputs[k].timeleft; + input_predtick = clients[i].inputs[k].pred_tick; + break; + } + } + + /* create delta */ int deltasize = snapshot_create_delta(deltashot, (SNAPSHOT*)data, deltadata); if(deltasize) { - // compress it + /* compress it */ unsigned char intdata[MAX_SNAPSHOT_SIZE]; int intsize = intpack_compress(deltadata, deltasize, intdata); @@ -288,7 +318,9 @@ static void server_do_snap() msg_pack_start_system(NETMSG_SNAP, 0); msg_pack_int(current_tick); - msg_pack_int(current_tick-delta_tick); // compressed with + msg_pack_int(current_tick-delta_tick); /* compressed with */ + msg_pack_int(input_predtick); + msg_pack_int((timeleft*1000)/time_freq()); msg_pack_int(crc); msg_pack_int(chunk); msg_pack_raw(&compdata[n*max_size], chunk); @@ -300,7 +332,9 @@ static void server_do_snap() { msg_pack_start_system(NETMSG_SNAPEMPTY, 0); msg_pack_int(current_tick); - msg_pack_int(current_tick-delta_tick); // compressed with + msg_pack_int(current_tick-delta_tick); /* compressed with */ + msg_pack_int(input_predtick); + msg_pack_int((timeleft*1000)/time_freq()); msg_pack_end(); server_send_msg(i); } @@ -313,9 +347,19 @@ static void server_do_snap() static int new_client_callback(int cid, void *user) { + int i; clients[cid].state = SRVCLIENT_STATE_CONNECTING; clients[cid].name[0] = 0; clients[cid].clan[0] = 0; + + /* reset input */ + for(i = 0; i < 200; i++) + { + clients[cid].inputs[i].game_tick = -1; + clients[cid].inputs[i].pred_tick = -1; + } + clients[cid].current_input = 0; + snapstorage_purge_all(&clients[cid].snapshots); clients[cid].last_acked_snapshot = -1; return 0; @@ -359,14 +403,14 @@ static void server_process_client_packet(NETPACKET *packet) int msg = msg_unpack_start(packet->data, packet->data_size, &sys); if(sys) { - // system message + /* system message */ if(msg == NETMSG_INFO) { char version[64]; strncpy(version, msg_unpack_string(), 64); if(strcmp(version, mods_net_version()) != 0) { - // OH FUCK! wrong version, drop him + /* OH FUCK! wrong version, drop him */ char reason[256]; sprintf(reason, "wrong version. server is running %s.", mods_net_version()); netserver_drop(net, cid, reason); @@ -377,7 +421,7 @@ static void server_process_client_packet(NETPACKET *packet) strncpy(clients[cid].clan, msg_unpack_string(), MAX_CLANNAME_LENGTH); const char *password = msg_unpack_string(); const char *skin = msg_unpack_string(); - (void)password; // ignore these variables + (void)password; /* ignore these variables */ (void)skin; server_send_map(cid); } @@ -392,12 +436,29 @@ static void server_process_client_packet(NETPACKET *packet) } else if(msg == NETMSG_INPUT) { - int input[MAX_INPUT_SIZE]; + int tick = msg_unpack_int(); int size = msg_unpack_int(); int i; + + CLIENT_INPUT *input = &clients[cid].inputs[clients[cid].current_input]; + input->timeleft = server_tick_start_time(tick)-time_get(); + input->pred_tick = tick; + + if(tick < server_tick()) + { + /* TODO: how should we handle this */ + dbg_msg("server", "input got in late for=%d cur=%d", tick, server_tick()); + tick = server_tick()+1; + } + + input->game_tick = tick; + for(i = 0; i < size/4; i++) - input[i] = msg_unpack_int(); - mods_client_input(cid, input); + input->data[i] = msg_unpack_int(); + + //time_get() + clients[cid].current_input++; + clients[cid].current_input %= 200; } else if(msg == NETMSG_SNAPACK) { @@ -413,7 +474,7 @@ static void server_process_client_packet(NETPACKET *packet) } else { - // game message + /* game message */ mods_message(msg, cid); } } @@ -427,7 +488,7 @@ static void server_send_serverinfo(NETADDR4 *addr) packer_add_raw(&p, SERVERBROWSE_INFO, sizeof(SERVERBROWSE_INFO)); packer_add_string(&p, config.sv_name, 128); packer_add_string(&p, config.sv_map, 128); - packer_add_int(&p, netserver_max_clients(net)); // max_players + packer_add_int(&p, netserver_max_clients(net)); /* max_players */ int c = 0; int i; for(i = 0; i < MAX_CLIENTS; i++) @@ -435,7 +496,7 @@ static void server_send_serverinfo(NETADDR4 *addr) if(!clients[i].state != SRVCLIENT_STATE_EMPTY) c++; } - packer_add_int(&p, c); // num_players + packer_add_int(&p, c); /* num_players */ packet.client_id = -1; packet.address = *addr; @@ -461,13 +522,13 @@ static void server_pump_network() { netserver_update(net); - // process packets + /* process packets */ NETPACKET packet; while(netserver_recv(net, &packet)) { if(packet.client_id == -1) { - // stateless + /* stateless */ if(packet.data_size == sizeof(SERVERBROWSE_GETINFO) && memcmp(packet.data, SERVERBROWSE_GETINFO, sizeof(SERVERBROWSE_GETINFO)) == 0) { @@ -501,23 +562,23 @@ static int server_run() { biggest_snapshot = 0; - net_init(); // For Windows compatibility. + net_init(); /* For Windows compatibility. */ snap_init_id(); - // load map + /* load map */ if(!map_load(config.sv_map)) { dbg_msg("server", "failed to load map. mapname='%s'", config.sv_map); return -1; } - // start server + /* start server */ NETADDR4 bindaddr; if(strlen(config.sv_bindaddr) && net_host_lookup(config.sv_bindaddr, config.sv_port, &bindaddr) != 0) { - // sweet! + /* sweet! */ } else { @@ -536,19 +597,16 @@ static int server_run() dbg_msg("server", "server name is '%s'", config.sv_name); dbg_msg("server", "masterserver is '%s'", config.masterserver); - if (net_host_lookup(config.masterserver, MASTERSERVER_PORT, &master_server) != 0) + if(net_host_lookup(config.masterserver, MASTERSERVER_PORT, &master_server) != 0) { - // TODO: fix me - //master_server = netaddr4(0, 0, 0, 0, 0); + /* TODO: fix me */ + /*master_server = netaddr4(0, 0, 0, 0, 0); */ } mods_init(); dbg_msg("server", "version %s", mods_net_version()); - int64 time_per_tick = time_freq()/SERVER_TICK_SPEED; int64 time_per_heartbeat = time_freq() * 30; - int64 starttime = time_get(); - lasttick = starttime; lastheartbeat = 0; int64 reporttime = time_get(); @@ -558,28 +616,48 @@ static int server_run() int64 snaptime = 0; int64 networktime = 0; int64 totaltime = 0; + + game_start_time = time_get(); if(config.debug) - dbg_msg("server", "baseline memory usage %dk", mem_allocated()/1024); + dbg_msg("server", "baseline memory usage %dk", mem_allocated()/1024); while(1) { int64 t = time_get(); - if(t-lasttick > time_per_tick) + if(t > server_tick_start_time(current_tick+1)) { + /* apply new input */ + { + int c, i; + for(c = 0; c < MAX_CLIENTS; c++) + { + if(clients[c].state == SRVCLIENT_STATE_EMPTY) + continue; + for(i = 0; i < 200; i++) + { + if(clients[c].inputs[i].game_tick == server_tick()) + { + mods_client_input(c, clients[c].inputs[i].data); + break; + } + } + } + } + + /* progress game */ { int64 start = time_get(); server_do_tick(); simulationtime += time_get()-start; } + /* snap game */ { int64 start = time_get(); server_do_snap(); snaptime += time_get()-start; } - - lasttick += time_per_tick; } if(config.sv_sendheartbeats) @@ -652,7 +730,7 @@ int main(int argc, char **argv) config_load(config_filename); - // parse arguments + /* parse arguments */ for(i = 1; i < argc; i++) config_set(argv[i]); diff --git a/src/engine/snapshot.c b/src/engine/snapshot.c index b9c832a21..b42b0157e 100644 --- a/src/engine/snapshot.c +++ b/src/engine/snapshot.c @@ -21,7 +21,7 @@ int snapshot_get_item_datasize(SNAPSHOT *snap, int index) int snapshot_get_item_index(SNAPSHOT *snap, int key) { - /* TODO: this should not be a linear search. very bad */ + /* TODO: OPT: this should not be a linear search. very bad */ int i; for(i = 0; i < snap->num_items; i++) { @@ -135,6 +135,8 @@ static void undiff_item(int *past, int *diff, int *out, int size) } } + +// TODO: OPT: this should be made much faster int snapshot_create_delta(SNAPSHOT *from, SNAPSHOT *to, void *dstdata) { SNAPSHOT_DELTA *delta = (SNAPSHOT_DELTA *)dstdata; diff --git a/src/game/client/game_client.cpp b/src/game/client/game_client.cpp index 87fe767c2..cc91c9234 100644 --- a/src/game/client/game_client.cpp +++ b/src/game/client/game_client.cpp @@ -39,6 +39,7 @@ struct client_data int team; int emoticon; int emoticon_start; + player_core predicted; } client_datas[MAX_CLIENTS]; inline float frandom() { return rand()/(float)(RAND_MAX); } @@ -137,6 +138,7 @@ void draw_sprite(float x, float y, float size) gfx_quads_draw(x, y, size*sprite_w_scale, size*sprite_h_scale); } +/* void move_point(vec2 *inout_pos, vec2 *inout_vel, float elasticity) { vec2 pos = *inout_pos; @@ -166,7 +168,7 @@ void move_point(vec2 *inout_pos, vec2 *inout_vel, float elasticity) { *inout_pos = pos + vel; } -} +}*/ class damage_indicators { @@ -309,7 +311,7 @@ public: particles[i].vel.y += particles[i].gravity*time_passed; particles[i].vel *= particles[i].friction; vec2 vel = particles[i].vel*time_passed; - move_point(&particles[i].pos, &vel, 0.1f+0.9f*frandom()); + move_point(&particles[i].pos, &vel, 0.1f+0.9f*frandom(), NULL); particles[i].vel = vel* (1.0f/time_passed); particles[i].life += time_passed; particles[i].rot += time_passed * particles[i].rotspeed; @@ -681,6 +683,75 @@ static void process_events(int s) must_process_events = false; } +static player_core predicted_prev_player; +static player_core predicted_player; + +extern "C" void modc_predict() +{ + // repredict player + { + world_core world; + int local_cid = -1; + + // search for players + for(int i = 0; i < snap_num_items(SNAP_CURRENT); i++) + { + SNAP_ITEM item; + const void *data = snap_get_item(SNAP_CURRENT, i, &item); + + if(item.type == OBJTYPE_PLAYER) + { + const obj_player *player = (const obj_player *)data; + client_datas[player->clientid].predicted.world = &world; + world.players[player->clientid] = &client_datas[player->clientid].predicted; + + client_datas[player->clientid].predicted.read(player); + if(player->local) + local_cid = player->clientid; + } + } + + // predict + for(int tick = client_tick(); tick <= client_predtick(); tick++) + { + // first calculate where everyone should move + for(int c = 0; c < MAX_CLIENTS; c++) + { + if(!world.players[c]) + continue; + + mem_zero(&world.players[c]->input, sizeof(world.players[c]->input)); + if(local_cid == c) + { + // apply player input + int *input = client_get_input(tick); + if(input) + world.players[c]->input = *((player_input*)input); + } + + world.players[c]->tick(); + } + + // move all players and quantize their data + for(int c = 0; c < MAX_CLIENTS; c++) + { + if(!world.players[c]) + continue; + + world.players[c]->move(); + world.players[c]->quantize(); + } + } + + // get the data from the local player + if(local_cid != -1) + { + predicted_prev_player = predicted_player; + predicted_player = *world.players[local_cid]; + } + } +} + extern "C" void modc_newsnapshot() { if(must_process_events) @@ -945,7 +1016,7 @@ static void render_tee(animstate *anim, int skin, int emote, vec2 dir, vec2 pos) // first pass we draw the outline // second pass we draw the filling int outline = p==0 ? 1 : 0; - int shift = charids[skin%16]; + int shift = skin; for(int f = 0; f < 2; f++) { @@ -1070,22 +1141,37 @@ void draw_round_rect(float x, float y, float w, float h, float r) gfx_quads_drawTL(x+w-r, y+r, r, h-r*2); // right } -static void render_player(const obj_player *prev, const obj_player *player) +static void render_player(const obj_player *prev_obj, const obj_player *player_obj) { - if(player->health < 0) // dont render dead players + obj_player prev; + obj_player player; + prev = *prev_obj; + player = *player_obj; + + if(player.health < 0) // dont render dead players return; + + if(player.local) + { + // apply predicted results + predicted_player.write(&player); + predicted_prev_player.write(&prev); + } - int skin = gametype == GAMETYPE_DM ? player->clientid : skinseed + player->team; + int skin = charids[player.clientid]; + + if(gametype != GAMETYPE_DM) + skin = player.team*9; // 0 or 9 - vec2 direction = get_direction(player->angle); - float angle = player->angle/256.0f; - vec2 position = mix(vec2(prev->x, prev->y), vec2(player->x, player->y), client_intratick()); + vec2 direction = get_direction(player.angle); + float angle = player.angle/256.0f; + vec2 position = mix(vec2(prev.x, prev.y), vec2(player.x, player.y), client_intratick()); - if(prev->health < 0) // Don't flicker from previous position - position = vec2(player->x, player->y); + if(prev.health < 0) // Don't flicker from previous position + position = vec2(player.x, player.y); - bool stationary = player->vx < 1 && player->vx > -1; - bool inair = col_check_point(player->x, player->y+16) == 0; + bool stationary = player.vx < 1 && player.vx > -1; + bool inair = col_check_point(player.x, player.y+16) == 0; // evaluate animation float walk_time = fmod(position.x, 100.0f)/100.0f; @@ -1099,26 +1185,26 @@ static void render_player(const obj_player *prev, const obj_player *player) else anim_eval_add(&state, &data->animations[ANIM_WALK], walk_time, 1.0f); - if (player->weapon == WEAPON_HAMMER) + if (player.weapon == WEAPON_HAMMER) { - float a = clamp((client_tick()-player->attacktick+client_intratick())/10.0f, 0.0f, 1.0f); + float a = clamp((client_tick()-player.attacktick+client_intratick())/10.0f, 0.0f, 1.0f); anim_eval_add(&state, &data->animations[ANIM_HAMMER_SWING], a, 1.0f); } - if (player->weapon == WEAPON_NINJA) + if (player.weapon == WEAPON_NINJA) { - float a = clamp((client_tick()-player->attacktick+client_intratick())/40.0f, 0.0f, 1.0f); + float a = clamp((client_tick()-player.attacktick+client_intratick())/40.0f, 0.0f, 1.0f); anim_eval_add(&state, &data->animations[ANIM_NINJA_SWING], a, 1.0f); } // draw hook - if (prev->hook_active && player->hook_active) + if (prev.hook_state>0 && player.hook_state>0) { gfx_texture_set(data->images[IMAGE_GAME].id); gfx_quads_begin(); //gfx_quads_begin(); vec2 pos = position; - vec2 hook_pos = mix(vec2(prev->hook_x, prev->hook_y), vec2(player->hook_x, player->hook_y), client_intratick()); + vec2 hook_pos = mix(vec2(prev.hook_x, prev.hook_y), vec2(player.hook_x, player.hook_y), client_intratick()); float d = distance(pos, hook_pos); vec2 dir = normalize(pos-hook_pos); @@ -1150,13 +1236,13 @@ static void render_player(const obj_player *prev, const obj_player *player) gfx_quads_setrotation(state.attach.angle*pi*2+angle); // normal weapons - int iw = clamp(player->weapon, 0, NUM_WEAPONS-1); + int iw = clamp(player.weapon, 0, NUM_WEAPONS-1); select_sprite(data->weapons[iw].sprite_body, direction.x < 0 ? SPRITE_FLAG_FLIP_Y : 0); vec2 dir = direction; float recoil = 0.0f; vec2 p; - if (player->weapon == WEAPON_HAMMER) + if (player.weapon == WEAPON_HAMMER) { // Static position for hammer p = position; @@ -1173,7 +1259,7 @@ static void render_player(const obj_player *prev, const obj_player *player) } draw_sprite(p.x, p.y, data->weapons[iw].visual_size); } - else if (player->weapon == WEAPON_NINJA) + else if (player.weapon == WEAPON_NINJA) { p = position; p.y += data->weapons[iw].offsety; @@ -1190,13 +1276,13 @@ static void render_player(const obj_player *prev, const obj_player *player) draw_sprite(p.x, p.y, data->weapons[iw].visual_size); // HADOKEN - if ((client_tick()-player->attacktick) <= (SERVER_TICK_SPEED / 6) && data->weapons[iw].nummuzzlesprites) + if ((client_tick()-player.attacktick) <= (SERVER_TICK_SPEED / 6) && data->weapons[iw].nummuzzlesprites) { int itex = rand() % data->weapons[iw].nummuzzlesprites; float alpha = 1.0f; if (alpha > 0.0f && data->weapons[iw].sprite_muzzle[itex].psprite) { - vec2 dir = vec2(player->x,player->y) - vec2(prev->x, prev->y); + vec2 dir = vec2(player.x,player.y) - vec2(prev.x, prev.y); dir = normalize(dir); float hadokenangle = atan(dir.y/dir.x); if (dir.x < 0.0f) @@ -1216,7 +1302,7 @@ static void render_player(const obj_player *prev, const obj_player *player) { // TODO: should be an animation recoil = 0; - float a = (client_tick()-player->attacktick+client_intratick())/5.0f; + float a = (client_tick()-player.attacktick+client_intratick())/5.0f; if(a < 1) recoil = sinf(a*pi); p = position + dir * data->weapons[iw].offsetx - dir*recoil*10.0f; @@ -1224,13 +1310,13 @@ static void render_player(const obj_player *prev, const obj_player *player) draw_sprite(p.x, p.y, data->weapons[iw].visual_size); } - if (player->weapon == WEAPON_GUN || player->weapon == WEAPON_SHOTGUN) + if (player.weapon == WEAPON_GUN || player.weapon == WEAPON_SHOTGUN) { // check if we're firing stuff - if (true)///prev->attackticks) + if (true)///prev.attackticks) { float alpha = 0.0f; - int phase1tick = (client_tick() - player->attacktick); + int phase1tick = (client_tick() - player.attacktick); if (phase1tick < (data->weapons[iw].muzzleduration + 3)) { float intratick = client_intratick(); @@ -1252,14 +1338,14 @@ static void render_player(const obj_player *prev, const obj_player *player) draw_sprite(p.x, p.y, data->weapons[iw].visual_size); /*gfx_quads_setcolor(1.0f,1.0f,1.0f,alpha); vec2 diry(-dir.y,dir.x); - p += dir * muzzleparams[player->weapon].offsetx + diry * offsety; - gfx_quads_draw(p.x,p.y,muzzleparams[player->weapon].sizex, muzzleparams[player->weapon].sizey);*/ + p += dir * muzzleparams[player.weapon].offsetx + diry * offsety; + gfx_quads_draw(p.x,p.y,muzzleparams[player.weapon].sizex, muzzleparams[player.weapon].sizey);*/ } } } gfx_quads_end(); - switch (player->weapon) + switch (player.weapon) { case WEAPON_GUN: render_hand(skin, p, direction, -3*pi/4, vec2(-15, 4)); break; case WEAPON_SHOTGUN: render_hand(skin, p, direction, -pi/2, vec2(-5, 4)); break; @@ -1269,9 +1355,15 @@ static void render_player(const obj_player *prev, const obj_player *player) } // render the tee - render_tee(&state, skin, player->emote, direction, position); + if(player.local && config.debug) + { + vec2 ghost_position = mix(vec2(prev_obj->x, prev_obj->y), vec2(player_obj->x, player_obj->y), client_intratick()); + render_tee(&state, 15, player.emote, direction, ghost_position); // render ghost + } + + render_tee(&state, skin, player.emote, direction, position); - if(player->state == STATE_CHATTING) + if(player.state == STATE_CHATTING) { gfx_texture_set(data->images[IMAGE_CHAT_BUBBLES].id); gfx_quads_begin(); @@ -1280,13 +1372,13 @@ static void render_player(const obj_player *prev, const obj_player *player) gfx_quads_end(); } - if (client_datas[player->clientid].emoticon_start != -1 && client_datas[player->clientid].emoticon_start + 2 * client_tickspeed() > client_tick()) + if (client_datas[player.clientid].emoticon_start != -1 && client_datas[player.clientid].emoticon_start + 2 * client_tickspeed() > client_tick()) { gfx_texture_set(data->images[IMAGE_EMOTICONS].id); gfx_quads_begin(); - int since_start = client_tick() - client_datas[player->clientid].emoticon_start; - int from_end = client_datas[player->clientid].emoticon_start + 2 * client_tickspeed() - client_tick(); + int since_start = client_tick() - client_datas[player.clientid].emoticon_start; + int from_end = client_datas[player.clientid].emoticon_start + 2 * client_tickspeed() - client_tick(); float a = 1; @@ -1307,7 +1399,7 @@ static void render_player(const obj_player *prev, const obj_player *player) gfx_quads_setcolor(1.0f,1.0f,1.0f,a); // client_datas::emoticon is an offset from the first emoticon - select_sprite(SPRITE_OOP + client_datas[player->clientid].emoticon); + select_sprite(SPRITE_OOP + client_datas[player.clientid].emoticon); gfx_quads_draw(position.x, position.y - 23 - 32*h, 64, 64*h); gfx_quads_end(); } @@ -1726,6 +1818,9 @@ void render_game() } } + local_player_pos = mix(predicted_prev_player.pos, predicted_player.pos, client_intratick()); + //local_player_pos = predicted_player.pos; + // everything updated, do events if(must_process_events) process_events(SNAP_PREV); diff --git a/src/game/game.h b/src/game/game.h index e3e4e99e1..2a35bf978 100644 --- a/src/game/game.h +++ b/src/game/game.h @@ -20,6 +20,80 @@ inline float get_angle(vec2 dir) return a; } + +template +inline T saturated_add(T min, T max, T current, T modifier) +{ + if(modifier < 0) + { + if(current < min) + return current; + current += modifier; + if(current < min) + current = min; + return current; + } + else + { + if(current > max) + return current; + current += modifier; + if(current > max) + current = max; + return current; + } +} + +void move_point(vec2 *inout_pos, vec2 *inout_vel, float elasticity, int *bounces); +void move_box(vec2 *inout_pos, vec2 *inout_vel, vec2 size, float elasticity); + + +// hooking stuff +enum +{ + HOOK_RETRACTED=-1, + HOOK_IDLE=0, + HOOK_FLYING, + HOOK_GRABBED +}; + +class world_core +{ +public: + world_core() + { + mem_zero(players, sizeof(players)); + } + + class player_core *players[MAX_CLIENTS]; +}; + +class player_core +{ +public: + world_core *world; + + vec2 pos; + vec2 vel; + + vec2 hook_pos; + vec2 hook_dir; + int hook_tick; + int hook_state; + int hooked_player; + + int jumped; + player_input input; + + void tick(); + void move(); + + void read(const obj_player_core *obj_core); + void write(obj_player_core *obj_core); + void quantize(); +}; + + #define LERP(a,b,t) (a + (b-a) * t) #define min(a, b) ( a > b ? b : a) #define max(a, b) ( a > b ? a : b) diff --git a/src/game/game_protocol.h b/src/game/game_protocol.h index cd28b2d5c..3ef491ef9 100644 --- a/src/game/game_protocol.h +++ b/src/game/game_protocol.h @@ -1,5 +1,22 @@ // NOTE: Be very careful when editing this file as it will change the network version +// --------- PHYSICS TWEAK! -------- +const float ground_control_speed = 7.0f; +const float ground_control_accel = 2.0f; +const float ground_friction = 0.5f; +const float ground_jump_speed = 13.5f; +const float air_control_speed = 3.5f; +const float air_control_accel = 1.2f; +const float air_friction = 0.95f; +const float hook_length = 34*10.0f; +const float hook_fire_speed = 45.0f; +const float hook_drag_accel = 3.0f; +const float hook_drag_speed = 15.0f; +const float gravity = 0.5f; +const float wall_friction = 0.80f; +const float wall_jump_speed_up = ground_jump_speed*0.8f; +const float wall_jump_speed_out = ground_jump_speed*0.8f; + // Network stuff enum { @@ -129,7 +146,19 @@ struct obj_flag int team; }; -struct obj_player + +struct obj_player_core +{ + int x, y; + int vx, vy; + int angle; + + int hook_state; + int hook_x, hook_y; + int hook_dx, hook_dy; +}; + +struct obj_player : public obj_player_core { int local; int clientid; @@ -139,10 +168,6 @@ struct obj_player int armor; int ammocount; - int x, y; - int vx, vy; - int angle; - int weapon; // current active weapon int attacktick; // num attack ticks left of current attack @@ -152,7 +177,5 @@ struct obj_player int latency_flux; int emote; - int hook_active; - int hook_x, hook_y; int team; }; diff --git a/src/game/game_variables.h b/src/game/game_variables.h index c8cf01508..429c18aa5 100644 --- a/src/game/game_variables.h +++ b/src/game/game_variables.h @@ -14,6 +14,9 @@ MACRO_CONFIG_INT(scorelimit, 20, 0, 1000) MACRO_CONFIG_INT(timelimit, 0, 0, 1000) MACRO_CONFIG_STR(gametype, 32, "dm") +MACRO_CONFIG_INT(dbg_bots, 0, 0, 7) +MACRO_CONFIG_INT(cl_predict, 1, 0, 1) + MACRO_CONFIG_INT(dynamic_camera, 1, 0, 1) diff --git a/src/game/server/game_server.cpp b/src/game/server/game_server.cpp index ebb35cf15..7ad4ec09b 100644 --- a/src/game/server/game_server.cpp +++ b/src/game/server/game_server.cpp @@ -9,26 +9,6 @@ data_container *data = 0x0; -// --------- DEBUG STUFF --------- -const int debug_bots = 3; - -// --------- PHYSICS TWEAK! -------- -const float ground_control_speed = 7.0f; -const float ground_control_accel = 2.0f; -const float ground_friction = 0.5f; -const float ground_jump_speed = 13.5f; -const float air_control_speed = 3.5f; -const float air_control_accel = 1.2f; -const float air_friction = 0.95f; -const float hook_length = 34*10.0f; -const float hook_fire_speed = 45.0f; -const float hook_drag_accel = 3.0f; -const float hook_drag_speed = 15.0f; -const float gravity = 0.5f; -const float wall_friction = 0.80f; -const float wall_jump_speed_up = ground_jump_speed*0.8f; -const float wall_jump_speed_out = ground_jump_speed*0.8f; - class player* get_player(int index); void create_damageind(vec2 p, float angle_mod, int amount); void create_explosion(vec2 p, int owner, int weapon, bool bnodamage); @@ -39,134 +19,6 @@ void create_sound(vec2 pos, int sound, int loopflags = 0); void create_targetted_sound(vec2 pos, int sound, int target, int loopflags = 0); class player *intersect_player(vec2 pos0, vec2 pos1, vec2 &new_pos, class entity *notthis = 0); -template -T saturated_add(T min, T max, T current, T modifier) -{ - if(modifier < 0) - { - if(current < min) - return current; - current += modifier; - if(current < min) - current = min; - return current; - } - else - { - if(current > max) - return current; - current += modifier; - if(current > max) - current = max; - return current; - } -} - -// TODO: rewrite this smarter! -void move_point(vec2 *inout_pos, vec2 *inout_vel, float elasticity, int *bounces) -{ - if(bounces) - *bounces = 0; - - vec2 pos = *inout_pos; - vec2 vel = *inout_vel; - if(col_check_point(pos + vel)) - { - int affected = 0; - if(col_check_point(pos.x + vel.x, pos.y)) - { - inout_vel->x *= -elasticity; - if(bounces) - (*bounces)++; - affected++; - } - - if(col_check_point(pos.x, pos.y + vel.y)) - { - inout_vel->y *= -elasticity; - if(bounces) - (*bounces)++; - affected++; - } - - if(affected == 0) - { - inout_vel->x *= -elasticity; - inout_vel->y *= -elasticity; - } - } - else - { - *inout_pos = pos + vel; - } -} - -// TODO: rewrite this smarter! -void move_box(vec2 *inout_pos, vec2 *inout_vel, vec2 size, float elasticity) -{ - // do the move - vec2 pos = *inout_pos; - vec2 vel = *inout_vel; - - float distance = length(vel); - int max = (int)distance; - - vec2 offsets[4] = { vec2(-size.x/2, -size.y/2), vec2( size.x/2, -size.y/2), - vec2(-size.x/2, size.y/2), vec2( size.x/2, size.y/2)}; - - if(distance > 0.00001f) - { - vec2 old_pos = pos; - for(int i = 0; i <= max; i++) - { - float amount = i/(float)max; - if(max == 0) - amount = 0; - - vec2 new_pos = pos + vel*amount; // TODO: this row is not nice - - for(int p = 0; p < 4; p++) - { - vec2 np = new_pos+offsets[p]; - vec2 op = old_pos+offsets[p]; - if(col_check_point(np)) - { - int affected = 0; - if(col_check_point(np.x, op.y)) - { - vel.x = -vel.x*elasticity; - pos.x = old_pos.x; - new_pos.x = old_pos.x; - affected++; - } - - if(col_check_point(op.x, np.y)) - { - vel.y = -vel.y*elasticity; - pos.y = old_pos.y; - new_pos.y = old_pos.y; - affected++; - } - - if(!affected) - { - new_pos = old_pos; - pos = old_pos; - vel *= -elasticity; - } - } - } - - old_pos = new_pos; - } - - pos = old_pos; - } - - *inout_pos = pos; - *inout_vel = vel; -} - ////////////////////////////////////////////////// // Event handler ////////////////////////////////////////////////// @@ -784,8 +636,8 @@ void player::reset() release_hooks(); pos = vec2(0.0f, 0.0f); - vel = vec2(0.0f, 0.0f); - direction = vec2(0.0f, 1.0f); + core.vel = vec2(0.0f, 0.0f); + //direction = vec2(0.0f, 1.0f); score = 0; dead = true; spawning = false; @@ -863,7 +715,9 @@ void player::try_respawn() spawning = false; pos = spawnpos; - defered_pos = pos; + + core.pos = pos; + core.hooked_player = -1; health = 10; @@ -873,8 +727,10 @@ void player::try_respawn() set_flag(entity::FLAG_ALIVE); state = STATE_PLAYING; + core.hook_state = HOOK_IDLE; + mem_zero(&input, sizeof(input)); - vel = vec2(0.0f, 0.0f); + core.vel = vec2(0.0f, 0.0f); // init weapons mem_zero(&weapons, sizeof(weapons)); @@ -905,13 +761,14 @@ bool player::is_grounded() // releases the hooked player void player::release_hooked() { - hook_state = HOOK_RETRACTED; - hooked_player = 0x0; + //hook_state = HOOK_RETRACTED; + //hooked_player = 0x0; } // release all hooks to this player void player::release_hooks() { + /* // TODO: loop thru players only for(entity *ent = world.first_entity; ent; ent = ent->next_entity) { @@ -921,11 +778,13 @@ void player::release_hooks() if(p->hooked_player == this) p->release_hooked(); } - } + }*/ } int player::handle_ninja() { + vec2 direction = normalize(vec2(input.target_x, input.target_y)); + if ((server_tick() - ninjaactivationtick) > (data->weapons[WEAPON_NINJA].duration * server_tickspeed() / 1000)) { // time's up, return @@ -956,18 +815,18 @@ int player::handle_ninja() if (currentmovetime == 0) { // reset player velocity - vel *= 0.2f; + core.vel *= 0.2f; //return MODIFIER_RETURNFLAGS_OVERRIDEWEAPON; } if (currentmovetime > 0) { // Set player velocity - vel = activationdir * data->weapons[WEAPON_NINJA].velocity; + core.vel = activationdir * data->weapons[WEAPON_NINJA].velocity; vec2 oldpos = pos; - move_box(&defered_pos, &vel, vec2(phys_size, phys_size), 0.0f); + move_box(&core.pos, &core.vel, vec2(phys_size, phys_size), 0.0f); // reset velocity so the client doesn't predict stuff - vel = vec2(0.0f,0.0f); + core.vel = vec2(0.0f,0.0f); if ((currentmovetime % 2) == 0) { create_smoke(pos); @@ -1015,6 +874,8 @@ int player::handle_ninja() int player::handle_weapons() { + vec2 direction = normalize(vec2(input.target_x, input.target_y)); + if(config.stress) { for(int i = 0; i < NUM_WEAPONS; i++) @@ -1175,7 +1036,7 @@ int player::handle_weapons() dir = normalize(target->pos - pos); else dir = vec2(0,-1); - target->vel += dir * 25.0f + vec2(0,-5.0f); + target->core.vel += dir * 25.0f + vec2(0,-5.0f); } } if (data->weapons[active_weapon].ammoregentime) @@ -1228,7 +1089,6 @@ void player::tick() try_respawn(); // TODO: rework the input to be more robust - // TODO: remove this tick count, it feels weird if(dead) { if(server_tick()-die_tick >= server_tickspeed()*5) // auto respawn after 3 sec @@ -1238,205 +1098,30 @@ void player::tick() return; } - // fetch some info - bool grounded = is_grounded(); - int wall_sliding = 0; - direction = normalize(vec2(input.target_x, input.target_y)); + //player_core core; + //core.pos = pos; + //core.jumped = jumped; + core.input = input; + core.tick(); - float max_speed = grounded ? ground_control_speed : air_control_speed; - float accel = grounded ? ground_control_accel : air_control_accel; - float friction = grounded ? ground_friction : air_friction; - - if(!grounded && vel.y > 0) - { - if(input.left && col_check_point((int)(pos.x-phys_size/2)-4, (int)(pos.y))) - wall_sliding = -1; - if(input.right && col_check_point((int)(pos.x+phys_size/2)+4, (int)(pos.y))) - wall_sliding = 1; - } - - if(wall_sliding) - vel.y *= wall_friction; - - // handle movement - if(input.left) - vel.x = saturated_add(-max_speed, max_speed, vel.x, -accel); - if(input.right) - vel.x = saturated_add(-max_speed, max_speed, vel.x, accel); - - if(!input.left && !input.right) - vel.x *= friction; - - // handle jumping - if(input.jump) - { - if(!jumped && (grounded || wall_sliding)) - { - create_sound(pos, SOUND_PLAYER_JUMP); - if(wall_sliding) - { - vel.y = -wall_jump_speed_up; - vel.x = -wall_jump_speed_out*wall_sliding; - } - else - vel.y = -ground_jump_speed; - jumped++; - } - } - else - jumped = 0; - - // do hook - if(input.hook) - { - if(hook_state == HOOK_IDLE) - { - hook_state = HOOK_FLYING; - hook_pos = pos; - hook_dir = direction; - hook_tick = -1; - } - else if(hook_state == HOOK_FLYING) - { - vec2 new_pos = hook_pos+hook_dir*hook_fire_speed; - - // Check against other players first - for(entity *ent = world.first_entity; ent; ent = ent->next_entity) - { - if(ent && ent->objtype == OBJTYPE_PLAYER) - { - player *p = (player*)ent; - if(p != this && !p->dead && distance(p->pos, new_pos) < p->phys_size) - { - hook_state = HOOK_GRABBED; - hooked_player = p; - break; - } - } - } - - if(hook_state == HOOK_FLYING) - { - // check against ground - if(col_intersect_line(hook_pos, new_pos, &new_pos)) - { - hook_state = HOOK_GRABBED; - hook_pos = new_pos; - } - else if(distance(pos, new_pos) > hook_length) - { - hook_state = HOOK_RETRACTED; - } - else - hook_pos = new_pos; - } - - if(hook_state == HOOK_GRABBED) - { - create_sound(pos, SOUND_HOOK_ATTACH); - hook_tick = server_tick(); - } - } - } - else - { - release_hooked(); - hook_state = HOOK_IDLE; - hook_pos = pos; - } - - if(hook_state == HOOK_GRABBED) - { - if(hooked_player) - { - hook_pos = hooked_player->pos; - - // keep players hooked for a max of 1.5sec - if(server_tick() > hook_tick+(server_tickspeed()*3)/2) - release_hooked(); - } - - /*if(hooked_player) - hook_pos = hooked_player->pos; - - float d = distance(pos, hook_pos); - vec2 dir = normalize(pos - hook_pos); - if(d > 10.0f) // TODO: fix tweakable variable - { - float accel = hook_drag_accel * (d/hook_length); - vel.x = saturated_add(-hook_drag_speed, hook_drag_speed, vel.x, -accel*dir.x*0.75f); - vel.y = saturated_add(-hook_drag_speed, hook_drag_speed, vel.y, -accel*dir.y); - }*/ - - // Old version feels much better (to me atleast) - if(distance(hook_pos, pos) > 46.0f) - { - vec2 hookvel = normalize(hook_pos-pos)*hook_drag_accel; - // the hook as more power to drag you up then down. - // this makes it easier to get on top of an platform - if(hookvel.y > 0) - hookvel.y *= 0.3f; - - // the hook will boost it's power if the player wants to move - // in that direction. otherwise it will dampen everything abit - if((hookvel.x < 0 && input.left) || (hookvel.x > 0 && input.right)) - hookvel.x *= 0.95f; - else - hookvel.x *= 0.75f; - - vec2 new_vel = vel+hookvel; - - // check if we are under the legal limit for the hook - if(length(new_vel) < hook_drag_speed || length(new_vel) < length(vel)) - vel = new_vel; // no problem. apply - } - } - - // fix influence of other players, collision + hook - // TODO: loop thru players only - for(entity *ent = world.first_entity; ent; ent = ent->next_entity) - { - if(ent && ent->objtype == OBJTYPE_PLAYER) - { - player *p = (player*)ent; - if(p == this || !(p->flags&FLAG_ALIVE)) - continue; // make sure that we don't nudge our self - - // handle player <-> player collision - float d = distance(pos, p->pos); - vec2 dir = normalize(pos - p->pos); - if(d < phys_size*1.25f) - { - float a = phys_size*1.25f - d; - vel = vel + dir*a; - } - - // handle hook influence - if(p->hooked_player == this) - { - if(d > phys_size*1.50f) // TODO: fix tweakable variable - { - float accel = hook_drag_accel * (d/hook_length); - vel.x = saturated_add(-hook_drag_speed, hook_drag_speed, vel.x, -accel*dir.x); - vel.y = saturated_add(-hook_drag_speed, hook_drag_speed, vel.y, -accel*dir.y); - } - } - } - } // handle weapons int retflags = handle_weapons(); + /* if (!(retflags & (MODIFIER_RETURNFLAGS_OVERRIDEVELOCITY | MODIFIER_RETURNFLAGS_OVERRIDEPOSITION))) { // add gravity - if (!(retflags & MODIFIER_RETURNFLAGS_OVERRIDEGRAVITY)) - vel.y += gravity; + //if (!(retflags & MODIFIER_RETURNFLAGS_OVERRIDEGRAVITY)) + //vel.y += gravity; // do the move defered_pos = pos; - move_box(&defered_pos, &vel, vec2(phys_size, phys_size), 0); - } + move_box(&core.pos, &vel, vec2(phys_size, phys_size), 0); + }*/ + //defered_pos = core.pos; + //jumped = core.jumped; + state = input.state; // Previnput @@ -1446,8 +1131,12 @@ void player::tick() void player::tick_defered() { + core.move(); + core.quantize(); + pos = core.pos; + // apply the new position - pos = defered_pos; + //pos = defered_pos; } void player::die(int killer, int weapon) @@ -1478,13 +1167,14 @@ void player::die(int killer, int weapon) bool player::take_damage(vec2 force, int dmg, int from, int weapon) { - vel += force; + core.vel += force; // player only inflicts half damage on self if(from == client_id) dmg = max(1, dmg/2); - if (gameobj->gametype == GAMETYPE_TDM && from >= 0 && players[from].team == team) + // CTF and TDM, + if (gameobj->gametype != GAMETYPE_DM && from >= 0 && players[from].team == team) return false; damage_taken++; @@ -1567,10 +1257,15 @@ void player::snap(int snaping_client) { obj_player *player = (obj_player *)snap_new_item(OBJTYPE_PLAYER, client_id, sizeof(obj_player)); - player->x = (int)pos.x; - player->y = (int)pos.y; - player->vx = (int)vel.x; - player->vy = (int)vel.y; + core.write(player); + + if(snaping_client != client_id) + { + player->vx = 0; // make sure that we don't send these to clients who don't need them + player->vy = 0; + player->hook_dx = 0; + player->hook_dy = 0; + } if (emote_stop < server_tick()) { @@ -1613,21 +1308,6 @@ void player::snap(int snaping_client) player->emote = EMOTE_BLINK; } - player->hook_active = hook_state>0?1:0; - player->hook_x = (int)hook_pos.x; - player->hook_y = (int)hook_pos.y; - - float a = 0; - if(input.target_x == 0) - a = atan((float)input.target_y); - else - a = atan((float)input.target_y/(float)input.target_x); - - if(input.target_x < 0) - a = a+pi; - - player->angle = (int)(a*256.0f); - player->score = score; player->team = team; @@ -2011,7 +1691,7 @@ void mods_tick() if(world.paused) // make sure that the game object always updates gameobj->tick(); - if(debug_bots) + if(config.dbg_bots) { static int count = 0; if(count >= 0) @@ -2019,13 +1699,14 @@ void mods_tick() count++; if(count == 10) { - for(int i = 0; i < debug_bots ; i++) + for(int i = 0; i < config.dbg_bots ; i++) { mods_client_enter(MAX_CLIENTS-i-1); strcpy(players[MAX_CLIENTS-i-1].name, "(bot)"); if(gameobj->gametype != GAMETYPE_DM) - players[MAX_CLIENTS-i-1].team = count&1; + players[MAX_CLIENTS-i-1].team = i&1; } + count = -1; } } @@ -2187,7 +1868,15 @@ void mods_init() players = new player[MAX_CLIENTS]; gameobj = new gameobject; + + // setup core world + for(int i = 0; i < MAX_CLIENTS; i++) + { + players[i].core.world = &world.core; + world.core.players[i] = &players[i].core; + } + // int start, num; map_get_type(MAPRES_ITEM, &start, &num); diff --git a/src/game/server/game_server.h b/src/game/server/game_server.h index b691e0de5..56f668f68 100644 --- a/src/game/server/game_server.h +++ b/src/game/server/game_server.h @@ -85,6 +85,8 @@ public: bool paused; bool reset_requested; + world_core core; + game_world(); int find_entities(vec2 pos, float radius, entity **ents, int max); int find_entities(vec2 pos, float radius, entity **ents, int max, const int* types, int maxtypes); @@ -229,9 +231,9 @@ public: int last_action; // we need a defered position so we can handle the physics correctly - vec2 defered_pos; - vec2 vel; - vec2 direction; + //vec2 defered_pos; + //vec2 vel; + //vec2 direction; // int client_id; @@ -269,21 +271,14 @@ public: int latency_avg; int latency_min; int latency_max; - - // hooking stuff - enum - { - HOOK_RETRACTED=-1, - HOOK_IDLE=0, - HOOK_FLYING, - HOOK_GRABBED - }; - int hook_state; - int hook_tick; - player *hooked_player; - vec2 hook_pos; - vec2 hook_dir; + player_core core; + + //int hook_state; + //int hook_tick; + //player *hooked_player; + //vec2 hook_pos; + //vec2 hook_dir; // player(); diff --git a/src/tools/crapnet.cpp b/src/tools/crapnet.cpp index 5b7adf52a..1c5e3dc3b 100644 --- a/src/tools/crapnet.cpp +++ b/src/tools/crapnet.cpp @@ -38,8 +38,8 @@ int run(int port, NETADDR4 dest) if(bytes <= 0) break; - if((rand()%10) == 0) // drop the packet - continue; + //if((rand()%10) == 0) // drop the packet + // continue; // create new packet packet *p = (packet *)mem_alloc(sizeof(packet)+bytes, 1); @@ -100,13 +100,13 @@ int run(int port, NETADDR4 dest) net_udp4_send(socket, &p->send_to, p->data, p->data_size); // update lag - double flux = rand()/(double)RAND_MAX; + double flux = 0; //rand()/(double)RAND_MAX; int ms_spike = 0; - int ms_flux = 20; - int ms_ping = 20; + int ms_flux = 00; + int ms_ping = 100; current_latency = ((time_freq()*ms_ping)/1000) + (int64)(((time_freq()*ms_flux)/1000)*flux); // 50ms - if((p->id%100) == 0) + if(ms_spike && (p->id%100) == 0) current_latency += (time_freq()*ms_spike)/1000; mem_free(p); diff --git a/src/tools/map_resave.c b/src/tools/map_resave.c new file mode 100644 index 000000000..91ed09a15 --- /dev/null +++ b/src/tools/map_resave.c @@ -0,0 +1,35 @@ +#include + +int main(int argc, char **argv) +{ + int i, id, type, size; + void *ptr; + DATAFILE *df; + DATAFILE_OUT *df_out; + + if(argc != 3) + return -1; + + df = datafile_load(argv[1]); + df_out = datafile_create(argv[2]); + + /* add all items */ + for(i = 0; i < datafile_num_items(df); i++) + { + ptr = datafile_get_item(df, i, &type, &id); + size = datafile_get_itemsize(df, i); + datafile_add_item(df_out, type, id, size, ptr); + } + + /* add all data */ + for(i = 0; i < datafile_num_data(df); i++) + { + ptr = datafile_get_data(df, i); + size = datafile_get_datasize(df, i); + datafile_add_data(df_out, size, ptr); + } + + datafile_unload(df); + datafile_finish(df_out); + return 0; +}