#include #include #include #include #include #include #include #include #include #include #include #include "ui.h" #include #include #include using namespace baselib; // --- string handling (MOVE THESE!!) --- void snap_encode_string(const char *src, int *dst, int length, int max_length) { const unsigned char *p = (const unsigned char *)src; // handle whole int for(int i = 0; i < length/4; i++) { *dst = (p[0]<<24|p[1]<<16|p[2]<<8|p[3]); p += 4; dst++; } // take care of the left overs int left = length%4; if(left) { unsigned last = 0; switch(left) { case 3: last |= p[2]<<8; case 2: last |= p[1]<<16; case 1: last |= p[0]<<24; } *dst = last; } } void snap_decode_string(const int *src, char *dst, int max_length) { dbg_assert((max_length%4) == 0, "length must be power of 4"); for(int i = 0; i < max_length; i++) dst[0] = 0; for(int i = 0; i < max_length/4; i++) { dst[0] = (*src>>24)&0xff; dst[1] = (*src>>16)&0xff; dst[2] = (*src>>8)&0xff; dst[3] = (*src)&0xff; src++; dst+=4; } dst[-1] = 0; // make sure to zero terminate } // --- 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]; 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 *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 snap_intratick() { return (time_get() - snapshot_start_time)/(float)(time_freq()/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(); float frametime = 0.0001f; float client_frametime() { return frametime; } void unpack(const char *src, const char *fmt, ...) { } /*int modc_onmsg(int msg) { msg_get("iis") }*/ /* i = int (int i) s = string (const char *str) r = raw data (int size, void *data) */ /* class packet2 { private: // packet data struct header { unsigned msg; unsigned ack; unsigned seq; }; unsigned char packet_data[MAX_PACKET_SIZE]; unsigned char *current; enum { MAX_PACKET_SIZE = 1024, }; public: packet2() { current = packet_data; current += sizeof(header); } int pack(char *dst, const char *fmt, ...) { va_list arg_list; va_start(arg_list, fmt); while(*fmt) { if(*fmt == 's') { // pack string const char *s = va_arg(arg_list, const char*); *dst++ = 2; while(*s) { *dst = *s; dst++; s++; } *dst = 0; // null terminate dst++; fmt++; } else if(*fmt == 'i') { // pack int int i = va_arg(arg_list, int); *dst++ = 1; *dst++ = (i>>24)&0xff; *dst++ = (i>>16)&0xff; *dst++ = (i>>8)&0xff; *dst++ = i&0xff; fmt++; } else { dbg_break(); // error break; } } va_end(arg_list); } }; */ /* int msg_get(const char *fmt) { } int client_msg_send(int msg, const char *fmt, ...) int server_msg_send(int msg, const char *fmt, ...) { }*/ // --- client --- class client { public: socket_udp4 socket; connection conn; int64 reconnect_timer; int snapshot_part; int debug_font; // TODO: rfemove this line // data to hold three snapshots // previous, bool fullscreen; enum { STATE_OFFLINE, STATE_CONNECTING, STATE_LOADING, STATE_ONLINE, STATE_BROKEN, STATE_QUIT, }; int state; int get_state() { return state; } void set_state(int s) { dbg_msg("game", "state change. last=%d current=%d", state, s); state = s; } void set_fullscreen(bool flag) { fullscreen = flag; } void send_packet(packet *p) { conn.send(p); } void send_connect() { recived_snapshots = 0; /* pack(NETMSG_CLIENT_CONNECT, "sssss", TEEWARS_NETVERSION, name, "no clan", "password", "myskin"); */ packet p(NETMSG_CLIENT_CONNECT); p.write_str(TEEWARS_VERSION); // payload p.write_str(config.player_name); p.write_str("no clan"); p.write_str("password"); p.write_str("myskin"); send_packet(&p); } void send_done() { packet p(NETMSG_CLIENT_DONE); send_packet(&p); } 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() { /* pack(NETMSG_CLIENT_ERROR, "s", error); */ packet p(NETMSG_CLIENT_INPUT); p.write_int(input_data_size); for(int i = 0; i < input_data_size/4; i++) p.write_int(input_data[i]); send_packet(&p); } void disconnect() { send_error("disconnected"); set_state(STATE_OFFLINE); map_unload(); } void connect(netaddr4 *server_address) { conn.init(&socket, server_address); // start by sending connect send_connect(); set_state(STATE_CONNECTING); reconnect_timer = time_get()+time_freq(); } bool load_data() { debug_font = gfx_load_texture("data/debug_font.png"); return true; } void render() { gfx_clear(0.0f,0.0f,0.0f); // this should be moved around abit if(get_state() == STATE_ONLINE) { modc_render(); } else if (get_state() != STATE_CONNECTING && get_state() != STATE_LOADING) { netaddr4 server_address; int status = modmenu_render(&server_address); if (status == -1) set_state(STATE_QUIT); else if (status) connect(&server_address); } else if (get_state() == STATE_CONNECTING) { 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(netaddr4 *server_address) { snapshot_part = 0; // init graphics and sound if(!gfx_init(fullscreen)) 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 if(!socket.open(0)) { dbg_msg("network/client", "failed to open socket"); return; } // connect to the server if wanted if (server_address) connect(server_address); //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); // pump the network pump_network(); // 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()) { unsigned sent, recved; conn.counter_get(&sent, &recved); dbg_msg("client/report", "fps=%.02f", frames/(float)(reportinterval/time_freq())); frames = 0; reporttime += reportinterval; conn.counter_reset(); } 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 process_packet(packet *p) { if(p->version() != TEEWARS_NETVERSION) { error("wrong version"); } else if(p->msg() == NETMSG_SERVER_ACCEPT) { const char *map; map = p->read_str(); if(p->is_good()) { dbg_msg("client/network", "connection accepted, map=%s", map); set_state(STATE_LOADING); if(map_load(map)) { modc_entergame(); send_done(); 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(p->msg() == NETMSG_SERVER_SNAP) { //dbg_msg("client/network", "got snapshot"); int num_parts = p->read_int(); int part = p->read_int(); int part_size = p->read_int(); if(p->is_good()) { if(snapshot_part == part) { const char *d = p->read_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; // decompress snapshot lzw_decompress(snapshots[SNAP_INCOMMING], 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; } } else { dbg_msg("client", "snapshot reset!"); snapshot_part = 0; } } } else { dbg_msg("server/client", "unknown packet %x", p->msg()); } } void pump_network() { while(1) { packet p; netaddr4 from; int bytes = socket.recv(&from, p.data(), p.max_size()); if(bytes <= 0) break; process_packet(&p); } // if(get_state() == STATE_CONNECTING && time_get() > reconnect_timer) { send_connect(); reconnect_timer = time_get() + time_freq(); } } }; int main(int argc, char **argv) { dbg_msg("client", "starting..."); config_reset(); config_load("teewars.cfg"); netaddr4 server_address(127, 0, 0, 1, 8303); //const char *name = "nameless jerk"; bool connect_at_once = false; bool fullscreen = true; // 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 i++; if(net_host_lookup(argv[i], 8303, &server_address) != 0) dbg_msg("main", "could not find the address of %s, connecting to localhost", argv[i]); else connect_at_once = true; } 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 fullscreen = false; } } // start the server client c; c.set_fullscreen(fullscreen); c.run(connect_at_once ? &server_address : 0x0); return 0; }