#include #include #include #include #include #include #include #include #include #include #include #include #include "ui.h" #include #include #include #include #include using namespace baselib; // --- input wrappers --- static int keyboard_state[2][input::last]; static int keyboard_current = 0; static int keyboard_first = 1; void inp_mouse_relative(int *x, int *y) { input::mouse_position(x, y); } int inp_key_pressed(int key) { return keyboard_state[keyboard_current][key]; } int inp_key_was_pressed(int key) { return keyboard_state[keyboard_current^1][key]; } int inp_key_down(int key) { return inp_key_pressed(key)&&!inp_key_was_pressed(key); } int inp_button_pressed(int button) { return input::pressed(button); } void inp_update() { if(keyboard_first) { // make sure to reset keyboard_first = 0; inp_update(); } keyboard_current = keyboard_current^1; for(int i = 0; i < input::last; i++) keyboard_state[keyboard_current][i] = input::pressed(i); } // --- input snapping --- static int input_data[MAX_INPUT_SIZE] = {0}; static int input_data_size; static int input_is_changed = 1; void snap_input(void *data, int size) { if(input_data_size != size || memcmp(input_data, data, size)) input_is_changed = 1; mem_copy(input_data, data, size); input_data_size = size; } // -- snapshot handling --- enum { SNAP_INCOMMING=2, NUM_SNAPSHOT_TYPES=3, }; static snapshot_storage snapshots_new; static int current_tick; static snapshot *snapshots[NUM_SNAPSHOT_TYPES]; static char snapshot_data[NUM_SNAPSHOT_TYPES][MAX_SNAPSHOT_SIZE]; static int recived_snapshots; static int64 snapshot_start_time; static int64 local_start_time; float client_localtime() { return (time_get()-local_start_time)/(float)(time_freq()); } void *snap_get_item(int snapid, int index, snap_item *item) { dbg_assert(snapid >= 0 && snapid < NUM_SNAPSHOT_TYPES, "invalid snapid"); snapshot::item *i = snapshots[snapid]->get_item(index); item->type = i->type(); item->id = i->id(); return (void *)i->data(); } int snap_num_items(int snapid) { dbg_assert(snapid >= 0 && snapid < NUM_SNAPSHOT_TYPES, "invalid snapid"); return snapshots[snapid]->num_items; } static void snap_init() { snapshots[SNAP_INCOMMING] = (snapshot*)snapshot_data[0]; snapshots[SNAP_CURRENT] = (snapshot*)snapshot_data[1]; snapshots[SNAP_PREV] = (snapshot*)snapshot_data[2]; mem_zero(snapshot_data, NUM_SNAPSHOT_TYPES*MAX_SNAPSHOT_SIZE); recived_snapshots = 0; } float client_intratick() { return (time_get() - snapshot_start_time)/(float)(time_freq()/SERVER_TICK_SPEED); } int client_tick() { return current_tick; } int client_tickspeed() { return SERVER_TICK_SPEED; } void *snap_find_item(int snapid, int type, int id) { // TODO: linear search. should be fixed. for(int i = 0; i < snapshots[snapid]->num_items; i++) { snapshot::item *itm = snapshots[snapid]->get_item(i); if(itm->type() == type && itm->id() == id) return (void *)itm->data(); } return 0x0; } int menu_loop(); // TODO: what is this? static float frametime = 0.0001f; float client_frametime() { return frametime; } static net_client net; int client_send_msg() { const msg_info *info = msg_get_info(); NETPACKET packet; mem_zero(&packet, sizeof(NETPACKET)); packet.client_id = 0; packet.data = info->data; packet.data_size = info->size; if(info->flags&MSGFLAG_VITAL) packet.flags = PACKETFLAG_VITAL; net.send(&packet); return 0; } static struct { server_info infos[MAX_SERVERS]; int64 request_times[MAX_SERVERS]; netaddr4 addresses[MAX_SERVERS]; int num; } servers; static int serverlist_lan = 1; static netaddr4 master_server; int client_serverbrowse_getlist(server_info **serverlist) { *serverlist = servers.infos; return servers.num; } void client_serverbrowse_init() { servers.num = 0; } void client_serverbrowse_use_lan(int use) { serverlist_lan = use; } void client_serverbrowse_refresh() { if(serverlist_lan) { dbg_msg("client", "broadcasting for servers"); NETPACKET packet; packet.client_id = -1; packet.address.ip[0] = 0; packet.address.ip[1] = 0; packet.address.ip[2] = 0; packet.address.ip[3] = 0; packet.address.port = 8303; packet.flags = PACKETFLAG_CONNLESS; packet.data_size = sizeof(SERVERBROWSE_GETINFO); packet.data = SERVERBROWSE_GETINFO; net.send(&packet); // reset the list servers.num = 0; } else { dbg_msg("client", "requesting server list"); NETPACKET packet; packet.client_id = -1; packet.address = master_server; packet.flags = PACKETFLAG_CONNLESS; packet.data_size = sizeof(SERVERBROWSE_GETLIST); packet.data = SERVERBROWSE_GETLIST; net.send(&packet); // reset the list servers.num = 0; } } enum { STATE_OFFLINE, STATE_CONNECTING, STATE_LOADING, STATE_ONLINE, STATE_BROKEN, STATE_QUIT, }; static netaddr4 server_address; static int state; static int get_state() { return state; } static void set_state(int s) { dbg_msg("game", "state change. last=%d current=%d", state, s); state = s; } void client_connect(const char *server_address_str) { dbg_msg("client", "connecting to '%s'", server_address_str); char buf[512]; strncpy(buf, server_address_str, 512); const char *port_str = 0; for(int k = 0; buf[k]; k++) { if(buf[k] == ':') { port_str = &(buf[k+1]); buf[k] = 0; break; } } int port = 8303; if(port_str) port = atoi(port_str); if(net_host_lookup(buf, port, &server_address) != 0) dbg_msg("client", "could not find the address of %s, connecting to localhost", buf); net.connect(&server_address); set_state(STATE_CONNECTING); } // --- client --- // TODO: remove this class class client { public: int info_request_begin; int info_request_end; int snapshot_part; int debug_font; // TODO: rfemove this line // data to hold three snapshots // previous, void send_info() { recived_snapshots = 0; msg_pack_start_system(NETMSG_INFO, MSGFLAG_VITAL); msg_pack_string(config.player_name, 128); msg_pack_string(config.clan_name, 128); msg_pack_string(config.password, 128); msg_pack_string("myskin", 128); msg_pack_end(); client_send_msg(); } void send_entergame() { msg_pack_start_system(NETMSG_ENTERGAME, MSGFLAG_VITAL); msg_pack_end(); client_send_msg(); } void send_error(const char *error) { /* pack(NETMSG_CLIENT_ERROR, "s", error); */ /* packet p(NETMSG_CLIENT_ERROR); p.write_str(error); send_packet(&p); //send_packet(&p); //send_packet(&p); */ } void send_input() { msg_pack_start_system(NETMSG_INPUT, 0); msg_pack_int(input_data_size); for(int i = 0; i < input_data_size/4; i++) msg_pack_int(input_data[i]); msg_pack_end(); client_send_msg(); } void disconnect() { send_error("disconnected"); set_state(STATE_OFFLINE); map_unload(); } bool load_data() { debug_font = gfx_load_texture("data/debug_font.png"); return true; } void debug_render() { gfx_blend_normal(); gfx_texture_set(debug_font); gfx_mapscreen(0,0,gfx_screenwidth(),gfx_screenheight()); static NETSTATS prev, current; static int64 last_snap = 0; if(time_get()-last_snap > time_freq()/10) { last_snap = time_get(); prev = current; net.stats(¤t); } char buffer[512]; sprintf(buffer, "send: %8d recv: %8d", (current.send_bytes-prev.send_bytes)*10, (current.recv_bytes-prev.recv_bytes)*10); gfx_quads_text(10, 10, 16, buffer); } void render() { gfx_clear(0.0f,0.0f,0.0f); // this should be moved around abit // TODO: clean this shit up! if(get_state() == STATE_ONLINE) { modc_render(); // debug render stuff debug_render(); } else if (get_state() != STATE_CONNECTING && get_state() != STATE_LOADING) { //netaddr4 server_address; int status = modmenu_render(); if (status == -1) set_state(STATE_QUIT); } else if (get_state() == STATE_CONNECTING || get_state() == STATE_LOADING) { static int64 start = time_get(); static int tee_texture; static int connecting_texture; static bool inited = false; if (!inited) { tee_texture = gfx_load_texture("data/gui_tee.png"); connecting_texture = gfx_load_texture("data/gui/connecting.png"); inited = true; } gfx_mapscreen(0,0,400.0f,300.0f); float t = (time_get() - start) / (double)time_freq(); float speed = 2*sin(t); speed = 1.0f; float x = 208 + sin(t*speed) * 32; float w = sin(t*speed + 3.149) * 64; ui_do_image(tee_texture, x, 95, w, 64); ui_do_image(connecting_texture, 88, 150, 256, 64); } } void run(const char *direct_connect_server) { local_start_time = time_get(); snapshot_part = 0; info_request_begin = 0; info_request_end = 0; client_serverbrowse_init(); // init graphics and sound if(!gfx_init()) return; snd_init(); // sound is allowed to fail // load data if(!load_data()) return; // init snapshotting snap_init(); // init the mod modc_init(); // init menu modmenu_init(); // open socket net.open(0, 0); // net_host_lookup(config.masterserver, MASTERSERVER_PORT, &master_server); // connect to the server if wanted if(direct_connect_server) client_connect(direct_connect_server); //int64 inputs_per_second = 50; //int64 time_per_input = time_freq()/inputs_per_second; int64 game_starttime = time_get(); int64 last_input = game_starttime; int64 reporttime = time_get(); int64 reportinterval = time_freq()*1; int frames = 0; input::set_mouse_mode(input::mode_relative); while (1) { frames++; int64 frame_start_time = time_get(); // send input if(get_state() == STATE_ONLINE) { if(input_is_changed || time_get() > last_input+time_freq()) { send_input(); input_is_changed = 0; last_input = time_get(); } } // update input inp_update(); // if(input::pressed(input::f1)) input::set_mouse_mode(input::mode_absolute); if(input::pressed(input::f2)) input::set_mouse_mode(input::mode_relative); // panic button if(input::pressed(input::lctrl) && input::pressed('Q')) break; // pump the network pump_network(); // update the server browser serverbrowse_update(); // render render(); // swap the buffers gfx_swap(); // check conditions if(get_state() == STATE_BROKEN || get_state() == STATE_QUIT) break; // be nice //thread_sleep(1); if(reporttime < time_get()) { dbg_msg("client/report", "fps=%.02f netstate=%d", frames/(float)(reportinterval/time_freq()), net.state()); frames = 0; reporttime += reportinterval; } if (input::pressed(input::esc)) if (get_state() == STATE_CONNECTING || get_state() == STATE_ONLINE) disconnect(); // update frametime frametime = (time_get()-frame_start_time)/(float)time_freq(); } modc_shutdown(); disconnect(); modmenu_shutdown(); gfx_shutdown(); snd_shutdown(); } void error(const char *msg) { dbg_msg("game", "error: %s", msg); send_error(msg); set_state(STATE_BROKEN); } void serverbrowse_request(int id) { dbg_msg("client", "requesting server info from %d.%d.%d.%d:%d", servers.addresses[id].ip[0], servers.addresses[id].ip[1], servers.addresses[id].ip[2], servers.addresses[id].ip[3], servers.addresses[id].port); NETPACKET packet; packet.client_id = -1; packet.address = servers.addresses[id]; packet.flags = PACKETFLAG_CONNLESS; packet.data_size = sizeof(SERVERBROWSE_GETINFO); packet.data = SERVERBROWSE_GETINFO; net.send(&packet); servers.request_times[id] = time_get(); } void serverbrowse_update() { int64 timeout = time_freq(); int64 now = time_get(); int max_requests = 10; // timeout old requests while(info_request_begin < servers.num && info_request_begin < info_request_end) { if(now > servers.request_times[info_request_begin]+timeout) info_request_begin++; else break; } // send new requests while(info_request_end < servers.num && info_request_end-info_request_begin < max_requests) { serverbrowse_request(info_request_end); info_request_end++; } } void process_packet(NETPACKET *packet) { if(packet->client_id == -1) { // connectionlesss if(packet->data_size >= (int)sizeof(SERVERBROWSE_LIST) && memcmp(packet->data, SERVERBROWSE_LIST, sizeof(SERVERBROWSE_LIST)) == 0) { // server listing int size = packet->data_size-sizeof(SERVERBROWSE_LIST); mem_copy(servers.addresses, (char*)packet->data+sizeof(SERVERBROWSE_LIST), size); servers.num = size/sizeof(NETADDR4); info_request_begin = 0; info_request_end = 0; for(int i = 0; i < servers.num; i++) { servers.infos[i].num_players = 0; servers.infos[i].max_players = 0; servers.infos[i].latency = 999; sprintf(servers.infos[i].address, "%d.%d.%d.%d:%d", servers.addresses[i].ip[0], servers.addresses[i].ip[1], servers.addresses[i].ip[2], servers.addresses[i].ip[3], servers.addresses[i].port); sprintf(servers.infos[i].name, "%d.%d.%d.%d:%d", servers.addresses[i].ip[0], servers.addresses[i].ip[1], servers.addresses[i].ip[2], servers.addresses[i].ip[3], servers.addresses[i].port); } } if(packet->data_size >= (int)sizeof(SERVERBROWSE_INFO) && memcmp(packet->data, SERVERBROWSE_INFO, sizeof(SERVERBROWSE_INFO)) == 0) { // we got ze info data_unpacker unpacker; unpacker.reset((unsigned char*)packet->data+sizeof(SERVERBROWSE_INFO), packet->data_size-sizeof(SERVERBROWSE_INFO)); if(serverlist_lan) { if(servers.num != MAX_SERVERS) { int i = servers.num; strncpy(servers.infos[i].name, unpacker.get_string(), 128); strncpy(servers.infos[i].map, unpacker.get_string(), 128); servers.infos[i].max_players = unpacker.get_int(); servers.infos[i].num_players = unpacker.get_int(); servers.infos[i].latency = 0; sprintf(servers.infos[i].address, "%d.%d.%d.%d:%d", packet->address.ip[0], packet->address.ip[1], packet->address.ip[2], packet->address.ip[3], packet->address.port); dbg_msg("client", "got server info"); servers.num++; } } else { for(int i = 0; i < servers.num; i++) { if(net_addr4_cmp(&servers.addresses[i], &packet->address) == 0) { strncpy(servers.infos[i].name, unpacker.get_string(), 128); strncpy(servers.infos[i].map, unpacker.get_string(), 128); servers.infos[i].max_players = unpacker.get_int(); servers.infos[i].num_players = unpacker.get_int(); servers.infos[i].latency = ((time_get() - servers.request_times[i])*1000)/time_freq(); dbg_msg("client", "got server info"); break; } } } } } else { int sys; int msg = msg_unpack_start(packet->data, packet->data_size, &sys); if(sys) { // system message if(msg == NETMSG_MAP) { const char *map = msg_unpack_string(); dbg_msg("client/network", "connection accepted, map=%s", map); set_state(STATE_LOADING); if(map_load(map)) { modc_entergame(); send_entergame(); dbg_msg("client/network", "loading done"); // now we will wait for two snapshots // to finish the connection } else { error("failure to load map"); } } else if(msg == NETMSG_SNAP || msg == NETMSG_SNAPSMALL || msg == NETMSG_SNAPEMPTY) { //dbg_msg("client/network", "got snapshot"); int game_tick = msg_unpack_int(); int delta_tick = game_tick-msg_unpack_int(); int num_parts = 1; int part = 0; int part_size = 0; if(msg == NETMSG_SNAP) { num_parts = msg_unpack_int(); part = msg_unpack_int(); } if(msg != NETMSG_SNAPEMPTY) part_size = msg_unpack_int(); if(snapshot_part == part) { // TODO: clean this up abit const char *d = (const char *)msg_unpack_raw(part_size); mem_copy((char*)snapshots[SNAP_INCOMMING] + part*MAX_SNAPSHOT_PACKSIZE, d, part_size); snapshot_part++; if(snapshot_part == num_parts) { snapshot *tmp = snapshots[SNAP_PREV]; snapshots[SNAP_PREV] = snapshots[SNAP_CURRENT]; snapshots[SNAP_CURRENT] = tmp; current_tick = game_tick; // decompress snapshot void *deltadata = snapshot_empty_delta(); int deltasize = sizeof(int)*3; unsigned char tmpbuffer[MAX_SNAPSHOT_SIZE]; unsigned char tmpbuffer2[MAX_SNAPSHOT_SIZE]; if(part_size) { int compsize = zerobit_decompress(snapshots[SNAP_INCOMMING], part_size, tmpbuffer); int intsize = intpack_decompress(tmpbuffer, compsize, tmpbuffer2); deltadata = tmpbuffer2; deltasize = intsize; } // find snapshot that we should use as delta static snapshot emptysnap; emptysnap.data_size = 0; emptysnap.num_items = 0; snapshot *deltashot = &emptysnap; int deltashot_size; if(delta_tick >= 0) { void *delta_data; deltashot_size = snapshots_new.get(delta_tick, 0, &delta_data); if(deltashot_size >= 0) { deltashot = (snapshot *)delta_data; } else { // TODO: handle this dbg_msg("client", "error, couldn't find the delta snapshot"); } } int snapsize = snapshot_unpack_delta(deltashot, (snapshot*)snapshots[SNAP_CURRENT], deltadata, deltasize); //snapshot *shot = (snapshot *)snapshots[SNAP_CURRENT]; // purge old snapshots snapshots_new.purge_until(delta_tick); snapshots_new.purge_until(game_tick-50); // TODO: change this to server tickrate // add new snapshots_new.add(game_tick, time_get(), snapsize, snapshots[SNAP_CURRENT]); // apply snapshot, cycle pointers recived_snapshots++; snapshot_start_time = time_get(); // we got two snapshots until we see us self as connected if(recived_snapshots == 2) { local_start_time = time_get(); set_state(STATE_ONLINE); } if(recived_snapshots > 2) modc_newsnapshot(); snapshot_part = 0; // ack snapshot msg_pack_start_system(NETMSG_SNAPACK, 0); msg_pack_int(game_tick); msg_pack_end(); client_send_msg(); } } else { dbg_msg("client", "snapshot reset!"); snapshot_part = 0; } } } else { // game message modc_message(msg); } } } void pump_network() { net.update(); // check for errors if(get_state() != STATE_OFFLINE && net.state() == NETSTATE_OFFLINE) { // TODO: add message to the user there set_state(STATE_OFFLINE); } // if(get_state() == STATE_CONNECTING && net.state() == NETSTATE_ONLINE) { // we switched to online dbg_msg("client", "connected, sending info"); set_state(STATE_LOADING); send_info(); } // process packets NETPACKET packet; while(net.recv(&packet)) process_packet(&packet); } }; int editor_main(int argc, char **argv); int main(int argc, char **argv) { dbg_msg("client", "starting..."); config_reset(); config_load("teewars.cfg"); const char *direct_connect_server = 0x0; snd_set_master_volume(config.volume / 255.0f); bool editor = false; // init network, need to be done first so we can do lookups net_init(); // parse arguments for(int i = 1; i < argc; i++) { if(argv[i][0] == '-' && argv[i][1] == 'c' && argv[i][2] == 0 && argc - i > 1) { // -c SERVER:PORT i++; direct_connect_server = argv[i]; } else if(argv[i][0] == '-' && argv[i][1] == 'n' && argv[i][2] == 0 && argc - i > 1) { // -n NAME i++; config_set_player_name(&config, argv[i]); } else if(argv[i][0] == '-' && argv[i][1] == 'w' && argv[i][2] == 0) { // -w config.fullscreen = 0; } else if(argv[i][0] == '-' && argv[i][1] == 'e' && argv[i][2] == 0) { editor = true; } } if(editor) editor_main(argc, argv); else { // start the client client c; c.run(direct_connect_server); } return 0; }