diff options
Diffstat (limited to 'src')
32 files changed, 1406 insertions, 127 deletions
diff --git a/src/engine/client/ec_client.c b/src/engine/client/ec_client.c index 8d8bdad7..3e7e8c27 100644 --- a/src/engine/client/ec_client.c +++ b/src/engine/client/ec_client.c @@ -23,6 +23,8 @@ #include <engine/e_huffman.h> +#include <engine/e_demorec.h> + #include <mastersrv/mastersrv.h> #include <versionsrv/versionsrv.h> @@ -70,8 +72,13 @@ static char versionstr[10] = "0"; /* pinging */ static int64 ping_start_time = 0; +/* */ +static char current_map[256] = {0}; +static int current_map_crc = 0; + /* map download */ static char mapdownload_filename[256] = {0}; +static char mapdownload_name[256] = {0}; static IOHANDLE mapdownload_file = 0; static int mapdownload_chunk = 0; static int mapdownload_crc = 0; @@ -85,7 +92,6 @@ static SERVER_INFO current_server_info = {0}; static int current_tick = 0; static float intratick = 0; static float ticktime = 0; - static int prev_tick = 0; /* predicted time */ @@ -231,6 +237,9 @@ static SNAPSTORAGE_HOLDER *snapshots[NUM_SNAPSHOT_TYPES]; static int recived_snapshots; static char snapshot_incomming_data[MAX_SNAPSHOT_SIZE]; +static SNAPSTORAGE_HOLDER demorec_snapshotholders[NUM_SNAPSHOT_TYPES]; +static char *demorec_snapshotdata[NUM_SNAPSHOT_TYPES][2][MAX_SNAPSHOT_SIZE]; + /* --- */ void *snap_get_item(int snapid, int index, SNAP_ITEM *item) @@ -310,8 +319,15 @@ int client_send_msg() packet.flags = NETSENDFLAG_VITAL; if(info->flags&MSGFLAG_FLUSH) packet.flags = NETSENDFLAG_FLUSH; + + if(info->flags&MSGFLAG_RECORD) + { + if(demorec_isrecording()) + demorec_record_write("MESG", packet.data_size, packet.data); + } - netclient_send(net, &packet); + if(!(info->flags&MSGFLAG_NOSEND)) + netclient_send(net, &packet); return 0; } @@ -525,6 +541,10 @@ void client_connect(const char *server_address_str) void client_disconnect_with_reason(const char *reason) { + /* stop demo playback */ + demorec_playback_stop(); + + /* */ rcon_authed = 0; netclient_disconnect(net, reason); client_set_state(CLIENTSTATE_OFFLINE); @@ -659,7 +679,7 @@ static void client_render() client_debug_render(); } -static const char *client_load_map(const char *filename, int wanted_crc) +static const char *client_load_map(const char *name, const char *filename, int wanted_crc) { static char errormsg[128]; DATAFILE *df; @@ -676,7 +696,7 @@ static const char *client_load_map(const char *filename, int wanted_crc) /* get the crc of the map */ crc = datafile_crc(filename); - if(crc != wanted_crc) + if(0 && crc != wanted_crc) /* TODO: FIX ME!!! */ { datafile_unload(df); str_format(errormsg, sizeof(errormsg), "map differs from the server. %08x != %08x", crc, wanted_crc); @@ -686,6 +706,10 @@ static const char *client_load_map(const char *filename, int wanted_crc) dbg_msg("client", "loaded map '%s'", filename); recived_snapshots = 0; map_set(df); + + str_copy(current_map, name, sizeof(current_map)); + current_map_crc = crc; + return NULL; } @@ -699,14 +723,14 @@ static const char *client_load_map_search(const char *mapname, int wanted_crc) /* try the normal maps folder */ str_format(buf, sizeof(buf), "maps/%s.map", mapname); - error = client_load_map(buf, wanted_crc); + error = client_load_map(mapname, buf, wanted_crc); if(!error) return error; /* try the downloaded maps */ str_format(buf2, sizeof(buf2), "maps/%s_%8x.map", mapname, wanted_crc); engine_savepath(buf2, buf, sizeof(buf)); - error = client_load_map(buf, wanted_crc); + error = client_load_map(mapname, buf, wanted_crc); return error; } @@ -862,6 +886,7 @@ static void client_process_packet(NETCHUNK *packet) dbg_msg("client/network", "starting to download map to '%s'", mapdownload_filename); mapdownload_chunk = 0; + str_copy(mapdownload_name, map, sizeof(mapdownload_name)); mapdownload_file = io_open(mapdownload_filename, IOFLAG_WRITE); mapdownload_crc = map_crc; mapdownload_totalsize = -1; @@ -904,7 +929,7 @@ static void client_process_packet(NETCHUNK *packet) mapdownload_totalsize = -1; /* load map */ - error = client_load_map(mapdownload_filename, mapdownload_crc); + error = client_load_map(mapdownload_name, mapdownload_filename, mapdownload_crc); if(!error) { dbg_msg("client/network", "loading done"); @@ -1107,6 +1132,28 @@ static void client_process_packet(NETCHUNK *packet) /* add new */ snapstorage_add(&snapshot_storage, game_tick, time_get(), snapsize, (SNAPSHOT*)tmpbuffer3, 1); + /* add snapshot to demo */ + if(demorec_isrecording()) + { + DEMOREC_TICKMARKER marker; + + /* write tick marker */ + marker.tick = game_tick; + swap_endian(&marker, sizeof(int), sizeof(marker)/sizeof(int)); + demorec_record_write("TICK", sizeof(marker), &marker); + + /* build snap and possibly add some messages */ + modc_recordkeyframe(); + + /* + snapbuild_init(&builder); + mods_snap(-1); + snapshot_size = snapbuild_finish(&builder, data);*/ + + /* write snapshot */ + demorec_record_write("SNAP", snapsize, tmpbuffer3); + } + /* apply snapshot, cycle pointers */ recived_snapshots++; @@ -1147,6 +1194,9 @@ static void client_process_packet(NETCHUNK *packet) else { /* game message */ + if(demorec_isrecording()) + demorec_record_write("MESG", packet->data_size, packet->data); + modc_message(msg); } } @@ -1161,20 +1211,23 @@ static void client_pump_network() netclient_update(net); - /* check for errors */ - if(client_state() != CLIENTSTATE_OFFLINE && netclient_state(net) == NETSTATE_OFFLINE) + if(client_state() != CLIENTSTATE_DEMOPLAYBACK) { - client_set_state(CLIENTSTATE_OFFLINE); - dbg_msg("client", "offline error='%s'", netclient_error_string(net)); - } + /* check for errors */ + if(client_state() != CLIENTSTATE_OFFLINE && netclient_state(net) == NETSTATE_OFFLINE) + { + client_set_state(CLIENTSTATE_OFFLINE); + dbg_msg("client", "offline error='%s'", netclient_error_string(net)); + } - /* */ - if(client_state() == CLIENTSTATE_CONNECTING && netclient_state(net) == NETSTATE_ONLINE) - { - /* we switched to online */ - dbg_msg("client", "connected, sending info"); - client_set_state(CLIENTSTATE_LOADING); - client_send_info(); + /* */ + if(client_state() == CLIENTSTATE_CONNECTING && netclient_state(net) == NETSTATE_ONLINE) + { + /* we switched to online */ + dbg_msg("client", "connected, sending info"); + client_set_state(CLIENTSTATE_LOADING); + client_send_info(); + } } /* process packets */ @@ -1182,11 +1235,89 @@ static void client_pump_network() client_process_packet(&packet); } +static void client_democallback(DEMOREC_CHUNK chunk, void *data) +{ + /* dbg_msg("client/playback", "got %c%c%c%c", chunk.type[0], chunk.type[1], chunk.type[2], chunk.type[3]); */ + + if(mem_comp(chunk.type, "SNAP", 4) == 0) + { + /* handle snapshots */ + SNAPSTORAGE_HOLDER *temp = snapshots[SNAP_PREV]; + snapshots[SNAP_PREV] = snapshots[SNAP_CURRENT]; + snapshots[SNAP_CURRENT] = temp; + + mem_copy(snapshots[SNAP_CURRENT]->snap, data, chunk.size); + mem_copy(snapshots[SNAP_CURRENT]->alt_snap, data, chunk.size); + + modc_newsnapshot(); + modc_predict(); + } + else if(mem_comp(chunk.type, "MESG", 4) == 0) + { + /* handle messages */ + int sys = 0; + int msg = msg_unpack_start(data, chunk.size, &sys); + if(!sys) + modc_message(msg); + } + +} + +const DEMOPLAYBACK_INFO *client_demoplayer_getinfo() +{ + static DEMOPLAYBACK_INFO ret; + const DEMOREC_PLAYBACKINFO *info = demorec_playback_info(); + ret.first_tick = info->first_tick; + ret.last_tick = info->last_tick; + ret.current_tick = info->current_tick; + ret.paused = info->paused; + ret.speed = info->speed; + return &ret; +} + +void client_demoplayer_setpos(float percent) +{ + const DEMOREC_PLAYBACKINFO *info = demorec_playback_info(); + int point = (int)((info->seekable_points-1)*percent); + demorec_playback_set(point); +} + +void client_demoplayer_setspeed(float speed) +{ + demorec_playback_setspeed(speed); +} + +void client_demoplayer_setpause(int paused) +{ + if(paused) + demorec_playback_pause(); + else + demorec_playback_unpause(); +} + static void client_update() { - /* switch snapshot */ - if(client_state() != CLIENTSTATE_OFFLINE && recived_snapshots >= 3) + if(client_state() == CLIENTSTATE_DEMOPLAYBACK) + { + demorec_playback_update(); + if(demorec_isplaying()) + { + /* update timers */ + const DEMOREC_PLAYBACKINFO *info = demorec_playback_info(); + current_tick = info->current_tick; + prev_tick = info->previous_tick; + intratick = info->intratick; + ticktime = info->ticktime; + } + else + { + /* disconnect on error */ + client_disconnect(); + } + } + else if(client_state() != CLIENTSTATE_OFFLINE && recived_snapshots >= 3) { + /* switch snapshot */ int repredict = 0; int64 freq = time_freq(); int64 now = st_get(&game_time, time_get()); @@ -1393,7 +1524,6 @@ static void client_run() int64 frame_start_time = time_get(); frames++; - perf_start(&rootscope); /* */ @@ -1582,6 +1712,67 @@ static void con_addfavorite(void *result, void *user_data) client_serverbrowse_addfavorite(addr); } +void client_demoplayer_play(const char *filename) +{ + int crc; + client_disconnect(); + + /* try to start playback */ + demorec_playback_registercallback(client_democallback); + + if(demorec_playback_load(filename)) + return; + + /* load map */ + crc = (demorec_playback_info()->header.crc[0]<<24)| + (demorec_playback_info()->header.crc[1]<<16)| + (demorec_playback_info()->header.crc[2]<<8)| + (demorec_playback_info()->header.crc[3]); + client_load_map_search(demorec_playback_info()->header.map, crc); + modc_connected(); + + /* setup buffers */ + mem_zero(demorec_snapshotdata, sizeof(demorec_snapshotdata)); + + snapshots[SNAP_CURRENT] = &demorec_snapshotholders[SNAP_CURRENT]; + snapshots[SNAP_PREV] = &demorec_snapshotholders[SNAP_PREV]; + + snapshots[SNAP_CURRENT]->snap = (SNAPSHOT *)demorec_snapshotdata[SNAP_CURRENT][0]; + snapshots[SNAP_CURRENT]->alt_snap = (SNAPSHOT *)demorec_snapshotdata[SNAP_CURRENT][1]; + snapshots[SNAP_CURRENT]->snap_size = 0; + snapshots[SNAP_CURRENT]->tick = -1; + + snapshots[SNAP_PREV]->snap = (SNAPSHOT *)demorec_snapshotdata[SNAP_PREV][0]; + snapshots[SNAP_PREV]->alt_snap = (SNAPSHOT *)demorec_snapshotdata[SNAP_PREV][1]; + snapshots[SNAP_PREV]->snap_size = 0; + snapshots[SNAP_PREV]->tick = -1; + + /* enter demo playback state */ + client_set_state(CLIENTSTATE_DEMOPLAYBACK); + + demorec_playback_play(); + modc_entergame(); +} + +static void con_play(void *result, void *user_data) +{ + client_demoplayer_play(console_arg_string(result, 0)); +} + +static void con_record(void *result, void *user_data) +{ + char filename[512]; + char path[512]; + str_format(filename, sizeof(filename), "demos/%s.demo", console_arg_string(result, 0)); + engine_savepath(filename, path, sizeof(path)); + demorec_record_start(path, modc_net_version(), current_map, current_map_crc, "client"); +} + +static void con_stoprecord(void *result, void *user_data) +{ + demorec_record_stop(); +} + static void client_register_commands() { MACRO_REGISTER_COMMAND("quit", "", con_quit, 0x0); @@ -1591,6 +1782,10 @@ static void client_register_commands() MACRO_REGISTER_COMMAND("screenshot", "", con_screenshot, 0x0); MACRO_REGISTER_COMMAND("rcon", "r", con_rcon, 0x0); + MACRO_REGISTER_COMMAND("play", "r", con_play, 0x0); + MACRO_REGISTER_COMMAND("record", "s", con_record, 0); + MACRO_REGISTER_COMMAND("stoprecord", "", con_stoprecord, 0); + MACRO_REGISTER_COMMAND("add_favorite", "s", con_addfavorite, 0x0); } @@ -1599,6 +1794,13 @@ void client_save_line(const char *line) engine_config_write_line(line); } +const char *client_user_directory() +{ + static char path[1024] = {0}; + fs_storage_path("Teeworlds", path, sizeof(path)); + return path; +} + int main(int argc, char **argv) { /* init the engine */ diff --git a/src/engine/e_demorec.c b/src/engine/e_demorec.c new file mode 100644 index 00000000..67f7f237 --- /dev/null +++ b/src/engine/e_demorec.c @@ -0,0 +1,354 @@ + +#include <base/system.h> +#include "e_demorec.h" +#include "e_memheap.h" +#include "e_if_other.h" + +static IOHANDLE record_file = 0; +static const unsigned char header_marker[8] = {'T', 'W', 'D', 'E', 'M', 'O', 0, 1}; + +/* Record */ + +int demorec_isrecording() { return record_file != 0; } + +int demorec_record_start(const char *filename, const char *netversion, const char *map, int crc, const char *type) +{ + DEMOREC_HEADER header; + if(record_file) + return -1; + + record_file = io_open(filename, IOFLAG_WRITE); + + if(!record_file) + { + dbg_msg("demorec/record", "Unable to open '%s' for recording", filename); + return -1; + } + + /* write header */ + mem_zero(&header, sizeof(header)); + mem_copy(header.marker, header_marker, sizeof(header.marker)); + str_copy(header.netversion, netversion, sizeof(header.netversion)); + str_copy(header.map, map, sizeof(header.map)); + str_copy(header.type, type, sizeof(header.type)); + header.crc[0] = (crc>>24)&0xff; + header.crc[1] = (crc>>16)&0xff; + header.crc[2] = (crc>>8)&0xff; + header.crc[3] = (crc)&0xff; + io_write(record_file, &header, sizeof(header)); + + dbg_msg("demorec/record", "Recording to '%s'", filename); + return 0; +} + +void demorec_record_write(const char *type, int size, const void *data) +{ + DEMOREC_CHUNK chunk; + if(!record_file) + return; + + chunk.type[0] = type[0]; + chunk.type[1] = type[1]; + chunk.type[2] = type[2]; + chunk.type[3] = type[3]; + chunk.size = size; + swap_endian(&chunk.size, sizeof(int), 1); + io_write(record_file, &chunk, sizeof(chunk)); + io_write(record_file, data, size); +} + +int demorec_record_stop() +{ + if(!record_file) + return -1; + + dbg_msg("demorec/record", "Stopped recording"); + io_close(record_file); + record_file = 0; + return 0; +} + +/* Playback */ + +typedef struct KEYFRAME +{ + long filepos; + int tick; +} KEYFRAME; + +typedef struct KEYFRAME_SEARCH +{ + KEYFRAME frame; + struct KEYFRAME_SEARCH *next; +} KEYFRAME_SEARCH; + +static IOHANDLE play_file = 0; +static DEMOREC_PLAYCALLBACK play_callback = 0; +static KEYFRAME *keyframes = 0; + +static DEMOREC_PLAYBACKINFO playbackinfo; +const DEMOREC_PLAYBACKINFO *demorec_playback_info() { return &playbackinfo; } + +int demorec_isplaying() { return play_file != 0; } + +int demorec_playback_registercallback(DEMOREC_PLAYCALLBACK cb) +{ + play_callback = cb; + return 0; +} + +static int read_chunk_header(DEMOREC_CHUNK *chunk) +{ + if(io_read(play_file, chunk, sizeof(*chunk)) != sizeof(*chunk)) + return -1; + swap_endian(&chunk->size, sizeof(int), 1); + return 0; +} + +static void scan_file() +{ + long start_pos; + HEAP *heap = 0; + KEYFRAME_SEARCH *first_key = 0; + KEYFRAME_SEARCH *current_key = 0; + DEMOREC_CHUNK chunk; + int i; + + heap = memheap_create(); + + start_pos = io_tell(play_file); + playbackinfo.seekable_points = 0; + + while(1) + { + long current_pos = io_tell(play_file); + + if(read_chunk_header(&chunk)) + break; + + /* read the chunk */ + + if(mem_comp(chunk.type, "TICK", 4) == 0) + { + KEYFRAME_SEARCH *key; + DEMOREC_TICKMARKER marker; + io_read(play_file, &marker, chunk.size); + swap_endian(&marker.tick, sizeof(int), 1); + + /* save the position */ + key = memheap_allocate(heap, sizeof(KEYFRAME_SEARCH)); + key->frame.filepos = current_pos; + key->frame.tick = marker.tick; + key->next = 0; + if(current_key) + current_key->next = key; + if(!first_key) + first_key = key; + current_key = key; + + if(playbackinfo.first_tick == -1) + playbackinfo.first_tick = marker.tick; + playbackinfo.last_tick = marker.tick; + playbackinfo.seekable_points++; + } + else + io_skip(play_file, chunk.size); + } + + /* copy all the frames to an array instead for fast access */ + keyframes = (KEYFRAME*)mem_alloc(playbackinfo.seekable_points*sizeof(KEYFRAME), 1); + for(current_key = first_key, i = 0; current_key; current_key = current_key->next, i++) + keyframes[i] = current_key->frame; + + /* destroy the temporary heap and seek back to the start */ + memheap_destroy(heap); + io_seek(play_file, start_pos, IOSEEK_START); +} + +static void do_tick() +{ + static char buffer[64*1024]; + DEMOREC_CHUNK chunk; + + /* update ticks */ + playbackinfo.previous_tick = playbackinfo.current_tick; + playbackinfo.current_tick = playbackinfo.next_tick; + + while(1) + { + int r = read_chunk_header(&chunk); + if(chunk.size > sizeof(buffer)) + { + dbg_msg("demorec/playback", "chunk is too big %d", chunk.size); + r = 1; + } + + if(r) + { + /* stop on error or eof */ + demorec_playback_stop(); + break; + } + + /* read the chunk */ + io_read(play_file, buffer, chunk.size); + + if(mem_comp(chunk.type, "TICK", 4) == 0) + { + DEMOREC_TICKMARKER marker = *(DEMOREC_TICKMARKER *)buffer; + swap_endian(&marker.tick, sizeof(int), 1); + playbackinfo.next_tick = marker.tick; + break; + } + else if(play_callback) + play_callback(chunk, buffer); + } +} + +void demorec_playback_pause() +{ + playbackinfo.paused = 1; +} + +void demorec_playback_unpause() +{ + if(playbackinfo.paused) + { + /*playbackinfo.start_tick = playbackinfo.current_tick; + playbackinfo.start_time = time_get();*/ + playbackinfo.paused = 0; + } +} + +int demorec_playback_load(const char *filename) +{ + play_file = io_open(filename, IOFLAG_READ); + if(!play_file) + { + dbg_msg("demorec/playback", "could not open '%s'", filename); + return -1; + } + + /* clear the playback info */ + mem_zero(&playbackinfo, sizeof(playbackinfo)); + playbackinfo.first_tick = -1; + playbackinfo.last_tick = -1; + /*playbackinfo.start_tick = -1;*/ + playbackinfo.next_tick = -1; + playbackinfo.current_tick = -1; + playbackinfo.previous_tick = -1; + playbackinfo.speed = 1; + + /* read the header */ + io_read(play_file, &playbackinfo.header, sizeof(playbackinfo.header)); + if(mem_comp(playbackinfo.header.marker, header_marker, sizeof(header_marker)) != 0) + { + dbg_msg("demorec/playback", "'%s' is not a demo file", filename); + io_close(play_file); + play_file = 0; + return -1; + } + + /* scan the file for interessting points */ + scan_file(); + + /* ready for playback */ + return 0; +} + +int demorec_playback_play() +{ + /* fill in previous and next tick */ + while(playbackinfo.previous_tick == -1) + do_tick(); + + /* set start info */ + /*playbackinfo.start_tick = playbackinfo.previous_tick; + playbackinfo.start_time = time_get();*/ + playbackinfo.current_time = playbackinfo.previous_tick*time_freq()/SERVER_TICK_SPEED; + playbackinfo.last_update = time_get(); + return 0; +} + +int demorec_playback_set(int keyframe) +{ + if(!play_file) + return -1; + if(keyframe < 0 || keyframe >= playbackinfo.seekable_points) + return -1; + + io_seek(play_file, keyframes[keyframe].filepos, IOSEEK_START); + + /*playbackinfo.start_tick = -1;*/ + playbackinfo.next_tick = -1; + playbackinfo.current_tick = -1; + playbackinfo.previous_tick = -1; + + demorec_playback_play(); + + return 0; +} + +void demorec_playback_setspeed(float speed) +{ + playbackinfo.speed = speed; +} + +int demorec_playback_update() +{ + int64 now = time_get(); + int64 deltatime = now-playbackinfo.last_update; + playbackinfo.last_update = now; + + if(playbackinfo.paused) + { + + } + else + { + int64 freq = time_freq(); + playbackinfo.current_time += (int64)(deltatime*(double)playbackinfo.speed); + + while(1) + { + int64 curtick_start = (playbackinfo.current_tick)*freq/SERVER_TICK_SPEED; + + /* break if we are ready */ + if(curtick_start > playbackinfo.current_time) + break; + + /* do one more tick */ + do_tick(); + } + + /* update intratick */ + { + int64 curtick_start = (playbackinfo.current_tick)*freq/SERVER_TICK_SPEED; + int64 prevtick_start = (playbackinfo.previous_tick)*freq/SERVER_TICK_SPEED; + playbackinfo.intratick = (playbackinfo.current_time - prevtick_start) / (float)(curtick_start-prevtick_start); + playbackinfo.ticktime = (playbackinfo.current_time - prevtick_start) / (float)freq; + } + + if(playbackinfo.current_tick == playbackinfo.previous_tick || + playbackinfo.current_tick == playbackinfo.next_tick) + { + dbg_msg("demorec/playback", "tick error prev=%d cur=%d next=%d", + playbackinfo.previous_tick, playbackinfo.current_tick, playbackinfo.next_tick); + } + } + + return 0; +} + +int demorec_playback_stop(const char *filename) +{ + if(!play_file) + return -1; + + dbg_msg("demorec/playback", "Stopped playback"); + io_close(play_file); + play_file = 0; + mem_free(keyframes); + keyframes = 0; + return 0; +} diff --git a/src/engine/e_demorec.h b/src/engine/e_demorec.h new file mode 100644 index 00000000..f6ceb382 --- /dev/null +++ b/src/engine/e_demorec.h @@ -0,0 +1,63 @@ + +typedef struct DEMOREC_HEADER +{ + char marker[8]; + char netversion[64]; + char map[64]; + unsigned char crc[4]; + char type[8]; +} DEMOREC_HEADER; + +typedef struct DEMOREC_CHUNK +{ + char type[4]; + int size; +} DEMOREC_CHUNK; + +typedef struct DEMOREC_TICKMARKER +{ + int tick; +} DEMOREC_TICKMARKER; + +typedef struct DEMOREC_PLAYBACKINFO +{ + DEMOREC_HEADER header; + + int paused; + float speed; + + int64 last_update; + int64 current_time; + + int first_tick; + int last_tick; + + int seekable_points; + + int next_tick; + int current_tick; + int previous_tick; + + float intratick; + float ticktime; +} DEMOREC_PLAYBACKINFO; + +int demorec_record_start(const char *filename, const char *netversion, const char *map, int map_crc, const char *type); +int demorec_isrecording(); +void demorec_record_write(const char *type, int size, const void *data); +int demorec_record_stop(); + +typedef void (*DEMOREC_PLAYCALLBACK)(DEMOREC_CHUNK chunk, void *data); + +int demorec_playback_registercallback(DEMOREC_PLAYCALLBACK cb); +int demorec_playback_load(const char *filename); +int demorec_playback_play(); +void demorec_playback_pause(); +void demorec_playback_unpause(); +void demorec_playback_setspeed(float speed); +int demorec_playback_set(int keyframe); +int demorec_playback_update(); +const DEMOREC_PLAYBACKINFO *demorec_playback_info(); +int demorec_isplaying(); +int demorec_playback_stop(); + diff --git a/src/engine/e_engine.c b/src/engine/e_engine.c index c8a1b07b..d739a589 100644 --- a/src/engine/e_engine.c +++ b/src/engine/e_engine.c @@ -63,6 +63,9 @@ void engine_init(const char *appname) str_format(path, sizeof(path), "%s/maps", application_save_path); fs_makedir(path); + + str_format(path, sizeof(path), "%s/demos", application_save_path); + fs_makedir(path); } } diff --git a/src/engine/e_if_client.h b/src/engine/e_if_client.h index 08402ad1..7aef86e9 100644 --- a/src/engine/e_if_client.h +++ b/src/engine/e_if_client.h @@ -17,12 +17,14 @@ enum CLIENTSTATE_CONNECTING - The client is trying to connect to a server. CLIENTSTATE_LOADING - The client has connected to a server and is loading resources. CLIENTSTATE_ONLINE - The client is connected to a server and running the game. + CLIENTSTATE_DEMOPLAYBACK - The client is playing a demo CLIENTSTATE_QUITING - The client is quiting. */ CLIENTSTATE_OFFLINE=0, CLIENTSTATE_CONNECTING, CLIENTSTATE_LOADING, CLIENTSTATE_ONLINE, + CLIENTSTATE_DEMOPLAYBACK, CLIENTSTATE_QUITING, /* Constants: Image Formats @@ -541,6 +543,23 @@ enum void client_serverbrowse_set(NETADDR *addr, int type, int token, SERVER_INFO *info); - int client_serverbrowse_refreshingmasters(); + + +typedef struct DEMOPLAYBACK_INFO +{ + int first_tick; + int last_tick; + int current_tick; + int paused; + float speed; +} DEMOPLAYBACK_INFO; + +void client_demoplayer_play(const char *filename); +const DEMOPLAYBACK_INFO *client_demoplayer_getinfo(); +void client_demoplayer_setpos(float percent); +void client_demoplayer_setpause(int paused); +void client_demoplayer_setspeed(float speed); +const char *client_user_directory(); + #endif diff --git a/src/engine/e_if_modc.h b/src/engine/e_if_modc.h index 8839d5f1..14ab9ded 100644 --- a/src/engine/e_if_modc.h +++ b/src/engine/e_if_modc.h @@ -142,4 +142,6 @@ int modc_snap_input(int *data); */ const char *modc_net_version(); + +void modc_recordkeyframe(); #endif diff --git a/src/engine/e_if_other.h b/src/engine/e_if_other.h index 85148d85..ca09f48b 100644 --- a/src/engine/e_if_other.h +++ b/src/engine/e_if_other.h @@ -16,7 +16,6 @@ enum SNAP_CURRENT=0, SNAP_PREV=1, - MASK_NONE=0, MASK_SET, @@ -306,7 +305,10 @@ void snap_set_staticsize(int type, int size); enum { MSGFLAG_VITAL=1, - MSGFLAG_FLUSH=2 + MSGFLAG_FLUSH=2, + MSGFLAG_NORECORD=4, + MSGFLAG_RECORD=8, + MSGFLAG_NOSEND=16 }; /* message sending */ diff --git a/src/engine/e_if_server.h b/src/engine/e_if_server.h index 98e2b452..b165ae3c 100644 --- a/src/engine/e_if_server.h +++ b/src/engine/e_if_server.h @@ -135,4 +135,6 @@ int server_tick(); */ int server_tickspeed(); +int server_ban_add(NETADDR addr, int type, int seconds); +int server_ban_remove(NETADDR addr); #endif diff --git a/src/engine/server/es_server.c b/src/engine/server/es_server.c index d761e81e..306227b8 100644 --- a/src/engine/server/es_server.c +++ b/src/engine/server/es_server.c @@ -18,6 +18,7 @@ #include <engine/e_config.h> #include <engine/e_packer.h> #include <engine/e_datafile.h> +#include <engine/e_demorec.h> #include <mastersrv/mastersrv.h> @@ -38,6 +39,8 @@ static int browseinfo_progression = -1; static int64 lastheartbeat; /*static NETADDR4 master_server;*/ +static IOHANDLE demorec_file = 0; + static char current_map[64]; static int current_map_crc; static unsigned char *current_map_data = 0; @@ -66,7 +69,6 @@ static int snap_id_usage; static int snap_id_inusage; static int snap_id_inited = 0; - enum { SRVCLIENT_STATE_EMPTY = 0, @@ -338,19 +340,26 @@ int server_send_msg(int client_id) if(info->flags&MSGFLAG_FLUSH) packet.flags |= NETSENDFLAG_FLUSH; - if(client_id == -1) + /* write message to demo recorder */ + if(!(info->flags&MSGFLAG_NORECORD)) + demorec_record_write("MESG", info->size, info->data); + + if(!(info->flags&MSGFLAG_NOSEND)) { - /* broadcast */ - int i; - for(i = 0; i < MAX_CLIENTS; i++) - if(clients[i].state == SRVCLIENT_STATE_INGAME) - { - packet.client_id = i; - netserver_send(net, &packet); - } + if(client_id == -1) + { + /* broadcast */ + int i; + for(i = 0; i < MAX_CLIENTS; i++) + if(clients[i].state == SRVCLIENT_STATE_INGAME) + { + packet.client_id = i; + netserver_send(net, &packet); + } + } + else + netserver_send(net, &packet); } - else - netserver_send(net, &packet); return 0; } @@ -364,7 +373,29 @@ static void server_do_snap() mods_presnap(); perf_end(); } + + /* create snapshot for demo recording */ + if(demorec_file) + { + char data[MAX_SNAPSHOT_SIZE]; + int snapshot_size; + DEMOREC_TICKMARKER marker; + + /* write tick marker */ + marker.tick = server_tick(); + swap_endian(&marker, sizeof(int), sizeof(marker)/sizeof(int)); + demorec_record_write("TICK", sizeof(marker), &marker); + + /* build snap and possibly add some messages */ + snapbuild_init(&builder); + mods_snap(-1); + snapshot_size = snapbuild_finish(&builder, data); + + /* write snapshot */ + demorec_record_write("SNAP", snapshot_size, data); + } + /* create snapshots for all clients */ for(i = 0; i < MAX_CLIENTS; i++) { /* client must be ingame to recive snapshots */ @@ -788,6 +819,17 @@ static void server_process_client_packet(NETCHUNK *packet) } } + +int server_ban_add(NETADDR addr, int type, int seconds) +{ + return netserver_ban_add(net, addr, type, seconds); +} + +int server_ban_remove(NETADDR addr) +{ + return netserver_ban_remove(net, addr); +} + static void server_send_serverinfo(NETADDR *addr, int token) { NETCHUNK packet; @@ -1116,6 +1158,15 @@ static void con_kick(void *result, void *user_data) server_kick(console_arg_int(result, 0), "kicked by console"); } +static void con_ban(void *result, void *user_data) +{ + NETADDR addr; + const char *str = console_arg_string(result, 0); + + if(net_addr_from_str(&addr, str) == 0) + server_ban_add(addr, 1, 60); +} + static void con_status(void *result, void *user_data) { int i; @@ -1135,14 +1186,27 @@ static void con_status(void *result, void *user_data) static void con_shutdown(void *result, void *user_data) { run_server = 0; - /*server_kick(console_arg_int(result, 0), "kicked by console");*/ +} + +static void con_record(void *result, void *user_data) +{ + demorec_record_start(console_arg_string(result, 0), mods_net_version(), current_map, current_map_crc, "server"); +} + +static void con_stoprecord(void *result, void *user_data) +{ + demorec_record_stop(); } static void server_register_commands() { MACRO_REGISTER_COMMAND("kick", "i", con_kick, 0); + MACRO_REGISTER_COMMAND("ban", "r", con_ban, 0); MACRO_REGISTER_COMMAND("status", "", con_status, 0); MACRO_REGISTER_COMMAND("shutdown", "", con_shutdown, 0); + + MACRO_REGISTER_COMMAND("record", "s", con_record, 0); + MACRO_REGISTER_COMMAND("stoprecord", "", con_stoprecord, 0); } int main(int argc, char **argv) diff --git a/src/game/client/clienthooks.cpp b/src/game/client/clienthooks.cpp index 88a7722a..1611b00d 100644 --- a/src/game/client/clienthooks.cpp +++ b/src/game/client/clienthooks.cpp @@ -17,6 +17,7 @@ extern "C" void modc_init() { gameclient.on_init(); } extern "C" void modc_connected() { gameclient.on_connected(); } extern "C" void modc_predict() { gameclient.on_predict(); } extern "C" void modc_newsnapshot() { gameclient.on_snapshot(); } +extern "C" void modc_recordkeyframe() { gameclient.on_recordkeyframe(); } extern "C" int modc_snap_input(int *data) { return gameclient.on_snapinput(data); } extern "C" void modc_statechange(int state, int old) { gameclient.on_statechange(state, old); } extern "C" void modc_render() { gameclient.on_render(); } diff --git a/src/game/client/component.hpp b/src/game/client/component.hpp index f03165d2..3d5edb86 100644 --- a/src/game/client/component.hpp +++ b/src/game/client/component.hpp @@ -18,6 +18,7 @@ public: virtual void on_save() {}; virtual void on_reset() {}; virtual void on_render() {}; + virtual void on_mapload() {}; virtual void on_message(int msg, void *rawmsg) {} virtual bool on_mousemove(float x, float y) { return false; } virtual bool on_input(INPUT_EVENT e) { return false; } diff --git a/src/game/client/components/hud.cpp b/src/game/client/components/hud.cpp index 86ce6257..2b1c1663 100644 --- a/src/game/client/components/hud.cpp +++ b/src/game/client/components/hud.cpp @@ -305,7 +305,8 @@ void HUD::on_render() render_goals(); render_fps(); - render_connectionwarning(); + if(client_state() != CLIENTSTATE_DEMOPLAYBACK) + render_connectionwarning(); render_tunewarning(); render_teambalancewarning(); render_voting(); diff --git a/src/game/client/components/mapimages.cpp b/src/game/client/components/mapimages.cpp index 9dc2447a..7f839da0 100644 --- a/src/game/client/components/mapimages.cpp +++ b/src/game/client/components/mapimages.cpp @@ -8,7 +8,7 @@ MAPIMAGES::MAPIMAGES() count = 0; } -void MAPIMAGES::on_reset() +void MAPIMAGES::on_mapload() { // unload all textures for(int i = 0; i < count; i++) diff --git a/src/game/client/components/mapimages.hpp b/src/game/client/components/mapimages.hpp index e1e0063d..cba46033 100644 --- a/src/game/client/components/mapimages.hpp +++ b/src/game/client/components/mapimages.hpp @@ -9,7 +9,7 @@ public: int get(int index) const { return textures[index]; } int num() const { return count; } - - virtual void on_reset(); + + virtual void on_mapload(); }; diff --git a/src/game/client/components/maplayers.cpp b/src/game/client/components/maplayers.cpp index e1518036..c6fea413 100644 --- a/src/game/client/components/maplayers.cpp +++ b/src/game/client/components/maplayers.cpp @@ -50,7 +50,7 @@ static void envelope_eval(float time_offset, int env, float *channels) void MAPLAYERS::on_render() { - if(client_state() != CLIENTSTATE_ONLINE) + if(client_state() != CLIENTSTATE_ONLINE && client_state() != CLIENTSTATE_DEMOPLAYBACK) return; vec2 center = gameclient.camera->center; diff --git a/src/game/client/components/menus.cpp b/src/game/client/components/menus.cpp index b1043870..f71f0d96 100644 --- a/src/game/client/components/menus.cpp +++ b/src/game/client/components/menus.cpp @@ -32,6 +32,11 @@ vec4 MENUS::color_tabbar_active; vec4 MENUS::color_tabbar_inactive_ingame; vec4 MENUS::color_tabbar_active_ingame; + +float MENUS::button_height = 25.0f; +float MENUS::listheader_height = 16.0f; +float MENUS::fontmod_height = 0.8f; + INPUT_EVENT MENUS::inputevents[MAX_INPUTEVENTS]; int MENUS::num_inputevents; @@ -84,9 +89,10 @@ MENUS::MENUS() menu_active = true; num_inputevents = 0; - last_input = time_get(); + demos = 0; + num_demos = 0; - button_height = 25.0f; + last_input = time_get(); } vec4 MENUS::button_color_mul(const void *id) @@ -111,22 +117,27 @@ void MENUS::ui_draw_browse_icon(int what, const RECT *r) void MENUS::ui_draw_menu_button(const void *id, const char *text, int checked, const RECT *r, const void *extra) { ui_draw_rect(r, vec4(1,1,1,0.5f)*button_color_mul(id), CORNER_ALL, 5.0f); - ui_do_label(r, text, 18.0f, 0); + ui_do_label(r, text, r->h*fontmod_height, 0); } void MENUS::ui_draw_keyselect_button(const void *id, const char *text, int checked, const RECT *r, const void *extra) { ui_draw_rect(r, vec4(1,1,1,0.5f)*button_color_mul(id), CORNER_ALL, 5.0f); - ui_do_label(r, text, 14.0f, 0); + ui_do_label(r, text, r->h*fontmod_height, 0); } void MENUS::ui_draw_menu_tab_button(const void *id, const char *text, int checked, const RECT *r, const void *extra) { + int corners = CORNER_T; + vec4 colormod(1,1,1,1); + if(extra) + corners = *(int *)extra; + if(checked) - ui_draw_rect(r, color_tabbar_active, CORNER_T, 10.0f); + ui_draw_rect(r, color_tabbar_active, corners, 10.0f); else - ui_draw_rect(r, color_tabbar_inactive, CORNER_T, 10.0f); - ui_do_label(r, text, 22.0f, 0); + ui_draw_rect(r, color_tabbar_inactive, corners, 10.0f); + ui_do_label(r, text, r->h*fontmod_height, 0); } @@ -136,7 +147,7 @@ void MENUS::ui_draw_settings_tab_button(const void *id, const char *text, int ch ui_draw_rect(r, color_tabbar_active, CORNER_R, 10.0f); else ui_draw_rect(r, color_tabbar_inactive, CORNER_R, 10.0f); - ui_do_label(r, text, 20.0f, 0); + ui_do_label(r, text, r->h*fontmod_height, 0); } void MENUS::ui_draw_grid_header(const void *id, const char *text, int checked, const RECT *r, const void *extra) @@ -145,7 +156,7 @@ void MENUS::ui_draw_grid_header(const void *id, const char *text, int checked, c ui_draw_rect(r, vec4(1,1,1,0.5f), CORNER_T, 5.0f); RECT t; ui_vsplit_l(r, 5.0f, 0, &t); - ui_do_label(&t, text, 14.0f, -1); + ui_do_label(&t, text, r->h*fontmod_height, -1); } void MENUS::ui_draw_list_row(const void *id, const char *text, int checked, const RECT *r, const void *extra) @@ -156,7 +167,7 @@ void MENUS::ui_draw_list_row(const void *id, const char *text, int checked, cons ui_margin(&sr, 1.5f, &sr); ui_draw_rect(&sr, vec4(1,1,1,0.5f), CORNER_ALL, 4.0f); } - ui_do_label(r, text, 14.0f, -1); + ui_do_label(r, text, r->h*fontmod_height, -1); } void MENUS::ui_draw_checkbox_common(const void *id, const char *text, const char *boxtext, const RECT *r) @@ -171,8 +182,8 @@ void MENUS::ui_draw_checkbox_common(const void *id, const char *text, const char ui_margin(&c, 2.0f, &c); ui_draw_rect(&c, vec4(1,1,1,0.25f)*button_color_mul(id), CORNER_ALL, 3.0f); c.y += 2; - ui_do_label(&c, boxtext, 12.0f, 0); - ui_do_label(&t, text, 14.0f, -1); + ui_do_label(&c, boxtext, r->h*fontmod_height*0.75f, 0); + ui_do_label(&t, text, r->h*fontmod_height*0.8f, -1); } void MENUS::ui_draw_checkbox(const void *id, const char *text, int checked, const RECT *r, const void *extra) @@ -489,31 +500,43 @@ int MENUS::render_menubar(RECT r) ui_vsplit_l(&box, 30.0f, 0, &box); } - ui_vsplit_l(&box, 110.0f, &button, &box); + ui_vsplit_l(&box, 100.0f, &button, &box); static int internet_button=0; - if (ui_do_button(&internet_button, "Internet", active_page==PAGE_INTERNET, &button, ui_draw_menu_tab_button, 0)) + int corners = CORNER_TL; + if (ui_do_button(&internet_button, "Internet", active_page==PAGE_INTERNET, &button, ui_draw_menu_tab_button, &corners)) { client_serverbrowse_refresh(BROWSETYPE_INTERNET); new_page = PAGE_INTERNET; } - ui_vsplit_l(&box, 4.0f, 0, &box); - ui_vsplit_l(&box, 90.0f, &button, &box); + //ui_vsplit_l(&box, 4.0f, 0, &box); + ui_vsplit_l(&box, 80.0f, &button, &box); static int lan_button=0; - if (ui_do_button(&lan_button, "LAN", active_page==PAGE_LAN, &button, ui_draw_menu_tab_button, 0)) + corners = 0; + if (ui_do_button(&lan_button, "LAN", active_page==PAGE_LAN, &button, ui_draw_menu_tab_button, &corners)) { client_serverbrowse_refresh(BROWSETYPE_LAN); new_page = PAGE_LAN; } - ui_vsplit_l(&box, 4.0f, 0, &box); - ui_vsplit_l(&box, 120.0f, &button, &box); + //ui_vsplit_l(&box, 4.0f, 0, &box); + ui_vsplit_l(&box, 110.0f, &button, &box); static int favorites_button=0; - if (ui_do_button(&favorites_button, "Favorites", active_page==PAGE_FAVORITES, &button, ui_draw_menu_tab_button, 0)) + corners = CORNER_TR; + if (ui_do_button(&favorites_button, "Favorites", active_page==PAGE_FAVORITES, &button, ui_draw_menu_tab_button, &corners)) { client_serverbrowse_refresh(BROWSETYPE_FAVORITES); new_page = PAGE_FAVORITES; } + + ui_vsplit_l(&box, 4.0f*5, 0, &box); + ui_vsplit_l(&box, 100.0f, &button, &box); + static int demos_button=0; + if (ui_do_button(&demos_button, "Demos", active_page==PAGE_DEMOS, &button, ui_draw_menu_tab_button, 0)) + { + //client_serverbrowse_refresh(BROWSETYPE_FAVORITES); + new_page = PAGE_DEMOS; + } } else { @@ -547,13 +570,13 @@ int MENUS::render_menubar(RECT r) ui_vsplit_r(&box, 30.0f, &box, 0); */ - ui_vsplit_r(&box, 110.0f, &box, &button); + ui_vsplit_r(&box, 90.0f, &box, &button); static int quit_button=0; if (ui_do_button(&quit_button, "Quit", 0, &button, ui_draw_menu_tab_button, 0)) popup = POPUP_QUIT; ui_vsplit_r(&box, 10.0f, &box, &button); - ui_vsplit_r(&box, 110.0f, &box, &button); + ui_vsplit_r(&box, 120.0f, &box, &button); static int settings_button=0; if (ui_do_button(&settings_button, "Settings", active_page==PAGE_SETTINGS, &button, ui_draw_menu_tab_button, 0)) new_page = PAGE_SETTINGS; @@ -663,7 +686,7 @@ int MENUS::render() if(popup == POPUP_NONE) { // do tab bar - ui_hsplit_t(&screen, 26.0f, &tab_bar, &main_view); + ui_hsplit_t(&screen, 24.0f, &tab_bar, &main_view); ui_vmargin(&tab_bar, 20.0f, &tab_bar); render_menubar(tab_bar); @@ -691,6 +714,8 @@ int MENUS::render() render_serverbrowser(main_view); else if(config.ui_page == PAGE_LAN) render_serverbrowser(main_view); + else if(config.ui_page == PAGE_DEMOS) + render_demolist(main_view); else if(config.ui_page == PAGE_FAVORITES) render_serverbrowser(main_view); else if(config.ui_page == PAGE_SETTINGS) @@ -922,7 +947,7 @@ void MENUS::on_statechange(int new_state, int old_state) popup = POPUP_CONNECTING; else if(new_state == CLIENTSTATE_CONNECTING) popup = POPUP_CONNECTING; - else if (new_state == CLIENTSTATE_ONLINE) + else if (new_state == CLIENTSTATE_ONLINE || new_state == CLIENTSTATE_DEMOPLAYBACK) { popup = POPUP_NONE; menu_active = false; @@ -931,17 +956,19 @@ void MENUS::on_statechange(int new_state, int old_state) void MENUS::on_render() { - if(client_state() != CLIENTSTATE_ONLINE) + if(client_state() != CLIENTSTATE_ONLINE && client_state() != CLIENTSTATE_DEMOPLAYBACK) menu_active = true; + + if(client_state() == CLIENTSTATE_DEMOPLAYBACK) + { + RECT screen = *ui_screen(); + gfx_mapscreen(screen.x, screen.y, screen.w, screen.h); + render_demoplayer(screen); + } if(!menu_active) return; - - - if(inp_key_down('M')) button_height += 1.0f; - if(inp_key_down('N')) button_height -= 1.0f; - // update colors vec3 rgb = hsl_to_rgb(vec3(config.ui_color_hue/255.0f, config.ui_color_sat/255.0f, config.ui_color_lht/255.0f)); gui_color = vec4(rgb.r, rgb.g, rgb.b, config.ui_color_alpha/255.0f); @@ -1002,8 +1029,8 @@ void MENUS::on_render() } else { - //render_background(); - render(); + if(client_state() != CLIENTSTATE_DEMOPLAYBACK) + render(); // render cursor gfx_texture_set(data->images[IMAGE_CURSOR].id); @@ -1024,6 +1051,7 @@ void MENUS::on_render() gfx_text_set_cursor(&cursor, 10, 10, 10, TEXTFLAG_RENDER); gfx_text_ex(&cursor, buf, -1); } + } num_inputevents = 0; diff --git a/src/game/client/components/menus.hpp b/src/game/client/components/menus.hpp index 8aae925f..51090b93 100644 --- a/src/game/client/components/menus.hpp +++ b/src/game/client/components/menus.hpp @@ -15,6 +15,9 @@ class MENUS : public COMPONENT static vec4 button_color_mul(const void *id); + + static void ui_draw_demoplayer_button(const void *id, const char *text, int checked, const RECT *r, const void *extra); + static void ui_draw_browse_icon(int what, const RECT *r); static void ui_draw_menu_button(const void *id, const char *text, int checked, const RECT *r, const void *extra); static void ui_draw_keyselect_button(const void *id, const char *text, int checked, const RECT *r, const void *extra); @@ -33,6 +36,19 @@ class MENUS : public COMPONENT static int ui_do_key_reader(void *id, const RECT *rect, int key); static void ui_do_getbuttons(int start, int stop, RECT view); + struct LISTBOXITEM + { + int visible; + int selected; + RECT rect; + }; + + static void ui_do_listbox_start(void *id, const RECT *rect, float row_height, const char *title, int num_items, int selected_index); + static LISTBOXITEM ui_do_listbox_nextitem(void *id); + static int ui_do_listbox_end(); + + //static void demolist_listdir_callback(const char *name, int is_dir, void *user); + //static void demolist_list_callback(const RECT *rect, int index, void *user); enum { @@ -53,6 +69,7 @@ class MENUS : public COMPONENT PAGE_INTERNET, PAGE_LAN, PAGE_FAVORITES, + PAGE_DEMOS, PAGE_SETTINGS, PAGE_SYSTEM, }; @@ -71,7 +88,9 @@ class MENUS : public COMPONENT static int num_inputevents; // some settings - float button_height; + static float button_height; + static float listheader_height; + static float fontmod_height; // for graphic settings bool need_restart; @@ -79,6 +98,20 @@ class MENUS : public COMPONENT // for call vote int callvote_selectedplayer; int callvote_selectedmap; + + // demo + struct DEMOITEM + { + char filename[512]; + char name[256]; + }; + + DEMOITEM *demos; + int num_demos; + + void demolist_populate(); + static void demolist_count_callback(const char *name, int is_dir, void *user); + static void demolist_fetch_callback(const char *name, int is_dir, void *user); // found in menus.cpp int render(); @@ -87,6 +120,10 @@ class MENUS : public COMPONENT int render_menubar(RECT r); void render_news(RECT main_view); + // found in menus_demo.cpp + void render_demoplayer(RECT main_view); + void render_demolist(RECT main_view); + // found in menus_ingame.cpp void render_game(RECT main_view); void render_serverinfo(RECT main_view); diff --git a/src/game/client/components/menus_browser.cpp b/src/game/client/components/menus_browser.cpp index a4e730e6..3cba5311 100644 --- a/src/game/client/components/menus_browser.cpp +++ b/src/game/client/components/menus_browser.cpp @@ -21,7 +21,7 @@ void MENUS::render_serverbrowser_serverlist(RECT view) RECT headers; RECT status; - ui_hsplit_t(&view, 16.0f, &headers, &view); + ui_hsplit_t(&view, listheader_height, &headers, &view); ui_hsplit_b(&view, 20.0f, &view, &status); // split of the scrollbar @@ -634,7 +634,7 @@ void MENUS::render_serverbrowser(RECT main_view) ui_vmargin(&button, 2.0f, &button); //ui_vmargin(&button, 2.0f, &button); static int join_button = 0; - if(ui_do_button(&join_button, "Connect", 0, &button, ui_draw_menu_button, 0) || inp_key_down(KEY_ENTER)) + if(ui_do_button(&join_button, "Connect", 0, &button, ui_draw_menu_button, 0)) // || inp_key_down(KEY_ENTER)) client_connect(config.ui_server_address); ui_hsplit_b(&button_box, 5.0f, &button_box, &button); diff --git a/src/game/client/components/menus_demo.cpp b/src/game/client/components/menus_demo.cpp new file mode 100644 index 00000000..81717bf7 --- /dev/null +++ b/src/game/client/components/menus_demo.cpp @@ -0,0 +1,390 @@ + +#include <base/math.hpp> + +//#include <string.h> // strcmp, strlen, strncpy +//#include <stdlib.h> // atoi + +#include <engine/e_client_interface.h> +#include <game/client/gc_render.hpp> +#include <game/client/gameclient.hpp> + +//#include <game/generated/g_protocol.hpp> +//#include <game/generated/gc_data.hpp> + +#include <game/client/ui.hpp> +//#include <game/client/gameclient.hpp> +//#include <game/client/animstate.hpp> + +#include "menus.hpp" + +void MENUS::ui_draw_demoplayer_button(const void *id, const char *text, int checked, const RECT *r, const void *extra) +{ + ui_draw_rect(r, vec4(1,1,1, checked ? 0.10f : 0.5f)*button_color_mul(id), CORNER_ALL, 5.0f); + ui_do_label(r, text, 14.0f, 0); +} + +void MENUS::render_demoplayer(RECT main_view) +{ + const DEMOPLAYBACK_INFO *info = client_demoplayer_getinfo(); + + const float seekbar_height = 15.0f; + const float buttonbar_height = 20.0f; + const float margins = 5.0f; + float total_height; + + if(menu_active) + total_height = seekbar_height+buttonbar_height+margins*3; + else + total_height = seekbar_height+margins*2; + + ui_hsplit_b(&main_view, total_height, 0, &main_view); + ui_vsplit_l(&main_view, 250.0f, 0, &main_view); + ui_vsplit_r(&main_view, 250.0f, &main_view, 0); + + ui_draw_rect(&main_view, color_tabbar_active, CORNER_T, 10.0f); + + ui_margin(&main_view, 5.0f, &main_view); + + RECT seekbar, buttonbar; + + if(menu_active) + { + ui_hsplit_t(&main_view, seekbar_height, &seekbar, &buttonbar); + ui_hsplit_t(&buttonbar, margins, 0, &buttonbar); + } + else + seekbar = main_view; + + // do seekbar + { + static int seekbar_id = 0; + void *id = &seekbar_id; + char buffer[128]; + + ui_draw_rect(&seekbar, vec4(0,0,0,0.5f), CORNER_ALL, 5.0f); + + int current_tick = info->current_tick - info->first_tick; + int total_ticks = info->last_tick - info->first_tick; + + float amount = current_tick/(float)total_ticks; + + RECT filledbar = seekbar; + filledbar.w = 10.0f + (filledbar.w-10.0f)*amount; + + ui_draw_rect(&filledbar, vec4(1,1,1,0.5f), CORNER_ALL, 5.0f); + + str_format(buffer, sizeof(buffer), "%d:%02d / %d:%02d", + current_tick/SERVER_TICK_SPEED/60, (current_tick/SERVER_TICK_SPEED)%60, + total_ticks/SERVER_TICK_SPEED/60, (total_ticks/SERVER_TICK_SPEED)%60); + ui_do_label(&seekbar, buffer, seekbar.h*0.70f, 0); + + // do the logic + int inside = ui_mouse_inside(&seekbar); + + if(ui_active_item() == id) + { + if(!ui_mouse_button(0)) + ui_set_active_item(0); + else + { + float amount = (ui_mouse_x()-seekbar.x)/(float)seekbar.w; + if(amount > 0 && amount < 1.0f) + { + gameclient.on_reset(); + client_demoplayer_setpos(amount); + } + } + } + else if(ui_hot_item() == id) + { + if(ui_mouse_button(0)) + ui_set_active_item(id); + } + + if(inside) + ui_set_hot_item(id); + } + + + if(menu_active) + { + // do buttons + RECT button; + + // pause button + ui_vsplit_l(&buttonbar, buttonbar_height, &button, &buttonbar); + static int pause_button = 0; + if(ui_do_button(&pause_button, "| |", info->paused, &button, ui_draw_demoplayer_button, 0)) + client_demoplayer_setpause(!info->paused); + + // play button + ui_vsplit_l(&buttonbar, margins, 0, &buttonbar); + ui_vsplit_l(&buttonbar, buttonbar_height, &button, &buttonbar); + static int play_button = 0; + if(ui_do_button(&play_button, ">", !info->paused, &button, ui_draw_demoplayer_button, 0)) + { + client_demoplayer_setpause(0); + client_demoplayer_setspeed(1.0f); + } + + // slowdown + ui_vsplit_l(&buttonbar, margins, 0, &buttonbar); + ui_vsplit_l(&buttonbar, buttonbar_height, &button, &buttonbar); + static int slowdown_button = 0; + if(ui_do_button(&slowdown_button, "<<", 0, &button, ui_draw_demoplayer_button, 0)) + { + if(info->speed > 4.0f) client_demoplayer_setspeed(4.0f); + else if(info->speed > 2.0f) client_demoplayer_setspeed(2.0f); + else if(info->speed > 1.0f) client_demoplayer_setspeed(1.0f); + else if(info->speed > 0.5f) client_demoplayer_setspeed(0.5f); + else client_demoplayer_setspeed(0.05f); + } + + // fastforward + ui_vsplit_l(&buttonbar, margins, 0, &buttonbar); + ui_vsplit_l(&buttonbar, buttonbar_height, &button, &buttonbar); + static int fastforward_button = 0; + if(ui_do_button(&fastforward_button, ">>", 0, &button, ui_draw_demoplayer_button, 0)) + { + if(info->speed < 0.5f) client_demoplayer_setspeed(0.5f); + else if(info->speed < 1.0f) client_demoplayer_setspeed(1.0f); + else if(info->speed < 2.0f) client_demoplayer_setspeed(2.0f); + else if(info->speed < 4.0f) client_demoplayer_setspeed(4.0f); + else client_demoplayer_setspeed(8.0f); + } + + // speed meter + ui_vsplit_l(&buttonbar, margins*3, 0, &buttonbar); + char buffer[64]; + if(info->speed >= 1.0f) + str_format(buffer, sizeof(buffer), "x%.0f", info->speed); + else + str_format(buffer, sizeof(buffer), "x%.1f", info->speed); + ui_do_label(&buttonbar, buffer, button.h*0.7f, -1); + + // exit button + ui_vsplit_r(&buttonbar, buttonbar_height*3, &buttonbar, &button); + static int exit_button = 0; + if(ui_do_button(&exit_button, "Exit", 0, &button, ui_draw_demoplayer_button, 0)) + client_disconnect(); + } +} + +static RECT listbox_originalview; +static RECT listbox_view; +static float listbox_rowheight; +static int listbox_itemindex; +static int listbox_selected_index; +static int listbox_new_selected; + +void MENUS::ui_do_listbox_start(void *id, const RECT *rect, float row_height, const char *title, int num_items, int selected_index) +{ + RECT scroll, row; + RECT view = *rect; + RECT header, footer; + + // draw header + ui_hsplit_t(&view, listheader_height, &header, &view); + ui_draw_rect(&header, vec4(1,1,1,0.25f), CORNER_T, 5.0f); + ui_do_label(&header, title, header.h*fontmod_height, 0); + + // draw footers + ui_hsplit_b(&view, listheader_height, &view, &footer); + ui_draw_rect(&footer, vec4(1,1,1,0.25f), CORNER_B, 5.0f); + ui_vsplit_l(&footer, 10.0f, 0, &footer); + + // background + ui_draw_rect(&view, vec4(0,0,0,0.15f), 0, 0); + + // prepare the scroll + ui_vsplit_r(&view, 15, &view, &scroll); + + // setup the variables + listbox_originalview = view; + listbox_selected_index = selected_index; + listbox_new_selected = selected_index; + listbox_itemindex = 0; + listbox_rowheight = row_height; + //int num_servers = client_serverbrowse_sorted_num(); + + + // do the scrollbar + ui_hsplit_t(&view, listbox_rowheight, &row, 0); + + int num = (int)(listbox_originalview.h/row.h); + static float scrollvalue = 0; + ui_hmargin(&scroll, 5.0f, &scroll); + scrollvalue = ui_do_scrollbar_v(id, &scroll, scrollvalue); + + int start = (int)(num*scrollvalue); + if(start < 0) + start = 0; + + // the list + listbox_view = listbox_originalview; + ui_vmargin(&listbox_view, 5.0f, &listbox_view); + ui_clip_enable(&listbox_view); + listbox_view.y -= scrollvalue*num*row.h; +} + +MENUS::LISTBOXITEM MENUS::ui_do_listbox_nextitem(void *id) +{ + RECT row; + LISTBOXITEM item = {0}; + ui_hsplit_t(&listbox_view, listbox_rowheight-2.0f, &row, &listbox_view); + ui_hsplit_t(&listbox_view, 2.0f, 0, &listbox_view); + + RECT select_hit_box = row; + + item.visible = 1; + if(listbox_selected_index == listbox_itemindex) + item.selected = 1; + + // make sure that only those in view can be selected + if(row.y+row.h > listbox_originalview.y) + { + + if(select_hit_box.y < listbox_originalview.y) // clip the selection + { + select_hit_box.h -= listbox_originalview.y-select_hit_box.y; + select_hit_box.y = listbox_originalview.y; + } + + if(ui_do_button(id, "", listbox_selected_index==listbox_itemindex, &select_hit_box, 0, 0)) + listbox_new_selected = listbox_itemindex; + } + else + item.visible = 0; + + item.rect = row; + + // check if we need to do more + if(row.y > listbox_originalview.y+listbox_originalview.h) + item.visible = 0; + + if(listbox_selected_index==listbox_itemindex) + { + //selected_index = i; + RECT r = row; + ui_margin(&r, 1.5f, &r); + ui_draw_rect(&r, vec4(1,1,1,0.5f), CORNER_ALL, 4.0f); + } + + listbox_itemindex++; + + ui_vmargin(&item.rect, 5.0f, &item.rect); + + return item; +} + +int MENUS::ui_do_listbox_end() +{ + ui_clip_disable(); + return listbox_new_selected; +} + +/* +void MENUS::demolist_listdir_callback(const char *name, int is_dir, void *user) +{ + + (*count)++; + LISTBOXITEM item = ui_do_listbox_nextitem((void*)(10+*count)); + if(item.visible) + ui_do_label(&item.rect, name, item.rect.h*fontmod_height, -1); +} + + + DEMOITEM *demos; + int num_demos; + */ + +void MENUS::demolist_count_callback(const char *name, int is_dir, void *user) +{ + if(is_dir || name[0] == '.') + return; + (*(int *)user)++; +} + +struct FETCH_CALLBACKINFO +{ + MENUS *self; + const char *prefix; + int count; +}; + +void MENUS::demolist_fetch_callback(const char *name, int is_dir, void *user) +{ + if(is_dir || name[0] == '.') + return; + + FETCH_CALLBACKINFO *info = (FETCH_CALLBACKINFO *)user; + + if(info->count == info->self->num_demos) + return; + + str_format(info->self->demos[info->count].filename, sizeof(info->self->demos[info->count].filename), "%s/%s", info->prefix, name); + str_copy(info->self->demos[info->count].name, name, sizeof(info->self->demos[info->count].name)); + info->count++; +} + + +void MENUS::demolist_populate() +{ + if(demos) + mem_free(demos); + demos = 0; + num_demos = 0; + + char buf[512]; + str_format(buf, sizeof(buf), "%s/demos", client_user_directory()); + fs_listdir(buf, demolist_count_callback, &num_demos); + fs_listdir("demos", demolist_count_callback, &num_demos); + + demos = (DEMOITEM *)mem_alloc(sizeof(DEMOITEM)*num_demos, 1); + mem_zero(demos, sizeof(DEMOITEM)*num_demos); + + FETCH_CALLBACKINFO info = {this, buf, 0}; + fs_listdir(buf, demolist_fetch_callback, &info); + info.prefix = "demos"; + fs_listdir("demos", demolist_fetch_callback, &info); +} + + +void MENUS::render_demolist(RECT main_view) +{ + static int inited = 0; + if(!inited) + demolist_populate(); + inited = 1; + + // render background + ui_draw_rect(&main_view, color_tabbar_active, CORNER_ALL, 10.0f); + ui_margin(&main_view, 10.0f, &main_view); + + RECT buttonbar; + ui_hsplit_b(&main_view, button_height+5.0f, &main_view, &buttonbar); + ui_hsplit_t(&buttonbar, 5.0f, 0, &buttonbar); + + static int selected_item = -1; + static int num_items = 0; + static int demolist_id = 0; + + ui_do_listbox_start(&demolist_id, &main_view, 17.0f, "Demos", num_items, selected_item); + for(int i = 0; i < num_demos; i++) + { + LISTBOXITEM item = ui_do_listbox_nextitem((void*)(10+i)); + if(item.visible) + ui_do_label(&item.rect, demos[i].name, item.rect.h*fontmod_height, -1); + } + selected_item = ui_do_listbox_end(); + + RECT button; + ui_vsplit_r(&buttonbar, 120.0f, &buttonbar, &button); + static int play_button = 0; + if(ui_do_button(&play_button, "Play", 0, &button, ui_draw_menu_button, 0)) + { + if(selected_item >= 0 && selected_item < num_demos) + client_demoplayer_play(demos[selected_item].filename); + } +} + diff --git a/src/game/client/components/menus_ingame.cpp b/src/game/client/components/menus_ingame.cpp index 33b4ffca..cfd92daa 100644 --- a/src/game/client/components/menus_ingame.cpp +++ b/src/game/client/components/menus_ingame.cpp @@ -283,7 +283,7 @@ void MENUS::render_servercontrol_map(RECT main_view) ui_vmargin(&button, 5.0f, &button); ui_do_label(&button, gameclient.maplist->name(i), 18.0f, -1); - } + } } void MENUS::render_servercontrol_kick(RECT main_view) diff --git a/src/game/client/components/players.cpp b/src/game/client/components/players.cpp index 6bf4221e..02977632 100644 --- a/src/game/client/components/players.cpp +++ b/src/game/client/components/players.cpp @@ -132,7 +132,7 @@ void PLAYERS::render_player( gameclient.clients[info.cid].angle = angle; vec2 direction = get_direction((int)(angle*256.0f)); - if(info.local && config.cl_predict) + if(info.local && config.cl_predict && client_state() != CLIENTSTATE_DEMOPLAYBACK) { if(!gameclient.snap.local_character || (gameclient.snap.local_character->health < 0) || (gameclient.snap.gameobj && gameclient.snap.gameobj->game_over)) { @@ -149,6 +149,8 @@ void PLAYERS::render_player( vec2 position = mix(vec2(prev.x, prev.y), vec2(player.x, player.y), intratick); vec2 vel = mix(vec2(prev.vx/256.0f, prev.vy/256.0f), vec2(player.vx/256.0f, player.vy/256.0f), intratick); + //dbg_msg("", "%d %d %d %d %f", prev.x, prev.y, player.x, player.y, intratick); + gameclient.flow->add(position, vel*100.0f, 10.0f); render_info.got_airjump = player.jumped&2?0:1; diff --git a/src/game/client/gameclient.cpp b/src/game/client/gameclient.cpp index 3aed1cd1..fee12eb0 100644 --- a/src/game/client/gameclient.cpp +++ b/src/game/client/gameclient.cpp @@ -262,8 +262,9 @@ void GAMECLIENT::on_connected() layers_init(); col_init(); render_tilemap_generate_skip(); - - on_reset(); + + for(int i = 0; i < all.num; i++) + all.components[i]->on_mapload(); // send the inital info send_info(true); @@ -313,8 +314,44 @@ void GAMECLIENT::update_local_character_pos() } } + +static void evolve(NETOBJ_CHARACTER *character, int tick) +{ + WORLD_CORE tempworld; + CHARACTER_CORE tempcore; + mem_zero(&tempcore, sizeof(tempcore)); + tempcore.world = &tempworld; + tempcore.read(character); + //tempcore.input.direction = character->wanted_direction; + while(character->tick < tick) + { + character->tick++; + tempcore.tick(false); + tempcore.move(); + tempcore.quantize(); + } + + tempcore.write(character); +} + + void GAMECLIENT::on_render() { + // perform dead reckoning + // TODO: move this to a betterlocation + /* + for(int i = 0; i < MAX_CLIENTS; i++) + { + if(!snap.characters[i].active) + continue; + + // perform dead reckoning + if(snap.characters[i].prev.tick) + evolve(&snap.characters[i].prev, client_prevtick()); + if(snap.characters[i].cur.tick) + evolve(&snap.characters[i].cur, client_tick()); + }*/ + // update the local character position update_local_character_pos(); @@ -393,6 +430,9 @@ void GAMECLIENT::on_message(int msgtype) if(clients[msg->cid].skin_name[0] == 'x' || clients[msg->cid].skin_name[1] == '_') str_copy(clients[msg->cid].skin_name, "default", 64); + clients[msg->cid].color_body = msg->color_body; + clients[msg->cid].color_feet = msg->color_feet; + clients[msg->cid].skin_info.color_body = skins->get_color(msg->color_body); clients[msg->cid].skin_info.color_feet = skins->get_color(msg->color_feet); clients[msg->cid].skin_info.size = 64; @@ -402,6 +442,8 @@ void GAMECLIENT::on_message(int msgtype) if(clients[msg->cid].skin_id < 0) clients[msg->cid].skin_id = 0; + clients[msg->cid].use_custom_color = msg->use_custom_color; + if(msg->use_custom_color) clients[msg->cid].skin_info.texture = gameclient.skins->get(clients[msg->cid].skin_id)->color_texture; else @@ -485,27 +527,6 @@ void GAMECLIENT::process_events() } } -static void evolve(NETOBJ_CHARACTER *character, int tick) -{ - WORLD_CORE tempworld; - CHARACTER_CORE tempcore; - mem_zero(&tempcore, sizeof(tempcore)); - tempcore.world = &tempworld; - tempcore.read(character); - //tempcore.input.direction = character->wanted_direction; - if(tick-character->tick > 50*3) - dbg_msg("", "%d -> %d = %d", character->tick, tick, tick-character->tick); - while(character->tick < tick) - { - character->tick++; - tempcore.tick(false); - tempcore.move(); - tempcore.quantize(); - } - - tempcore.write(character); -} - void GAMECLIENT::on_snapshot() { // clear out the invalid pointers @@ -582,7 +603,6 @@ void GAMECLIENT::on_snapshot() snap.characters[item.id].prev = *((const NETOBJ_CHARACTER *)old); snap.characters[item.id].cur = *((const NETOBJ_CHARACTER *)data); - // perform dead reckoning if(snap.characters[item.id].prev.tick) evolve(&snap.characters[item.id].prev, client_prevtick()); if(snap.characters[item.id].cur.tick) @@ -596,6 +616,9 @@ void GAMECLIENT::on_snapshot() } } + if(client_state() == CLIENTSTATE_DEMOPLAYBACK) + gameclient.snap.spectate = true; + // setup local pointers if(snap.local_cid >= 0) { @@ -779,6 +802,25 @@ void GAMECLIENT::send_kill(int client_id) client_send_msg(); } +void GAMECLIENT::on_recordkeyframe() +{ + for(int i = 0; i < MAX_CLIENTS; i++) + { + if(!snap.player_infos[i]) + continue; + + NETMSG_SV_SETINFO msg; + msg.cid = i; + msg.name = clients[i].name; + msg.skin = clients[i].skin_name; + msg.use_custom_color = clients[i].use_custom_color; + msg.color_body = clients[i].color_body; + msg.color_feet = clients[i].color_feet; + msg.pack(MSGFLAG_NOSEND|MSGFLAG_RECORD); + client_send_msg(); + } +} + void GAMECLIENT::con_team(void *result, void *user_data) { ((GAMECLIENT*)user_data)->send_switch_team(console_arg_int(result, 0)); diff --git a/src/game/client/gameclient.hpp b/src/game/client/gameclient.hpp index 3ced19ce..b9ee325e 100644 --- a/src/game/client/gameclient.hpp +++ b/src/game/client/gameclient.hpp @@ -83,6 +83,10 @@ public: // client data struct CLIENT_DATA { + int use_custom_color; + int color_body; + int color_feet; + char name[64]; char skin_name[64]; int skin_id; @@ -115,6 +119,7 @@ public: void on_snapshot(); void on_predict(); int on_snapinput(int *data); + void on_recordkeyframe(); // actions // TODO: move these diff --git a/src/game/server/entities/character.cpp b/src/game/server/entities/character.cpp index 071407b1..0516ddc5 100644 --- a/src/game/server/entities/character.cpp +++ b/src/game/server/entities/character.cpp @@ -828,7 +828,7 @@ bool CHARACTER::take_damage(vec2 force, int dmg, int from, int weapon) void CHARACTER::snap(int snaping_client) { - if(distance(game.players[snaping_client]->view_pos, pos) > 1000.0f) + if(networkclipped(snaping_client)) return; NETOBJ_CHARACTER *character = (NETOBJ_CHARACTER *)snap_new_item(NETOBJTYPE_CHARACTER, player->client_id, sizeof(NETOBJ_CHARACTER)); diff --git a/src/game/server/entities/laser.cpp b/src/game/server/entities/laser.cpp index 4e9909ba..8b512d82 100644 --- a/src/game/server/entities/laser.cpp +++ b/src/game/server/entities/laser.cpp @@ -100,7 +100,7 @@ void LASER::tick() void LASER::snap(int snapping_client) { - if(distance(game.players[snapping_client]->view_pos, pos) > 1000.0f) + if(networkclipped(snapping_client)) return; NETOBJ_LASER *obj = (NETOBJ_LASER *)snap_new_item(NETOBJTYPE_LASER, id, sizeof(NETOBJ_LASER)); diff --git a/src/game/server/entities/projectile.cpp b/src/game/server/entities/projectile.cpp index a2e10437..cd15ba10 100644 --- a/src/game/server/entities/projectile.cpp +++ b/src/game/server/entities/projectile.cpp @@ -78,9 +78,7 @@ void PROJECTILE::tick() if(flags & PROJECTILE_FLAGS_EXPLODE) game.create_explosion(curpos, owner, weapon, false); else if(targetchr) - { targetchr->take_damage(direction * max(0.001f, force), damage, owner, weapon); - } game.world.destroy_entity(this); } @@ -100,7 +98,7 @@ void PROJECTILE::snap(int snapping_client) { float ct = (server_tick()-start_tick)/(float)server_tickspeed(); - if(distance(game.players[snapping_client]->view_pos, get_pos(ct)) > 1000.0f) + if(networkclipped(snapping_client, get_pos(ct))) return; NETOBJ_PROJECTILE *proj = (NETOBJ_PROJECTILE *)snap_new_item(NETOBJTYPE_PROJECTILE, id, sizeof(NETOBJ_PROJECTILE)); diff --git a/src/game/server/entity.cpp b/src/game/server/entity.cpp index 2cc7c8f7..1af5f60a 100644 --- a/src/game/server/entity.cpp +++ b/src/game/server/entity.cpp @@ -26,3 +26,17 @@ ENTITY::~ENTITY() game.world.remove_entity(this); snap_free_id(id); } + +int ENTITY::networkclipped(int snapping_client) +{ + return networkclipped(snapping_client, pos); +} + +int ENTITY::networkclipped(int snapping_client, vec2 check_pos) +{ + if(snapping_client == -1) + return 0; + if(distance(game.players[snapping_client]->view_pos, check_pos) > 1000.0f) + return 1; + return 0; +} diff --git a/src/game/server/entity.hpp b/src/game/server/entity.hpp index 8ccb2d9a..debe57b6 100644 --- a/src/game/server/entity.hpp +++ b/src/game/server/entity.hpp @@ -74,34 +74,34 @@ public: /* Function: destroy - Destorys the entity. + Destorys the entity. */ virtual void destroy() { delete this; } /* Function: reset - Called when the game resets the map. Puts the entity - back to it's starting state or perhaps destroys it. + Called when the game resets the map. Puts the entity + back to it's starting state or perhaps destroys it. */ virtual void reset() {} /* Function: tick - Called progress the entity to the next tick. Updates - and moves the entity to it's new state and position. + Called progress the entity to the next tick. Updates + and moves the entity to it's new state and position. */ virtual void tick() {} /* Function: tick_defered - Called after all entities tick() function has been called. + Called after all entities tick() function has been called. */ virtual void tick_defered() {} /* Function: snap - Called when a new snapshot is being generated for a specific - client. + Called when a new snapshot is being generated for a specific + client. Arguments: snapping_client - ID of the client which snapshot is @@ -110,16 +110,34 @@ public: recording. */ virtual void snap(int snapping_client) {} + + /* + Function: networkclipped(int snapping_client) + Performs a series of test to see if a client can see the + entity. + + Arguments: + snapping_client - ID of the client which snapshot is + being generated. Could be -1 to create a complete + snapshot of everything in the game for demo + recording. + + Returns: + Non-zero if the entity doesn't have to be in the snapshot. + */ + int networkclipped(int snapping_client); + int networkclipped(int snapping_client, vec2 check_pos); + /* Variable: proximity_radius - Contains the physical size of the entity. + Contains the physical size of the entity. */ float proximity_radius; /* Variable: pos - Contains the current posititon of the entity. + Contains the current posititon of the entity. */ vec2 pos; }; diff --git a/src/game/server/eventhandler.cpp b/src/game/server/eventhandler.cpp index ce6a3b71..761eaf2c 100644 --- a/src/game/server/eventhandler.cpp +++ b/src/game/server/eventhandler.cpp @@ -36,10 +36,10 @@ void EVENTHANDLER::snap(int snapping_client) { for(int i = 0; i < num_events; i++) { - if(cmask_is_set(client_masks[i], snapping_client)) + if(snapping_client == -1 || cmask_is_set(client_masks[i], snapping_client)) { NETEVENT_COMMON *ev = (NETEVENT_COMMON *)&data[offsets[i]]; - if(distance(game.players[snapping_client]->view_pos, vec2(ev->x, ev->y)) < 1500.0f) + if(snapping_client == -1 || distance(game.players[snapping_client]->view_pos, vec2(ev->x, ev->y)) < 1500.0f) { void *d = snap_new_item(types[i], i, sizes[i]); mem_copy(d, &data[offsets[i]], sizes[i]); diff --git a/src/game/server/gamecontext.cpp b/src/game/server/gamecontext.cpp index fb426ad6..0cdd4227 100644 --- a/src/game/server/gamecontext.cpp +++ b/src/game/server/gamecontext.cpp @@ -171,7 +171,7 @@ void GAMECONTEXT::send_chat(int chatter_cid, int team, const char *text) } -void GAMECONTEXT::send_info(int who, int to_who) +void GAMECONTEXT::send_info(int who, int to_who, bool recordonly) { NETMSG_SV_SETINFO msg; msg.cid = who; @@ -180,9 +180,17 @@ void GAMECONTEXT::send_info(int who, int to_who) msg.use_custom_color = players[who]->use_custom_color; msg.color_body = players[who]->color_body; msg.color_feet = players[who]->color_feet; - msg.pack(MSGFLAG_VITAL); - server_send_msg(to_who); + if(recordonly) + { + msg.pack(MSGFLAG_NOSEND); + server_send_msg(to_who); + } + else + { + msg.pack(MSGFLAG_VITAL); + server_send_msg(to_who); + } } void GAMECONTEXT::send_emoticon(int cid, int emoticon) @@ -333,6 +341,18 @@ void GAMECONTEXT::tick() void GAMECONTEXT::snap(int client_id) { + // check if we are recording a demo + if(client_id == -1) + { + // we are recording, make sure that we set + // the info for all players all the time + for(int i = 0; i < MAX_CLIENTS; i++) + { + if(game.players[i]) + send_info(i, -1, true); + } + } + world.snap(client_id); controller->snap(client_id); events.snap(client_id); diff --git a/src/game/server/gamecontext.hpp b/src/game/server/gamecontext.hpp index 124df645..8a1ff918 100644 --- a/src/game/server/gamecontext.hpp +++ b/src/game/server/gamecontext.hpp @@ -77,7 +77,7 @@ public: void send_emoticon(int cid, int emoticon); void send_weapon_pickup(int cid, int weapon); void send_broadcast(const char *text, int cid); - void send_info(int who, int to_who); + void send_info(int who, int to_who, bool recordonly = false); }; diff --git a/src/game/server/gamecontroller.cpp b/src/game/server/gamecontroller.cpp index 7329240b..aa8c09b7 100644 --- a/src/game/server/gamecontroller.cpp +++ b/src/game/server/gamecontroller.cpp @@ -457,8 +457,19 @@ void GAMECONTROLLER::snap(int snapping_client) gameobj->round_num = (strlen(config.sv_maprotation) || config.sv_rounds_per_map > 1) ? config.sv_rounds_per_map : 0; gameobj->round_current = round_count+1; - gameobj->teamscore_red = is_teamplay() ? teamscore[0] : game.players[snapping_client]->score; - gameobj->teamscore_blue = teamscore[1]; + + if(snapping_client == -1) + { + // we are recording a demo, just set the scores + gameobj->teamscore_red = teamscore[0]; + gameobj->teamscore_blue = teamscore[1]; + } + else + { + // TODO: this little hack should be removed + gameobj->teamscore_red = is_teamplay() ? teamscore[0] : game.players[snapping_client]->score; + gameobj->teamscore_blue = teamscore[1]; + } } int GAMECONTROLLER::get_auto_team(int notthisid) |