a lot of changes. client side prediction added

This commit is contained in:
Magnus Auvinen 2007-09-09 18:21:14 +00:00
parent 350e968f51
commit 3f4587ede8
18 changed files with 672 additions and 611 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 71 KiB

View file

@ -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)

View file

@ -18,12 +18,26 @@
#include <mastersrv/mastersrv.h>
/*
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)

View file

@ -1,23 +1,23 @@
#include "system.h"
#include <string.h>
// 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_;

View file

@ -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)

View file

@ -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 */

View file

@ -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);

View file

@ -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();

View file

@ -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]);

View file

@ -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;

View file

@ -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);

View file

@ -20,6 +20,80 @@ inline float get_angle(vec2 dir)
return a;
}
template<typename T>
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)

View file

@ -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;
};

View file

@ -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)

View file

@ -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<typename T>
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);

View file

@ -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();

View file

@ -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);

35
src/tools/map_resave.c Normal file
View file

@ -0,0 +1,35 @@
#include <engine/datafile.h>
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;
}