diff options
| author | Magnus Auvinen <magnus.auvinen@gmail.com> | 2008-10-06 18:05:01 +0000 |
|---|---|---|
| committer | Magnus Auvinen <magnus.auvinen@gmail.com> | 2008-10-06 18:05:01 +0000 |
| commit | 12472ef7f405f5e8eb620059cbf95926a458538a (patch) | |
| tree | 712cc453e491ff46c96b48785a94093b1d17cb1f /src | |
| parent | d1b55351ccc2252917ad494b74bb6ad562df34ce (diff) | |
| download | zcatch-12472ef7f405f5e8eb620059cbf95926a458538a.tar.gz zcatch-12472ef7f405f5e8eb620059cbf95926a458538a.zip | |
major update. continued on ban support. added demo recording (client and server side). added demo player. added demo menu. demos have some quirks and file size optimizations havn't been done yet. some interface tweaks
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) |