diff options
| author | Magnus Auvinen <magnus.auvinen@gmail.com> | 2007-05-24 20:54:08 +0000 |
|---|---|---|
| committer | Magnus Auvinen <magnus.auvinen@gmail.com> | 2007-05-24 20:54:08 +0000 |
| commit | 82023866ab4c7483652e9d4605290e39ced3bec3 (patch) | |
| tree | cbff99cb472b4434d18e8e1fe3c556ca194096a6 /src/engine | |
| parent | 34e3df396630e9bb271ea8965869d23260900a7d (diff) | |
| download | zcatch-82023866ab4c7483652e9d4605290e39ced3bec3.tar.gz zcatch-82023866ab4c7483652e9d4605290e39ced3bec3.zip | |
large change. moved around all source. splitted server and client into separate files
Diffstat (limited to 'src/engine')
| -rw-r--r-- | src/engine/client/client.cpp | 712 | ||||
| -rw-r--r-- | src/engine/client/gfx.cpp | 583 | ||||
| -rw-r--r-- | src/engine/client/snd.cpp | 520 | ||||
| -rw-r--r-- | src/engine/client/ui.cpp | 115 | ||||
| -rw-r--r-- | src/engine/client/ui.h | 33 | ||||
| -rw-r--r-- | src/engine/datafile.cpp | 444 | ||||
| -rw-r--r-- | src/engine/datafile.h | 20 | ||||
| -rw-r--r-- | src/engine/interface.h | 711 | ||||
| -rw-r--r-- | src/engine/lzw.cpp | 223 | ||||
| -rw-r--r-- | src/engine/lzw.h | 2 | ||||
| -rw-r--r-- | src/engine/map.cpp | 48 | ||||
| -rw-r--r-- | src/engine/packet.h | 288 | ||||
| -rw-r--r-- | src/engine/server/server.cpp | 5 | ||||
| -rw-r--r-- | src/engine/snapshot.h | 19 | ||||
| -rw-r--r-- | src/engine/versions.h | 2 |
15 files changed, 3725 insertions, 0 deletions
diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp new file mode 100644 index 00000000..4aceaf0f --- /dev/null +++ b/src/engine/client/client.cpp @@ -0,0 +1,712 @@ +#include <baselib/system.h> +#include <baselib/keys.h> +#include <baselib/mouse.h> +#include <baselib/audio.h> +#include <baselib/stream/file.h> + +#include <string.h> +#include <stdarg.h> +#include <math.h> +#include <engine/interface.h> + +#include <engine/packet.h> +#include <engine/snapshot.h> +#include "ui.h" + +#include <engine/lzw.h> + +#include <engine/versions.h> + +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][keys::last]; +static int keyboard_current = 0; +static int keyboard_first = 1; + +void inp_mouse_relative(int *x, int *y) { 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_mouse_button_pressed(int button) { return mouse::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 < keys::last; i++) + keyboard_state[keyboard_current][i] = keys::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, + char name[MAX_NAME_LENGTH]; + + 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_name(const char *new_name) + { + mem_zero(name, MAX_NAME_LENGTH); + strncpy(name, new_name, MAX_NAME_LENGTH); + name[MAX_NAME_LENGTH-1] = 0; + } + + 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_NETVERSION); // payload + p.write_str(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_tga("data/debug_font.tga"); + 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, name, MAX_NAME_LENGTH); + + 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_tga("data/gui_tee.tga"); + connecting_texture = gfx_load_texture_tga("data/gui/connecting.tga"); + + 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; + + mouse::set_mode(mouse::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(keys::pressed(keys::f1)) + mouse::set_mode(mouse::mode_absolute); + if(keys::pressed(keys::f2)) + mouse::set_mode(mouse::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 (keys::pressed(keys::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->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..."); + 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++; + name = 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_name(name); + c.set_fullscreen(fullscreen); + c.run(connect_at_once ? &server_address : 0x0); + return 0; +} diff --git a/src/engine/client/gfx.cpp b/src/engine/client/gfx.cpp new file mode 100644 index 00000000..a824feac --- /dev/null +++ b/src/engine/client/gfx.cpp @@ -0,0 +1,583 @@ +#include <baselib/opengl.h> +#include <baselib/vmath.h> +#include <baselib/stream/file.h> + +#include <engine/interface.h> + +using namespace baselib; + +static opengl::context context; + +struct custom_vertex +{ + vec3 pos; + vec2 tex; + vec4 color; +}; + +const int vertexBufferSize = 2048; +//static custom_vertex vertices[4]; +static custom_vertex* g_pVertices = 0; +static int g_iVertexStart = 0; +static int g_iVertexEnd = 0; +static vec4 g_QuadColor[4]; +static vec2 g_QuadTexture[4]; + +static opengl::vertex_buffer vertex_buffer; +//static int screen_width = 800; +//static int screen_height = 600; +static int screen_width = 1024; +static int screen_height = 768; +static float rotation = 0; +static int quads_drawing = 0; + + +struct texture_holder +{ + opengl::texture tex; + int flags; + int next; +}; + +static const int MAX_TEXTURES = 128; + +static texture_holder textures[MAX_TEXTURES]; +static int first_free_texture; + +static const char null_texture_data[] = { + 0xff,0x00,0x00,0xff, 0xff,0x00,0x00,0xff, 0x00,0xff,0x00,0xff, 0x00,0xff,0x00,0xff, + 0xff,0x00,0x00,0xff, 0xff,0x00,0x00,0xff, 0x00,0xff,0x00,0xff, 0x00,0xff,0x00,0xff, + 0x00,0x00,0xff,0xff, 0x00,0x00,0xff,0xff, 0xff,0xff,0x00,0xff, 0xff,0xff,0x00,0xff, + 0x00,0x00,0xff,0xff, 0x00,0x00,0xff,0xff, 0xff,0xff,0x00,0xff, 0xff,0xff,0x00,0xff, +}; + +static void draw_quad(bool _bflush = false) +{ + if (!_bflush && ((g_iVertexEnd + 4) < vertexBufferSize)) + { + // Just add + g_iVertexEnd += 4; + } + else if (g_iVertexEnd) + { + if (!_bflush) + g_iVertexEnd += 4; + if(GLEW_VERSION_2_0) + { + // set the data + vertex_buffer.data(g_pVertices, vertexBufferSize * sizeof(custom_vertex), GL_DYNAMIC_DRAW); + opengl::stream_vertex(&vertex_buffer, 3, GL_FLOAT, sizeof(custom_vertex), 0); + opengl::stream_texcoord(&vertex_buffer, 0, 2, GL_FLOAT, + sizeof(custom_vertex), + sizeof(vec3)); + opengl::stream_color(&vertex_buffer, 4, GL_FLOAT, + sizeof(custom_vertex), + sizeof(vec3)+sizeof(vec2)); + opengl::draw_arrays(GL_QUADS, 0, g_iVertexEnd); + } + else + { + glVertexPointer(3, GL_FLOAT, + sizeof(custom_vertex), + (char*)g_pVertices); + glTexCoordPointer(2, GL_FLOAT, + sizeof(custom_vertex), + (char*)g_pVertices + sizeof(vec3)); + glColorPointer(4, GL_FLOAT, + sizeof(custom_vertex), + (char*)g_pVertices + sizeof(vec3) + sizeof(vec2)); + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glEnableClientState(GL_COLOR_ARRAY); + glDrawArrays(GL_QUADS, 0, g_iVertexEnd); + } + // Reset pointer + g_iVertexEnd = 0; + } +} + +bool gfx_init(bool fullscreen) +{ + if(!context.create(screen_width, screen_height, 24, 8, 16, 0, fullscreen?opengl::context::FLAG_FULLSCREEN:0)) + { + dbg_msg("game", "failed to create gl context"); + return false; + } + // Init vertices + if (g_pVertices) + mem_free(g_pVertices); + g_pVertices = (custom_vertex*)mem_alloc(sizeof(custom_vertex) * vertexBufferSize, 1); + g_iVertexStart = 0; + g_iVertexEnd = 0; + + context.set_title("---"); + + /* + dbg_msg("gfx", "OpenGL version %d.%d.%d", context.version_major(), + context.version_minor(), + context.version_rev());*/ + + gfx_mapscreen(0,0,screen_width, screen_height); + + // TODO: make wrappers for this + glEnable(GL_BLEND); + + // model + mat4 mat = mat4::identity; + opengl::matrix_modelview(&mat); + + // Set all z to -5.0f + for (int i = 0; i < vertexBufferSize; i++) + g_pVertices[i].pos.z = -5.0f; + + if(GLEW_VERSION_2_0) + { + // set the streams + vertex_buffer.data(g_pVertices, sizeof(vertexBufferSize), GL_DYNAMIC_DRAW); + opengl::stream_vertex(&vertex_buffer, 3, GL_FLOAT, sizeof(custom_vertex), 0); + opengl::stream_texcoord(&vertex_buffer, 0, 2, GL_FLOAT, + sizeof(custom_vertex), + sizeof(vec3)); + opengl::stream_color(&vertex_buffer, 4, GL_FLOAT, + sizeof(custom_vertex), + sizeof(vec3)+sizeof(vec2)); + } + + // init textures + first_free_texture = 0; + for(int i = 0; i < MAX_TEXTURES; i++) + textures[i].next = i+1; + textures[MAX_TEXTURES-1].next = -1; + + // create null texture, will get id=0 + gfx_load_texture_raw(4,4,null_texture_data); + + return true; +} + +int gfx_unload_texture(int index) +{ + textures[index].tex.clear(); + textures[index].next = first_free_texture; + first_free_texture = index; + return 0; +} + +void gfx_blend_normal() +{ + // TODO: wrapper for this + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); +} + +void gfx_blend_additive() +{ + // TODO: wrapper for this + glBlendFunc(GL_SRC_ALPHA, GL_ONE); +} + +int gfx_load_texture_raw(int w, int h, const void *data) +{ + // grab texture + int tex = first_free_texture; + first_free_texture = textures[tex].next; + textures[tex].next = -1; + + // set data and return + // TODO: should be RGBA, not BGRA + dbg_msg("gfx", "%d = %dx%d", tex, w, h); + textures[tex].tex.data2d(w, h, GL_RGBA, GL_BGRA, GL_UNSIGNED_BYTE, data); + return tex; +} + +// simple uncompressed RGBA loaders +int gfx_load_texture_tga(const char *filename) +{ + image_info img; + + if(gfx_load_tga(&img, filename)) + { + int id = gfx_load_texture_raw(img.width, img.height, img.data); + mem_free(img.data); + return id; + } + + return 0; +} + +int gfx_load_tga(image_info *img, const char *filename) +{ + // open file for reading + file_stream file; + if(!file.open_r(filename)) + { + dbg_msg("game/tga", "failed to open file. filename='%s'", filename); + return 0; + } + + // read header + unsigned char headers[18]; + file.read(headers, sizeof(headers)); + img->width = headers[12]+(headers[13]<<8); + img->height = headers[14]+(headers[15]<<8); + + bool flipx = (headers[17] >> 4) & 1; + bool flipy = !((headers[17] >> 5) & 1); + + (void)flipx; // TODO: make use of this flag + + if(headers[2] != 2) // needs to be uncompressed RGB + { + dbg_msg("game/tga", "tga not uncompressed rgb. filename='%s'", filename); + return 0; + } + + if(headers[16] != 32) // needs to be RGBA + { + dbg_msg("game/tga", "tga is 32bit. filename='%s'", filename); + return 0; + } + + // read data + int data_size = img->width*img->height*4; + img->data = mem_alloc(data_size, 1); + + if (flipy) + { + for (int i = 0; i < img->height; i++) + { + file.read((char *)img->data + (img->height-i-1)*img->width*4, img->width*4); + } + } + else + file.read(img->data, data_size); + file.close(); + + return 1; +} + +void gfx_shutdown() +{ + if (g_pVertices) + mem_free(g_pVertices); + context.destroy(); +} + +void gfx_swap() +{ + context.swap(); +} + +int gfx_screenwidth() +{ + return screen_width; +} + +int gfx_screenheight() +{ + return screen_height; +} + +void gfx_texture_set(int slot) +{ + dbg_assert(quads_drawing == 0, "called gfx_texture_set within quads_begin"); + if(slot == -1) + opengl::texture_disable(0); + else + opengl::texture_2d(0, &textures[slot].tex); +} + +void gfx_clear(float r, float g, float b) +{ + glClearColor(r,g,b,1.0f); + opengl::clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); +} + +void gfx_mapscreen(float tl_x, float tl_y, float br_x, float br_y) +{ + mat4 mat; + mat.ortho(tl_x, br_x, br_y, tl_y, 1.0f, 10.f); + opengl::matrix_projection(&mat); +} + +void gfx_setoffset(float x, float y) +{ + //const float scale = 0.75f; + const float scale = 1.0f; + mat4 mat = mat4::identity; + mat.m[0] = scale; + mat.m[5] = scale; + mat.m[10] = scale; + mat.m[12] = x*scale; + mat.m[13] = y*scale; + opengl::matrix_modelview(&mat); +} + +void gfx_quads_begin() +{ + dbg_assert(quads_drawing == 0, "called quads_begin twice"); + quads_drawing++; + + gfx_quads_setsubset(0,0,1,1); + gfx_quads_setrotation(0); + gfx_quads_setcolor(1,1,1,1); +} + +void gfx_quads_end() +{ + dbg_assert(quads_drawing == 1, "called quads_end without quads_begin"); + draw_quad(true); + quads_drawing--; +} + + +void gfx_quads_setrotation(float angle) +{ + dbg_assert(quads_drawing == 1, "called gfx_quads_setrotation without quads_begin"); + rotation = angle; +} + +void gfx_quads_setcolorvertex(int i, float r, float g, float b, float a) +{ + dbg_assert(quads_drawing == 1, "called gfx_quads_setcolorvertex without quads_begin"); + g_QuadColor[i].r = r; + g_QuadColor[i].g = g; + g_QuadColor[i].b = b; + g_QuadColor[i].a = a; +} + +void gfx_quads_setcolor(float r, float g, float b, float a) +{ + dbg_assert(quads_drawing == 1, "called gfx_quads_setcolor without quads_begin"); + g_QuadColor[0] = vec4(r,g,b,a); + g_QuadColor[1] = vec4(r,g,b,a); + g_QuadColor[2] = vec4(r,g,b,a); + g_QuadColor[3] = vec4(r,g,b,a); + /*gfx_quads_setcolorvertex(0,r,g,b,a); + gfx_quads_setcolorvertex(1,r,g,b,a); + gfx_quads_setcolorvertex(2,r,g,b,a); + gfx_quads_setcolorvertex(3,r,g,b,a);*/ +} + +void gfx_quads_setsubset(float tl_u, float tl_v, float br_u, float br_v) +{ + dbg_assert(quads_drawing == 1, "called gfx_quads_setsubset without quads_begin"); + + g_QuadTexture[0].x = tl_u; + g_QuadTexture[0].y = tl_v; + //g_pVertices[g_iVertexEnd].tex.u = tl_u; + //g_pVertices[g_iVertexEnd].tex.v = tl_v; + + g_QuadTexture[1].x = br_u; + g_QuadTexture[1].y = tl_v; + //g_pVertices[g_iVertexEnd + 2].tex.u = br_u; + //g_pVertices[g_iVertexEnd + 2].tex.v = tl_v; + + g_QuadTexture[2].x = br_u; + g_QuadTexture[2].y = br_v; + //g_pVertices[g_iVertexEnd + 1].tex.u = tl_u; + //g_pVertices[g_iVertexEnd + 1].tex.v = br_v; + + g_QuadTexture[3].x = tl_u; + g_QuadTexture[3].y = br_v; + //g_pVertices[g_iVertexEnd + 3].tex.u = br_u; + //g_pVertices[g_iVertexEnd + 3].tex.v = br_v; +} + +static void rotate(vec3 ¢er, vec3 &point) +{ + vec3 p = point-center; + point.x = p.x * cosf(rotation) - p.y * sinf(rotation) + center.x; + point.y = p.x * sinf(rotation) + p.y * cosf(rotation) + center.y; +} + +void gfx_quads_draw(float x, float y, float w, float h) +{ + gfx_quads_drawTL(x-w/2, y-h/2,w,h); +} + +void gfx_quads_drawTL(float x, float y, float width, float height) +{ + dbg_assert(quads_drawing == 1, "called quads_draw without quads_begin"); + + vec3 center; + center.x = x + width/2; + center.y = y + height/2; + center.z = 0; + + g_pVertices[g_iVertexEnd].pos.x = x; + g_pVertices[g_iVertexEnd].pos.y = y; + g_pVertices[g_iVertexEnd].tex.u = g_QuadTexture[0].x; + g_pVertices[g_iVertexEnd].tex.v = g_QuadTexture[0].y; + g_pVertices[g_iVertexEnd].color = g_QuadColor[0]; + rotate(center, g_pVertices[g_iVertexEnd].pos); + + g_pVertices[g_iVertexEnd + 1].pos.x = x+width; + g_pVertices[g_iVertexEnd + 1].pos.y = y; + g_pVertices[g_iVertexEnd + 1].tex.u = g_QuadTexture[1].x; + g_pVertices[g_iVertexEnd + 1].tex.v = g_QuadTexture[1].y; + g_pVertices[g_iVertexEnd + 1].color = g_QuadColor[1]; + rotate(center, g_pVertices[g_iVertexEnd + 1].pos); + + g_pVertices[g_iVertexEnd + 2].pos.x = x + width; + g_pVertices[g_iVertexEnd + 2].pos.y = y+height; + g_pVertices[g_iVertexEnd + 2].tex.u = g_QuadTexture[2].x; + g_pVertices[g_iVertexEnd + 2].tex.v = g_QuadTexture[2].y; + g_pVertices[g_iVertexEnd + 2].color = g_QuadColor[2]; + rotate(center, g_pVertices[g_iVertexEnd + 2].pos); + + g_pVertices[g_iVertexEnd + 3].pos.x = x; + g_pVertices[g_iVertexEnd + 3].pos.y = y+height; + g_pVertices[g_iVertexEnd + 3].tex.u = g_QuadTexture[3].x; + g_pVertices[g_iVertexEnd + 3].tex.v = g_QuadTexture[3].y; + g_pVertices[g_iVertexEnd + 3].color = g_QuadColor[3]; + rotate(center, g_pVertices[g_iVertexEnd + 3].pos); + + draw_quad(); +} + +void gfx_quads_draw_freeform( + float x0, float y0, + float x1, float y1, + float x2, float y2, + float x3, float y3) +{ + dbg_assert(quads_drawing == 1, "called quads_draw_freeform without quads_begin"); + + g_pVertices[g_iVertexEnd].pos.x = x0; + g_pVertices[g_iVertexEnd].pos.y = y0; + g_pVertices[g_iVertexEnd].tex.u = g_QuadTexture[0].x; + g_pVertices[g_iVertexEnd].tex.v = g_QuadTexture[0].y; + g_pVertices[g_iVertexEnd].color = g_QuadColor[0]; + + g_pVertices[g_iVertexEnd + 1].pos.x = x1; + g_pVertices[g_iVertexEnd + 1].pos.y = y1; + g_pVertices[g_iVertexEnd + 1].tex.u = g_QuadTexture[1].x; + g_pVertices[g_iVertexEnd + 1].tex.v = g_QuadTexture[1].y; + g_pVertices[g_iVertexEnd + 1].color = g_QuadColor[1]; + + g_pVertices[g_iVertexEnd + 2].pos.x = x3; + g_pVertices[g_iVertexEnd + 2].pos.y = y3; + g_pVertices[g_iVertexEnd + 2].tex.u = g_QuadTexture[2].x; + g_pVertices[g_iVertexEnd + 2].tex.v = g_QuadTexture[2].y; + g_pVertices[g_iVertexEnd + 2].color = g_QuadColor[2]; + + g_pVertices[g_iVertexEnd + 3].pos.x = x2; + g_pVertices[g_iVertexEnd + 3].pos.y = y2; + g_pVertices[g_iVertexEnd + 3].tex.u = g_QuadTexture[3].x; + g_pVertices[g_iVertexEnd + 3].tex.v = g_QuadTexture[3].y; + g_pVertices[g_iVertexEnd + 3].color = g_QuadColor[3]; + + draw_quad(); +} + +void gfx_quads_text(float x, float y, float size, const char *text) +{ + gfx_quads_begin(); + while(*text) + { + char c = *text; + text++; + + gfx_quads_setsubset( + (c%16)/16.0f, + (c/16)/16.0f, + (c%16)/16.0f+1.0f/16.0f, + (c/16)/16.0f+1.0f/16.0f); + + gfx_quads_drawTL(x,y,size,size); + x += size/2; + } + + gfx_quads_end(); +} + +struct pretty_font +{ + float m_CharStartTable[256]; + float m_CharEndTable[256]; + int font_texture; +}; + +pretty_font default_font = +{ +{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0.421875, 0.359375, 0.265625, 0.25, 0.1875, 0.25, 0.4375, 0.390625, 0.390625, 0.34375, 0.28125, 0.421875, 0.390625, 0.4375, 0.203125, + 0.265625, 0.28125, 0.28125, 0.265625, 0.25, 0.28125, 0.28125, 0.265625, 0.28125, 0.265625, 0.4375, 0.421875, 0.3125, 0.28125, 0.3125, 0.3125, + 0.25, 0.234375, 0.28125, 0.265625, 0.265625, 0.296875, 0.3125, 0.25, 0.25, 0.421875, 0.28125, 0.265625, 0.328125, 0.171875, 0.234375, 0.25, + 0.28125, 0.234375, 0.265625, 0.265625, 0.28125, 0.265625, 0.234375, 0.09375, 0.234375, 0.234375, 0.265625, 0.390625, 0.203125, 0.390625, 0.296875, 0.28125, + 0.375, 0.3125, 0.3125, 0.3125, 0.296875, 0.3125, 0.359375, 0.296875, 0.3125, 0.4375, 0.390625, 0.328125, 0.4375, 0.203125, 0.3125, 0.296875, + 0.3125, 0.296875, 0.359375, 0.3125, 0.328125, 0.3125, 0.296875, 0.203125, 0.296875, 0.296875, 0.328125, 0.375, 0.421875, 0.375, 0.28125, 0.3125, + 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, + 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, + 0, 0.421875, 0.3125, 0.265625, 0.25, 0.25, 0.421875, 0.265625, 0.375, 0.21875, 0.375, 0.328125, 0.3125, 0, 0.21875, 0.28125, + 0.359375, 0.28125, 0.34375, 0.34375, 0.421875, 0.3125, 0.265625, 0.421875, 0.421875, 0.34375, 0.375, 0.328125, 0.125, 0.125, 0.125, 0.296875, + 0.234375, 0.234375, 0.234375, 0.234375, 0.234375, 0.234375, 0.109375, 0.265625, 0.296875, 0.296875, 0.296875, 0.296875, 0.375, 0.421875, 0.359375, 0.390625, + 0.21875, 0.234375, 0.25, 0.25, 0.25, 0.25, 0.25, 0.296875, 0.21875, 0.265625, 0.265625, 0.265625, 0.265625, 0.234375, 0.28125, 0.3125, + 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.1875, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.375, 0.421875, 0.359375, 0.390625, + 0.3125, 0.3125, 0.296875, 0.296875, 0.296875, 0.296875, 0.296875, 0.28125, 0.28125, 0.3125, 0.3125, 0.3125, 0.3125, 0.296875, 0.3125, 0.296875, +}, +{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0.2, 0.5625, 0.625, 0.71875, 0.734375, 0.796875, 0.765625, 0.546875, 0.59375, 0.59375, 0.65625, 0.703125, 0.546875, 0.59375, 0.5625, 0.6875, + 0.71875, 0.609375, 0.703125, 0.703125, 0.71875, 0.703125, 0.703125, 0.6875, 0.703125, 0.703125, 0.5625, 0.546875, 0.671875, 0.703125, 0.671875, 0.671875, + 0.734375, 0.75, 0.734375, 0.734375, 0.734375, 0.6875, 0.6875, 0.734375, 0.71875, 0.5625, 0.65625, 0.765625, 0.703125, 0.8125, 0.75, 0.734375, + 0.734375, 0.765625, 0.71875, 0.71875, 0.703125, 0.71875, 0.75, 0.890625, 0.75, 0.75, 0.71875, 0.59375, 0.6875, 0.59375, 0.6875, 0.703125, + 0.5625, 0.671875, 0.6875, 0.671875, 0.671875, 0.671875, 0.625, 0.671875, 0.671875, 0.5625, 0.546875, 0.703125, 0.5625, 0.78125, 0.671875, 0.671875, + 0.6875, 0.671875, 0.65625, 0.671875, 0.65625, 0.671875, 0.6875, 0.78125, 0.6875, 0.671875, 0.65625, 0.609375, 0.546875, 0.609375, 0.703125, 0.671875, + 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, + 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, + 0, 0.5625, 0.671875, 0.734375, 0.734375, 0.734375, 0.546875, 0.71875, 0.609375, 0.765625, 0.609375, 0.65625, 0.671875, 0, 0.765625, 0.703125, + 0.625, 0.703125, 0.640625, 0.640625, 0.609375, 0.671875, 0.703125, 0.546875, 0.5625, 0.578125, 0.609375, 0.65625, 0.859375, 0.859375, 0.859375, 0.671875, + 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.84375, 0.734375, 0.6875, 0.6875, 0.6875, 0.6875, 0.5625, 0.609375, 0.640625, 0.59375, + 0.734375, 0.75, 0.734375, 0.734375, 0.734375, 0.734375, 0.734375, 0.6875, 0.765625, 0.71875, 0.71875, 0.71875, 0.71875, 0.75, 0.734375, 0.6875, + 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.796875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.5625, 0.609375, 0.625, 0.59375, + 0.6875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.703125, 0.703125, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.6875, 0.671875, +}, +0 +}; + +pretty_font *current_font = &default_font; + +void gfx_pretty_text(float x, float y, float size, const char *text) +{ + const float spacing = 0.05f; + + gfx_quads_begin(); + + while (*text) + { + const int c = *text; + const float width = current_font->m_CharEndTable[c] - current_font->m_CharStartTable[c]; + + text++; + + gfx_quads_setsubset( + (c%16)/16.0f + current_font->m_CharStartTable[c]/16.0f, // startx + (c/16)/16.0f, // starty + (c%16)/16.0f + current_font->m_CharEndTable[c]/16.0f + 0.001, // endx + (c/16)/16.0f+1.0f/16.0f); // endy + + gfx_quads_drawTL(x, y, width * size, size); + + x += (width + spacing) * size; + } + + gfx_quads_end(); +} + +float gfx_pretty_text_width(float size, const char *text) +{ + const float spacing = 0.05f; + float width = 0.0f; + + while (*text) + { + const int c = *text++; + width += size * (current_font->m_CharEndTable[c] - current_font->m_CharStartTable[c] + spacing); + } + + return width; +} diff --git a/src/engine/client/snd.cpp b/src/engine/client/snd.cpp new file mode 100644 index 00000000..dd6baa9a --- /dev/null +++ b/src/engine/client/snd.cpp @@ -0,0 +1,520 @@ +#include <baselib/system.h> +#include <baselib/audio.h> +#include <baselib/stream/file.h> + +#include <engine/interface.h> + +using namespace baselib; + +static const int NUM_FRAMES_STOP = 512; +static const float NUM_FRAMES_STOP_INV = 1.0f/(float)NUM_FRAMES_STOP; +static const int NUM_FRAMES_LERP = 512; +static const float NUM_FRAMES_LERP_INV = 1.0f/(float)NUM_FRAMES_LERP; + +static const float GLOBAL_VOLUME_SCALE = 0.75f; + +static const int64 GLOBAL_SOUND_DELAY = 1000; + +// --- sound --- +class sound_data +{ +public: + short *data; + int num_samples; + int rate; + int channels; + int sustain_start; + int sustain_end; + int64 last_played; +}; + +inline short clamp(int i) +{ + if(i > 0x7fff) + return 0x7fff; + if(i < -0x7fff) + return -0x7fff; + return i; +} + +class mixer : public audio_stream +{ +public: + class channel + { + public: + channel() + { data = 0; lerp = -1; stop = -1; } + + sound_data *data; + int tick; + int loop; + float pan; + float vol; + float old_vol; + float new_vol; + int lerp; + int stop; + }; + + enum + { + MAX_CHANNELS=8, + }; + + channel channels[MAX_CHANNELS]; + + virtual void fill(void *output, unsigned long frames) + { + //dbg_msg("snd", "mixing!"); + + short *out = (short*)output; + bool clamp_flag = false; + + int active_channels = 0; + for(unsigned long i = 0; i < frames; i++) + { + int left = 0; + int right = 0; + + for(int c = 0; c < MAX_CHANNELS; c++) + { + if(channels[c].data) + { + if(channels[c].data->channels == 1) + { + left += (1.0f-(channels[c].pan+1.0f)*0.5f) * channels[c].vol * channels[c].data->data[channels[c].tick]; + right += (channels[c].pan+1.0f)*0.5f * channels[c].vol * channels[c].data->data[channels[c].tick]; + channels[c].tick++; + } + else + { + float pl = channels[c].pan<0.0f?-channels[c].pan:1.0f; + float pr = channels[c].pan>0.0f?1.0f-channels[c].pan:1.0f; + left += pl*channels[c].vol * channels[c].data->data[channels[c].tick]; + right += pr*channels[c].vol * channels[c].data->data[channels[c].tick + 1]; + channels[c].tick += 2; + } + + if(channels[c].loop) + { + if(channels[c].data->sustain_start >= 0 && channels[c].tick >= channels[c].data->sustain_end) + channels[c].tick = channels[c].data->sustain_start; + else if(channels[c].tick > channels[c].data->num_samples) + channels[c].tick = 0; + } + else if(channels[c].tick > channels[c].data->num_samples) + channels[c].data = 0; + + if(channels[c].stop == 0) + { + channels[c].stop = -1; + channels[c].data = 0; + } + else if(channels[c].stop > 0) + { + channels[c].vol = channels[c].old_vol * (float)channels[c].stop * NUM_FRAMES_STOP_INV; + channels[c].stop--; + } + if(channels[c].lerp > 0) + { + channels[c].vol = (1.0f - (float)channels[c].lerp * NUM_FRAMES_LERP_INV) * channels[c].new_vol + + (float)channels[c].lerp * NUM_FRAMES_LERP_INV * channels[c].old_vol; + channels[c].lerp--; + } + active_channels++; + } + } + + // TODO: remove these + + *out = clamp(left); // left + if(*out != left) clamp_flag = true; + out++; + *out = clamp(right); // right + if(*out != right) clamp_flag = true; + out++; + } + + if(clamp_flag) + dbg_msg("snd", "CLAMPED!"); + } + + int play(sound_data *sound, unsigned loop, float vol, float pan) + { + if(time_get() - sound->last_played < GLOBAL_SOUND_DELAY) + return -1; + + for(int c = 0; c < MAX_CHANNELS; c++) + { + if(channels[c].data == 0) + { + channels[c].data = sound; + channels[c].tick = 0; + channels[c].loop = loop; + channels[c].vol = vol * GLOBAL_VOLUME_SCALE; + channels[c].pan = pan; + sound->last_played = time_get(); + return c; + } + } + + return -1; + } + + void stop(int id) + { + dbg_assert(id >= 0 && id < MAX_CHANNELS, "id out of bounds"); + channels[id].old_vol = channels[id].vol; + channels[id].stop = NUM_FRAMES_STOP; + } + + void set_vol(int id, float vol) + { + dbg_assert(id >= 0 && id < MAX_CHANNELS, "id out of bounds"); + channels[id].new_vol = vol * GLOBAL_VOLUME_SCALE; + channels[id].old_vol = channels[id].vol; + channels[id].lerp = NUM_FRAMES_LERP; + } +}; + +static mixer mixer; +//static sound_data test_sound; + +/* +extern "C" +{ +#include "wavpack/wavpack.h" +}*/ + +/* +static file_stream *read_func_filestream; +static int32_t read_func(void *buff, int32_t bcount) +{ + return read_func_filestream->read(buff, bcount); +} +static uchar *format_samples(int bps, uchar *dst, int32_t *src, uint32_t samcnt) +{ + int32_t temp; + + switch (bps) { + + case 1: + while (samcnt--) + *dst++ = *src++ + 128; + + break; + + case 2: + while (samcnt--) { + *dst++ = (uchar)(temp = *src++); + *dst++ = (uchar)(temp >> 8); + } + + break; + + case 3: + while (samcnt--) { + *dst++ = (uchar)(temp = *src++); + *dst++ = (uchar)(temp >> 8); + *dst++ = (uchar)(temp >> 16); + } + + break; + + case 4: + while (samcnt--) { + *dst++ = (uchar)(temp = *src++); + *dst++ = (uchar)(temp >> 8); + *dst++ = (uchar)(temp >> 16); + *dst++ = (uchar)(temp >> 24); + } + + break; + } + + return dst; +}*/ + +/* +struct sound_holder +{ + sound_data sound; + int next; +}; + +static const int MAX_SOUNDS = 256; +static sound_holder sounds[MAX_SOUNDS]; +static int first_free_sound; + +bool snd_load_wv(const char *filename, sound_data *snd) +{ + // open file + file_stream file; + if(!file.open_r(filename)) + { + dbg_msg("sound/wv", "failed to open file. filename='%s'", filename); + return false; + } + read_func_filestream = &file; + + // get info + WavpackContext *wpc; + char error[128]; + wpc = WavpackOpenFileInput(read_func, error); + if(!wpc) + { + dbg_msg("sound/wv", "failed to open file. err=%s filename='%s'", error, filename); + return false; + } + + + snd->num_samples = WavpackGetNumSamples(wpc); + int bps = WavpackGetBytesPerSample(wpc); + int channels = WavpackGetReducedChannels(wpc); + snd->rate = WavpackGetSampleRate(wpc); + int bits = WavpackGetBitsPerSample(wpc); + + (void)bps; + (void)channels; + (void)bits; + + // decompress + int datasize = snd->num_samples*2; + snd->data = (short*)mem_alloc(datasize, 1); + int totalsamples = 0; + while(1) + { + int buffer[1024*4]; + int samples_unpacked = WavpackUnpackSamples(wpc, buffer, 1024*4); + totalsamples += samples_unpacked; + + if(samples_unpacked) + { + // convert + } + } + + if(snd->num_samples != totalsamples) + { + dbg_msg("sound/wv", "wrong amount of samples. filename='%s'", filename); + mem_free(snd->data); + return false;; + } + + return false; +}*/ + +struct sound_holder +{ + sound_data sound; + int next; +}; + +static const int MAX_SOUNDS = 1024; +static sound_holder sounds[MAX_SOUNDS]; +static int first_free_sound; + +bool snd_init() +{ + first_free_sound = 0; + for(int i = 0; i < MAX_SOUNDS; i++) + sounds[i].next = i+1; + sounds[MAX_SOUNDS-1].next = -1; + return mixer.create(); +} + +bool snd_shutdown() +{ + mixer.destroy(); + return true; +} + +static int snd_alloc_sound() +{ + if(first_free_sound < 0) + return -1; + int id = first_free_sound; + first_free_sound = sounds[id].next; + sounds[id].next = -1; + return id; +} + +int snd_load_wav(const char *filename) +{ + sound_data snd; + + // open file for reading + file_stream file; + if(!file.open_r(filename)) + { + dbg_msg("sound/wav", "failed to open file. filename='%s'", filename); + return -1; + } + + int id = -1; + int state = 0; + while(1) + { + // read chunk header + unsigned char head[8]; + if(file.read(head, sizeof(head)) != 8) + { + break; + } + + int chunk_size = head[4] | (head[5]<<8) | (head[6]<<16) | (head[7]<<24); + head[4] = 0; + + if(state == 0) + { + // read the riff and wave headers + if(head[0] != 'R' || head[1] != 'I' || head[2] != 'F' || head[3] != 'F') + { + dbg_msg("sound/wav", "not a RIFF file. filename='%s'", filename); + return -1; + } + + unsigned char type[4]; + file.read(type, 4); + + if(type[0] != 'W' || type[1] != 'A' || type[2] != 'V' || type[3] != 'E') + { + dbg_msg("sound/wav", "RIFF file is not a WAVE. filename='%s'", filename); + return -1; + } + + state++; + } + else if(state == 1) + { + // read the format chunk + if(head[0] == 'f' && head[1] == 'm' && head[2] == 't' && head[3] == ' ') + { + unsigned char fmt[16]; + if(file.read(fmt, sizeof(fmt)) != sizeof(fmt)) + { + dbg_msg("sound/wav", "failed to read format. filename='%s'", filename); + return -1; + } + + // decode format + int compression_code = fmt[0] | (fmt[1]<<8); + snd.channels = fmt[2] | (fmt[3]<<8); + snd.rate = fmt[4] | (fmt[5]<<8) | (fmt[6]<<16) | (fmt[7]<<24); + + if(compression_code != 1) + { + dbg_msg("sound/wav", "file is not uncompressed. filename='%s'", filename); + return -1; + } + + if(snd.channels > 2) + { + dbg_msg("sound/wav", "file is not mono or stereo. filename='%s'", filename); + return -1; + } + + if(snd.rate != 44100) + { + dbg_msg("sound/wav", "file is %d Hz, not 44100 Hz. filename='%s'", snd.rate, filename); + return -1; + } + + int bps = fmt[14] | (fmt[15]<<8); + if(bps != 16) + { + dbg_msg("sound/wav", "bps is %d, not 16, filname='%s'", bps, filename); + return -1; + } + + // skip extra bytes (not used for uncompressed) + //int extra_bytes = fmt[14] | (fmt[15]<<8); + //dbg_msg("sound/wav", "%d", extra_bytes); + //file.skip(extra_bytes); + + // next state + state++; + } + else + file.skip(chunk_size); + } + else if(state == 2) + { + // read the data + if(head[0] == 'd' && head[1] == 'a' && head[2] == 't' && head[3] == 'a') + { + snd.data = (short*)mem_alloc(chunk_size, 1); + file.read(snd.data, chunk_size); + snd.num_samples = chunk_size/(2); + snd.sustain_start = -1; + snd.sustain_end = -1; + snd.last_played = 0; + id = snd_alloc_sound(); + sounds[id].sound = snd; + state++; + } + else + file.skip(chunk_size); + } + else if(state == 3) + { + if(head[0] == 's' && head[1] == 'm' && head[2] == 'p' && head[3] == 'l') + { + int smpl[9]; + int loop[6]; + + file.read(smpl, sizeof(smpl)); + + if(smpl[7] > 0) + { + file.read(loop, sizeof(loop)); + sounds[id].sound.sustain_start = loop[2] * sounds[id].sound.channels; + sounds[id].sound.sustain_end = loop[3] * sounds[id].sound.channels; + } + + if(smpl[7] > 1) + file.skip((smpl[7]-1) * sizeof(loop)); + + file.skip(smpl[8]); + state++; + } + else + file.skip(chunk_size); + } + else + file.skip(chunk_size); + } + + if(id >= 0) + dbg_msg("sound/wav", "loaded %s", filename); + else + dbg_msg("sound/wav", "failed to load %s", filename); + + return id; +} + +int snd_play(int id, int loop, float vol, float pan) +{ + if(id < 0) + { + dbg_msg("snd", "bad sound id"); + return -1; + } + + dbg_assert(sounds[id].sound.data != 0, "null sound"); + dbg_assert(sounds[id].next == -1, "sound isn't allocated"); + return mixer.play(&sounds[id].sound, loop, vol, pan); +} + +void snd_stop(int id) +{ + if(id >= 0) + mixer.stop(id); +} + +void snd_set_vol(int id, float vol) +{ + if(id >= 0) + mixer.set_vol(id, vol); +} diff --git a/src/engine/client/ui.cpp b/src/engine/client/ui.cpp new file mode 100644 index 00000000..3353feca --- /dev/null +++ b/src/engine/client/ui.cpp @@ -0,0 +1,115 @@ +#include <engine/interface.h> +#include "ui.h" + +/******************************************************** + UI +*********************************************************/ +//static unsigned mouse_buttons_last = 0; + +struct pretty_font +{ + char m_CharStartTable[256]; + char m_CharEndTable[256]; + int font_texture; +}; + +extern pretty_font *current_font; +void gfx_pretty_text(float x, float y, float size, const char *text); + + +static void *hot_item = 0; +static void *active_item = 0; +static void *becomming_hot_item = 0; +static float mouse_x, mouse_y; // in gui space +static float mouse_wx, mouse_wy; // in world space +static unsigned mouse_buttons = 0; + +float ui_mouse_x() { return mouse_x; } +float ui_mouse_y() { return mouse_y; } +float ui_mouse_world_x() { return mouse_wx; } +float ui_mouse_world_y() { return mouse_wy; } +int ui_mouse_button(int index) { return (mouse_buttons>>index)&1; } + +void ui_set_hot_item(void *id) { becomming_hot_item = id; } +void ui_set_active_item(void *id) { active_item = id; } +void *ui_hot_item() { return hot_item; } +void *ui_active_item() { return active_item; } + +int ui_update(float mx, float my, float mwx, float mwy, int buttons) +{ + //mouse_buttons_last = mouse_buttons; + mouse_x = mx; + mouse_y = my; + mouse_wx = mwx; + mouse_wy = mwy; + mouse_buttons = buttons; + hot_item = becomming_hot_item; + becomming_hot_item = 0; + return 0; +} + +/* +static int ui_mouse_button_released(int index) +{ + return ((mouse_buttons_last>>index)&1) && !(); +}*/ + +int ui_mouse_inside(float x, float y, float w, float h) +{ + if(mouse_x >= x && mouse_x <= x+w && mouse_y >= y && mouse_y <= y+h) + return 1; + return 0; +} + +void ui_do_image(int texture, float x, float y, float w, float h) +{ + gfx_blend_normal(); + gfx_texture_set(texture); + gfx_quads_begin(); + gfx_quads_setcolor(1,1,1,1); + gfx_quads_setsubset( + 0.0f, // startx + 0.0f, // starty + 1.0f, // endx + 1.0f); // endy + gfx_quads_drawTL(x,y,w,h); + gfx_quads_end(); +} + +void ui_do_label(float x, float y, char *text) +{ + gfx_blend_normal(); + gfx_texture_set(current_font->font_texture); + gfx_pretty_text(x, y, 18.f, text); +} + +int ui_do_button(void *id, const char *text, int checked, float x, float y, float w, float h, draw_button_callback draw_func, void *extra) +{ + // logic + int r = 0; + int inside = ui_mouse_inside(x,y,w,h); + + if(inside) + { + ui_set_hot_item(id); + + if(ui_mouse_button(0)) + ui_set_active_item(id); + } + + if(ui_active_item() == id && ui_hot_item() == id && !ui_mouse_button(0)) + { + ui_set_active_item(0); + r = 1; + } + + draw_func(id, text, checked, x, y, w, h, extra); + + return r; +} + +int ui_do_button(void *id, const char *text, int checked, float x, float y, float w, float h, draw_button_callback draw_func) +{ + return ui_do_button(id, text, checked, x, y, w, h, draw_func, 0x0); +} + diff --git a/src/engine/client/ui.h b/src/engine/client/ui.h new file mode 100644 index 00000000..1a420906 --- /dev/null +++ b/src/engine/client/ui.h @@ -0,0 +1,33 @@ +#ifndef _UI_H +#define _UI_H +/* +extern void *hot_item; +extern void *active_item; +extern void *becomming_hot_item; +extern float mouse_x, mouse_y; // in gui space +extern float mouse_wx, mouse_wy; // in world space +extern unsigned mouse_buttons;*/ + +int ui_update(float mx, float my, float mwx, float mwy, int buttons); + +float ui_mouse_x(); +float ui_mouse_y(); +float ui_mouse_world_x(); +float ui_mouse_world_y(); +int ui_mouse_button(int index); + +void ui_set_hot_item(void *id); +void ui_set_active_item(void *id); +void *ui_hot_item(); +void *ui_active_item(); + +int ui_mouse_inside(float x, float y, float w, float h); + +typedef void (*draw_button_callback)(void *id, const char *text, int checked, float x, float y, float w, float h, void *extra); + +void ui_do_image(int texture, float x, float y, float w, float h); +void ui_do_label(float x, float y, char *text); +int ui_do_button(void *id, const char *text, int checked, float x, float y, float w, float h, draw_button_callback draw_func, void *extra); +int ui_do_button(void *id, const char *text, int checked, float x, float y, float w, float h, draw_button_callback draw_func); + +#endif diff --git a/src/engine/datafile.cpp b/src/engine/datafile.cpp new file mode 100644 index 00000000..789aa722 --- /dev/null +++ b/src/engine/datafile.cpp @@ -0,0 +1,444 @@ +#include <baselib/system.h> +#include <baselib/stream/file.h> + +#include "datafile.h" + +static const int DEBUG=0; + +struct item_type +{ + int type; + int start; + int num; +}; + +struct item +{ + int type_and_id; + int size; +}; + +struct datafile_header +{ + int id; + int version; + int size; + int swaplen; + int num_item_types; + int num_items; + int num_raw_data; + int item_size; + int data_size; +}; + +struct datafile_data +{ + int num_item_types; + int num_items; + int num_raw_data; + int item_size; + int data_size; + char start[4]; +}; + +struct datafile_info +{ + item_type *item_types; + int *item_offsets; + int *data_offsets; + + char *item_start; + char *data_start; +}; + +struct datafile +{ + datafile_info info; + datafile_data data; +}; + +datafile *datafile_load(const char *filename) +{ + dbg_msg("datafile", "datafile loading. filename='%s'", filename); + + baselib::file_stream file; + if(!file.open_r(filename)) + return 0; + + // TODO: change this header + int header[4]; + file.read(header, sizeof(header)); + if(((header[0]>>24)&0xff) != 'D' || ((header[0]>>16)&0xff) != 'A' || (header[0]>>8)&0xff != 'T' || (header[0]&0xff)!= 'A') + { + dbg_msg("datafile", "wrong signature. %x %x %x %x", header[0], header[1], header[2], header[3]); + return 0; + } + + int version = header[1]; + if(version != 3) + { + dbg_msg("datafile", "wrong version. version=%x", version); + return 0; + } + + unsigned size = header[2]; + unsigned swapsize = header[3]; + + if(DEBUG) + dbg_msg("datafile", "loading. size=%d", size); + + // TODO: use this variable for good and awesome + (void)swapsize; + + //map_unload(); + datafile *df = (datafile*)mem_alloc(size+sizeof(datafile_info), 1); + unsigned readsize = file.read(&df->data, size); + if(readsize != size) + { + dbg_msg("datafile", "couldn't load the whole thing, wanted=%d got=%d", size, readsize); + return 0; + } + + // TODO: byteswap + //map->byteswap(); + + if(DEBUG) + dbg_msg("datafile", "item_size=%d", df->data.item_size); + + + df->info.item_types = (item_type *)df->data.start; + df->info.item_offsets = (int *)&df->info.item_types[df->data.num_item_types]; + df->info.data_offsets = (int *)&df->info.item_offsets[df->data.num_items]; + + df->info.item_start = (char *)&df->info.data_offsets[df->data.num_raw_data]; + df->info.data_start = df->info.item_start + df->data.item_size; + + if(DEBUG) + dbg_msg("datafile", "datafile loading done. datafile='%s'", filename); + + if(DEBUG) + { + for(int i = 0; i < df->data.num_raw_data; i++) + { + void *p = datafile_get_data(df, i); + dbg_msg("datafile", "%d %d", (int)((char*)p - (char*)(&df->data)), size); + } + + for(int i = 0; i < datafile_num_items(df); i++) + { + int type, id; + void *data = datafile_get_item(df, i, &type, &id); + dbg_msg("map", "\t%d: type=%x id=%x p=%p offset=%d", i, type, id, data, df->info.item_offsets[i]); + int *idata = (int*)data; + for(int k = 0; k < 3; k++) + dbg_msg("datafile", "\t\t%d=%d (%x)", k, idata[k], idata[k]); + } + + for(int i = 0; i < df->data.num_item_types; i++) + { + dbg_msg("map", "\t%d: type=%x start=%d num=%d", i, + df->info.item_types[i].type, + df->info.item_types[i].start, + df->info.item_types[i].num); + for(int k = 0; k < df->info.item_types[i].num; k++) + { + int type, id; + datafile_get_item(df, df->info.item_types[i].start+k, &type, &id); + if(type != df->info.item_types[i].type) + dbg_msg("map", "\tERROR"); + } + } + } + + return df; +} + +void *datafile_get_data(datafile *df, int index) +{ + return df->info.data_start+df->info.data_offsets[index]; +} + +void *datafile_get_item(datafile *df, int index, int *type, int *id) +{ + item *i = (item *)(df->info.item_start+df->info.item_offsets[index]); + if(type) + *type = (i->type_and_id>>16)&0xffff; // remove sign extention + if(id) + *id = i->type_and_id&0xffff; + return (void *)(i+1); +} + +void datafile_get_type(datafile *df, int type, int *start, int *num) +{ + for(int i = 0; i < df->data.num_item_types; i++) + { + if(df->info.item_types[i].type == type) + { + *start = df->info.item_types[i].start; + *num = df->info.item_types[i].num; + return; + } + } + + *start = 0; + *num = 0; +} + +void *datafile_find_item(datafile *df, int type, int id) +{ + int start, num; + datafile_get_type(df, type, &start, &num); + for(int i = 0; i < num; i++) + { + int item_id; + void *item = datafile_get_item(df, start+i,0, &item_id); + if(id == item_id) + return item; + } + return 0; +} + +int datafile_num_items(datafile *df) +{ + return df->data.num_items; +} + +void datafile_unload(datafile *df) +{ + if(df) + mem_free(df); +} + +// DATAFILE output +struct data_info +{ + int size; + void *data; +}; + +struct item_info +{ + int type; + int id; + int size; + int next; + int prev; + void *data; +}; + +struct itemtype_info +{ + int num; + int first; + int last; +}; + +// +struct datafile_out +{ + baselib::file_stream file; + int num_items; + int num_datas; + int num_item_types; + itemtype_info item_types[0xffff]; + item_info items[1024]; + data_info datas[1024]; +}; + +datafile_out *datafile_create(const char *filename) +{ + datafile_out *df = new datafile_out; + if(!df->file.open_w(filename)) + { + delete df; + return 0; + } + + df->num_items = 0; + df->num_datas = 0; + df->num_item_types = 0; + mem_zero(&df->item_types, sizeof(df->item_types)); + + for(int i = 0; i < 0xffff; i++) + { + df->item_types[i].first = -1; + df->item_types[i].last = -1; + } + + return df; +} + +int datafile_add_item(datafile_out *df, int type, int id, int size, void *data) +{ + df->items[df->num_items].type = type; + df->items[df->num_items].id = id; + df->items[df->num_items].size = size; + + // copy data + df->items[df->num_items].data = mem_alloc(size, 1); + mem_copy(df->items[df->num_items].data, data, size); + + if(!df->item_types[type].num) // count item types + df->num_item_types++; + + // link + df->items[df->num_items].prev = df->item_types[type].last; + df->items[df->num_items].next = -1; + + if(df->item_types[type].last != -1) + df->items[df->item_types[type].last].next = df->num_items; + df->item_types[type].last = df->num_items; + + if(df->item_types[type].first == -1) + df->item_types[type].first = df->num_items; + + df->item_types[type].num++; + + df->num_items++; + return df->num_items-1; +} + +int datafile_add_data(datafile_out *df, int size, void *data) +{ + df->datas[df->num_items].size = size; + df->datas[df->num_datas].data = data; + df->num_datas++; + return df->num_datas-1; +} + +int datafile_finish(datafile_out *df) +{ + // we should now write this file! + if(DEBUG) + dbg_msg("datafile", "writing"); + + // calculate sizes + int itemsize = 0; + for(int i = 0; i < df->num_items; i++) + { + if(DEBUG) + dbg_msg("datafile", "item=%d size=%d (%d)", i, df->items[i].size, df->items[i].size+sizeof(item)); + itemsize += df->items[i].size + sizeof(item); + } + + int datasize = 0; + for(int i = 0; i < df->num_datas; i++) + datasize += df->datas[i].size; + + // calculate the complete size + int typessize = df->num_item_types*sizeof(item_type); + int headersize = sizeof(datafile_header); + int offsetsize = df->num_items*sizeof(int) + df->num_datas*sizeof(int); + int filesize = headersize + typessize + offsetsize + itemsize + datasize; + int swapsize = filesize - datasize; + + if(DEBUG) + dbg_msg("datafile", "num_item_types=%d typessize=%d itemsize=%d datasize=%d", df->num_item_types, typessize, itemsize, datasize); + + // construct header + datafile_header header; + header.id = ('D'<<24) | ('A'<<16) | ('T'<<8) | ('A'); + header.version = 3; + header.size = filesize - 16; + header.swaplen = swapsize - 16; + header.num_item_types = df->num_item_types; + header.num_items = df->num_items; + header.num_raw_data = df->num_datas; + header.item_size = itemsize; + header.data_size = datasize; + + // TODO: apply swapping + // write header + if(DEBUG) + dbg_msg("datafile", "headersize=%d", sizeof(header)); + df->file.write(&header, sizeof(header)); + + // write types + for(int i = 0, count = 0; i < 0xffff; i++) + { + if(df->item_types[i].num) + { + // write info + item_type info; + info.type = i; + info.start = count; + info.num = df->item_types[i].num; + if(DEBUG) + dbg_msg("datafile", "writing type=%x start=%d num=%d", info.type, info.start, info.num); + df->file.write(&info, sizeof(info)); + + count += df->item_types[i].num; + } + } + + // write item offsets + for(int i = 0, offset = 0; i < 0xffff; i++) + { + if(df->item_types[i].num) + { + // write all items in of this type + int k = df->item_types[i].first; + while(k != -1) + { + if(DEBUG) + dbg_msg("datafile", "writing item offset num=%d offset=%d", k, offset); + df->file.write(&offset, sizeof(offset)); + offset += df->items[k].size + sizeof(item); + + // next + k = df->items[k].next; + } + } + } + + // write data offsets + for(int i = 0, offset = 0; i < df->num_datas; i++) + { + if(DEBUG) + dbg_msg("datafile", "writing data offset num=%d offset=%d", i, offset); + df->file.write(&offset, sizeof(offset)); + offset += df->datas[i].size; + } + + // write items + for(int i = 0; i < 0xffff; i++) + { + if(df->item_types[i].num) + { + // write all items in of this type + int k = df->item_types[i].first; + while(k != -1) + { + item itm; + itm.type_and_id = (i<<16)|df->items[k].id; + itm.size = df->items[k].size; + if(DEBUG) + dbg_msg("datafile", "writing item type=%x idx=%d id=%d size=%d", i, k, df->items[k].id, df->items[k].size); + df->file.write(&itm, sizeof(itm)); + df->file.write(df->items[k].data, df->items[k].size); + + // next + k = df->items[k].next; + } + } + } + + // write data + for(int i = 0; i < df->num_datas; i++) + { + if(DEBUG) + dbg_msg("datafile", "writing data id=%d size=%d", i, df->datas[i].size); + df->file.write(df->datas[i].data, df->datas[i].size); + } + + // free data + for(int i = 0; i < df->num_items; i++) + mem_free(df->items[i].data); + + + delete df; + + if(DEBUG) + dbg_msg("datafile", "done"); + return 0; +} diff --git a/src/engine/datafile.h b/src/engine/datafile.h new file mode 100644 index 00000000..4e49fa03 --- /dev/null +++ b/src/engine/datafile.h @@ -0,0 +1,20 @@ + +// raw datafile access +struct datafile; + +// read access +datafile *datafile_load(const char *filename); +datafile *datafile_load_old(const char *filename); +void *datafile_get_data(datafile *df, int index); +void *datafile_get_item(datafile *df, int index, int *type, int *id); +void datafile_get_type(datafile *df, int type, int *start, int *num); +void *datafile_find_item(datafile *df, int type, int id); +int datafile_num_items(datafile *df); +void datafile_unload(datafile *df); + +// write access +struct datafile_out; +datafile_out *datafile_create(const char *filename); +int datafile_add_data(datafile_out *df, int size, void *data); +int datafile_add_item(datafile_out *df, int type, int id, int size, void *data); +int datafile_finish(datafile_out *df); diff --git a/src/engine/interface.h b/src/engine/interface.h new file mode 100644 index 00000000..95ea0252 --- /dev/null +++ b/src/engine/interface.h @@ -0,0 +1,711 @@ +#ifndef FILE_INTERFACE_H +#define FILE_INTERFACE_H + +/* + Title: Engine Interface +*/ + +// TODO: Move the definitions of these keys here +#include <baselib/keys.h> + +enum +{ + MAX_CLIENTS=8, + SERVER_TICK_SPEED=50, + SERVER_CLIENT_TIMEOUT=5, + SNAP_CURRENT=0, + SNAP_PREV=1, +}; + +struct snap_item +{ + int type; + int id; +}; + +struct client_info +{ +public: + const char *name; + int latency; +}; + +struct image_info +{ + int width, height; + void *data; +}; + +int gfx_load_tga(image_info *img, const char *filename); + + +/* + Group: Graphics +*/ + +// graphics +bool gfx_init(bool fullscreen); // NOT EXPOSED +void gfx_shutdown(); // NOT EXPOSED +void gfx_swap(); // NOT EXPOSED + +// textures +/* + Function: gfx_load_texture_tga + Loads a TGA from file. + + Arguments: + filename - Null terminated string to the file to load. + + Returns: + An ID to the texture. -1 on failure. + + See Also: + <gfx_unload_texture> +*/ +int gfx_load_texture_tga(const char *filename); + +/* + Function: gfx_load_texture_raw + Loads a texture from memory. + + Arguments: + w - Width of the texture. + h - Height of the texture. + data - Pointer to the pixel data. + + Returns: + An ID to the texture. -1 on failure. + + Remarks: + The pixel data should be in RGBA format with 8 bit per component. + So the total size of the data should be w*h*4. + + See Also: + <gfx_unload_texture> +*/ +int gfx_load_texture_raw(int w, int h, const void *data); + +/* + Function: gfx_texture_set + Sets the active texture. + + Arguments: + id - ID to the texture to set. +*/ +void gfx_texture_set(int id); + +/* + Function: gfx_unload_texture + Unloads a texture. + + Arguments: + id - ID to the texture to unload. + + See Also: + <gfx_load_texture_tga>, <gfx_load_texture_raw> + + Remarks: + NOT IMPLEMENTED +*/ +int gfx_unload_texture(int id); // NOT IMPLEMENTED + +void gfx_clear(float r, float g, float b); + +/* + Function: gfx_screenwidth + Returns the screen width. + + See Also: + <gfx_screenheight> +*/ +int gfx_screenwidth(); + +/* + Function: gfx_screenheight + Returns the screen height. + + See Also: + <gfx_screenwidth> +*/ +int gfx_screenheight(); + +/* + Function: gfx_mapscreen + Specifies the coordinate system for the screen. + + Arguments: + tl_x - Top-left X + tl_y - Top-left Y + br_x - Bottom-right X + br_y - Bottom-right y +*/ +void gfx_mapscreen(float tl_x, float tl_y, float br_x, float br_y); + +/* + Function: gfx_blend_normal + Set the active blending mode to normal (src, 1-src). + + Remarks: + This must be used before calling <gfx_quads_begin>. + This is equal to glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA). + + See Also: + <gfx_blend_additive> +*/ +void gfx_blend_normal(); + +/* + Function: gfx_blend_additive + Set the active blending mode to additive (src, one). + + Remarks: + This must be used before calling <gfx_quads_begin>. + This is equal to glBlendFunc(GL_SRC_ALPHA, GL_ONE). + + See Also: + <gfx_blend_normal> +*/ +void gfx_blend_additive(); + +/* + Function: gfx_quads_begin + Begins a quad drawing session. + + Remarks: + This functions resets the rotation, color and subset. + End the session by using <gfx_quads_end>. + You can't change texture or blending mode during a session. + + See Also: + <gfx_quads_end> +*/ +void gfx_quads_begin(); + +/* + Function: gfx_quads_end + Ends a quad session. + + See Also: + <gfx_quads_begin> +*/ +void gfx_quads_end(); + +/* + Function: gfx_quads_setrotation + Sets the rotation to use when drawing a quad. + + Arguments: + angle - Angle in radians. + + Remarks: + The angle is reset when <gfx_quads_begin> is called. +*/ +void gfx_quads_setrotation(float angle); + +/* + Function: gfx_quads_setcolorvertex + Sets the color of a vertex. + + Arguments: + i - Index to the vertex. + r - Red value. + g - Green value. + b - Blue value. + a - Alpha value. + + Remarks: + The color values are from 0.0 to 1.0. + The color is reset when <gfx_quads_begin> is called. +*/ +void gfx_quads_setcolorvertex(int i, float r, float g, float b, float a); + +/* + Function: gfx_quads_setcolor + Sets the color of all the vertices. + + Arguments: + r - Red value. + g - Green value. + b - Blue value. + a - Alpha value. + + Remarks: + The color values are from 0.0 to 1.0. + The color is reset when <gfx_quads_begin> is called. +*/ +void gfx_quads_setcolor(float r, float g, float b, float a); + +/* + Function: gfx_quads_setsubset + Sets the uv coordinates to use. + + Arguments: + tl_u - Top-left U value. + tl_v - Top-left V value. + br_u - Bottom-right U value. + br_v - Bottom-right V value. + + Remarks: + O,0 is top-left of the texture and 1,1 is bottom-right. + The color is reset when <gfx_quads_begin> is called. +*/ +void gfx_quads_setsubset(float tl_u, float tl_v, float br_u, float br_v); + +/* + Function: gfx_quads_drawTL + Draws a quad by specifying the top-left point. + + Arguments: + x - X coordinate of the top-left corner. + y - Y coordinate of the top-left corner. + width - Width of the quad. + height - Height of the quad. + + Remarks: + Rotation still occurs from the center of the quad. + You must call <gfx_quads_begin> before calling this function. + + See Also: + <gfx_quads_draw> +*/ +void gfx_quads_drawTL(float x, float y, float width, float height); + +/* + Function: gfx_quads_draw + Draws a quad by specifying the center point. + + Arguments: + x - X coordinate of the center. + y - Y coordinate of the center. + width - Width of the quad. + height - Height of the quad. + + Remarks: + You must call <gfx_quads_begin> before calling this function. + + See Also: + <gfx_quads_drawTL> +*/ +void gfx_quads_draw(float x, float y, float w, float h); + +void gfx_quads_draw_freeform( + float x0, float y0, + float x1, float y1, + float x2, float y2, + float x3, float y3); + +void gfx_quads_text(float x, float y, float size, const char *text); + +// sound (client) +enum +{ + SND_PLAY_ONCE = 0, + SND_LOOP +}; + +bool snd_init(); +int snd_load_wav(const char *filename); +int snd_play(int sound, int loop = SND_PLAY_ONCE, float vol = 1.0f, float pan = 0.0f); +void snd_stop(int id); +void snd_set_vol(int id, float vol); +bool snd_shutdown(); + +/* + Group: Input +*/ + +/* + Function: inp_mouse_relative + Fetches the mouse movements. + + Arguments: + x - Pointer to the variable that should get the X movement. + y - Pointer to the variable that should get the Y movement. +*/ +void inp_mouse_relative(int *x, int *y); + +/* + Function: inp_mouse_button_pressed + Checks if a mouse button is pressed. + + Arguments: + button - Index to the button to check. + * 0 - Left mouse button. + * 1 - Right mouse button. + * 2 - Middle mouse button. + * Others over 2 is undefined mouse buttons. + + Returns: + Returns 1 if the button is pressed, otherwise 0. +*/ +int inp_mouse_button_pressed(int button); + +/* + Function: inp_key_pressed + Checks if a key is pressed. + + Arguments: + key - Index to the key to check + + Returns: + Returns 1 if the button is pressed, otherwise 0. + + Remarks: + Check baselib/include/baselib/keys.h for the keys. +*/ +int inp_key_pressed(int key); + +/* + Group: Map +*/ + +int map_load(const char *mapname); // NOT EXPOSED +void map_unload(); // NOT EXPOSED + +/* + Function: map_is_loaded + Checks if a map is loaded. + + Returns: + Returns 1 if the button is pressed, otherwise 0. +*/ +int map_is_loaded(); + +/* + Function: map_num_items + Checks the number of items in the loaded map. + + Returns: + Returns the number of items. 0 if no map is loaded. +*/ +int map_num_items(); + +/* + Function: map_find_item + Searches the map for an item. + + Arguments: + type - Item type. + id - Item ID. + + Returns: + Returns a pointer to the item if it exists, otherwise it returns NULL. +*/ +void *map_find_item(int type, int id); + +/* + Function: map_get_item + Gets an item from the loaded map from index. + + Arguments: + index - Item index. + type - Pointer that recives the item type (can be NULL). + id - Pointer that recives the item id (can be NULL). + + Returns: + Returns a pointer to the item if it exists, otherwise it returns NULL. +*/ +void *map_get_item(int index, int *type, int *id); + +/* + Function: map_get_type + Gets the index range of an item type. + + Arguments: + type - Item type to search for. + start - Pointer that recives the starting index. + num - Pointer that recives the number of items. + + Returns: + If the item type is not in the map, start and num will be set to 0. +*/ +void map_get_type(int type, int *start, int *num); + +/* + Function: map_get_data + Fetches a pointer to a raw data chunk in the map. + + Arguments: + index - Index to the data to fetch. + + Returns: + A pointer to the raw data, otherwise 0. +*/ +void *map_get_data(int index); + +/* + Group: Network (Server) +*/ +/* + Function: snap_new_item + Creates a new item that should be sent. + + Arguments: + type - Type of the item. + id - ID of the item. + size - Size of the item. + + Returns: + A pointer to the item data, otherwise 0. + + Remarks: + The item data should only consist pf 4 byte integers as + they are subject to byte swapping. This means that the size + argument should be dividable by 4. +*/ +void *snap_new_item(int type, int id, int size); + +/* + Group: Network (Client) +*/ +/* + Function: snap_num_items + Check the number of items in a snapshot. + + Arguments: + snapid - Snapshot ID to the data to fetch. + * SNAP_PREV for previous snapshot. + * SNAP_CUR for current snapshot. + + Returns: + The number of items in the snapshot. +*/ +int snap_num_items(int snapid); + +/* + Function: snap_get_item + Gets an item from a snapshot. + + Arguments: + snapid - Snapshot ID to the data to fetch. + * SNAP_PREV for previous snapshot. + * SNAP_CUR for current snapshot. + index - Index of the item. + item - Pointer that recives the item info. + + Returns: + Returns a pointer to the item if it exists, otherwise NULL. +*/ +void *snap_get_item(int snapid, int index, snap_item *item); + +/* + Function: snap_find_item + Searches a snapshot for an item. + + Arguments: + snapid - Snapshot ID to the data to fetch. + * SNAP_PREV for previous snapshot. + * SNAP_CUR for current snapshot. + type - Type of the item. + id - ID of the item. + + Returns: + Returns a pointer to the item if it exists, otherwise NULL. +*/ +void *snap_find_item(int snapid, int type, int id); + +/* + Function: snap_input + Sets the input data to send to the server. + + Arguments: + data - Pointer to the data. + size - Size of the data. + + Remarks: + The data should only consist of 4 bytes integer as they are + subject to byte swapping. +*/ +void snap_input(void *data, int size); + +/* + Function: snap_intratick + Returns the intra-tick mixing value. + + Returns: + Returns the mixing value between the previous snapshot + and the current snapshot. + + Remarks: + DOCTODO: Explain how to use it. +*/ +float snap_intratick(); + +/* + Group: Server Callbacks +*/ +/* + Function: mods_init + Called when the server is started. + + Remarks: + It's called after the map is loaded so all map items are available. +*/ +void mods_init(); + +/* + Function: mods_shutdown + Called when the server quits. + + Remarks: + Should be used to clean up all resources used. +*/ +void mods_shutdown(); + +/* + Function: mods_client_enter + Called when a client has joined the game. + + Arguments: + cid - Client ID. Is 0 - MAX_CLIENTS. + + Remarks: + It's called when the client is finished loading and should enter gameplay. +*/ +void mods_client_enter(int cid); + +/* + Function: mods_client_drop + Called when a client drops from the server. + + Arguments: + cid - Client ID. Is 0 - MAX_CLIENTS +*/ +void mods_client_drop(int cid); + +/* + Function: mods_client_input + Called when the server recives new input from a client. + + Arguments: + cid - Client ID. Is 0 - MAX_CLIENTS. + input - Pointer to the input data. + size - Size of the data. (NOT IMPLEMENTED YET) +*/ +void mods_client_input(int cid, void *input); + +/* + Function: mods_tick + Called with a regular interval to progress the gameplay. + + Remarks: + The SERVER_TICK_SPEED tells the number of ticks per second. +*/ +void mods_tick(); + +/* + Function: mods_presnap + Called before the server starts to construct snapshots for the clients. +*/ +void mods_presnap(); + +/* + Function: mods_snap + Called to create the snapshot for a client. + + Arguments: + cid - Client ID. Is 0 - MAX_CLIENTS. + + Remarks: + The game should make a series of calls to <snap_new_item> to construct + the snapshot for the client. +*/ +void mods_snap(int cid); + +/* + Function: mods_postsnap + Called after the server is done sending the snapshots. +*/ +void mods_postsnap(); + +/* + Group: Client Callbacks +*/ +/* + Function: modc_init + Called when the client starts. + + Remarks: + The game should load resources that are used during the entire + time of the game. No map is loaded. +*/ +void modc_init(); + +/* + Function: modc_newsnapshot + Called when the client progressed to a new snapshot. + + Remarks: + The client can check for items in the snapshot and perform one time + events like playing sounds, spawning client side effects etc. +*/ +void modc_newsnapshot(); + +/* + Function: modc_entergame + Called when the client has successfully connect to a server and + loaded a map. + + Remarks: + The client can check for items in the map and load them. +*/ +void modc_entergame(); + +/* + Function: modc_shutdown + Called when the client closes down. +*/ +void modc_shutdown(); + +/* + Function: modc_render + Called every frame to let the game render it self. +*/ +void modc_render(); + + + +/* + Group: Menu Callbacks +*/ +/* + Function: modmenu_init + Called when the menu starts. + + Remarks: + The menu should load resources that are used during the entire + time of the menu use. +*/ +void modmenu_init(); + +/* + Function: modmenu_shutdown + Called when the menu closes down. +*/ +void modmenu_shutdown(); + +/* + Function: modmenu_render + Called every frame to let the menu render it self. +*/ +int modmenu_render(void *server_address, char *name, int max_len); + +void snap_encode_string(const char *src, int *dst, int length, int max_length); +void snap_decode_string(const int *src, char *dst, int length); + +int server_getclientinfo(int client_id, client_info *info); +int server_tick(); +int server_tickspeed(); + +int inp_key_was_pressed(int key); +int inp_key_down(int key); +void inp_update(); +float client_frametime(); +float client_localtime(); + +#define MASTER_SERVER_ADDRESS "master.teewars.com" +#define MASTER_SERVER_PORT 8300 + + + +#endif diff --git a/src/engine/lzw.cpp b/src/engine/lzw.cpp new file mode 100644 index 00000000..80dd1c22 --- /dev/null +++ b/src/engine/lzw.cpp @@ -0,0 +1,223 @@ +#include <string.h> + +// LZW Compressor +struct SYM +{ + unsigned char *data; + int size; + int next; +}; + +struct SYMBOLS +{ + SYM syms[512]; + int jumptable[256]; + int currentsym; +}; + +static SYMBOLS symbols; + +// symbol info +inline int sym_size(int i) { return symbols.syms[i].size; } +inline unsigned char *sym_data(int i) { return symbols.syms[i].data; } + +static void sym_index(int sym) +{ + int table = symbols.syms[sym].data[0]; + symbols.syms[sym].next = symbols.jumptable[table]; + symbols.jumptable[table] = sym; +} + +static void sym_unindex(int sym) +{ + int table = symbols.syms[sym].data[0]; + int prev = -1; + int current = symbols.jumptable[table]; + + while(current != -1) + { + if(current == sym) + { + if(prev != -1) + symbols.syms[prev].next = symbols.syms[current].next; + else + symbols.jumptable[table] = symbols.syms[current].next; + break; + } + + prev = current; + current = symbols.syms[current].next; + } +} + +static int sym_add(unsigned char *sym, long len) +{ + int i = 256+symbols.currentsym; + symbols.syms[i].data = sym; + symbols.syms[i].size = len; + symbols.currentsym = (symbols.currentsym+1)%255; + return i; +} + +static int sym_add_and_index(unsigned char *sym, long len) +{ + if(symbols.syms[256+symbols.currentsym].size) + sym_unindex(256+symbols.currentsym); + int s = sym_add(sym, len); + sym_index( s); + return s; +} + +static void sym_init() +{ + static unsigned char table[256]; + for(int i = 0; i < 256; i++) + { + table[i] = i; + symbols.syms[i].data = &table[i]; + symbols.syms[i].size = 1; + symbols.jumptable[i] = -1; + } + + for(int i = 0; i < 512; i++) + symbols.syms[i].next = -1; + + /* + // insert some symbols to start with + static unsigned char zeros[8] = {0,0,0,0,0,0,0,0}; + //static unsigned char one1[4] = {0,0,0,1}; + //static unsigned char one2[4] = {1,0,0,0}; + sym_add_and_index(zeros, 2); + sym_add_and_index(zeros, 3); + sym_add_and_index(zeros, 4); + sym_add_and_index(zeros, 5); + sym_add_and_index(zeros, 6); + sym_add_and_index(zeros, 7); + sym_add_and_index(zeros, 8); + + //sym_add_and_index(one1, 4); + //sym_add_and_index(one2, 4);*/ + + symbols.currentsym = 0; +} + +static int sym_find(unsigned char *data, int size, int avoid) +{ + int best = data[0]; + int bestlen = 1; + int current = symbols.jumptable[data[0]]; + + while(current != -1) + { + if(current != avoid && symbols.syms[current].size <= size && memcmp(data, symbols.syms[current].data, symbols.syms[current].size) == 0) + { + if(bestlen < symbols.syms[current].size) + { + bestlen = symbols.syms[current].size; + best = current; + } + } + + current = symbols.syms[current].next; + } + + return best; +} + +// +// compress +// +long lzw_compress(const void *src_, int size, void *dst_) +{ + unsigned char *src = (unsigned char *)src_; + unsigned char *end = (unsigned char *)src_+size; + unsigned char *dst = (unsigned char *)dst_; + long left = (end-src); + int lastsym = -1; + + // init symboltable + sym_init(); + + bool done = false; + while(!done) + { + unsigned char *flagptr = dst; + unsigned char flagbits = 0; + int b = 0; + + dst++; // skip a byte where the flags are + + for(; b < 8; b++) + { + if(left <= 0) // check for EOF + { + // write EOF symbol + flagbits |= 1<<b; + *dst++ = 255; + done = true; + break; + } + + int sym = sym_find(src, left, lastsym); + int symsize = sym_size( sym); + + if(sym&0x100) + flagbits |= 1<<b; // add bit that says that its a symbol + + *dst++ = sym&0xff; // set symbol + + if(left > symsize+1) // create new symbol + lastsym = sym_add_and_index(src, symsize+1); + + src += symsize; // advance src + left -= symsize; + } + + // write the flags + *flagptr = flagbits; + } + + return (long)(dst-(unsigned char*)dst_); +} + +// +// decompress +// +long lzw_decompress(const void *src_, void *dst_) +{ + unsigned char *src = (unsigned char *)src_; + unsigned char *dst = (unsigned char *)dst_; + unsigned char *prevdst = 0; + int prevsize = -1; + int item; + + sym_init(); + + while(1) + { + unsigned char flagbits = 0; + flagbits = *src++; // read flags + + int b = 0; + for(; b < 8; b++) + { + item = *src++; + if(flagbits&(1<<b)) + item |= 256; + + if(item == 0x1ff) // EOF symbol + return (dst-(unsigned char *)dst_); + + if(prevdst) // this one could be removed + sym_add(prevdst, prevsize+1); + + memcpy(dst, sym_data(item), sym_size(item)); + prevdst = dst; + prevsize = sym_size(item); + dst += sym_size(item); + } + + } + + return 0; +} diff --git a/src/engine/lzw.h b/src/engine/lzw.h new file mode 100644 index 00000000..af29665e --- /dev/null +++ b/src/engine/lzw.h @@ -0,0 +1,2 @@ +long lzw_compress(const void *src, int size, void *dst); +long lzw_decompress(const void *src, void *dst); diff --git a/src/engine/map.cpp b/src/engine/map.cpp new file mode 100644 index 00000000..3e76547e --- /dev/null +++ b/src/engine/map.cpp @@ -0,0 +1,48 @@ +#include <baselib/system.h> +#include <baselib/stream/file.h> + +#include "datafile.h" + +static datafile *map; + +void *map_get_data(int index) +{ + return datafile_get_data(map, index); +} + +void *map_get_item(int index, int *type, int *id) +{ + return datafile_get_item(map, index, type, id); +} + +void map_get_type(int type, int *start, int *num) +{ + datafile_get_type(map, type, start, num); +} + +void *map_find_item(int type, int id) +{ + return datafile_find_item(map, type, id); +} + +int map_num_items() +{ + return datafile_num_items(map); +} + +void map_unload() +{ + datafile_unload(map); + map = 0x0; +} + +int map_is_loaded() +{ + return map != 0; +} + +int map_load(const char *mapname) +{ + map = datafile_load(mapname); + return map != 0; +} diff --git a/src/engine/packet.h b/src/engine/packet.h new file mode 100644 index 00000000..6dc99043 --- /dev/null +++ b/src/engine/packet.h @@ -0,0 +1,288 @@ +#include <baselib/stream/file.h> +#include <baselib/network.h> + +// TODO: this is not KISS +class packet +{ +protected: + enum + { + MAX_PACKET_SIZE = 1024, + }; + + // packet data + struct header + { + unsigned msg; + unsigned ack; + unsigned seq; + }; + + unsigned char packet_data[MAX_PACKET_SIZE]; + unsigned char *current; + + // these are used to prepend data in the packet + // used for debugging so we have checks that we + // pack and unpack the same way + enum + { + DEBUG_TYPE_INT=0x1, + DEBUG_TYPE_STR=0x2, + DEBUG_TYPE_RAW=0x3, + }; + + // writes an int to the packet + void write_int_raw(int i) + { + // TODO: check for overflow + *(int*)current = i; + current += sizeof(int); + } + + // reads an int from the packet + int read_int_raw() + { + // TODO: check for overflow + int i = *(int*)current; + current += sizeof(int); + return i; + } + + void debug_insert_mark(int type, int size) + { + write_int_raw((type<<16)|size); + } + + void debug_verify_mark(int type, int size) + { + if(read_int_raw() != ((type<<16) | size)) + dbg_assert(0, "error during packet disassembly"); + } + +public: + packet(unsigned msg=0) + { + current = packet_data; + current += sizeof(header); + ((header*)packet_data)->msg = msg; + } + + void set_header(unsigned ack, unsigned seq) + { + ((header*)packet_data)->ack = ack; + ((header*)packet_data)->seq = seq; + } + + // writes an int to the packet + void write_int(int i) + { + debug_insert_mark(DEBUG_TYPE_INT, 4); + write_int_raw(i); + } + + void write_raw(const char *raw, int size) + { + debug_insert_mark(DEBUG_TYPE_RAW, size); + while(size--) + *current++ = *raw++; + } + + // writes a string to the packet + void write_str(const char *str) + { + debug_insert_mark(DEBUG_TYPE_STR, 0); + int s = strlen(str)+1; + write_int_raw(s); + for(;*str; current++, str++) + *current = *str; + *current = 0; + current++; + } + + // reads an int from the packet + int read_int() + { + debug_verify_mark(DEBUG_TYPE_INT, 4); + return read_int_raw(); + } + + // reads a string from the packet + const char *read_str() + { + debug_verify_mark(DEBUG_TYPE_STR, 0); + int size = read_int_raw(); + const char *s = (const char *)current; + //dbg_msg("packet", "reading string '%s' (%d)", s, size); + current += size; + return s; + } + + const char *read_raw(int size) + { + debug_verify_mark(DEBUG_TYPE_RAW, size); + const char *d = (const char *)current; + current += size; + return d; + } + + // TODO: impelement this + bool is_good() const { return true; } + + unsigned msg() const { return ((header*)packet_data)->msg; } + unsigned seq() const { return ((header*)packet_data)->seq; } + unsigned ack() const { return ((header*)packet_data)->ack; } + + // access functions to get the size and data + int size() const { return (int)(current-(unsigned char*)packet_data); } + int max_size() const { return MAX_PACKET_SIZE; } + void *data() { return packet_data; } +}; + +// +class connection +{ + baselib::socket_udp4 *socket; + baselib::netaddr4 addr; + unsigned seq; + unsigned ack; + unsigned last_ack; + + unsigned counter_sent_bytes; + unsigned counter_recv_bytes; + +public: + void counter_reset() + { + counter_sent_bytes = 0; + counter_recv_bytes = 0; + } + + void counter_get(unsigned *sent, unsigned *recved) + { + *sent = counter_sent_bytes; + *recved = counter_recv_bytes; + } + + void init(baselib::socket_udp4 *socket, const baselib::netaddr4 *addr) + { + this->addr = *addr; + this->socket = socket; + last_ack = 0; + ack = 0; + seq = 0; + counter_reset(); + } + + void send(packet *p) + { + if(p->msg()&(31<<1)) + { + // vital packet + seq++; + // TODO: save packet, we might need to retransmit + } + + p->set_header(ack, seq); + socket->send(&address(), p->data(), p->size()); + counter_sent_bytes += p->size(); + } + + packet *feed(packet *p) + { + counter_recv_bytes += p->size(); + + if(p->msg()&(31<<1)) + { + if(p->seq() == ack+1) + { + // packet in seqence, ack it + ack++; + //dbg_msg("network/connection", "packet in sequence. seq/ack=%x", ack); + return p; + } + else if(p->seq() > ack) + { + // TODO: request resend + // packet loss + dbg_msg("network/connection", "packet loss! seq=%x ack=%x+1", p->seq(), ack); + return p; + } + else + { + // we already got this packet + return 0x0; + } + } + + if(last_ack != p->ack()) + { + // TODO: remove acked packets + } + + return p; + } + + const baselib::netaddr4 &address() const { return addr; } + + void update() + { + } +}; + +//const char *NETWORK_VERSION = "development"; + +enum +{ + NETMSG_VITAL=0x80000000, + NETMSG_CONTEXT_CONNECT=0x00010000, + NETMSG_CONTEXT_GAME=0x00020000, + NETMSG_CONTEXT_GLOBAL=0x00040000, + + // connection phase + NETMSG_CLIENT_CONNECT=NETMSG_CONTEXT_CONNECT|1, + // str32 name + // str32 clan + // str32 password + // str32 skin + + // TODO: These should be implemented to make the server send the map to the client on connect + // NETMSG_CLIENT_FETCH, + // NETMSG_SERVER_MAPDATA, + + NETMSG_SERVER_ACCEPT=NETMSG_CONTEXT_CONNECT|2, + // str32 mapname + + + NETMSG_CLIENT_DONE=NETMSG_VITAL|NETMSG_CONTEXT_CONNECT|3, + // nothing + + // game phase + NETMSG_SERVER_SNAP = NETMSG_CONTEXT_GAME|1, // server will spam these + // int num_parts + // int part + // int size + // data * + + NETMSG_CLIENT_INPUT = NETMSG_CONTEXT_GAME|1, // client will spam these + // int input[MAX_INPUTS] + + NETMSG_SERVER_EVENT = NETMSG_CONTEXT_GAME|NETMSG_VITAL|2, + NETMSG_CLIENT_EVENT = NETMSG_CONTEXT_GAME|NETMSG_VITAL|2, + + NETMSG_CLIENT_CHECKALIVE = NETMSG_CONTEXT_GAME|NETMSG_VITAL|3, // check if client is alive + + NETMSG_CLIENT_ERROR=0x0fffffff, + // str128 reason + + NETMSG_SERVER_ERROR=0x0fffffff, + // str128 reason +}; + +enum +{ + MAX_NAME_LENGTH=32, + MAX_CLANNAME_LENGTH=32, + MAX_INPUT_SIZE=128, + MAX_SNAPSHOT_SIZE=64*1024, + MAX_SNAPSHOT_PACKSIZE=768 +}; diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp new file mode 100644 index 00000000..d9151a43 --- /dev/null +++ b/src/engine/server/server.cpp @@ -0,0 +1,5 @@ +#include <stdio.h> #include <string.h> #include <baselib/system.h> #include <engine/interface.h> //#include "socket.h" #include <engine/packet.h> #include <engine/snapshot.h> #include <engine/lzw.h> #include <engine/versions.h> namespace baselib {} using namespace baselib; int net_addr4_cmp(const NETADDR4 *a, const NETADDR4 *b) { if( a->ip[0] != b->ip[0] || a->ip[1] != b->ip[1] || a->ip[2] != b->ip[2] || a->ip[3] != b->ip[3] || a->port != b->port ) return 1; return 0; } // --- 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; } } class snapshot_builder { public: static const int MAX_ITEMS = 512; //static const int MAX_DATA_SIZE=1*1024; char data[MAX_SNAPSHOT_SIZE]; int data_size; int offsets[MAX_ITEMS]; int num_items; int top_size; int top_items; int snapnum; snapshot_builder() { top_size = 0; top_items = 0; snapnum = 0; } void start() { data_size = 0; num_items = 0; } int finish(void *snapdata) { snapnum++; // collect some data /* int change = 0; if(data_size > top_size) { change++; top_size = data_size; } if(num_items > top_items) { change++; top_items = num_items; } if(change) { dbg_msg("snapshot", "new top, items=%d size=%d", top_items, top_size); }*/ // flattern and make the snapshot snapshot *snap = (snapshot *)snapdata; snap->num_items = num_items; int offset_size = sizeof(int)*num_items; mem_copy(snap->offsets, offsets, offset_size); mem_copy(snap->data_start(), data, data_size); return sizeof(int) + offset_size + data_size; } void *new_item(int type, int id, int size) { snapshot::item *obj = (snapshot::item *)(data+data_size); obj->type_and_id = (type<<16)|id; offsets[num_items] = data_size; data_size += sizeof(int) + size; num_items++; dbg_assert(data_size < MAX_SNAPSHOT_SIZE, "too much data"); dbg_assert(num_items < MAX_ITEMS, "too many items"); return &obj->data; } }; static snapshot_builder builder; void *snap_new_item(int type, int id, int size) { dbg_assert(type >= 0 && type <=0xffff, "incorrect type"); dbg_assert(id >= 0 && id <=0xffff, "incorrect id"); return builder.new_item(type, id, size); } // class client { public: enum { STATE_EMPTY = 0, STATE_CONNECTING = 1, STATE_INGAME = 2, }; // connection state info int state; // (ticks) if lastactivity > 5 seconds kick him int64 lastactivity; connection conn; char name[MAX_NAME_LENGTH]; char clan[MAX_CLANNAME_LENGTH]; /* client() { state = STATE_EMPTY; name[0] = 0; clan[0] = 0; } ~client() { dbg_assert(state == STATE_EMPTY, "client destoyed while in use"); }*/ bool is_empty() const { return state == STATE_EMPTY; } bool is_ingame() const { return state == STATE_INGAME; } const netaddr4 &address() const { return conn.address(); } }; static client clients[MAX_CLIENTS]; static int current_tick = 0; static int send_heartbeats = 1; int server_tick() { return current_tick; } int server_tickspeed() { return 50; } int server_init() { for(int i = 0; i < MAX_CLIENTS; i++) { clients[i].state = client::STATE_EMPTY; clients[i].name[0] = 0; clients[i].clan[0] = 0; clients[i].lastactivity = 0; } current_tick = 0; return 0; } int server_getclientinfo(int client_id, client_info *info) { dbg_assert(client_id >= 0 && client_id < MAX_CLIENTS, "client_id is not valid"); dbg_assert(info != 0, "info can not be null"); if(clients[client_id].is_ingame()) { info->name = clients[client_id].name; info->latency = 0; return 1; } return 0; } // class server { public: socket_udp4 game_socket; + + const char *map_name; const char *server_name; int64 lasttick; int64 lastheartbeat; netaddr4 master_server; int biggest_snapshot; bool run(const char *servername, const char *mapname) { biggest_snapshot = 0; net_init(); // For Windows compatibility. map_name = mapname; server_name = servername; // load map if(!map_load(mapname)) { dbg_msg("server", "failed to load map. mapname='%s'"); return false; } // start server if(!game_socket.open(8303)) { dbg_msg("network/server", "couldn't open socket"); return false; } for(int i = 0; i < MAX_CLIENTS; i++) dbg_msg("network/server", "\t%d: %d", i, clients[i].state); if (net_host_lookup(MASTER_SERVER_ADDRESS, MASTER_SERVER_PORT, &master_server) != 0) { // TODO: fix me //master_server = netaddr4(0, 0, 0, 0, 0); } mods_init(); int64 time_per_tick = time_freq()/SERVER_TICK_SPEED; int64 time_per_heartbeat = time_freq() * 30; int64 starttime = time_get(); //int64 lasttick = starttime; lasttick = starttime; lastheartbeat = 0; int64 reporttime = time_get(); int64 reportinterval = time_freq()*3; int64 simulationtime = 0; int64 snaptime = 0; int64 networktime = 0; while(1) { int64 t = time_get(); if(t-lasttick > time_per_tick) { { int64 start = time_get(); tick(); simulationtime += time_get()-start; } { int64 start = time_get(); snap(); snaptime += time_get()-start; } // Check for client timeouts for (int i = 0; i < MAX_CLIENTS; i++) { if (clients[i].state != client::STATE_EMPTY) { // check last activity time if (((lasttick - clients[i].lastactivity) / time_freq()) > SERVER_CLIENT_TIMEOUT) client_timeout(i); } } lasttick += time_per_tick; } if(send_heartbeats) { if (t > lastheartbeat+time_per_heartbeat) { if (master_server.port != 0) { int players = 0; for (int i = 0; i < MAX_CLIENTS; i++) if (!clients[i].is_empty()) players++; // TODO: fix me netaddr4 me(127, 0, 0, 0, 8303); send_heartbeat(0, &me, players, MAX_CLIENTS, server_name, mapname); } lastheartbeat = t+time_per_heartbeat; } } { int64 start = time_get(); pump_network(); networktime += time_get()-start; } if(reporttime < time_get()) { int64 totaltime = simulationtime+snaptime+networktime; dbg_msg("server/report", "sim=%.02fms snap=%.02fms net=%.02fms total=%.02fms load=%.02f%%", simulationtime/(float)reportinterval*1000, snaptime/(float)reportinterval*1000, networktime/(float)reportinterval*1000, totaltime/(float)reportinterval*1000, (simulationtime+snaptime+networktime)/(float)reportinterval*100.0f); unsigned sent_total=0, recv_total=0; for (int i = 0; i < MAX_CLIENTS; i++) if (!clients[i].is_empty()) { unsigned s,r; clients[i].conn.counter_get(&s,&r); clients[i].conn.counter_reset(); sent_total += s; recv_total += r; } dbg_msg("server/report", "biggestsnap=%d send=%d recv=%d", biggest_snapshot, sent_total/3, recv_total/3); simulationtime = 0; snaptime = 0; networktime = 0; reporttime += reportinterval; } thread_sleep(1); } mods_shutdown(); map_unload(); } void tick() { current_tick++; mods_tick(); } void snap() { mods_presnap(); for(int i = 0; i < MAX_CLIENTS; i++) { if(clients[i].is_ingame()) { char data[MAX_SNAPSHOT_SIZE]; char compdata[MAX_SNAPSHOT_SIZE]; builder.start(); mods_snap(i); // finish snapshot int snapshot_size = builder.finish(data); // compress it int compsize = lzw_compress(data, snapshot_size, compdata); snapshot_size = compsize; if(snapshot_size > biggest_snapshot) biggest_snapshot = snapshot_size; const int max_size = MAX_SNAPSHOT_PACKSIZE; int numpackets = (snapshot_size+max_size-1)/max_size; for(int n = 0, left = snapshot_size; left; n++) { int chunk = left < max_size ? left : max_size; left -= chunk; packet p(NETMSG_SERVER_SNAP); p.write_int(numpackets); p.write_int(n); p.write_int(chunk); p.write_raw(&compdata[n*max_size], chunk); clients[i].conn.send(&p); } } } mods_postsnap(); } void send_accept(client *client, const char *map) { packet p(NETMSG_SERVER_ACCEPT); p.write_str(map); client->conn.send(&p); } void drop(int cid, const char *reason) { if(clients[cid].state == client::STATE_EMPTY) return; clients[cid].state = client::STATE_EMPTY; mods_client_drop(cid); dbg_msg("game", "player dropped. reason='%s' cid=%x name='%s'", reason, cid, clients[cid].name); } int find_client(const netaddr4 *addr) { // fetch client for(int i = 0; i < MAX_CLIENTS; i++) { if(!clients[i].is_empty() && clients[i].address() == *addr) return i; } return -1; } void client_process_packet(int cid, packet *p) { clients[cid].lastactivity = lasttick; if(p->msg() == NETMSG_CLIENT_DONE) { dbg_msg("game", "player as entered the game. cid=%x", cid); clients[cid].state = client::STATE_INGAME; mods_client_enter(cid); } else if(p->msg() == NETMSG_CLIENT_INPUT) { int input[MAX_INPUT_SIZE]; int size = p->read_int(); for(int i = 0; i < size/4; i++) input[i] = p->read_int(); if(p->is_good()) { //dbg_msg("network/server", "applying input %d %d %d", input[0], input[1], input[2]); mods_client_input(cid, input); } } else if(p->msg() == NETMSG_CLIENT_ERROR) { const char *reason = p->read_str(); if(p->is_good()) dbg_msg("network/server", "client error. cid=%x reason='%s'", cid, reason); else dbg_msg("network/server", "client error. cid=%x", cid); drop(cid, "client error"); } else { dbg_msg("network/server", "invalid message. cid=%x msg=%x", cid, p->msg()); drop(cid, "invalid message"); } } void process_packet(packet *p, netaddr4 *from) { if(p->msg() == NETMSG_CLIENT_CONNECT) { // we got no state for this client yet const char *version; const char *name; const char *clan; const char *password; const char *skin; version = p->read_str(); name = p->read_str(); clan = p->read_str(); password = p->read_str(); skin = p->read_str(); if(p->is_good()) { // check version if(strcmp(version, TEEWARS_NETVERSION) != 0) { dbg_msg("network/server", "wrong version connecting '%s'", version); + // TODO: send error return; } // look for empty slot, linear search int id = -1; for(int i = 0; i < MAX_CLIENTS; i++) if(clients[i].is_empty()) { id = i; break; } if(id != -1) { // slot found // TODO: perform correct copy here mem_copy(clients[id].name, name, MAX_NAME_LENGTH); mem_copy(clients[id].clan, clan, MAX_CLANNAME_LENGTH); clients[id].state = client::STATE_CONNECTING; clients[id].conn.init(&game_socket, from); clients[id].lastactivity = lasttick; clients[id].name[MAX_NAME_LENGTH-1] = 0; clients[id].clan[MAX_CLANNAME_LENGTH-1] = 0; dbg_msg("network/server", "client connected. '%s' on slot %d", name, id); // TODO: return success send_accept(&clients[id], map_name); } else { // no slot found // TODO: send error dbg_msg("network/server", "client connected but server is full"); for(int i = 0; i < MAX_CLIENTS; i++) dbg_msg("network/server", "\t%d: %d", i, clients[i].state); } } } else { int cid = find_client(from); if(cid >= 0) { if(clients[cid].conn.feed(p)) { // packet is ok unsigned msg = p->msg(); // client found, check state if(((msg>>16)&0xff)&clients[cid].state) { // state is ok client_process_packet(cid, p); } else { // invalid state, disconnect the client drop(cid, "invalid message at this state"); } } else { drop(cid, "connection error"); } } else dbg_msg("network/server", "packet from strange address."); } } void client_timeout(int clientId) { drop(clientId, "client timedout"); } void pump_network() { while(1) { packet p; netaddr4 from; //int bytes = net_udp4_recv( int bytes = game_socket.recv(&from, p.data(), p.max_size()); //int bytes = game_socket.recv(&from, p.data(), p.max_size()); if(bytes <= 0) break; process_packet(&p, &from); } // TODO: check for client timeouts } char *write_int(char *buffer, int integer) { *buffer++ = integer >> 24; *buffer++ = integer >> 16; *buffer++ = integer >> 8; *buffer++ = integer; return buffer; } char *write_netaddr4(char *buffer, NETADDR4 *address) { *buffer++ = address->ip[0]; *buffer++ = address->ip[1]; *buffer++ = address->ip[2]; *buffer++ = address->ip[3]; return write_int(buffer, address->port); } void send_heartbeat(int version, netaddr4 *address, int players, int max_players, const char *name, const char *map_name) { char buffer[216] = {0}; char *d = buffer; d = write_int(d, 'TWHB'); d = write_int(d, version); d = write_netaddr4(d, address); d = write_int(d,players); d = write_int(d, max_players); int len = strlen(name); if (len > 128) len = 128; memcpy(d, name, len); d += 128; len = strlen(map_name); if (len > 64) len = 64; memcpy(d, map_name, len); d += 64; + game_socket.send(&master_server, buffer, sizeof(buffer)); } }; int main(int argc, char **argv) { dbg_msg("server", "starting..."); const char *mapname = "data/demo.map"; const char *servername = 0; // parse arguments for(int i = 1; i < argc; i++) { if(argv[i][0] == '-' && argv[i][1] == 'm' && argv[i][2] == 0 && argc - i > 1) { // -m map i++; mapname = argv[i]; } else if(argv[i][0] == '-' && argv[i][1] == 'n' && argv[i][2] == 0 && argc - i > 1) { // -n server name i++; servername = argv[i]; } else if(argv[i][0] == '-' && argv[i][1] == 'p' && argv[i][2] == 0) { // -p (private server) send_heartbeats = 0; } } if(!mapname) { dbg_msg("server", "no map given (-m MAPNAME)"); return 0; } if(!servername) { dbg_msg("server", "no server name given (-n \"server name\")"); return 0; } server_init(); server s; s.run(servername, mapname); return 0; } \ No newline at end of file diff --git a/src/engine/snapshot.h b/src/engine/snapshot.h new file mode 100644 index 00000000..9d803486 --- /dev/null +++ b/src/engine/snapshot.h @@ -0,0 +1,19 @@ + +struct snapshot +{ + int num_items; + int offsets[1]; + + struct item + { + int type_and_id; + char data[1]; + + int type() { return type_and_id>>16; } + int id() { return type_and_id&(0xffff); } + }; + + char *data_start() { return (char *)&offsets[num_items]; } + item *get_item(int index) { return (item *)(data_start() + offsets[index]); }; +}; + diff --git a/src/engine/versions.h b/src/engine/versions.h new file mode 100644 index 00000000..d70ee721 --- /dev/null +++ b/src/engine/versions.h @@ -0,0 +1,2 @@ +#define TEEWARS_NETVERSION "dev v2" +#define TEEWARS_VERSION "0.2.1-dev" |