From 73aa9b71c1d8b5c5065d1e474f13601da3ca6b20 Mon Sep 17 00:00:00 2001 From: Magnus Auvinen Date: Tue, 22 May 2007 15:03:32 +0000 Subject: started the major restructure of svn --- src/client.cpp | 599 +++++++++++ src/datafile.cpp | 444 ++++++++ src/datafile.h | 20 + src/editor.cpp | 1229 +++++++++++++++++++++ src/game/game.h | 231 ++++ src/game/game_client.cpp | 1992 ++++++++++++++++++++++++++++++++++ src/game/game_server.cpp | 2122 +++++++++++++++++++++++++++++++++++++ src/game/mapres.h | 7 + src/game/mapres_col.cpp | 44 + src/game/mapres_col.h | 10 + src/game/mapres_image.cpp | 41 + src/game/mapres_image.h | 18 + src/game/mapres_tilemap.cpp | 54 + src/game/mapres_tilemap.h | 19 + src/gfx.cpp | 581 ++++++++++ src/interface.h | 711 +++++++++++++ src/lzw.cpp | 213 ++++ src/lzw.h | 2 + src/main.cpp | 25 + src/map.cpp | 48 + src/menu.cpp | 597 +++++++++++ src/packet.h | 297 ++++++ src/server.cpp | 661 ++++++++++++ src/snapshot.h | 19 + src/snd.cpp | 519 +++++++++ src/ui.cpp | 115 ++ src/ui.h | 33 + src/wavpack/arm.S | 461 ++++++++ src/wavpack/arml.S | 491 +++++++++ src/wavpack/bits.c | 140 +++ src/wavpack/coldfire.S | 525 +++++++++ src/wavpack/float.c | 50 + src/wavpack/license.txt | 25 + src/wavpack/metadata.c | 105 ++ src/wavpack/readme.txt | 68 ++ src/wavpack/unpack.c | 785 ++++++++++++++ src/wavpack/wavpack.h | 384 +++++++ src/wavpack/words.c | 560 ++++++++++ src/wavpack/wputils.c | 351 ++++++ src/wavpack/wvfilter.c.no_compile | 200 ++++ 40 files changed, 14796 insertions(+) create mode 100644 src/client.cpp create mode 100644 src/datafile.cpp create mode 100644 src/datafile.h create mode 100644 src/editor.cpp create mode 100644 src/game/game.h create mode 100644 src/game/game_client.cpp create mode 100644 src/game/game_server.cpp create mode 100644 src/game/mapres.h create mode 100644 src/game/mapres_col.cpp create mode 100644 src/game/mapres_col.h create mode 100644 src/game/mapres_image.cpp create mode 100644 src/game/mapres_image.h create mode 100644 src/game/mapres_tilemap.cpp create mode 100644 src/game/mapres_tilemap.h create mode 100644 src/gfx.cpp create mode 100644 src/interface.h create mode 100644 src/lzw.cpp create mode 100644 src/lzw.h create mode 100644 src/main.cpp create mode 100644 src/map.cpp create mode 100644 src/menu.cpp create mode 100644 src/packet.h create mode 100644 src/server.cpp create mode 100644 src/snapshot.h create mode 100644 src/snd.cpp create mode 100644 src/ui.cpp create mode 100644 src/ui.h create mode 100644 src/wavpack/arm.S create mode 100644 src/wavpack/arml.S create mode 100644 src/wavpack/bits.c create mode 100644 src/wavpack/coldfire.S create mode 100644 src/wavpack/float.c create mode 100644 src/wavpack/license.txt create mode 100644 src/wavpack/metadata.c create mode 100644 src/wavpack/readme.txt create mode 100644 src/wavpack/unpack.c create mode 100644 src/wavpack/wavpack.h create mode 100644 src/wavpack/words.c create mode 100644 src/wavpack/wputils.c create mode 100644 src/wavpack/wvfilter.c.no_compile (limited to 'src') diff --git a/src/client.cpp b/src/client.cpp new file mode 100644 index 00000000..3332a630 --- /dev/null +++ b/src/client.cpp @@ -0,0 +1,599 @@ +#include +#include +#include +#include +#include + +#include +#include +#include "interface.h" + + +#include "packet.h" +#include "snapshot.h" +#include "ui.h" + +#include "lzw.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; +void snap_input(void *data, int size) +{ + 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; +} + +// --- 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; + + packet p(NETMSG_CLIENT_CONNECT); + p.write_str("dev v1", 32); // payload + p.write_str(name,MAX_NAME_LENGTH); + p.write_str("no clan", MAX_CLANNAME_LENGTH); + p.write_str("password", 32); + p.write_str("myskin", 32); + send_packet(&p); + } + + void send_done() + { + packet p(NETMSG_CLIENT_DONE); + send_packet(&p); + } + + void send_error(const char *error) + { + packet p(NETMSG_CLIENT_ERROR); + p.write_str(error, 128); + send_packet(&p); + //send_packet(&p); + //send_packet(&p); + } + + void send_input() + { + 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 lastinput = 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(); + + if(time_get()-lastinput > time_per_input) + { + if(get_state() == STATE_ONLINE) + send_input(); + lastinput += time_per_input; + } + + // 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) + { + char map[32]; + p->read_str(map, 32); + + 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) + { + p->read_raw((char*)snapshots[SNAP_INCOMMING] + part*MAX_SNAPSHOT_PACKSIZE, 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 + /* + snapshot *tmp = snapshots[SNAP_PREV]; + snapshots[SNAP_PREV] = snapshots[SNAP_CURRENT]; + snapshots[SNAP_CURRENT] = snapshots[SNAP_INCOMMING]; + snapshots[SNAP_INCOMMING] = tmp; + */ + + 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 client_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 + i++; + 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/datafile.cpp b/src/datafile.cpp new file mode 100644 index 00000000..789aa722 --- /dev/null +++ b/src/datafile.cpp @@ -0,0 +1,444 @@ +#include +#include + +#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/datafile.h b/src/datafile.h new file mode 100644 index 00000000..4e49fa03 --- /dev/null +++ b/src/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/editor.cpp b/src/editor.cpp new file mode 100644 index 00000000..9b633730 --- /dev/null +++ b/src/editor.cpp @@ -0,0 +1,1229 @@ +#include +#include +#include +#include + +#include "interface.h" +#include "datafile.h" +#include "ui.h" + +#include "game/mapres_image.h" +#include "game/mapres_tilemap.h" +//#include "game/mapres_col.h" +#include "game/mapres.h" +#include "game/game.h" + +using namespace baselib; + +static int font_texture = 0; + +struct ent_type +{ + const char *name; + int id; + int item_id; +}; + +static ent_type ent_types[] = { + {"spawn", MAPRES_SPAWNPOINT, 0}, + {"gun", MAPRES_ITEM, ITEM_WEAPON_GUN}, + {"shotgun", MAPRES_ITEM, ITEM_WEAPON_SHOTGUN}, + {"rocket", MAPRES_ITEM, ITEM_WEAPON_ROCKET}, + {"sniper", MAPRES_ITEM, ITEM_WEAPON_SNIPER}, + {"hammer", MAPRES_ITEM, ITEM_WEAPON_HAMMER}, + {"health_1", MAPRES_ITEM, ITEM_HEALTH_1}, + {"health_5", MAPRES_ITEM, ITEM_HEALTH_5}, + {"health_10", MAPRES_ITEM, ITEM_HEALTH_10}, + {"armor_1", MAPRES_ITEM, ITEM_ARMOR_1}, + {"armor_5", MAPRES_ITEM, ITEM_ARMOR_5}, + {"armor_10", MAPRES_ITEM, ITEM_ARMOR_10}, + {"ninja!", MAPRES_ITEM, ITEM_NINJA}, + {0, 0} +}; + + +/******************************************************** + ENTITIES +*********************************************************/ +struct entity +{ + int type; + int x, y; +}; + +static const int MAX_ENTITIES = 1024; +static entity entites[MAX_ENTITIES]; +static int num_entities = 0; + +static int ents_count() +{ + return num_entities; +} + +static entity *ents_get(int index) +{ + return &entites[index]; +} + +static int ents_delete(int index) +{ + if(index < 0 || index >= ents_count()) + return -1; + num_entities--; + entites[index] = entites[num_entities]; + return 0; +} + +static int ents_new(int type, int x, int y) +{ + entites[num_entities].type = type; + entites[num_entities].x = x; + entites[num_entities].y = y; + num_entities++; + return num_entities-1; +} + +/******************************************************** + TILEMAP +*********************************************************/ +struct tile +{ + unsigned char index; + unsigned char flags; +}; + +struct tilemap +{ + int width; + int height; + tile *tiles; +}; + +// allocates a new tilemap and inits the structure +static void tilemap_new(tilemap *tm, int width, int height) +{ + unsigned size = sizeof(tile)*width*height; + mem_zero(tm, sizeof(tilemap)); + + tm->width = width; + tm->height = height; + tm->tiles = (tile *)mem_alloc(size, 1); + mem_zero(tm->tiles, size); +} + +// resizes an tilemap, copies the old data +static void tilemap_resize(tilemap *tm, int new_width, int new_height, int snap) +{ + if(new_width <= 0) new_width = 1; + if(new_height <= 0) new_height = 1; + + new_width = (new_width+snap-1)/snap*snap; + new_height = (new_height+snap-1)/snap*snap; + + unsigned size = sizeof(tile)*new_width*new_height; + tile *newtiles = (tile *)mem_alloc(size, 1); + mem_zero(newtiles, size); + + // copy old tiles + int w = new_width < tm->width ? new_width : tm->width; + int h = new_height < tm->height ? new_height : tm->height; + for(int y = 0; y < h; y++) + for(int x = 0; x < w; x++) + newtiles[y*new_width+x] = tm->tiles[y*tm->width+x]; + + // free old tiles and set new values + mem_free(tm->tiles); + tm->tiles = newtiles; + tm->width = new_width; + tm->height = new_height; +} + +static void tilemap_destroy(tilemap *tm) +{ + mem_free(tm->tiles); + tm->tiles = 0; + tm->width = 0; + tm->height = 0; +} + +static int tilemap_blit(tilemap *dst, tilemap *src, int x, int y) +{ + int count = 0; + // TODO: performance of this could be better + for(int iy = 0; iy < src->height; iy++) + for(int ix = 0; ix < src->width; ix++) + { + if(x+ix >= dst->width || y+iy >= dst->height) + continue; + if(x+ix < 0 || y+iy < 0) + continue; + + count++; + dst->tiles[(y+iy)*dst->width + x+ix] = src->tiles[iy*src->width + ix]; + } + return count; +} + +/******************************************************** + LAYERS +*********************************************************/ +struct layer +{ + tilemap tm; + int tileset_id; + int visible; + int main_layer; +}; + +static const int MAX_LAYERS = 64; +static layer layers[MAX_LAYERS]; +static int num_layers = 0; +static int current_layer = 0; + +static int layers_remove(int index) +{ + if(index < 0 || index >= num_layers) + return 0; + + // free the memory + mem_free(layers[index].tm.tiles); + + // move the layers + for(int i = index; i < num_layers-1; i++) + layers[i] = layers[i+1]; + num_layers--; + return 1; +} + +static int layers_count() +{ + return num_layers; +} + +static layer *layers_get_current() +{ + return &layers[current_layer]; +} + +static layer *layers_get(int index) +{ + return &layers[index]; +} + +static int layers_new(int w, int h) +{ + if(num_layers+1 >= MAX_LAYERS) + return -1; + + tilemap_new(&layers[num_layers].tm, w, h); + layers[num_layers].tileset_id = -1; + layers[num_layers].visible = 1; + layers[num_layers].main_layer = 0; + num_layers++; + return num_layers-1; +} + +static int layers_swap(int index1, int index2) +{ + // swap the two layers + layer temp = layers[index1]; + layers[index1] = layers[index2]; + layers[index2] = temp; + return -1; +} + +static int layers_moveup(int index) +{ + if(index < 0 || index >= num_layers) + return index; + + if(index == 0) + return 0; + + layers_swap(index, index-1); + return index-1; +} + +static int layers_movedown(int index) +{ + if(index < 0 || index >= num_layers) + return index; + + if(index+1 == num_layers) + return index; + + layers_swap(index, index+1); + return index+1; +} + +/******************************************************** + TILESET +*********************************************************/ +struct tileset +{ + int tex_id; + image_info img; +}; + +static const int MAX_TILESETS = 64; +static tileset tilesets[MAX_TILESETS]; +static int num_tilesets = 0; + +static int tilesets_new() +{ + tilesets[num_tilesets].img.width = 0; + tilesets[num_tilesets].img.height = 0; + tilesets[num_tilesets].img.data = 0; + tilesets[num_tilesets].tex_id = -1;// gfx_load_texture_raw(img.width, img.height, img.data); + num_tilesets++; + return num_tilesets-1; +} + + +static void tilesets_clear() +{ + // TODO: remove texture aswell + for(int i = 0; i < num_tilesets; i++) + mem_free(tilesets[num_tilesets].img.data); + num_tilesets = 0; + +} + +static int tilesets_set_img(int index, int w, int h, void *data) +{ + tilesets[index].img.width = w; + tilesets[index].img.height = h; + + if(tilesets[index].img.data) + mem_free(tilesets[index].img.data); + tilesets[index].img.data = data; + tilesets[index].tex_id = gfx_load_texture_raw(w, h, data); + return index; +} + +static int tilesets_count() +{ + return num_tilesets; +} + +static tileset *tilesets_get(int index) +{ + return &tilesets[index]; +} + +/******************************************************** + UI +*********************************************************/ +static void render_tilemap(tilemap *tm, float sx, float sy, float scale) +{ + float frac = (1.0f/512.0f); //2.0f; + gfx_quads_begin(); + for(int y = 0; y < tm->height; y++) + for(int x = 0; x < tm->width; x++) + { + unsigned char d = tm->tiles[y*tm->width+x].index; + if(d) + { + gfx_quads_setsubset( + (d%16)/16.0f+frac, + (d/16)/16.0f+frac, + (d%16)/16.0f+1.0f/16.0f-frac, + (d/16)/16.0f+1.0f/16.0f-frac); + gfx_quads_drawTL(sx+x*scale, sy+y*scale, scale, scale); + } + + //gfx_quads_setsubset(x/16.0f,y/16.0f,(x+1)/16.0f,(y+1)/16.0f); + //gfx_quads_drawTL(sx+x*w,sy+y*h,w,h); + } + gfx_quads_end(); +} + +/******************************************************** + EDITOR +*********************************************************/ + +static tilemap brush = {0}; +static tilemap chooser = {0}; +static float world_offset_x = 0, world_offset_y = 0; +static int world_zoom = 3; +static const char *editor_filename = 0; +static int editor_mode = 0; // 0 == tiles, 1 == ents +static int editor_selected_ent = -1; + +static const int TILEMAPFLAG_READONLY = 1; +static const int TILEMAPFLAG_UISPACE = 2; + +static int ui_do_tilemap(void *id, tilemap *tm, int flags, float x, float y, float scale) +{ + /* + int do_input = 1; + if(inp_key_pressed(keys::lalt) || inp_key_pressed(keys::ralt)) + do_input = 0;*/ + + + float mx = ui_mouse_world_x(); + float my = ui_mouse_world_y(); + if(flags&TILEMAPFLAG_UISPACE) + { + mx = ui_mouse_x(); + my = ui_mouse_y(); + } + + int tmx = (int)((mx-x)/scale); // tilemap x + int tmy = (int)((my-y)/scale); // tilemap y + + static int start_tmx, start_tmy; + static int grabbing = 0; + + //float start_tmx_wx = start_tmx*scale+x; + //float start_tmx_wy = start_tmy*scale+y; + + int select_x = 0; + int select_y = 0; + int select_w = 1; + int select_h = 1; + float select_wx = 0; + float select_wy = 0; + float select_ww = 0; + float select_wh = 0; + + if(ui_hot_item() == id) + { + int x0 = start_tmx; + int y0 = start_tmy; + int x1 = tmx; + int y1 = tmy; + + if(x1 < x0) + { + int tmp = x1; + x1 = x0; + x0 = tmp; + } + + if(y1 < y0) + { + int tmp = y1; + y1 = y0; + y0 = tmp; + } + + select_w = x1-x0; + select_h = y1-y0; + select_w++; + select_h++; + select_x = x0; + select_y = y0; + + select_wx = select_x*scale+x; + select_wy = select_y*scale+y; + select_ww = select_w*scale; + select_wh = select_h*scale; + } + // ui_do_tilemap always tries to steal the focus + ui_set_hot_item(id); + + // render the tilemap + render_tilemap(tm, x, y, scale); + + if(ui_hot_item() == id) + { + if(brush.tiles != 0) + { + // draw brush + render_tilemap(&brush, (tmx-brush.width/2)*scale, (tmy-brush.height/2)*scale, scale); + + gfx_texture_set(-1); + gfx_blend_additive(); + gfx_quads_begin(); + gfx_quads_setcolor(1.0f, 0.0f, 0.0f, 0.25f); + gfx_quads_drawTL((tmx-brush.width/2)*scale, (tmy-brush.height/2)*scale, brush.width*scale, brush.height*scale); + gfx_quads_end(); + gfx_blend_normal(); + } + + if(grabbing == 0) + { + if(ui_mouse_button(0)) + { + //grabbing = 1; + start_tmx = (int)((mx-x)/scale); + start_tmy = (int)((my-y)/scale); + if(brush.tiles == 0) + { + dbg_msg("editor", "grabbing..."); + grabbing = 1; // grab tiles + } + else + { + // paint + if(!(flags&TILEMAPFLAG_READONLY)) + { + layer *l = layers_get_current(); + int px = tmx-brush.width/2; + int py = tmy-brush.height/2; + tilemap_blit(&l->tm, &brush, px, py); + //dbg_msg("editor", "painted %d tiles at (%d,%d)", c, px, py); + } + } + } + } + else + { + gfx_texture_set(-1); + gfx_blend_additive(); + gfx_quads_begin(); + gfx_quads_setcolor(1.0f, 1.0f, 1.0f, 0.25f); + gfx_quads_drawTL(select_wx, select_wy, select_ww, select_wh); + gfx_quads_end(); + gfx_blend_normal(); + + if(!ui_mouse_button(0)) + { + grabbing = 0; + + if(brush.tiles == 0) + { + // create brush + dbg_msg("editor", "creating brush w=%d h=%d p0=(%d,%d)", select_w, select_h, select_x, select_y); + tilemap_new(&brush, select_w, select_h); + + // copy data + for(int y = 0; y < select_h; y++) + for(int x = 0; x < select_w; x++) + brush.tiles[y*select_w+x] = tm->tiles[(select_y+y)*tm->width + select_x+x]; + } + } + } + } + + // raw rect around tilemap + gfx_texture_set(-1); + gfx_quads_begin(); + float w = tm->width*scale; + float h = tm->height*scale; + gfx_quads_drawTL(x-2, y-2, 2, h+4); + gfx_quads_drawTL(x-2, y-2, w+4, 2); + gfx_quads_drawTL(x+w, y-2, 2, h+4); + gfx_quads_drawTL(x-2, y+h, w+4, 2); + gfx_quads_end(); + + return 0; +} + +static int ui_do_entity(void *id, entity *ent, int selected) +{ + float x = (float)ent->x; + float y = (float)ent->y; + float w = 16; + float h = 16; + + float mx = ui_mouse_world_x(); + float my = ui_mouse_world_y(); + + int inside = 0; + int r = 0; + if(mx > x-w/2 && mx < x+w/2 && my > y-h/2 && my < y+h/2) + inside = 1; + + if(inside) + ui_set_hot_item(id); + + if(ui_hot_item() == id && ui_mouse_button(0)) + { + ui_set_active_item(id); + r = 1; + } + + if(ui_active_item() == id) + { + if(!ui_mouse_button(0)) + ui_set_active_item(0); + ent->x = (int)ui_mouse_world_x(); + ent->y = (int)ui_mouse_world_y(); + ent->x = (ent->x/32)*32+16; + ent->y = (ent->y/32)*32+16; + } + + // raw rect around tilemap + gfx_texture_set(-1); + gfx_quads_begin(); + if(selected) + gfx_quads_setcolor(1.0f, 0.5f, 0.5f, 0.95f); + else if(ui_hot_item() == id) + gfx_quads_setcolor(1.0f, 1.0f, 1.0f, 0.95f); + else + gfx_quads_setcolor(0.75f, 0.75f, 0.75f, 0.95f); + + gfx_quads_drawTL(x-w/2, y-w/2, w, h); + gfx_quads_end(); + + gfx_texture_set(font_texture); + if(ent->type >= 0) + gfx_quads_text(x-4, y-h/2-4, 24.0f, ent_types[ent->type].name); + + return r; +} + + +static int editor_reset() +{ + // delete all layers + while(layers_count()) + layers_remove(layers_count()-1); + + //layers_new(50, 50); + tilemap_destroy(&brush); + current_layer = 0; + + tilesets_clear(); + + // init chooser + static tile chooser_tiles[256]; + for(int i = 0; i < 256; i++) + { + chooser_tiles[i].index = i; + chooser_tiles[i].flags = 0; + } + + chooser.width = 16; + chooser.height = 16; + chooser.tiles = chooser_tiles; + + return 0; +} + +void draw_editor_button(void *id, const char *text, int checked, float x, float y, float w, float h, void *extra) +{ + gfx_blend_normal(); + gfx_texture_set(-1); + gfx_quads_begin(); + if(ui_hot_item() == id) + gfx_quads_setcolor(1,1,1,1); + else if(checked) + gfx_quads_setcolor(0.75f,0.5f,0.5f,1); + else + gfx_quads_setcolor(0.5f,0.5f,0.5f,1); + + gfx_quads_drawTL(x,y,w,h); + gfx_quads_end(); + gfx_texture_set(font_texture); + gfx_quads_text(x, y, 6.2f, text); +} + +static int editor_loadimage = -1; + +static void editor_listdir_callback(const char *name, int is_dir, void *user) +{ + if(name[0] == '.') // skip this shit! + return; + + int *y = (int*)user; + if(ui_do_button((void*)(*y + 1), name, 0, 10, 10 + *y * 8, 100, 6, draw_editor_button)) + { + char buf[512]; + sprintf(buf, "tilesets/%s", name); + + image_info img; + if(!gfx_load_tga(&img, buf)) + return; + + tilesets_set_img(editor_loadimage, img.width, img.height, img.data); + editor_loadimage = -1; + } + *y += 1; +} + +static void editor_render_loadfile_dialog() +{ + gfx_clear(0.2f,0.2f,0.8f); + // GUI coordsys + gfx_mapscreen(0,0,400.0f,300.0f); + + int index = 0; + fs_listdir("tilesets", editor_listdir_callback, &index); +} + +static void editor_render() +{ + if(editor_loadimage != -1) + { + editor_render_loadfile_dialog(); + return; + } + + // background color + gfx_clear(0.2f,0.2f,0.8f); + + // world coordsys + float zoom = world_zoom; + gfx_mapscreen(world_offset_x,world_offset_y,world_offset_x+400.0f*zoom,world_offset_y+300.0f*zoom); + + for(int i = 0; i < layers_count(); i++) + { + layer *l = layers_get(i); + + gfx_texture_set(-1); + if(l->tileset_id >= 0 && l->tileset_id < tilesets_count()) + gfx_texture_set(tilesets_get(l->tileset_id)->tex_id); + + if(editor_mode == 0) + { + if(l == layers_get_current()) + { + // do current layer + ui_do_tilemap(&l->tm, &l->tm, 0, 0, 0, 32.0f); + } + else if(l->visible) + { + // render layer + render_tilemap(&l->tm, 0, 0, 32.0f); + } + } + else + render_tilemap(&l->tm, 0, 0, 32.0f); + } + + if(editor_mode == 1) + { + // ents mode + for(int i = 0; i < ents_count(); i++) + { + if(ui_do_entity(ents_get(i), ents_get(i), i == editor_selected_ent)) + editor_selected_ent = i; + + } + } + + // GUI coordsys + gfx_mapscreen(0,0,400.0f,300.0f); + + // toolbox + float toolbox_width = 50.0f; + + if(editor_mode == 0) + { + float layerbox_x = 0; + float layerbox_y = 0; + int count = 1; + int main_layer = -1; + for(int i = 0; i < layers_count(); i++) + { + layer *l = layers_get(i); + char buf[128]; + if(l->main_layer) + { + main_layer = i; + sprintf(buf, "Main Layer (%dx%d)", l->tm.width, l->tm.height); + count = 1; + } + else + { + if(main_layer == -1) + sprintf(buf, "Background %d (%dx%d)", count, l->tm.width, l->tm.height); + else + sprintf(buf, "Foreground %d (%dx%d)", count, l->tm.width, l->tm.height); + count++; + } + + // show / hide layer + const char *text = " "; + if(layers_get(i)->visible) + text = "V"; + + if(ui_do_button(&layers_get(i)->visible, text, 0, layerbox_x, layerbox_y+i*8, 6, 6, draw_editor_button)) + layers_get(i)->visible = layers_get(i)->visible^1; + + if(ui_do_button(&layers_get(i)->tileset_id, buf, current_layer == i, layerbox_x+8, layerbox_y+i*8, toolbox_width-8, 6, draw_editor_button)) + { + // select layer + current_layer = i; + } + } + + // draw buttons + { + static int push_button, pull_button; + float y = 150; + float x = 0; + if(ui_do_button(&push_button, "push", 0, x, y, toolbox_width, 6, draw_editor_button)) + current_layer = layers_moveup(current_layer); + y += 7; + + if(ui_do_button(&pull_button, "pull", 0, x, y, toolbox_width, 6, draw_editor_button)) + current_layer = layers_movedown(current_layer); + y += 10; + + static int w_inc, w_dec; + int resize_amount = 10; + if(ui_do_button(&w_dec, "width-", 0, x, y, toolbox_width, 6, draw_editor_button)) + tilemap_resize(&layers_get_current()->tm, layers_get_current()->tm.width-resize_amount, layers_get_current()->tm.height, resize_amount); + y += 7; + if(ui_do_button(&w_inc, "width+", 0, x, y, toolbox_width, 6, draw_editor_button)) + tilemap_resize(&layers_get_current()->tm, layers_get_current()->tm.width+resize_amount, layers_get_current()->tm.height, resize_amount); + y += 10; + + static int h_inc, h_dec; + if(ui_do_button(&h_dec, "height-", 0, x, y, toolbox_width, 6, draw_editor_button)) + tilemap_resize(&layers_get_current()->tm, layers_get_current()->tm.width, layers_get_current()->tm.height-resize_amount, resize_amount); + y += 7; + if(ui_do_button(&h_inc, "height+", 0, x, y, toolbox_width, 6, draw_editor_button)) + tilemap_resize(&layers_get_current()->tm, layers_get_current()->tm.width, layers_get_current()->tm.height+resize_amount, resize_amount); + y += 10; + } + + + float tilesetsbox_x = 0; + float tilesetsbox_y = 230; + for(int i = 0; i < tilesets_count(); i++) + { + char buf[128]; + sprintf(buf, "#%d %dx%d", i, tilesets_get(i)->img.width, tilesets_get(i)->img.height); + if(ui_do_button(&tilesets_get(i)->img, "L", layers_get(current_layer)->tileset_id == i, tilesetsbox_x, tilesetsbox_y+i*7, 6, 6, draw_editor_button)) + { + // load image + editor_loadimage = i; + } + + if(ui_do_button(tilesets_get(i), buf, layers_get(current_layer)->tileset_id == i, tilesetsbox_x+8, tilesetsbox_y+i*7, toolbox_width-8, 6, draw_editor_button)) + { + // select tileset for layer + dbg_msg("editor", "settings tileset %d=i", current_layer, i); + layers_get(current_layer)->tileset_id = i; + } + } + + // (add) button for tilesets + static int load_tileset; + if(ui_do_button(&load_tileset, "(Add)", 0, tilesetsbox_x, tilesetsbox_y+tilesets_count()*7+3, toolbox_width, 6, draw_editor_button)) + tilesets_new(); + + if(brush.tiles != 0) + { + // right mouse button or C clears the brush + if(ui_mouse_button(1) || inp_key_pressed('C')) + tilemap_destroy(&brush); + } + + if(inp_key_pressed(keys::space)) + { + // render chooser + float chooser_x = 0; + float chooser_y = 0; + + gfx_texture_set(-1); + layer *l = layers_get_current(); + if(l && l->tileset_id >= 0 && l->tileset_id < tilesets_count()) + gfx_texture_set(tilesets_get(l->tileset_id)->tex_id); + ui_do_tilemap(&chooser, &chooser, TILEMAPFLAG_READONLY|TILEMAPFLAG_UISPACE, chooser_x, chooser_y, 16.0f); + } + } + else + { + int current_type = -1; + if(editor_selected_ent >= 0 && editor_selected_ent < ents_count()) + current_type = ents_get(editor_selected_ent)->type; + + float y = 0; + for(int i = 0; ent_types[i].name; i++) + { + if(ui_do_button(&ent_types[i], ent_types[i].name, current_type==i, 0, y, toolbox_width, 6, draw_editor_button)) + { + if(editor_selected_ent >= 0 && editor_selected_ent < ents_count()) + ents_get(editor_selected_ent)->type = i; + } + y += 8; + } + + y += 8; + static int add, del; + if(ui_do_button(&add, "Add", 0, 0, y, toolbox_width, 6, draw_editor_button)) + { + int x = (int)(world_offset_x+400*zoom/2)/32*32+16; + int y = (int)(world_offset_y+300*zoom/2)/32*32+16; + ents_new(0, x, y); + } + + y += 8; + if(ui_do_button(&del, "Del", 0, 0, y, toolbox_width, 6, draw_editor_button)) + ents_delete(editor_selected_ent); + } +} + +int editor_load(const char *filename) +{ + datafile *df = datafile_load(filename); + if(!df) + return 0; + + // load tilesets + { + int start, count; + datafile_get_type(df, MAPRES_IMAGE, &start, &count); + for(int i = 0; i < count; i++) + { + mapres_image *img = (mapres_image *)datafile_get_item(df, start+i, 0, 0); + void *data = datafile_get_data(df, img->image_data); + int id = tilesets_new(); + void *data_cpy = mem_alloc(img->width*img->height*4, 1); + mem_copy(data_cpy, data, img->width*img->height*4); + tilesets_set_img(id, img->width, img->height, data_cpy); + } + } + + // load tilemaps + { + int start, num; + datafile_get_type(df, MAPRES_TILEMAP, &start, &num); + for(int t = 0; t < num; t++) + { + mapres_tilemap *tmap = (mapres_tilemap *)datafile_get_item(df, start+t,0,0); + //unsigned char *data = (unsigned char *)datafile_get_data(df, tmap->data); + + layer *l = layers_get(layers_new(tmap->width, tmap->height)); + mem_copy(l->tm.tiles, datafile_get_data(df, tmap->data), tmap->width*tmap->height*2); + l->tileset_id = tmap->image; + l->main_layer = tmap->main; + + // force a main layer + if(num == 1) + l->main_layer = 1; + } + } + + // load entities + { + int type = -1; + for(int i = 0; ent_types[i].name; i++) + { + if(ent_types[i].id == MAPRES_SPAWNPOINT) + { + type = i; + break; + } + } + + int start, num; + datafile_get_type(df, MAPRES_SPAWNPOINT, &start, &num); + for(int t = 0; t < num; t++) + { + mapres_spawnpoint *sp = (mapres_spawnpoint *)datafile_get_item(df, start+t,0,0); + ents_new(type, sp->x, sp->y); + } + } + + { + int start, num; + datafile_get_type(df, MAPRES_ITEM, &start, &num); + for(int t = 0; t < num; t++) + { + mapres_item *it = (mapres_item *)datafile_get_item(df, start+t,0,0); + + int type = -1; + for(int i = 0; ent_types[i].name; i++) + { + if(ent_types[i].id == MAPRES_ITEM && ent_types[i].item_id == it->type) + { + dbg_msg("editor", "i type=%x mapped=%d", it->type, i); + type = i; + break; + } + } + + ents_new(type, it->x, it->y); + } + } + return 1; +} + +int editor_save(const char *filename) +{ + datafile_out *df = datafile_create(filename); + + // add tilesets + for(int i = 0; i < tilesets_count(); i++) + { + mapres_image img; + tileset *ts = tilesets_get(i); + img.width = ts->img.width; + img.height = ts->img.height; + img.image_data = datafile_add_data(df, ts->img.width*ts->img.height*4, ts->img.data); + datafile_add_item(df, MAPRES_IMAGE, i, sizeof(img), &img); + } + + // add tilemaps + for(int i = 0; i < layers_count(); i++) + { + layer *l = layers_get(i); + mapres_tilemap tm; + tm.image = l->tileset_id; + tm.width = l->tm.width; + tm.height = l->tm.height; + tm.x = 0; + tm.y = 0; + tm.main = l->main_layer; + tm.scale = 1<<16; + tm.data = datafile_add_data(df, l->tm.width*l->tm.height*2, l->tm.tiles); + datafile_add_item(df, MAPRES_TILEMAP, i, sizeof(tm), &tm); + } + + // add collision + char *collisiondata = 0x0; + for(int i = 0; i < layers_count(); i++) + { + layer *l = layers_get(i); + if(l->main_layer) + { + mapres_collision col; + col.width = l->tm.width; + col.height = l->tm.height; + + collisiondata = (char *)mem_alloc(col.width*col.height, 1); + for(int y = 0, c = 0; y < col.height; y++) + for(int x = 0; x < col.width; x++, c++) + { + if(l->tm.tiles[c].index) + collisiondata[c] = 1; + else + collisiondata[c] = 0; + } + + col.data_index = datafile_add_data(df, col.width*col.height, collisiondata); + datafile_add_item(df, MAPRES_COLLISIONMAP, 0, sizeof(col), &col); + break; + } + } + + // add spawnpoints + for(int i = 0, id = 0; i < ents_count(); i++) + { + entity *ent = ents_get(i); + if(ent->type >= 0 && ent_types[ent->type].id == MAPRES_SPAWNPOINT) + { + mapres_spawnpoint sp; + sp.x = ent->x; + sp.y = ent->y; + sp.type = 0; + datafile_add_item(df, MAPRES_SPAWNPOINT, id, sizeof(sp), &sp); + id++; + } + } + + // add items + for(int i = 0, id = 0; i < ents_count(); i++) + { + entity *ent = ents_get(i); + if(ent->type >= 0 && ent_types[ent->type].id == MAPRES_ITEM) + { + mapres_item it; + it.x = ent->x; + it.y = ent->y; + it.type = ent_types[ent->type].item_id; + dbg_msg("editor", "i mapped=%d type=%x", ent->type, it.type); + datafile_add_item(df, MAPRES_ITEM, id, sizeof(it), &it); + id++; + } + } + + // finish adn clean up + datafile_finish(df); + mem_free(collisiondata); + + return 0; +} + +static int editor_loop() +{ + int mouse_x = 0; + int mouse_y = 0; + + mouse::set_mode(mouse::mode_relative); + + while(!inp_key_pressed(keys::esc)) + { + // update input + inp_update(); + + // handle mouse movement + float mx, my, mwx, mwy; + int rx, ry; + { + inp_mouse_relative(&rx, &ry); + mouse_x += rx; + mouse_y += ry; + if(mouse_x < 0) mouse_x = 0; + if(mouse_y < 0) mouse_y = 0; + if(mouse_x > gfx_screenwidth()) mouse_x = gfx_screenwidth(); + if(mouse_y > gfx_screenheight()) mouse_y = gfx_screenheight(); + + // update the ui + mx = (mouse_x/(float)gfx_screenwidth())*400.0f; + my = (mouse_y/(float)gfx_screenheight())*300.0f; + mwx = world_offset_x+mx*world_zoom; // adjust to zoom and offset + mwy = world_offset_y+my*world_zoom; // adjust to zoom and offset + + int buttons = 0; + if(inp_mouse_button_pressed(0)) buttons |= 1; + if(inp_mouse_button_pressed(1)) buttons |= 2; + if(inp_mouse_button_pressed(2)) buttons |= 4; + + ui_update(mx,my,mwx,mwy,buttons); + } + + // + editor_render(); + + if(inp_key_pressed(keys::lalt) || inp_key_pressed(keys::ralt)) + { + static int moveid; + ui_set_hot_item(&moveid); + if(inp_mouse_button_pressed(0)) + { + world_offset_x -= rx*2; + world_offset_y -= ry*2; + } + } + + + // render butt ugly mouse cursor + gfx_texture_set(-1); + gfx_quads_begin(); + gfx_quads_setcolor(0,0,0,1); + gfx_quads_draw_freeform(mx,my,mx,my, + mx+7,my, + mx,my+7); + gfx_quads_setcolor(1,1,1,1); + gfx_quads_draw_freeform(mx+1,my+1,mx+1,my+1, + mx+5,my+1, + mx+1,my+5); + gfx_quads_end(); + + // swap the buffers + gfx_swap(); + + // + if(keys::pressed(keys::f1)) + mouse::set_mode(mouse::mode_absolute); + if(keys::pressed(keys::f2)) + mouse::set_mode(mouse::mode_relative); + + + // mode switch + if(inp_key_down(keys::tab)) + editor_mode ^= 1; + + // zoom in + if(inp_key_down(keys::kp_add)) + { + world_zoom--; + if(world_zoom < 3) + world_zoom = 3; + } + + // zoom out + if(inp_key_down(keys::kp_subtract)) + { + world_zoom++; + if(world_zoom > 8) + world_zoom = 8; + } + + if(inp_key_pressed(keys::lctrl) || inp_key_pressed(keys::rctrl)) + { + if(inp_key_down('L')) + { + int w = 50, h = 50; + for(int i = 0; i < layers_count(); i++) + { + layer *l = layers_get(i); + if(l->main_layer) + { + w = l->tm.width; + h = l->tm.height; + break; + } + } + // copy main layer size + layers_new(w, h); + } + + if(inp_key_down('S')) + { + dbg_msg("editor", "save"); + editor_save(editor_filename); + } + + } + + if(inp_key_down(keys::f5)) + { + dbg_msg("editor", "quick save"); + editor_save("quicksave.map"); + } + + if(inp_key_down(keys::f8)) + { + dbg_msg("editor", "quick load"); + int s = current_layer; + editor_reset(); + editor_load("quicksave.map"); + current_layer = s; + if(current_layer >= layers_count()) + current_layer = layers_count(); + + } + + // be nice + thread_sleep(1); + } + + return 0; +} + + +int editor_main(int argc, char **argv) +{ + dbg_msg("editor", "starting..."); + + // parse arguments + for(int i = 1; i < argc; i++) + { + if(argv[i][0] == '-' && argv[i][1] == 'e' && argv[i][2] == 0 && argc - i > 1) + { + // -e NAME + i++; + editor_filename = argv[i]; + } + } + + if(!editor_filename) + { + dbg_msg("editor", "no filename given"); + return -1; + } + + if(!gfx_init(false)) + return -1; + + // reset and start + font_texture = gfx_load_texture_tga("data/debug_font.tga"); + editor_reset(); + + // load or new + if(!editor_load(editor_filename)) + { + layer *l = layers_get(layers_new(50, 50)); + l->main_layer = 1; + } + + /* + ents_new(0, 10, 10); + ents_new(0, 10, 10); + ents_new(0, 10, 10); + ents_new(0, 10, 10); + */ + + editor_loop(); + + return 0; +} diff --git a/src/game/game.h b/src/game/game.h new file mode 100644 index 00000000..a1817eea --- /dev/null +++ b/src/game/game.h @@ -0,0 +1,231 @@ +#include +#include +#include +#include "../interface.h" +#include "mapres_col.h" + +// Don't tweak :) +const float pi = 3.14159265358979f; + +#define LERP(a,b,t) (a + (b-a) * t) +#define min(a, b) ( a > b ? b : a) +#define max(a, b) ( a > b ? a : b) + +inline baselib::vec2 get_direction(int angle) +{ + float a = angle/256.0f; + return baselib::vec2(cosf(a), sinf(a)); +} + +inline float get_angle(baselib::vec2 dir) +{ + float a = atan(dir.y/dir.x); + if(dir.x < 0) + a = a+pi; + return a; +} + +inline bool col_check_point(float x, float y) { return col_check_point((int)x, (int)y); } +inline bool col_check_point(baselib::vec2 p) { return col_check_point(p.x, p.y); } + +// Network stuff + +enum +{ + OBJTYPE_NULL=0, + OBJTYPE_PLAYER, + OBJTYPE_PROJECTILE, + OBJTYPE_POWERUP, + EVENT_EXPLOSION, + EVENT_HEALTHMOD, + EVENT_SOUND, + EVENT_SMOKE, +}; + +enum +{ + EMOTE_NORMAL=0, + EMOTE_BLINK, + EMOTE_WINK, + EMOTE_PAIN, + EMOTE_HAPPY, +}; + +struct player_input +{ + int left; + int right; + int angle; + int jump; + int fire; + int hook; + int blink; + int activeweapon; +}; + +struct ev_explosion +{ + int x, y; +}; + +struct ev_sound +{ + int x, y; + int sound; // if (0x80000000 flag is set -> looping) if (0x40000000 is set -> stop looping +}; + +struct ev_healthmod +{ + int x, y; + int amount; +}; + +struct obj_projectile +{ + int type; + int x, y; + int vx, vy; +}; + +struct obj_powerup +{ + int type; + int subtype; + int x, y; + int vx, vy; +}; + +struct obj_player +{ + int name[8]; + + int local; + int clientid; + + int health; + int armor; + int ammocount; + + int x, y; + int vx, vy; + int angle; + + // current active weapon + int weapon; + // current active modifier + int modifier; + + // num attack ticks left of current attck + int attackticks; + int attacklen; + int visualtimeattack; + + int score; + int emote; + + int hook_active; + int hook_x, hook_y; +}; + +enum +{ + WEAPON_TYPE_GUN = 0, + WEAPON_TYPE_ROCKET = 1, + WEAPON_TYPE_SHOTGUN = 2, + WEAPON_TYPE_MELEE = 3, + WEAPON_NUMWEAPONS, + //WEAPON_TYPE_SNIPER = 2, + + POWERUP_TYPE_HEALTH = 0, + POWERUP_TYPE_ARMOR = 1, + POWERUP_TYPE_WEAPON = 2, + POWERUP_TYPE_NINJA = 3, + POWERUP_TYPE_TIMEFIELD = 4, + POWERUP_TYPE_NUMPOWERUPS, + + PLAYER_MAXHEALTH = 10, + PLAYER_MAXARMOR = 10, + + MODIFIER_TYPE_NINJA = 0, + MODIFIER_TYPE_TIMEFIELD = 1, + MODIFIER_NUMMODIFIERS, +}; + + +struct mapres_spawnpoint +{ + int x, y; + int type; +}; + +struct mapres_item +{ + int x, y; + int type; +}; + +enum +{ + MAPRES_SPAWNPOINT=1, + MAPRES_ITEM=2, + + ITEM_NULL=0, + ITEM_WEAPON_GUN=0x00010001, + ITEM_WEAPON_SHOTGUN=0x00010002, + ITEM_WEAPON_ROCKET=0x00010003, + ITEM_WEAPON_SNIPER=0x00010004, + ITEM_WEAPON_HAMMER=0x00010005, + ITEM_HEALTH_1 =0x00020001, + ITEM_HEALTH_5 =0x00020005, + ITEM_HEALTH_10=0x00020010, + ITEM_ARMOR_1=0x00030001, + ITEM_ARMOR_5=0x00030005, + ITEM_ARMOR_10=0x00030010, + ITEM_NINJA=0x00040001, +}; + +// sound categories and stuff +enum +{ + SOUND_FIRE_GUN = 0, + SOUND_FIRE_SHOTGUN, + SOUND_FIRE_ROCKET, + SOUND_FIRE_MELEE, + SOUND_FIRE_NINJA, + + + // impacts with world + SOUND_IMPACT_PROJECTILE_GUN, + SOUND_IMPACT_PROJECTILE_SHOTGUN, + SOUND_IMPACT_PROJECTILE_ROCKET, + + + // chain ? + + // Player movement + SOUND_PLAYER_JUMP, + SOUND_PLAYER_HURT_SHORT, + SOUND_PLAYER_HURT_LONG, + SOUND_PLAYER_SPAWN, + SOUND_PLAYER_CHAIN_LOOP, + SOUND_PLAYER_CHAIN_IMPACT, + SOUND_PLAYER_IMPACT, + SOUND_PLAYER_IMPACT_NINJA, + SOUND_PLAYER_DIE, + SOUND_PLAYER_SWITCHWEAPON, + SOUND_PLAYER_EQUIP, + SOUND_PLAYER_LAND, + + SOUND_NUMSOUNDS, + + + // extra defs (for future?) + SOUND_EQUIP_GUN = SOUND_PLAYER_EQUIP, + SOUND_EQUIP_ROCKET = SOUND_PLAYER_EQUIP, + SOUND_EQUIP_SHOTGUN = SOUND_PLAYER_EQUIP, + SOUND_EQUIP_MELEE = SOUND_PLAYER_EQUIP, + + SOUND_LOOPFLAG_STARTLOOP = 0x80000000, + SOUND_LOOPFLAG_STOPLOOP = 0x40000000, + SOUND_MASK = ~(SOUND_LOOPFLAG_STARTLOOP | SOUND_LOOPFLAG_STOPLOOP), +}; diff --git a/src/game/game_client.cpp b/src/game/game_client.cpp new file mode 100644 index 00000000..6e431591 --- /dev/null +++ b/src/game/game_client.cpp @@ -0,0 +1,1992 @@ +#include +#include +#include "game.h" +#include "mapres_image.h" +#include "mapres_tilemap.h" + +using namespace baselib; + +static int texture_char_default = 0; +static int texture_game = 0; +static int texture_weapon = 0; +static int texture_sun = 0; +static int texture_particles = 0; + +struct weapontexcell +{ + float x; + float y; + float w; + float h; +}; +struct renderparams +{ + float sizex; + float sizey; + float offsetx; + float offsety; +}; +int numcellsx = 32; +int numcellsy = 32; +renderparams weaponrenderparams[WEAPON_NUMWEAPONS]; +renderparams modifierrenderparams[WEAPON_NUMWEAPONS]; + +weapontexcell weaponprojtexcoord[WEAPON_NUMWEAPONS]; +weapontexcell weapontexcoord[WEAPON_NUMWEAPONS]; +weapontexcell weapontexcoordcursor[WEAPON_NUMWEAPONS]; + +weapontexcell poweruptexcoord[POWERUP_TYPE_NUMPOWERUPS]; + +weapontexcell modifiertexcoord[MODIFIER_NUMMODIFIERS]; +weapontexcell modifiertexcoordcursor[MODIFIER_NUMMODIFIERS]; + +int nummuzzletex[WEAPON_NUMWEAPONS]; +weapontexcell muzzletexcoord[WEAPON_NUMWEAPONS][3]; +renderparams muzzleparams[WEAPON_NUMWEAPONS]; + +#define NUMHADOKENS 6 +#define NUMSTARS 2 +#define NUMPARTICLES 9 +int particlesnumcellsx = 16; +int particlesnumcellsy = 16; +weapontexcell chaintexcoord; +weapontexcell chainheadtexcoord; +weapontexcell stars[NUMSTARS]; + +float lifemodifier[NUMPARTICLES]; +vec4 particlecolors[NUMPARTICLES]; +weapontexcell particlestexcoord[NUMPARTICLES]; + +int charnumcellsx = 8; +int charnumcellsy = 32; +int charoffsety = 2; +weapontexcell body[2]; +weapontexcell leye; +weapontexcell reye; +weapontexcell feet[2]; + +int charids[16] = { 2,10,0,4,12,6,14,1,9,15,13,11,7,5,8,3 }; + +renderparams hadokenparams[6]; +weapontexcell hadoken[6]; + +float recoils[WEAPON_NUMWEAPONS] = { 10.0f, 10.0f, 10.0f, 10.0f }; + +static int font_texture = 0; +static vec2 mouse_pos; + + +static vec2 local_player_pos; +static obj_player *local_player; + +float frandom() +{ + return rand()/(float)(RAND_MAX); +} + +float sign(float f) +{ + return f<0.0f?-1.0f:1.0f; +} + +// sound helpers +template +class sound_kit +{ +private: + int sounds[N]; + int last_id; +public: + sound_kit() : last_id(-1) { } + + int& operator[](int id) { return sounds[id]; } + + inline void play_random(float vol = 1.0f, float pan = 0.0f); +}; + +template<> +inline void sound_kit<1>::play_random(float vol, float pan) +{ + snd_play(sounds[0], SND_PLAY_ONCE, vol, pan); +} + +template +inline void sound_kit::play_random(float vol, float pan) +{ + int id; + do { + id = rand() % N; + } while(id == last_id); + snd_play(sounds[id], SND_PLAY_ONCE, vol, pan); + last_id = id; +} + + +// sound volume tweak +static const float stereo_separation = 0.01f; +static const float stereo_separation_deadzone = 512.0f; +static const float volume_distance_falloff = 100.0f; +static const float volume_distance_deadzone = 512.0f; +static const float volume_gun = 0.5f; +static const float volume_tee = 0.5f; +static const float volume_hit = 0.5f; +static const float volume_music = 0.8f; + +// sounds +sound_kit<3> sound_gun_fire; +sound_kit<3> sound_shotty_fire; +sound_kit<3> sound_flump_launch; +sound_kit<3> sound_hammer_swing; +sound_kit<3> sound_ninja_attack; + +sound_kit<3> sound_flump_explode; +sound_kit<4> sound_ninja_hit; + +sound_kit<3> sound_weapon_switch; + +sound_kit<12> sound_pain_short; +sound_kit<2> sound_pain_long; + +sound_kit<4> sound_body_jump; +sound_kit<4> sound_body_land; +sound_kit<2> sound_body_splat; + +sound_kit<7> sound_spawn; +sound_kit<2> sound_tee_cry; + +sound_kit<1> sound_hook_loop; +sound_kit<3> sound_hook_attach; + +void sound_vol_pan(const vec2& p, float *vol, float *pan) +{ + vec2 player_to_ev = p - local_player_pos; + *pan = 0.0f; + *vol = 1.0f; + + if(abs(player_to_ev.x) > stereo_separation_deadzone) + { + *pan = stereo_separation * (player_to_ev.x - sign(player_to_ev.x)*stereo_separation_deadzone); + if(*pan < -1.0f) *pan = -1.0f; + if(*pan > 1.0f) *pan = 1.0f; + } + + float len = length(player_to_ev); + if(len > volume_distance_deadzone) + { + *vol = volume_distance_falloff / (len - volume_distance_deadzone); + + if(*vol < 0.0f) *vol = 0.0f; + if(*vol > 1.0f) *vol = 1.0f; + } +} + +// TODO: we should do something nicer then this +static void cell_select_ex(int cx, int cy, float x, float y, float w, float h) +{ + gfx_quads_setsubset(x/(float)cx,y/(float)cy,(x+w)/(float)cx,(y+h)/(float)cy); +} + +static void cell_select_ex_flip_x(int cx, int cy, float x, float y, float w, float h) +{ + gfx_quads_setsubset((x+w)/(float)cx,y/(float)cy,x /(float)cx,(y+h)/(float)cy); +} + +static void cell_select_ex_flip_y(int cx, int cy, float x, float y, float w, float h) +{ + gfx_quads_setsubset(x/(float)cx,(y+h)/(float)cy,(x+w)/(float)cx,y/(float)cy); +} + +static void cell_select(int x, int y, int w, int h) +{ + gfx_quads_setsubset(x/16.0f,y/16.0f,(x+w)/16.0f,(y+h)/16.0f); +} + +inline void cell_select_flip_x(int x, int y, int w, int h) +{ + gfx_quads_setsubset((x+w)/16.0f,y/16.0f,(x)/16.0f,(y+h)/16.0f); +} + +inline void cell_select_flip_y(int x, int y, int w, int h) +{ + gfx_quads_setsubset(x/16.0f,(y+h)/16.0f,(x+w)/16.0f,(y)/16.0f); +} + +struct particle +{ + vec2 pos; + vec2 vel; + float life; + float max_life; + float size; + + float rot; + float rotspeed; + + float gravity; + float friction; + int iparticle; + + vec4 color; +}; + +void move_point(vec2 *inout_pos, vec2 *inout_vel, float elasticity) +{ + vec2 pos = *inout_pos; + vec2 vel = *inout_vel; + if(col_check_point(pos + vel)) + { + int affected = 0; + if(col_check_point(pos.x + vel.x, pos.y)) + { + inout_vel->x *= -elasticity; + affected++; + } + + if(col_check_point(pos.x, pos.y + vel.y)) + { + inout_vel->y *= -elasticity; + affected++; + } + + if(affected == 0) + { + inout_vel->x *= -elasticity; + inout_vel->y *= -elasticity; + } + } + else + { + *inout_pos = pos + vel; + } +} + +class health_texts +{ +public: + int64 lastupdate; + struct item + { + vec2 pos; + vec2 vel; + int amount; + int istar; + float life; + float startangle; + }; + + enum + { + MAX_ITEMS=16, + }; + + health_texts() + { + lastupdate = 0; + } + + item items[MAX_ITEMS]; + int num_items; + + item *create_i() + { + if (num_items < MAX_ITEMS) + { + item *p = &items[num_items]; + num_items++; + return p; + } + return 0; + } + + void destroy_i(item *i) + { + num_items--; + *i = items[num_items]; + } + + void create(vec2 pos, int amount) + { + amount = max(1,amount); + for (int j = 0; j < amount; j++) + { + float a = j/(float)amount-0.5f; + item *i = create_i(); + if (i) + { + i->pos = pos; + i->pos.y -= 20.0f; + i->pos.x += ((float)rand()/(float)RAND_MAX) * 5.0f; + i->amount = amount; + i->life = 1.5f; + i->istar = rand() % NUMSTARS; + i->vel = vec2(((float)rand()/(float)RAND_MAX) * 50.0f,-150.0f); + i->startangle = (( (float)rand()/(float)RAND_MAX) - 1.0f) * 2.0f * pi; + } + } + } + + void render() + { + if (!lastupdate) + lastupdate = time_get(); + + int64 lasttime = lastupdate; + lastupdate = time_get(); + + float delta = (float) (lastupdate - lasttime) / (float)time_freq(); + gfx_texture_set(texture_particles); + gfx_quads_begin(); + for(int i = 0; i < num_items;) + { + items[i].vel += vec2(0,500.0f) * delta; + items[i].pos += items[i].vel * delta; + items[i].life -= delta; + //items[i].pos.y -= frametime*15.0f; + if(items[i].life < 0.0f) + destroy_i(&items[i]); + else + { + gfx_quads_setcolor(1.0f,1.0f,1.0f, items[i].life / 1.5f); + gfx_quads_setrotation(items[i].startangle + items[i].life * 2.0f); + float size = 64.0f; + cell_select_ex(particlesnumcellsx,particlesnumcellsy, stars[items[i].istar].x,stars[items[i].istar].y, stars[items[i].istar].w, stars[items[i].istar].h); + gfx_quads_draw(items[i].pos.x-size/2, items[i].pos.y-size/2, size, size); + /*char buf[32]; + if(items[i].amount < 0) + { + sprintf(buf, "%d", items[i].amount*-1); + } + else + { + sprintf(buf, "%d", items[i].amount); + } + float size = 42.0f; + if(items[i].life > 1.25f) + size += 42.0f * ((items[i].life - 1.25f) * 4); + gfx_quads_text(items[i].pos.x-size/2, items[i].pos.y, size, buf);*/ + i++; + } + } + gfx_quads_end(); + } + +}; + +/*class texture_animator +{ +public: + + int texture; + int numframes; + float duration; + float* framereltime; + weapontexcell* params; + texture_animator() + { + texture = -1; + numframes = 0; + duration = 0; + framereltime = 0; + params = 0; + } + + ~texture_animator() + { + if (params) + mem_free(params); + if (framereltime) + mem_free(framereltime); + } + + void create_anim(int texture, int numframes, float duration) + { + framereltime = 0; + params = 0; + this->texture = texture; + this->numframes = numframes; + this->duration = duration; + if (numframes) + { + framereltime = (float*)mem_alloc(sizeof(float) * numframes,1); + params = (weapontexcell*)mem_alloc(sizeof(renderparams) * numframes,1); + float delta = 1.0f / (float)(numframes - 1); + for (int i = 0; i < numframes; i++) + { + framereltime[i] = delta * i; + } + } + } + + static void create_gunmuzzle(texture_animator& anim, int texture, float duration) + { + anim.create_anim(texture, 3, duration); + anim.params[0].x = 8; + anim.params[0].y = 4; + anim.params[0].w = 3; + anim.params[0].h = 2; + anim.params[1].x = 12; + anim.params[1].y = 4; + anim.params[1].w = 3; + anim.params[1].h = 2; + anim.params[2].x = 16; + anim.params[2].y = 4; + anim.params[2].w = 3; + anim.params[2].h = 2; + } + + static void create_shotgunmuzzle() + { + + } +};*/ + +class keyframe +{ +public: + vec2 pos; + float angle; + float relativetime; +}; + +class anim +{ +public: + keyframe* keyframes; + int numframes; + float duration; + anim() + { + numframes = 0; + keyframes = 0; + } + ~anim() + { + if (keyframes) + mem_free(keyframes); + } + + void create_anim(int numframes, float duration) + { + if (keyframes) + mem_free(keyframes); + + this->numframes = numframes; + this->duration = duration; + keyframes = (keyframe*)mem_alloc(sizeof(keyframe) * numframes,1); + float delta = 1.0f / (float) (numframes - 1); + for (int i = 0; i < numframes; i++) + { + keyframes[i].pos = vec2(0.0f,0.0f); + keyframes[i].angle = 0; + keyframes[i].relativetime = delta * (float)i; + } + } + + void getframes(float relativetime, keyframe*& frame1, keyframe*& frame2, float& blend) + { + for (int i = 1; i < numframes; i++) + { + if (keyframes[i-1].relativetime <= relativetime && keyframes[i].relativetime >= relativetime) + { + frame1 = &keyframes[i-1]; + frame2 = &keyframes[i]; + blend = (relativetime - frame1->relativetime) / (frame2->relativetime - frame1->relativetime); + } + } + } + + void evalanim(float time, vec2& pos, float& angle) + { + float reltime = max(0.0f, min(1.0f, time / duration)); + keyframe* frame1 = 0; + keyframe* frame2 = 0; + float blend = 0.0f; + getframes(reltime, frame1, frame2, blend); + + if (frame1 && frame2) + { + pos = mix(frame1->pos, frame2->pos, blend); + angle = LERP(frame1->angle, frame2->angle, blend); + } + } + + static void setup_hammer(anim& hammeranim) + { + // straight up = -0.25 + // frame0 = standard pose time 0 + // frame1 = back a little time 0.3 + // frame2 = over head time 0.4 + // frame3 = on ground smashed time 0.5 + // frame4 = back to standard pose time 1.0 + hammeranim.create_anim(5, 1.0f); + // only angles... (for now...) + hammeranim.keyframes[0].angle = -0.35f * pi * 2.0f; + hammeranim.keyframes[1].angle = -0.4f * pi * 2.0f; + hammeranim.keyframes[1].relativetime = 0.3f; + hammeranim.keyframes[2].angle = -0.25f; + hammeranim.keyframes[2].relativetime = 0.4f; + hammeranim.keyframes[3].angle = 0.0f * pi * 2.0f; + hammeranim.keyframes[3].relativetime = 0.5f; + hammeranim.keyframes[4].angle = -0.35f * pi * 2.0f; + hammeranim.keyframes[4].relativetime = 1.0f; + } + + static void setup_ninja(anim& ninjanim) + { + // (straight up = -0.25) + // frame0 = standard pose straight back time 0.0 + // frame1 = overhead attack frame 1 time 0.1 + // frame2 = attack end frame time 0.15 + // frame3 = attack hold frame (a bit up) time 0.4 + // frame4 = attack hold frame end time 0.7 + // frame5 = endframe time 1.0 + ninjanim.create_anim(6, 1.0f); + // only angles... (for now...) + ninjanim.keyframes[0].angle = -0.5f * pi * 2.0f; + + ninjanim.keyframes[1].angle = -0.3f * pi * 2.0f; + ninjanim.keyframes[1].relativetime = 0.1f; + + ninjanim.keyframes[2].angle = 0.1f * pi * 2.0f; + ninjanim.keyframes[2].relativetime = 0.15f; + + ninjanim.keyframes[3].angle = -0.05f * pi * 2.0f; + ninjanim.keyframes[3].relativetime = 0.42; + + ninjanim.keyframes[4].angle = -0.05f * pi * 2.0f; + ninjanim.keyframes[4].relativetime = 0.5f; + + ninjanim.keyframes[5].angle = -0.5f * pi * 2.0f; + ninjanim.keyframes[5].relativetime = 1.0f; + } +}; + +static anim hammeranim; +static anim ninjaanim; +static health_texts healthmods; + +class particle_system +{ +public: + enum + { + MAX_PARTICLES=1024, + }; + + particle particles[MAX_PARTICLES]; + int num_particles; + + particle_system() + { + num_particles = 0; + } + + void new_particle(vec2 pos, vec2 vel, float life, float size, float gravity, float friction) + { + if (num_particles >= MAX_PARTICLES) + return; + + particles[num_particles].iparticle = rand() % NUMPARTICLES; + particles[num_particles].pos = pos; + particles[num_particles].vel = vel; + particles[num_particles].life = life - lifemodifier[particles[num_particles].iparticle] * life; + particles[num_particles].size = size; + particles[num_particles].max_life = life; + particles[num_particles].gravity = gravity; + particles[num_particles].friction = friction; + particles[num_particles].rot = frandom()*pi*2; + particles[num_particles].rotspeed = frandom() * 10.0f; + num_particles++; + } + + void update(float time_passed) + { + for(int i = 0; i < num_particles; i++) + { + particles[i].vel.y += particles[i].gravity*time_passed; + particles[i].vel *= particles[i].friction; + vec2 vel = particles[i].vel*time_passed; + move_point(&particles[i].pos, &vel, 0.1f+0.9f*frandom()); + particles[i].vel = vel* (1.0f/time_passed); + particles[i].life += time_passed; + particles[i].rot += time_passed * particles[i].rotspeed; + + // check particle death + if(particles[i].life > particles[i].max_life) + { + num_particles--; + particles[i] = particles[num_particles]; + i--; + } + } + } + + void render() + { + gfx_blend_additive(); + gfx_texture_set(texture_particles); + gfx_quads_begin(); + //cell_select(4,1,1,1); + //cell_select(0,6,2,2); + //gfx_quads_setrotation(get_angle(vec2(proj->vx, proj->vy))); + for(int i = 0; i < num_particles; i++) + { + int type = particles[i].iparticle; + cell_select_ex(particlesnumcellsx,particlesnumcellsy,particlestexcoord[type].x, particlestexcoord[type].y, particlestexcoord[type].w, particlestexcoord[type].h); + float a = 1 - particles[i].life / particles[i].max_life; + vec2 p = particles[i].pos; + //a *= length(particles[i].vel) * 0.01f; + gfx_quads_setrotation(particles[i].rot); + gfx_quads_setcolor(particlecolors[type].x,particlecolors[type].y,particlecolors[type].z,pow(a,0.75f)); + //gfx_quads_setcolor(particlecolors[type].x * 0.5,particlecolors[type].y * 0.5,particlecolors[type].z* 0.5,pow(a,0.75f)); + //gfx_quads_setcolor(particlecolors[type].x * 0.0,particlecolors[type].y * 0.0,particlecolors[type].z* 0.0,pow(a,0.75f)); + //gfx_quads_setcolor(0.64f*2,0.28f*2,0.16f*2,pow(a,0.75f)); + gfx_quads_draw(p.x, p.y,particles[i].size,particles[i].size); + } + gfx_quads_end(); + gfx_blend_normal(); + } +}; + +static particle_system temp_system; + +void modc_init() +{ + // load textures + texture_weapon = gfx_load_texture_tga("data/tileset_weapons.tga"); + texture_game = gfx_load_texture_tga("data/game_main.tga"); + texture_char_default = gfx_load_texture_tga("data/char_teefault.tga"); + texture_sun = gfx_load_texture_tga("data/sun.tga"); + texture_particles = gfx_load_texture_tga("data/tileset_particles.tga"); + font_texture = gfx_load_texture_tga("data/debug_font.tga"); + + + // load sounds + sound_gun_fire[0] = snd_load_wav("data/audio/wp_gun_fire-01.wav"); + sound_gun_fire[0] = snd_load_wav("data/audio/wp_gun_fire-01.wav"); + sound_gun_fire[1] = snd_load_wav("data/audio/wp_gun_fire-02.wav"); + sound_shotty_fire[0] = snd_load_wav("data/audio/wp_shotty_fire-01.wav"); + sound_shotty_fire[1] = snd_load_wav("data/audio/wp_shotty_fire-02.wav"); + sound_shotty_fire[2] = snd_load_wav("data/audio/wp_shotty_fire-03.wav"); + sound_flump_launch[0] = snd_load_wav("data/audio/wp_flump_launch-01.wav"); + sound_flump_launch[1] = snd_load_wav("data/audio/wp_flump_launch-02.wav"); + sound_flump_launch[2] = snd_load_wav("data/audio/wp_flump_launch-03.wav"); + sound_hammer_swing[0] = snd_load_wav("data/audio/wp_hammer_swing-01.wav"); + sound_hammer_swing[1] = snd_load_wav("data/audio/wp_hammer_swing-02.wav"); + sound_hammer_swing[2] = snd_load_wav("data/audio/wp_hammer_swing-03.wav"); + sound_ninja_attack[0] = snd_load_wav("data/audio/wp_ninja_attack-01.wav"); + sound_ninja_attack[1] = snd_load_wav("data/audio/wp_ninja_attack-02.wav"); + sound_ninja_attack[2] = snd_load_wav("data/audio/wp_ninja_attack-03.wav"); + + sound_flump_explode[0] = snd_load_wav("data/audio/wp_flump_explo-01.wav"); + sound_flump_explode[1] = snd_load_wav("data/audio/wp_flump_explo-02.wav"); + sound_flump_explode[2] = snd_load_wav("data/audio/wp_flump_explo-03.wav"); + sound_ninja_hit[0] = snd_load_wav("data/audio/wp_ninja_hit-01.wav"); + sound_ninja_hit[1] = snd_load_wav("data/audio/wp_ninja_hit-02.wav"); + sound_ninja_hit[2] = snd_load_wav("data/audio/wp_ninja_hit-03.wav"); + sound_ninja_hit[3] = snd_load_wav("data/audio/wp_ninja_hit-04.wav"); + + sound_weapon_switch[0] = snd_load_wav("data/audio/wp_switch-01.wav"); + sound_weapon_switch[1] = snd_load_wav("data/audio/wp_switch-02.wav"); + sound_weapon_switch[2] = snd_load_wav("data/audio/wp_switch-03.wav"); + + sound_pain_short[0] = snd_load_wav("data/audio/vo_teefault_pain_short-01.wav"); + sound_pain_short[1] = snd_load_wav("data/audio/vo_teefault_pain_short-02.wav"); + sound_pain_short[2] = snd_load_wav("data/audio/vo_teefault_pain_short-03.wav"); + sound_pain_short[3] = snd_load_wav("data/audio/vo_teefault_pain_short-04.wav"); + sound_pain_short[4] = snd_load_wav("data/audio/vo_teefault_pain_short-05.wav"); + sound_pain_short[5] = snd_load_wav("data/audio/vo_teefault_pain_short-06.wav"); + sound_pain_short[6] = snd_load_wav("data/audio/vo_teefault_pain_short-07.wav"); + sound_pain_short[7] = snd_load_wav("data/audio/vo_teefault_pain_short-08.wav"); + sound_pain_short[8] = snd_load_wav("data/audio/vo_teefault_pain_short-09.wav"); + sound_pain_short[9] = snd_load_wav("data/audio/vo_teefault_pain_short-10.wav"); + sound_pain_short[10] = snd_load_wav("data/audio/vo_teefault_pain_short-11.wav"); + sound_pain_short[11] = snd_load_wav("data/audio/vo_teefault_pain_short-12.wav"); + + sound_pain_long[0] = snd_load_wav("data/audio/vo_teefault_pain_long-01.wav"); + sound_pain_long[1] = snd_load_wav("data/audio/vo_teefault_pain_long-02.wav"); + + sound_body_land[0] = snd_load_wav("data/audio/foley_land-01.wav"); + sound_body_land[1] = snd_load_wav("data/audio/foley_land-02.wav"); + sound_body_land[2] = snd_load_wav("data/audio/foley_land-03.wav"); + sound_body_land[3] = snd_load_wav("data/audio/foley_land-04.wav"); + sound_body_jump[0] = snd_load_wav("data/audio/foley_foot_left-01.wav"); + sound_body_jump[1] = snd_load_wav("data/audio/foley_foot_left-02.wav"); + sound_body_jump[2] = snd_load_wav("data/audio/foley_foot_left-03.wav"); + sound_body_jump[3] = snd_load_wav("data/audio/foley_foot_left-04.wav"); + sound_body_jump[4] = snd_load_wav("data/audio/foley_foot_right-01.wav"); + sound_body_jump[5] = snd_load_wav("data/audio/foley_foot_right-02.wav"); + sound_body_jump[6] = snd_load_wav("data/audio/foley_foot_right-03.wav"); + sound_body_jump[7] = snd_load_wav("data/audio/foley_foot_right-04.wav"); + + sound_body_splat[1] = snd_load_wav("data/audio/foley_body_splat-02.wav"); + sound_body_splat[2] = snd_load_wav("data/audio/foley_body_splat-03.wav"); + sound_body_splat[3] = snd_load_wav("data/audio/foley_body_splat-04.wav"); + + sound_spawn[0] = snd_load_wav("data/audio/vo_teefault_spawn-01.wav"); + sound_spawn[1] = snd_load_wav("data/audio/vo_teefault_spawn-02.wav"); + sound_spawn[2] = snd_load_wav("data/audio/vo_teefault_spawn-03.wav"); + sound_spawn[3] = snd_load_wav("data/audio/vo_teefault_spawn-04.wav"); + sound_spawn[4] = snd_load_wav("data/audio/vo_teefault_spawn-05.wav"); + sound_spawn[5] = snd_load_wav("data/audio/vo_teefault_spawn-06.wav"); + sound_spawn[6] = snd_load_wav("data/audio/vo_teefault_spawn-07.wav"); + + sound_tee_cry[0] = snd_load_wav("data/audio/vo_teefault_cry-01.wav"); + sound_tee_cry[1] = snd_load_wav("data/audio/vo_teefault_cry-02.wav"); + + //sound_hook_loop[0] = snd_load_wav("data/audio/hook_loop-01.wav"); + sound_hook_loop[0] = snd_load_wav("data/audio/hook_loop-02.wav"); + sound_hook_attach[0] = snd_load_wav("data/audio/hook_attach-01.wav"); + sound_hook_attach[1] = snd_load_wav("data/audio/hook_attach-02.wav"); + sound_hook_attach[2] = snd_load_wav("data/audio/hook_attach-03.wav"); + + poweruptexcoord[POWERUP_TYPE_HEALTH].x = 10; + poweruptexcoord[POWERUP_TYPE_HEALTH].y = 2; + poweruptexcoord[POWERUP_TYPE_HEALTH].w = 2; + poweruptexcoord[POWERUP_TYPE_HEALTH].h = 2; + + poweruptexcoord[POWERUP_TYPE_ARMOR].x = 12; + poweruptexcoord[POWERUP_TYPE_ARMOR].y = 2; + poweruptexcoord[POWERUP_TYPE_ARMOR].w = 2; + poweruptexcoord[POWERUP_TYPE_ARMOR].h = 2; + + poweruptexcoord[POWERUP_TYPE_WEAPON].x = 3; + poweruptexcoord[POWERUP_TYPE_WEAPON].y = 0; + poweruptexcoord[POWERUP_TYPE_WEAPON].w = 6; + poweruptexcoord[POWERUP_TYPE_WEAPON].h = 2; + + poweruptexcoord[POWERUP_TYPE_NINJA].x = 3; + poweruptexcoord[POWERUP_TYPE_NINJA].y = 10; + poweruptexcoord[POWERUP_TYPE_NINJA].w = 7; + poweruptexcoord[POWERUP_TYPE_NINJA].h = 2; + + poweruptexcoord[POWERUP_TYPE_TIMEFIELD].x = 3; + poweruptexcoord[POWERUP_TYPE_TIMEFIELD].y = 0; + poweruptexcoord[POWERUP_TYPE_TIMEFIELD].w = 6; + poweruptexcoord[POWERUP_TYPE_TIMEFIELD].h = 2; + + // Setup weapon cell coords + float sizemodifier = 1.0f; + weaponrenderparams[WEAPON_TYPE_GUN].sizex = 60.0f * sizemodifier; + weaponrenderparams[WEAPON_TYPE_GUN].sizey = 30.0f * sizemodifier; + weaponrenderparams[WEAPON_TYPE_GUN].offsetx = 32.0f; + weaponrenderparams[WEAPON_TYPE_GUN].offsety = 4.0f; + weapontexcoordcursor[WEAPON_TYPE_GUN].x = 0; + weapontexcoordcursor[WEAPON_TYPE_GUN].y = 4; + weapontexcoordcursor[WEAPON_TYPE_GUN].w = 2; + weapontexcoordcursor[WEAPON_TYPE_GUN].h = 2; + weapontexcoord[WEAPON_TYPE_GUN].x = 2; + weapontexcoord[WEAPON_TYPE_GUN].y = 4; + weapontexcoord[WEAPON_TYPE_GUN].w = 4; + weapontexcoord[WEAPON_TYPE_GUN].h = 2; + weaponprojtexcoord[WEAPON_TYPE_GUN].x = 6; + weaponprojtexcoord[WEAPON_TYPE_GUN].y = 4; + weaponprojtexcoord[WEAPON_TYPE_GUN].w = 2; + weaponprojtexcoord[WEAPON_TYPE_GUN].h = 2; + + nummuzzletex[WEAPON_TYPE_GUN] = 3; + muzzletexcoord[WEAPON_TYPE_GUN][0].x = 8; + muzzletexcoord[WEAPON_TYPE_GUN][0].y = 4; + muzzletexcoord[WEAPON_TYPE_GUN][0].w = 3; + muzzletexcoord[WEAPON_TYPE_GUN][0].h = 2; + muzzletexcoord[WEAPON_TYPE_GUN][1].x = 12; + muzzletexcoord[WEAPON_TYPE_GUN][1].y = 4; + muzzletexcoord[WEAPON_TYPE_GUN][1].w = 3; + muzzletexcoord[WEAPON_TYPE_GUN][1].h = 2; + muzzletexcoord[WEAPON_TYPE_GUN][2].x = 16; + muzzletexcoord[WEAPON_TYPE_GUN][2].y = 4; + muzzletexcoord[WEAPON_TYPE_GUN][2].w = 3; + muzzletexcoord[WEAPON_TYPE_GUN][2].h = 2; + + muzzleparams[WEAPON_TYPE_GUN].sizex = 60.0f * sizemodifier; + muzzleparams[WEAPON_TYPE_GUN].sizey = 40.0f * sizemodifier; + muzzleparams[WEAPON_TYPE_GUN].offsetx = 50.0f * sizemodifier; + muzzleparams[WEAPON_TYPE_GUN].offsety = 6.0f * sizemodifier; + + sizemodifier = 1.3f; + weaponrenderparams[WEAPON_TYPE_ROCKET].sizex = 70.0f * sizemodifier; + weaponrenderparams[WEAPON_TYPE_ROCKET].sizey = 20.0f * sizemodifier; + weaponrenderparams[WEAPON_TYPE_ROCKET].offsetx = 24.0f; + weaponrenderparams[WEAPON_TYPE_ROCKET].offsety = -2.0f; + weapontexcoordcursor[WEAPON_TYPE_ROCKET].x = 0; + weapontexcoordcursor[WEAPON_TYPE_ROCKET].y = 8; + weapontexcoordcursor[WEAPON_TYPE_ROCKET].w = 2; + weapontexcoordcursor[WEAPON_TYPE_ROCKET].h = 2; + weapontexcoord[WEAPON_TYPE_ROCKET].x = 2; + weapontexcoord[WEAPON_TYPE_ROCKET].y = 8; + weapontexcoord[WEAPON_TYPE_ROCKET].w = 7; + weapontexcoord[WEAPON_TYPE_ROCKET].h = 2; + weaponprojtexcoord[WEAPON_TYPE_ROCKET].x = 10; + weaponprojtexcoord[WEAPON_TYPE_ROCKET].y = 8; + weaponprojtexcoord[WEAPON_TYPE_ROCKET].w = 2; + weaponprojtexcoord[WEAPON_TYPE_ROCKET].h = 2; + + /*weaponrenderparams[WEAPON_TYPE_SNIPER].sizex = 60.0f; + weaponrenderparams[WEAPON_TYPE_SNIPER].sizey = 20.0f; + weaponrenderparams[WEAPON_TYPE_SNIPER].offsetx = 16.0f; + weaponrenderparams[WEAPON_TYPE_SNIPER].offsety = 4.0f; + weapontexcoordcursor[WEAPON_TYPE_SNIPER].x = 0; + weapontexcoordcursor[WEAPON_TYPE_SNIPER].y = 6; + weapontexcoordcursor[WEAPON_TYPE_SNIPER].w = 2; + weapontexcoordcursor[WEAPON_TYPE_SNIPER].h = 2; + weapontexcoord[WEAPON_TYPE_SNIPER].x = 3; + weapontexcoord[WEAPON_TYPE_SNIPER].y = 6; + weapontexcoord[WEAPON_TYPE_SNIPER].w = 6; + weapontexcoord[WEAPON_TYPE_SNIPER].h = 2; + weaponprojtexcoord[WEAPON_TYPE_SNIPER].x = 10; + weaponprojtexcoord[WEAPON_TYPE_SNIPER].y = 6; + weaponprojtexcoord[WEAPON_TYPE_SNIPER].w = 1; + weaponprojtexcoord[WEAPON_TYPE_SNIPER].h = 1;*/ + + weaponrenderparams[WEAPON_TYPE_SHOTGUN].sizex = 80.0f * sizemodifier; + weaponrenderparams[WEAPON_TYPE_SHOTGUN].sizey = 20.0f * sizemodifier; + weaponrenderparams[WEAPON_TYPE_SHOTGUN].offsetx = 24.0f; + weaponrenderparams[WEAPON_TYPE_SHOTGUN].offsety = -2.0f; + weapontexcoordcursor[WEAPON_TYPE_SHOTGUN].x = 0; + weapontexcoordcursor[WEAPON_TYPE_SHOTGUN].y = 6; + weapontexcoordcursor[WEAPON_TYPE_SHOTGUN].w = 2; + weapontexcoordcursor[WEAPON_TYPE_SHOTGUN].h = 2; + weapontexcoord[WEAPON_TYPE_SHOTGUN].x = 2; + weapontexcoord[WEAPON_TYPE_SHOTGUN].y = 6; + weapontexcoord[WEAPON_TYPE_SHOTGUN].w = 8; + weapontexcoord[WEAPON_TYPE_SHOTGUN].h = 2; + weaponprojtexcoord[WEAPON_TYPE_SHOTGUN].x = 10; + weaponprojtexcoord[WEAPON_TYPE_SHOTGUN].y = 6; + weaponprojtexcoord[WEAPON_TYPE_SHOTGUN].w = 2; + weaponprojtexcoord[WEAPON_TYPE_SHOTGUN].h = 2; + + nummuzzletex[WEAPON_TYPE_SHOTGUN] = 3; + muzzletexcoord[WEAPON_TYPE_SHOTGUN][0].x = 12; + muzzletexcoord[WEAPON_TYPE_SHOTGUN][0].y = 6; + muzzletexcoord[WEAPON_TYPE_SHOTGUN][0].w = 3; + muzzletexcoord[WEAPON_TYPE_SHOTGUN][0].h = 2; + muzzletexcoord[WEAPON_TYPE_SHOTGUN][1].x = 16; + muzzletexcoord[WEAPON_TYPE_SHOTGUN][1].y = 6; + muzzletexcoord[WEAPON_TYPE_SHOTGUN][1].w = 3; + muzzletexcoord[WEAPON_TYPE_SHOTGUN][1].h = 2; + muzzletexcoord[WEAPON_TYPE_SHOTGUN][2].x = 20; + muzzletexcoord[WEAPON_TYPE_SHOTGUN][2].y = 6; + muzzletexcoord[WEAPON_TYPE_SHOTGUN][2].w = 3; + muzzletexcoord[WEAPON_TYPE_SHOTGUN][2].h = 2; + + muzzleparams[WEAPON_TYPE_SHOTGUN].sizex = 60.0f * sizemodifier; + muzzleparams[WEAPON_TYPE_SHOTGUN].sizey = 40.0f * sizemodifier; + muzzleparams[WEAPON_TYPE_SHOTGUN].offsetx = 50.0f * sizemodifier; + muzzleparams[WEAPON_TYPE_SHOTGUN].offsety = 6.0f * sizemodifier; + + + + weaponrenderparams[WEAPON_TYPE_MELEE].sizex = 60.0f * sizemodifier; + weaponrenderparams[WEAPON_TYPE_MELEE].sizey = 50.0f * sizemodifier; + weaponrenderparams[WEAPON_TYPE_MELEE].offsetx = 20.0f; + weaponrenderparams[WEAPON_TYPE_MELEE].offsety = -4.0f; + weapontexcoordcursor[WEAPON_TYPE_MELEE].x = 0; + weapontexcoordcursor[WEAPON_TYPE_MELEE].y = 0; + weapontexcoordcursor[WEAPON_TYPE_MELEE].w = 2; + weapontexcoordcursor[WEAPON_TYPE_MELEE].h = 2; + weapontexcoord[WEAPON_TYPE_MELEE].x = 2; + weapontexcoord[WEAPON_TYPE_MELEE].y = 1; + weapontexcoord[WEAPON_TYPE_MELEE].w = 4; + weapontexcoord[WEAPON_TYPE_MELEE].h = 3; + weaponprojtexcoord[WEAPON_TYPE_MELEE].x = 0; + weaponprojtexcoord[WEAPON_TYPE_MELEE].y = 0; + weaponprojtexcoord[WEAPON_TYPE_MELEE].w = 0; + weaponprojtexcoord[WEAPON_TYPE_MELEE].h = 0; + + + // MODIFIERS + sizemodifier = 2.0; + modifierrenderparams[MODIFIER_TYPE_NINJA].sizex = 60.0f * sizemodifier; + modifierrenderparams[MODIFIER_TYPE_NINJA].sizey = 20.0f * sizemodifier; + modifierrenderparams[MODIFIER_TYPE_NINJA].offsetx = 20.0f; + modifierrenderparams[MODIFIER_TYPE_NINJA].offsety = 4.0f; + modifiertexcoord[MODIFIER_TYPE_NINJA].x = 2; + modifiertexcoord[MODIFIER_TYPE_NINJA].y = 10; + modifiertexcoord[MODIFIER_TYPE_NINJA].w = 7; + modifiertexcoord[MODIFIER_TYPE_NINJA].h = 2; + modifiertexcoordcursor[MODIFIER_TYPE_NINJA].x = 0; + modifiertexcoordcursor[MODIFIER_TYPE_NINJA].y = 10; + modifiertexcoordcursor[MODIFIER_TYPE_NINJA].w = 2; + modifiertexcoordcursor[MODIFIER_TYPE_NINJA].h = 2; + + modifierrenderparams[MODIFIER_TYPE_TIMEFIELD].sizex = 60.0f * sizemodifier; + modifierrenderparams[MODIFIER_TYPE_TIMEFIELD].sizey = 20.0f * sizemodifier; + modifierrenderparams[MODIFIER_TYPE_TIMEFIELD].offsetx = 16.0f; + modifierrenderparams[MODIFIER_TYPE_TIMEFIELD].offsety = 4.0f; + modifiertexcoord[MODIFIER_TYPE_TIMEFIELD].x = 0; + modifiertexcoord[MODIFIER_TYPE_TIMEFIELD].y = 0; + modifiertexcoord[MODIFIER_TYPE_TIMEFIELD].w = 0; + modifiertexcoord[MODIFIER_TYPE_TIMEFIELD].h = 0; + + stars[0].x = 0; + stars[0].y = 0; + stars[0].w = 2; + stars[0].h = 2; + + stars[1].x = 0; + stars[1].y = 2; + stars[1].w = 2; + stars[1].h = 2; + + particlecolors[0].x = 0.7f; + particlecolors[0].y = 0.7f; + particlecolors[0].z = 0.7f; + particlecolors[0].w = 1.0f; + particlestexcoord[0].x = 2; + particlestexcoord[0].y = 0; + particlestexcoord[0].w = 2; + particlestexcoord[0].h = 2; + particlecolors[1].x = 1.0f; + particlecolors[1].y = 1.0f; + particlecolors[1].z = 1.0f; + particlecolors[1].w = 1.0f; + particlestexcoord[1].x = 4; + particlestexcoord[1].y = 0; + particlestexcoord[1].w = 2; + particlestexcoord[1].h = 2; + particlecolors[2].x = 0.8f; + particlecolors[2].y = 0.8f; + particlecolors[2].z = 0.8f; + particlecolors[2].w = 1.0f; + particlestexcoord[2].x = 6; + particlestexcoord[2].y = 0; + particlestexcoord[2].w = 2; + particlestexcoord[2].h = 2; + particlecolors[3].x = 0.988f; + particlecolors[3].y = 1.0f; + particlecolors[3].z = 0.16f; + particlecolors[3].w = 1.0f; + particlestexcoord[3].x = 8; + particlestexcoord[3].y = 0; + particlestexcoord[3].w = 2; + particlestexcoord[3].h = 2; + particlecolors[4].x = 1.0f; + particlecolors[4].y = 1.0f; + particlecolors[4].z = 1.0f; + particlecolors[4].w = 1.0f; + particlestexcoord[4].x = 10; + particlestexcoord[4].y = 0; + particlestexcoord[4].w = 2; + particlestexcoord[4].h = 2; + particlecolors[5].x = 0.6f; + particlecolors[5].y = 0.6f; + particlecolors[5].z = 0.6f; + particlecolors[5].w = 1.0f; + particlestexcoord[5].x = 2; + particlestexcoord[5].y = 2; + particlestexcoord[5].w = 2; + particlestexcoord[5].h = 2; + particlecolors[6].x = 1.0f; + particlecolors[6].y = 1.0f; + particlecolors[6].z = 1.0f; + particlecolors[6].w = 1.0f; + particlestexcoord[6].x = 4; + particlestexcoord[6].y = 2; + particlestexcoord[6].w = 2; + particlestexcoord[6].h = 2; + particlecolors[5].x = 0.9f; + particlecolors[5].y = 0.9f; + particlecolors[5].z = 0.9f; + particlecolors[5].w = 1.0f; + particlestexcoord[7].x = 6; + particlestexcoord[7].y = 2; + particlestexcoord[7].w = 2; + particlestexcoord[7].h = 2; + particlecolors[8].x = 1.0f; + particlecolors[8].y = 1.0f; + particlecolors[8].z = 1.0f; + particlecolors[8].w = 1.0f; + particlestexcoord[8].x = 8; + particlestexcoord[8].y = 2; + particlestexcoord[8].w = 2; + particlestexcoord[8].h = 2; + lifemodifier[0] = 0.5f; + lifemodifier[1] = 0.5f; + lifemodifier[2] = 0.5f; + lifemodifier[3] = 0.7f; + lifemodifier[4] = 0.7f; + lifemodifier[5] = 1.0f; + lifemodifier[6] = 1.0f; + lifemodifier[7] = 1.5f; + lifemodifier[8] = 0.4f; + + chaintexcoord.x = 2; + chaintexcoord.y = 0; + chaintexcoord.w = 1; + chaintexcoord.h = 1; + + chainheadtexcoord.x = 3; + chainheadtexcoord.y = 0; + chainheadtexcoord.w = 2; + chainheadtexcoord.h = 1; + + + // anims + anim::setup_hammer(hammeranim); + anim::setup_ninja(ninjaanim); + + for (int i = 0; i < NUMHADOKENS; i++) + { + hadoken[i].x = 1; + hadoken[i].y = 12; + hadoken[i].w = 7; + hadoken[i].h = 4; + hadokenparams[i].sizex = 0.0f; + hadokenparams[i].sizey = 0.0f; + hadokenparams[i].offsetx = 0.0f; + hadokenparams[i].offsety = 0.0f;//-hadokenparams[0].sizey * 0.15f; + } + + // hadoken + hadoken[0].x = 1; + hadoken[0].y = 12; + hadoken[0].w = 7; + hadoken[0].h = 4; + hadokenparams[0].sizex = 70.0f * 2.5f; + hadokenparams[0].sizey = 40.0f * 2.5f; + hadokenparams[0].offsetx = -60.0f; + hadokenparams[0].offsety = 0;//-hadokenparams[0].sizey * 0.15f; + + hadoken[2].x = 8; + hadoken[2].y = 12; + hadoken[2].w = 8; + hadoken[2].h = 4; + hadokenparams[2].sizex = 80.0f * 2.5f; + hadokenparams[2].sizey = 40.0f * 2.5f; + hadokenparams[2].offsetx = -60.0f; + hadokenparams[2].offsety = 0;//-hadokenparams[1].sizey * 0.5f; + + hadoken[4].x = 17; + hadoken[4].y = 12; + hadoken[4].w = 7; + hadoken[4].h = 4; + hadokenparams[4].sizex = 70.0f * 2.5f; + hadokenparams[4].sizey = 40.0f * 2.5f; + hadokenparams[4].offsetx = -60.0f; + hadokenparams[4].offsety = 0;//-hadokenparams[2].sizey * 0.5f; + + // 0 = outline, 1 = body + body[0].x = 2; + body[0].y = 0; + body[0].w = 2; + body[0].h = 2; + body[1].x = 0; + body[1].y = 0; + body[1].w = 2; + body[1].h = 2; + + feet[0].x = 4; + feet[0].y = 1; + feet[0].w = 1; + feet[0].h = 0.5; + feet[1].x = 4; + feet[1].y = 1.52; + feet[1].w = 1; + feet[1].h = 0.48; + + leye.x = 5; + leye.y = 1; + leye.w = 0.5; + leye.h = 0.5; + reye.x = 5; + reye.y = 1.5; + reye.w = 0.5; + reye.h = 0.5; +} + +void modc_entergame() +{ + col_init(32); + img_init(); + tilemap_init(); +} + +void modc_shutdown() +{ +} + +void modc_newsnapshot() +{ + int num = snap_num_items(SNAP_CURRENT); + for(int i = 0; i < num; i++) + { + snap_item item; + void *data = snap_get_item(SNAP_CURRENT, i, &item); + + if(item.type == EVENT_HEALTHMOD) + { + ev_healthmod *ev = (ev_healthmod *)data; + healthmods.create(vec2(ev->x, ev->y), ev->amount); + } + else if(item.type == EVENT_EXPLOSION) + { + ev_explosion *ev = (ev_explosion *)data; + vec2 p(ev->x, ev->y); + + // center explosion + temp_system.new_particle(p, vec2(0,0), 0.3f, 96.0f, 0, 0.95f); + temp_system.new_particle(p, vec2(0,0), 0.3f, 64.0f, 0, 0.95f); + temp_system.new_particle(p, vec2(0,0), 0.3f, 32.0f, 0, 0.95f); + temp_system.new_particle(p, vec2(0,0), 0.3f, 16.0f, 0, 0.95f); + + for(int i = 0; i < 16; i++) + { + vec2 v = normalize(vec2(frandom()-0.5f, frandom()-0.5f))*(128.0f+frandom()*128.0f); + temp_system.new_particle(p, v, 0.2f+0.25f*frandom(), 16.0f, 0, 0.985f); + } + + for(int i = 0; i < 16; i++) + { + vec2 v = normalize(vec2(frandom()-0.5f, frandom()-0.5f))*(256.0f+frandom()*512.0f); + temp_system.new_particle(p, v, 0.2f+0.25f*frandom(), 16.0f, 128.0f, 0.985f); + } + + for(int i = 0; i < 64; i++) + { + vec2 v = normalize(vec2(frandom()-0.5f, frandom()-0.5f))*(frandom()*256.0f); + temp_system.new_particle(p, v, 0.2f+0.25f*frandom(), 24.0f, 128.0f, 0.985f); + } + } + else if(item.type == EVENT_SMOKE) + { + ev_explosion *ev = (ev_explosion *)data; + vec2 p(ev->x, ev->y); + + // center explosion + vec2 v = normalize(vec2(frandom()-0.5f, -frandom()))*(32.0f+frandom()*32.0f); + temp_system.new_particle(p, v, 1.2f, 64.0f, 0, 0.95f); + v = normalize(vec2(frandom()-0.5f, -frandom()))*(128.0f+frandom()*128.0f); + temp_system.new_particle(p, v, 1.2f, 32.0f, 0, 0.95f); + v = normalize(vec2(frandom()-0.5f, -frandom()))*(128.0f+frandom()*128.0f); + temp_system.new_particle(p, v, 1.2f, 16.0f, 0, 0.95f); + + for(int i = 0; i < 8; i++) + { + vec2 v = normalize(vec2(frandom()-0.5f, frandom()-0.5f))*(64.0f+frandom()*64.0f); + temp_system.new_particle(p, v, 0.5f+0.5f*frandom(), 16.0f, 0, 0.985f); + } + + for(int i = 0; i < 8; i++) + { + vec2 v = normalize(vec2(frandom()-0.5f, frandom()-0.5f))*(128.0f+frandom()*256.0f); + temp_system.new_particle(p, v, 0.5f+0.5f*frandom(), 16.0f, 128.0f, 0.985f); + } + } + else if(item.type == EVENT_SOUND) + { + ev_sound *ev = (ev_sound *)data; + vec2 p(ev->x, ev->y); + int sound = (ev->sound & SOUND_MASK); + bool bstartloop = (ev->sound & SOUND_LOOPFLAG_STARTLOOP) != 0; + bool bstoploop = (ev->sound & SOUND_LOOPFLAG_STOPLOOP) != 0; + float vol, pan; + sound_vol_pan(p, &vol, &pan); + + switch(sound) + { + + // FIRE! + case SOUND_FIRE_GUN: + sound_gun_fire.play_random(volume_gun*vol, pan); + break; + case SOUND_FIRE_SHOTGUN: + sound_shotty_fire.play_random(volume_gun*vol, pan); + break; + case SOUND_FIRE_ROCKET: + sound_flump_launch.play_random(volume_gun*vol, pan); + break; + case SOUND_FIRE_MELEE: + sound_hammer_swing.play_random(volume_gun*vol, pan); + break; + case SOUND_FIRE_NINJA: + sound_ninja_attack.play_random(volume_gun*vol, pan); + break; + + // IMPACT + case SOUND_IMPACT_PROJECTILE_GUN: + break; + case SOUND_IMPACT_PROJECTILE_SHOTGUN: + break; + case SOUND_IMPACT_PROJECTILE_ROCKET: + sound_flump_explode.play_random(volume_hit*vol, pan); + break; + + // PLAYER + case SOUND_PLAYER_JUMP: + sound_body_jump.play_random(volume_tee*vol, pan); + break; + case SOUND_PLAYER_HURT_SHORT: + sound_pain_short.play_random(volume_tee*vol, pan); + break; + case SOUND_PLAYER_HURT_LONG: + sound_pain_long.play_random(volume_tee*vol, pan); + break; + case SOUND_PLAYER_SPAWN: + sound_spawn.play_random(volume_tee*vol, pan); + break; + case SOUND_PLAYER_CHAIN_LOOP: + sound_hook_loop.play_random(volume_gun*vol, pan); + break; + case SOUND_PLAYER_CHAIN_IMPACT: + sound_hook_attach.play_random(volume_gun*vol, pan); + break; + case SOUND_PLAYER_IMPACT: + sound_body_land.play_random(volume_hit*vol, pan); + break; + case SOUND_PLAYER_IMPACT_NINJA: + sound_ninja_hit.play_random(volume_hit*vol, pan); + break; + case SOUND_PLAYER_DIE: + sound_body_splat.play_random(volume_tee*vol, pan); + break; + case SOUND_PLAYER_SWITCHWEAPON: + sound_weapon_switch.play_random(volume_gun*vol, pan); + break; + case SOUND_PLAYER_EQUIP: + break; + case SOUND_PLAYER_LAND: + sound_body_land.play_random(volume_tee*vol, pan); + break; + } + } + } +} + +static void render_projectile(obj_projectile *prev, obj_projectile *current) +{ + gfx_texture_set(texture_weapon); + gfx_quads_begin(); + cell_select_ex(numcellsx,numcellsy,weaponprojtexcoord[current->type].x, weaponprojtexcoord[current->type].y, weaponprojtexcoord[current->type].w, weaponprojtexcoord[current->type].h); + vec2 vel(current->vx, current->vy); + + // TODO: interpolare angle aswell + if(length(vel) > 0.00001f) + gfx_quads_setrotation(get_angle(vel)); + else + gfx_quads_setrotation(0); + + vec2 pos = mix(vec2(prev->x, prev->y), vec2(current->x, current->y), snap_intratick()); + gfx_quads_draw(pos.x, pos.y,32,32); + gfx_quads_setrotation(0); + gfx_quads_end(); +} + +static void render_powerup(obj_powerup *prev, obj_powerup *current) +{ + //dbg_msg("client", "rendering powerup at %d,%d", current->x, current->y); + + gfx_texture_set(texture_weapon); + gfx_quads_begin(); + float angle = 0.0f; + float sizex = 64.0f; + float sizey = 64.0f; + if (current->type == POWERUP_TYPE_WEAPON) + { + angle = -0.25f * pi * 2.0f; + cell_select_ex(numcellsx,numcellsy,weapontexcoord[current->subtype].x, weapontexcoord[current->subtype].y, weapontexcoord[current->subtype].w, weapontexcoord[current->subtype].h); + sizex = weaponrenderparams[current->subtype].sizex; + sizey = weaponrenderparams[current->subtype].sizey; + } + else + cell_select_ex(numcellsx,numcellsy,poweruptexcoord[current->type].x, poweruptexcoord[current->type].y, poweruptexcoord[current->type].w, poweruptexcoord[current->type].h); + vec2 vel(current->vx, current->vy); + + gfx_quads_setrotation(angle); + // TODO: interpolare angle aswell + /*if(length(vel) > 0.00001f) + gfx_quads_setrotation(get_angle(vel)); + else + gfx_quads_setrotation(0);*/ + + vec2 pos = mix(vec2(prev->x, prev->y), vec2(current->x, current->y), snap_intratick()); + float offset = pos.y/32.0f + pos.x/32.0f; + gfx_quads_draw(pos.x+cosf(client_localtime()*2.0f+offset)*2.5f, pos.y+sinf(client_localtime()*2.0f+offset)*2.5f,sizex * 0.65f,sizey * 0.65f); + gfx_quads_setrotation(0); + gfx_quads_end(); +} + +float getmeleeangle(vec2 direction, obj_player* prev, obj_player* player) +{ + vec2 meleedir(0.53, -0.84); + meleedir = normalize(meleedir); + vec2 meleedirattack(0.95, -0.3); + meleedirattack = normalize(meleedirattack); + + if(direction.x < 0) + { + meleedir.x = -meleedir.x; + meleedirattack.x = -meleedirattack.x; + } + + // 0 -> visualtimeattack go to end pose, (len - visualime) -> go back to normal pose + + float angle = get_angle(meleedir); + if (prev->attackticks) + { + float angleattack = get_angle(meleedirattack); + int phase1tick = (player->attacklen - player->attackticks); + if (phase1tick < player->visualtimeattack) + { + float intratick = snap_intratick(); + float t = ((((float)phase1tick) + intratick)/(float)player->visualtimeattack); + angle = LERP(angle, angleattack, min(1.0f,max(0.0f,t))); + } + else + { + // go back to normal pose + int phase2tick = (player->attacklen - player->visualtimeattack - player->attackticks); + float intratick = snap_intratick(); + float t = ((((float)phase2tick) + intratick)/(float)player->visualtimeattack); + angle = LERP(angleattack, angle, min(1.0f,max(0.0f,t))); + } + } + /*if (prev->attackticks && !player->attackticks) + { + // blend back to normal + float angleattack = get_angle(meleedirattack); + angle = LERP(angleattack, angle, min(1.0f,max(0.0f,snap_intratick()))); + } + else if (player->attackticks) + { + float angleattack = get_angle(meleedirattack); + float intratick = snap_intratick(); + float t = ((((float)player->attackticks) - intratick)/(float)player->attacklen); + angle = LERP(angleattack, angle, min(1.0f,max(0.0f,t))); + }*/ + + return angle; +} + +float gethammereangle(vec2 direction, obj_player* prev, obj_player* player) +{ + float t = 0.0f; + if (prev->attackticks) + t = 1.0f - ((((float)player->attackticks) - snap_intratick())/(float)player->attacklen); + + vec2 pos; + float angle = 0.0f; + hammeranim.evalanim(t,pos,angle); + if(direction.x < 0) + angle = pi -angle;// + ; + //dbg_msg("anim", "Time: %f", t); + return angle; +} + +float getninjaangle(vec2 direction, obj_player* prev, obj_player* player) +{ + float t = 0.0f; + if (prev->attackticks) + t = 1.0f - ((((float)player->attackticks) - snap_intratick())/(float)player->attacklen); + + vec2 pos; + float angle = 0.0f; + ninjaanim.evalanim(t,pos,angle); + if(direction.x < 0) + angle = pi -angle;// + ; + //dbg_msg("anim", "Time: %f", t); + return angle; +} + + +float getrecoil(obj_player* prev, obj_player* player) +{ + // attack = -10 + float recoil = 0.0f; + if (prev->attackticks) + { + float attackrecoil = recoils[player->weapon]; + int phase1tick = (player->attacklen - player->attackticks); + if (phase1tick < player->visualtimeattack) + { + float intratick = snap_intratick(); + float t = ((((float)phase1tick) + intratick)/(float)player->visualtimeattack); + recoil = LERP(0, attackrecoil, min(1.0f,max(0.0f,t))); + } + else + { + // go back to normal pose + int phase2tick = (player->attacklen - player->visualtimeattack - player->attackticks); + float intratick = snap_intratick(); + float t = ((((float)phase2tick) + intratick)/(float)(player->attacklen - player->visualtimeattack)); + recoil = LERP(attackrecoil, 0.0f, min(1.0f,max(0.0f,t))); + } + } + return recoil; +} + +static void render_player(obj_player *prev, obj_player *player) +{ + vec2 direction = get_direction(player->angle); + float angle = player->angle/256.0f; + vec2 position = mix(vec2(prev->x, prev->y), vec2(player->x, player->y), snap_intratick()); + + // draw hook + if(player->hook_active) + { + gfx_texture_set(texture_weapon); + gfx_quads_begin(); + //gfx_quads_begin(); + + vec2 pos = position; + + vec2 hook_pos = mix(vec2(prev->hook_x, prev->hook_y), vec2(player->hook_x, player->hook_y), snap_intratick()); + + float d = distance(pos, hook_pos); + vec2 dir = normalize(pos-hook_pos); + + gfx_quads_setrotation(get_angle(dir)+pi); + + // render head + cell_select_ex(numcellsx,numcellsy, chainheadtexcoord.x,chainheadtexcoord.y, chainheadtexcoord.w, chainheadtexcoord.h); + gfx_quads_draw(hook_pos.x, hook_pos.y, 24,16); + + // render chain + cell_select_ex(numcellsx,numcellsy, chaintexcoord.x, chaintexcoord.y, chaintexcoord.w, chaintexcoord.h); + for(float f = 24; f < d; f += 24) + { + vec2 p = hook_pos + dir*f; + gfx_quads_draw(p.x, p.y,24,16); + } + + gfx_quads_setrotation(0); + gfx_quads_end(); + } + + // draw gun + { + gfx_texture_set(texture_weapon); + gfx_quads_begin(); + gfx_quads_setrotation(angle); + + if (player->modifier & (1 << MODIFIER_TYPE_NINJA)) + { + float playerangle = angle; + // render NINJA!!! (0.53, 0.84) when idle to -> (0.95, 0.3) at the end of attack + if(direction.x < 0) + cell_select_ex_flip_y(numcellsx, numcellsy, modifiertexcoord[MODIFIER_TYPE_NINJA].x, modifiertexcoord[MODIFIER_TYPE_NINJA].y, modifiertexcoord[MODIFIER_TYPE_NINJA].w, modifiertexcoord[MODIFIER_TYPE_NINJA].h); + else + cell_select_ex(numcellsx, numcellsy, modifiertexcoord[MODIFIER_TYPE_NINJA].x, modifiertexcoord[MODIFIER_TYPE_NINJA].y, modifiertexcoord[MODIFIER_TYPE_NINJA].w, modifiertexcoord[MODIFIER_TYPE_NINJA].h); + + angle = getninjaangle(direction, prev, player);//getmeleeangle(direction, prev, player); + vec2 ninjadir = get_direction(angle * 256.0f); + gfx_quads_setrotation(angle); + vec2 p = position + vec2(0,modifierrenderparams[MODIFIER_TYPE_NINJA].offsety)+ ninjadir * modifierrenderparams[MODIFIER_TYPE_NINJA].offsetx; + // if attack is active hold it differently and draw speedlines behind us? + gfx_quads_draw(p.x,p.y/*+bob*/,modifierrenderparams[MODIFIER_TYPE_NINJA].sizex, modifierrenderparams[MODIFIER_TYPE_NINJA].sizey); + + if ((player->attacklen - player->attackticks) <= (SERVER_TICK_SPEED / 5)) + { + gfx_quads_setrotation(playerangle); + int ihadoken = rand() % NUMHADOKENS; + cell_select_ex(numcellsx, numcellsy, hadoken[ihadoken].x, hadoken[ihadoken].y, hadoken[ihadoken].w, hadoken[ihadoken].h); + vec2 p = position + vec2(0,hadokenparams[ihadoken].offsety)+ direction * hadokenparams[ihadoken].offsetx; + gfx_quads_draw(p.x,p.y/*+bob*/,hadokenparams[ihadoken].sizex, hadokenparams[ihadoken].sizey); + } + } + else + { + // normal weapons + if(direction.x < 0) + cell_select_ex_flip_y(numcellsx, numcellsy, weapontexcoord[player->weapon].x, weapontexcoord[player->weapon].y, weapontexcoord[player->weapon].w, weapontexcoord[player->weapon].h); + else + cell_select_ex(numcellsx, numcellsy, weapontexcoord[player->weapon].x, weapontexcoord[player->weapon].y, weapontexcoord[player->weapon].w, weapontexcoord[player->weapon].h); + + vec2 dir = direction; + float recoil = 0.0f; + if (player->weapon == WEAPON_TYPE_MELEE) + { + // if attack is under way, bash stuffs + //angle = getmeleeangle(direction, prev, player); + angle = gethammereangle(direction, prev, player); + gfx_quads_setrotation(angle); + dir = get_direction(angle * 256.0f); + } + else + { + recoil = getrecoil(prev, player); + } + + vec2 p = position + vec2(0,weaponrenderparams[player->weapon].offsety) + dir * weaponrenderparams[player->weapon].offsetx - dir * recoil; + gfx_quads_draw(p.x,p.y/*+bob*/,weaponrenderparams[player->weapon].sizex, weaponrenderparams[player->weapon].sizey); + // draw muzzleflare + if (player->weapon == WEAPON_TYPE_GUN || player->weapon == WEAPON_TYPE_SHOTGUN) + { + // check if we're firing stuff + if (true)///prev->attackticks) + { + float alpha = 0.0f; + int phase1tick = (player->attacklen - player->attackticks); + if (phase1tick < (player->visualtimeattack + 3)) + { + float intratick = snap_intratick(); + float t = ((((float)phase1tick) + intratick)/(float)player->visualtimeattack); + alpha = LERP(2.0, 0.0f, min(1.0f,max(0.0f,t))); + } + + if (alpha > 0.0f) + { + float offsety = -muzzleparams[player->weapon].offsety; + int itex = rand() % nummuzzletex[player->weapon]; + if(direction.x < 0) + { + offsety = -offsety; + cell_select_ex_flip_y(numcellsx, numcellsy, muzzletexcoord[player->weapon][itex].x, muzzletexcoord[player->weapon][itex].y, muzzletexcoord[player->weapon][itex].w, muzzletexcoord[player->weapon][itex].h); + } + else + cell_select_ex(numcellsx, numcellsy, muzzletexcoord[player->weapon][itex].x, muzzletexcoord[player->weapon][itex].y, muzzletexcoord[player->weapon][itex].w, muzzletexcoord[player->weapon][itex].h); + + gfx_quads_setcolor(1.0f,1.0f,1.0f,alpha); + vec2 diry(-dir.y,dir.x); + p += dir * muzzleparams[player->weapon].offsetx + diry * offsety; + gfx_quads_draw(p.x,p.y/*+bob*/,muzzleparams[player->weapon].sizex, muzzleparams[player->weapon].sizey); + } + } + } + } + /*else + { + // minigun + if(direction.x < 0) + cell_select_flip_y(4,4,8,2); + else + cell_select(4,4,8,2); + vec2 p = position + vec2(0,3); + gfx_quads_draw(p.x,p.y,8*8,8*2); + }*/ + + gfx_quads_setrotation(0); + gfx_quads_end(); + } + + + gfx_texture_set(texture_char_default); + gfx_quads_begin(); + + float bob = 0; + + // draw foots + const float cyclelength = 128.0f; + const float steplength = 26; + const float lift = 4.0f; + bool stationary = player->vx < 1 && player->vx > -1; + bool inair = col_check_point(player->x, player->y+16) == 0; + + for(int p = 0; p < 2; p++) + { + // first pass we draw the outline + // second pass we draw the filling + + //int v_offset = p?0:5; + int outline = p;// ? 1 : 0; + float offsety = charids[player->clientid % 16] * 2.0f; + + for(int f = 0; f < 2; f++) + { + float basesize = 10.0f; + if(f == 1) + { + // draw body + float t = fmod(position.x, cyclelength/2)/(cyclelength/2); + bob = -sinf(pow(t,2)*pi) * 3; + cell_select_ex(charnumcellsx,charnumcellsy, body[outline].x,body[outline].y + offsety,body[outline].w,body[outline].h); + //cell_select_ex(16,16, 0,0+v_offset,4,4); + //const float size = 64.0f; + if(stationary || inair) + bob = 0; + gfx_quads_draw(position.x, position.y-5+bob, 4*basesize, 4*basesize); + + // draw eyes + if(p == 1) + { + //cell_select_ex(16,16, 8,3,1,1); + vec2 md = get_direction(player->angle); + float mouse_dir_x = md.x; + float mouse_dir_y = md.y; + + // normal + cell_select_ex(charnumcellsx,charnumcellsy, leye.x,leye.y + offsety,leye.w,leye.h); + gfx_quads_draw(position.x-4+mouse_dir_x*4, position.y-8+mouse_dir_y*3+bob, basesize, basesize); + cell_select_ex(charnumcellsx,charnumcellsy, reye.x,reye.y + offsety,reye.w,reye.h); + gfx_quads_draw(position.x+4+mouse_dir_x*4, position.y-8+mouse_dir_y*3+bob, basesize, basesize); + } + } + + // draw feet + //cell_select_ex(16,16, 5,2+v_offset, 2,2); + cell_select_ex(charnumcellsx,charnumcellsy, feet[outline].x,feet[outline].y + offsety, feet[outline].w,feet[outline].h); + float w = basesize*2.5f; + float h = basesize*1.425f; + if(inair) + { + float r = 0.0f; + if(player->vy < 0.0f) + r = player->vy/3.0f; + else + r = player->vy/15.0f; + + // clamp the rotation + if(r > 0.5f) r = 0.5f; + if(r < -0.5f) r = -0.5f; + + if(player->vx > 0.0f) + r *= -1.0f; + gfx_quads_setrotation(r); + gfx_quads_drawTL(position.x-4+f*7-w/2, position.y+16 - h, w, h); + gfx_quads_setrotation(0); + } + else if(stationary) + { + // stationary + gfx_quads_drawTL(position.x-7+f*14-w/2, position.y+16 - h, w, h); + } + else + { + /* + The walk cycle, 2 parts + + 111 + 1 1 + 2 1 + 2 1 + 2222221 + GROUND GROUND GROUND + */ + + // moving + float tx = position.x+f*(cyclelength/2); + float t = fmod(tx, cyclelength) / cyclelength; + if(player->vx < 0) + t = 1.0f-t; + + float y; + float x = 0; + float r = 0; + float r_back = 1.5f; + + if(t < 0.5f) + { + // stomp down foot (part 1) + float st = t*2; + y = 1.0f-pow(st, 0.5f) + sinf(pow(st,2)*pi)*0.5f; + x = -steplength/2 + st*steplength; + r = r_back*(1-st) + sinf(pow(st,1.5f)*pi*2); + } + else + { + // lift foot up again (part 2) + float st = (t-0.5f)*2; + y = pow(st, 5.0f); + x = steplength/2 - st*steplength; + r = y*r_back; + } + + + if(player->vx > 0) + { + gfx_quads_setrotation(r); + gfx_quads_drawTL(position.x+x-w/2, position.y+16-y*lift - h, w, h); + } + else + { + gfx_quads_setrotation(-r); + gfx_quads_drawTL(position.x-x-w/2, position.y+16-y*lift - h, w, h); + } + gfx_quads_setrotation(0); + } + + } + } + + gfx_quads_end(); + + +} + +static player_input oldinput; +static bool bfirst = true; +void modc_render() +{ + if (bfirst) + { + bfirst = false; + oldinput.activeweapon = 0; + oldinput.angle = 0; + oldinput.blink = 0; + oldinput.fire = 0; + oldinput.hook = 0; + oldinput.jump = 0; + oldinput.left = 0; + oldinput.right = 0; + } + // fetch new input + { + int x, y; + inp_mouse_relative(&x, &y); + mouse_pos += vec2(x, y); + float l = length(mouse_pos); + if(l > 600.0f) + mouse_pos = normalize(mouse_pos)*600.0f; + } + + // snap input + { + player_input input; + input.left = inp_key_pressed('A'); + input.right = inp_key_pressed('D'); + float a = atan((float)mouse_pos.y/(float)mouse_pos.x); + if(mouse_pos.x < 0) + a = a+pi; + input.angle = (int)(a*256.0f); + input.jump = inp_key_pressed(baselib::keys::space) || inp_key_pressed('W'); + + input.fire = inp_mouse_button_pressed(0);// | (oldinput.fire << 16); + //oldinput.fire = input.fire & 0x0000ffff; + + input.hook = inp_mouse_button_pressed(1) || inp_key_pressed(baselib::keys::lctrl); // be nice to mac users O.o + input.blink = inp_key_pressed('S'); + + // Weapon switching + input.activeweapon = inp_key_pressed('1') ? 0x80000000 : 0; + if (!input.activeweapon) + input.activeweapon = inp_key_pressed('2') ? 0x80000000 | 1 : 0; + if (!input.activeweapon) + input.activeweapon = inp_key_pressed('3') ? 0x80000000 | 2 : 0; + if (!input.activeweapon) + input.activeweapon = inp_key_pressed('4') ? 0x80000000 | 3 : 0; + /*if (!input.activeweapon) + input.activeweapon = inp_key_pressed('5') ? 0x80000000 | 4 : 0;*/ + + snap_input(&input, sizeof(input)); + } + + // setup world view + { + // 1. fetch local player + // 2. set him to the center + + int num = snap_num_items(SNAP_CURRENT); + for(int i = 0; i < num; i++) + { + snap_item item; + void *data = snap_get_item(SNAP_CURRENT, i, &item); + + if(item.type == OBJTYPE_PLAYER) + { + obj_player *player = (obj_player *)data; + if(player->local) + { + local_player = player; + local_player_pos = vec2(player->x, player->y); + + void *p = snap_find_item(SNAP_PREV, item.type, item.id); + if(p) + local_player_pos = mix(vec2(((obj_player *)p)->x, ((obj_player *)p)->y), local_player_pos, snap_intratick()); + break; + } + } + } + } + + // pseudo format + float zoom = inp_key_pressed('T') ? 1.0 : 3.0f; + + float width = 400*zoom; + float height = 300*zoom; + float screen_x = 0; + float screen_y = 0; + + // center at char but can be moved when mouse is far away + float offx = 0, offy = 0; + int deadzone = 300; + if(mouse_pos.x > deadzone) offx = mouse_pos.x-deadzone; + if(mouse_pos.x <-deadzone) offx = mouse_pos.x+deadzone; + if(mouse_pos.y > deadzone) offy = mouse_pos.y-deadzone; + if(mouse_pos.y <-deadzone) offy = mouse_pos.y+deadzone; + offx = offx*2/3; + offy = offy*2/3; + + screen_x = local_player_pos.x+offx; + screen_y = local_player_pos.y+offy; + + gfx_mapscreen(screen_x-width/2, screen_y-height/2, screen_x+width/2, screen_y+height/2); + + // draw background + gfx_clear(0.65f,0.78f,0.9f); + + { + + vec2 pos(local_player_pos.x*0.5f, local_player_pos.y*0.5f); + + gfx_texture_set(-1); + gfx_blend_additive(); + gfx_quads_begin(); + const int rays = 10; + gfx_quads_setcolor(1.0f,1.0f,1.0f,0.025f); + for(int r = 0; r < rays; r++) + { + float a = r/(float)rays + client_localtime()*0.05f; + float size = (1.0f/(float)rays)*0.25f; + vec2 dir0(sinf((a-size)*pi*2.0f), cosf((a-size)*pi*2.0f)); + vec2 dir1(sinf((a+size)*pi*2.0f), cosf((a+size)*pi*2.0f)); + + //gfx_quads_draw_freeform(0,0, -100,0, -100,-100, 0,-100); + + gfx_quads_setcolorvertex(0, 1.0f,1.0f,1.0f,0.025f); + gfx_quads_setcolorvertex(1, 1.0f,1.0f,1.0f,0.025f); + gfx_quads_setcolorvertex(2, 1.0f,1.0f,1.0f,0.0f); + gfx_quads_setcolorvertex(3, 1.0f,1.0f,1.0f,0.0f); + const float range = 1000.0f; + gfx_quads_draw_freeform( + pos.x+dir0.x, pos.y+dir0.y, + pos.x+dir1.x, pos.y+dir1.y, + pos.x+dir0.x*range, pos.y+dir0.y*range, + pos.x+dir1.x*range, pos.y+dir1.y*range); + } + gfx_quads_end(); + gfx_blend_normal(); + + gfx_texture_set(texture_sun); + gfx_quads_begin(); + gfx_quads_draw(pos.x, pos.y, 256, 256); + gfx_quads_end(); + } + + // render map + tilemap_render(32.0f, 0); +#ifdef _DEBUG + float speed = 0.0f; +#endif + // render items + int num = snap_num_items(SNAP_CURRENT); + for(int i = 0; i < num; i++) + { + snap_item item; + void *data = snap_get_item(SNAP_CURRENT, i, &item); + + if(item.type == OBJTYPE_PLAYER) + { + void *prev = snap_find_item(SNAP_PREV, item.type, item.id); + if(prev) + { + render_player((obj_player *)prev, (obj_player *)data); +/*#ifdef _DEBUG + { + obj_player *p = (obj_player *)prev; + obj_player *c = (obj_player *)data; + vec2 positionold = vec2(p->x, p->y); + vec2 poscur = vec2(c->x, c->y); + speed = distance(positionold,poscur); + } +#endif*/ + } + } + else if(item.type == OBJTYPE_PROJECTILE) + { + void *prev = snap_find_item(SNAP_PREV, item.type, item.id); + if(prev) + render_projectile((obj_projectile *)prev, (obj_projectile *)data); + } + else if(item.type == OBJTYPE_POWERUP) + { + void *prev = snap_find_item(SNAP_PREV, item.type, item.id); + if(prev) + render_powerup((obj_powerup*)prev, (obj_powerup *)data); + } + } + + // render particles + temp_system.update(client_frametime()); + temp_system.render(); + + tilemap_render(32.0f, 1); + + // render health mods + healthmods.render(); + + // render cursor + // FIXME CURSOR!!! + + if(local_player) + { + gfx_texture_set(texture_weapon); + gfx_quads_begin(); + if (local_player->modifier & (1 << MODIFIER_TYPE_NINJA)) + cell_select_ex(numcellsx,numcellsy, modifiertexcoordcursor[MODIFIER_TYPE_NINJA].x, modifiertexcoordcursor[MODIFIER_TYPE_NINJA].y, modifiertexcoordcursor[MODIFIER_TYPE_NINJA].w, modifiertexcoordcursor[MODIFIER_TYPE_NINJA].h); + else + cell_select_ex(numcellsx,numcellsy, weapontexcoordcursor[local_player->weapon].x, weapontexcoordcursor[local_player->weapon].y, weapontexcoordcursor[local_player->weapon].w, weapontexcoordcursor[local_player->weapon].h); + float cursorsize = 64; + gfx_quads_draw(local_player_pos.x+mouse_pos.x, local_player_pos.y+mouse_pos.y,cursorsize,cursorsize); + + + // render ammo count + // render gui stuff + gfx_quads_end(); + gfx_quads_begin(); + gfx_mapscreen(0,0,400,300); + cell_select_ex(numcellsx,numcellsy, weaponprojtexcoord[local_player->weapon].x, weaponprojtexcoord[local_player->weapon].y, weaponprojtexcoord[local_player->weapon].w, weaponprojtexcoord[local_player->weapon].h); + for (int i = 0; i < local_player->ammocount; i++) + { + gfx_quads_drawTL(10+i*12,34,10,10); + } + gfx_quads_end(); + + gfx_texture_set(texture_game); + gfx_quads_begin(); + int h = 0; + cell_select_ex(32,16, 0,0, 4,4); + for(; h < local_player->health; h++) + gfx_quads_drawTL(10+h*12,10,10,10); + + cell_select_ex(32,16, 5,0, 4,4); + for(; h < 10; h++) + gfx_quads_drawTL(10+h*12,10,10,10); + + h = 0; + cell_select_ex(32,16, 0,5, 4,4); + for(; h < local_player->armor; h++) + gfx_quads_drawTL(10+h*12,22,10,10); + + cell_select_ex(32,16, 5,5, 4,4); + for(; h < 10; h++) + gfx_quads_drawTL(10+h*12,22,10,10); + gfx_quads_end(); + + // render speed +/*#ifdef _DEBUG + gfx_texture_set(font_texture); + char text[256]; + sprintf(text,"speed: %f",speed); + gfx_quads_text(300,20,10,text); +#endif*/ + } + // render gui stuff + gfx_mapscreen(0,0,400,300); + // render score board + if(inp_key_pressed(baselib::keys::tab)) + { + gfx_texture_set(font_texture); + gfx_quads_text(10, 50, 8, "Score Board"); + + int num = snap_num_items(SNAP_CURRENT); + int row = 1; + for(int i = 0; i < num; i++) + { + snap_item item; + void *data = snap_get_item(SNAP_CURRENT, i, &item); + + if(item.type == OBJTYPE_PLAYER) + { + obj_player *player = (obj_player *)data; + if(player) + { + char buf[128]; + char name[32]; + snap_decode_string(player->name, name, 32); + sprintf(buf, "%4d %s", player->score, name); + gfx_quads_text(10, 50 + 10 * row, 8, buf); + row++; + } + } + } + } +} diff --git a/src/game/game_server.cpp b/src/game/game_server.cpp new file mode 100644 index 00000000..5e93165a --- /dev/null +++ b/src/game/game_server.cpp @@ -0,0 +1,2122 @@ +#include +#include +#include "game.h" + +using namespace baselib; + +// --------- PHYSICS TWEAK! -------- +const float ground_control_speed = 7.0f; +const float ground_control_accel = 2.0f; +const float ground_friction = 0.5f; +const float ground_jump_speed = 12.0f; +const float air_control_speed = 3.5f; +const float air_control_accel = 1.2f; +const float air_friction = 0.95f; +const float hook_length = 32*10.0f; +const float hook_fire_speed = 45.0f; +const float hook_drag_accel = 3.0f; +const float hook_drag_speed = 15.0f; +const float gravity = 0.5f; + +class player* get_player(int index); +void create_healthmod(vec2 p, int amount); +void create_explosion(vec2 p, int owner = -1, bool bnodamage = false); +void create_smoke(vec2 p); +void create_sound(vec2 pos, int sound, int loopflags = 0); +class player* intersect_player(vec2 pos0, vec2 pos1, vec2& new_pos, class entity* notthis = 0); + +// TODO: rewrite this smarter! +void move_box(vec2 *inout_pos, vec2 *inout_vel, vec2 size, float elasticity) +{ + // do the move + vec2 pos = *inout_pos; + vec2 vel = *inout_vel; + + float distance = length(vel); + int max = (int)distance; + + vec2 offsets[4] = { vec2(-size.x/2, -size.y/2), vec2( size.x/2, -size.y/2), + vec2(-size.x/2, size.y/2), vec2( size.x/2, size.y/2)}; + + if(distance > 0.00001f) + { + vec2 old_pos = pos; + for(int i = 0; i <= max; i++) + { + float amount = i/(float)max; + if(max == 0) + amount = 0; + + vec2 new_pos = pos + vel*amount; // TODO: this row is not nice + + for(int p = 0; p < 4; p++) + { + vec2 np = new_pos+offsets[p]; + vec2 op = old_pos+offsets[p]; + if(col_check_point(np)) + { + int affected = 0; + if(col_check_point(np.x, op.y)) + { + vel.x = -vel.x*elasticity; + pos.x = old_pos.x; + new_pos.x = old_pos.x; + affected++; + } + + if(col_check_point(op.x, np.y)) + { + vel.y = -vel.y*elasticity; + pos.y = old_pos.y; + new_pos.y = old_pos.y; + affected++; + } + + if(!affected) + { + new_pos = old_pos; + pos = old_pos; + vel *= -elasticity; + } + } + } + + old_pos = new_pos; + } + + pos = old_pos; + } + + *inout_pos = pos; + *inout_vel = vel; +} + +// TODO: rewrite this smarter! +bool intersect_line(vec2 pos0, vec2 pos1, vec2 *out) +{ + float d = distance(pos0, pos1); + + for(float f = 0; f < d; f++) + { + float a = f/d; + vec2 pos = mix(pos0, pos1, a); + if(col_check_point(pos)) + { + if(out) + *out = pos; + return true; + } + } + if(out) + *out = pos1; + return false; +} + +// +class event_handler +{ + static const int MAX_EVENTS = 128; + static const int MAX_DATASIZE = 128*4; + + int types[MAX_EVENTS]; // TODO: remove some of these arrays + int offsets[MAX_EVENTS]; + int sizes[MAX_EVENTS]; + char data[MAX_DATASIZE]; + + int current_offset; + int num_events; +public: + event_handler() + { + num_events = 0; + } + + void *create(int type, int size) + { + void *p = &data[current_offset]; + offsets[num_events] = current_offset; + types[num_events] = type; + sizes[num_events] = size; + current_offset += size; + num_events++; + return p; + } + + void clear() + { + num_events = 0; + current_offset = 0; + } + + void snap(int snapping_client) + { + for(int i = 0; i < num_events; i++) + { + void *d = snap_new_item(types[i], i, sizes[i]); + mem_copy(d, &data[offsets[i]], sizes[i]); + } + } +}; + +static event_handler events; +/* +template +class pool +{ + struct element + { + int next_free; + T data; + }; + + element elements[SIZE]; + int first_free; +public: + pool() + { + first_free = 0; + for(int i = 0; i < SIZE; i++) + elements[i].next_free = i+1; + elements[SIZE-1].next_free = -1; + } +};*/ + +// a basic entity +class entity +{ +private: + friend class game_world; + friend class player; + entity *prev_entity; + entity *next_entity; + int index; + +public: + vec2 pos; + float proximity_radius; + unsigned flags; + int objtype; + + enum + { + FLAG_DESTROY=0x00000001, + }; + + entity(int objtype) + { + this->objtype = objtype; + pos = vec2(0,0); + flags = 0; + proximity_radius = 0; + } + + virtual ~entity() + { + } + + virtual void destroy() { delete this; } + virtual void tick() {} + virtual void snap(int snapping_client) {} + + virtual bool take_damage(vec2 force, int dmg, int from) { return true; } +}; + +class powerup : public entity +{ +public: + static const int phys_size = 14; + enum + { + POWERUP_FLAG_HOOKABLE = 1 << 0, + }; + vec2 vel; + class player* playerhooked; + int type; + int id; + int subtype; // weapon type for instance? + int numitems; // number off powerup items + int flags; + int spawntick; + powerup(int _type, int _subtype = 0, int _numitems = 0, int _flags = 0); + + static void spawnrandom(int _lifespan); + + void tick(); + + void snap(int snapping_client); +}; + +// game world. handles all entities +class game_world +{ +public: + entity *first_entity; + game_world() + { + first_entity = 0x0; + } + + int find_entities(vec2 pos, float radius, entity **ents, int max) + { + int num = 0; + for(entity *ent = first_entity; ent; ent = ent->next_entity) + { + if(distance(ent->pos, pos) < radius+ent->proximity_radius) + { + ents[num] = ent; + num++; + if(num == max) + break; + } + } + + return num; + } + + int find_entities(vec2 pos, float radius, entity **ents, int max, const int* types, int maxtypes) + { + int num = 0; + for(entity *ent = first_entity; ent; ent = ent->next_entity) + { + for (int i = 0; i < maxtypes; i++) + { + if (ent->objtype != types[i]) + continue; + + if(distance(ent->pos, pos) < radius+ent->proximity_radius) + { + ents[num] = ent; + num++; + if(num == max) + break; + } + } + } + + return num; + } + + void insert_entity(entity *ent) + { + // insert it + if(first_entity) + first_entity->prev_entity = ent; + ent->next_entity = first_entity; + ent->prev_entity = 0x0; + first_entity = ent; + } + + void destroy_entity(entity *ent) + { + ent->flags |= entity::FLAG_DESTROY; + // call destroy + //remove_entity(ent); + //ent->destroy(); + } + + void remove_entity(entity *ent) + { + // remove + if(ent->prev_entity) + ent->prev_entity->next_entity = ent->next_entity; + else + first_entity = ent->next_entity; + if(ent->next_entity) + ent->next_entity->prev_entity = ent->prev_entity; + } + + // + void snap(int snapping_client) + { + for(entity *ent = first_entity; ent; ent = ent->next_entity) + ent->snap(snapping_client); + } + + void tick() + { + // update all objects + for(entity *ent = first_entity; ent; ent = ent->next_entity) + ent->tick(); + + // destroy objects marked for destruction + entity *ent = first_entity; + while(ent) + { + entity *next = ent->next_entity; + if(ent->flags&entity::FLAG_DESTROY) + { + remove_entity(ent); + ent->destroy(); + } + ent = next; + } + } +}; + +static game_world world; + +// projectile entity +class projectile : public entity +{ +public: + enum + { + PROJECTILE_FLAGS_EXPLODE = 1 << 0, + }; + vec2 vel; + entity* powner; + int lifespan; + int id; + int owner; + int type; + int flags; + int damage; + int sound_impact; + float force; + + projectile(int type, int owner, vec2 pos, vec2 vel, int span, entity* powner, int damage, int flags = 0, float force = 0.0f, int sound_impact = -1) : + entity(OBJTYPE_PROJECTILE) + { + static int current_id = 0; + this->id = current_id++; + this->type = type; + this->pos = pos; + this->vel = vel; + this->lifespan = span; + this->owner = owner; + this->powner = powner; + this->flags = flags; + this->force = force; + this->damage = damage; + this->sound_impact = sound_impact; + world.insert_entity(this); + } + + void tick() + { + vec2 oldpos = pos; + vel.y += 0.25f; + pos += vel; + lifespan--; + // check player intersection as well + entity* targetplayer = (entity*)intersect_player(oldpos, pos, oldpos, powner); + if(targetplayer || lifespan < 0 || col_check_point((int)pos.x, (int)pos.y)) + { + if (lifespan >= 0) + create_sound(pos, sound_impact); + if (flags & PROJECTILE_FLAGS_EXPLODE) + { + create_explosion(oldpos, owner); + } + else if (targetplayer) + { + targetplayer->take_damage(normalize(vel) * force, damage, owner); + } + world.destroy_entity(this); + } + } + + void snap(int snapping_client) + { + obj_projectile *proj = (obj_projectile *)snap_new_item(OBJTYPE_PROJECTILE, id, sizeof(obj_projectile)); + proj->x = (int)pos.x; + proj->y = (int)pos.y; + proj->vx = (int)vel.x; + proj->vy = (int)vel.y; + proj->type = type; + } +}; + +// player entity +class player : public entity +{ +public: + static const int phys_size = 28; + enum + { + WEAPON_NEEDRELOAD = 1 << 0, + WEAPON_DROPONUNEQUIP = 1 << 1, + WEAPON_DRAWSAMMO = 1 << 2, + WEAPON_HASSECONDARY = 1 << 3, + WEAPON_ISACTIVE = 1 << 4, // has the item + WEAPON_AUTOFIRE = 1 << 5, + + WEAPON_PROJECTILETYPE_GUN = 0, + WEAPON_PROJECTILETYPE_ROCKET = 1, + WEAPON_PROJECTILETYPE_SHOTGUN = 2, + + // Gun + + + // modifiers + MODIFIER_HASACTIVATIONS = 1 << 0, + MODIFIER_TIMELIMITED = 1 << 1, + MODIFIER_ISACTIVE = 1 << 2, + MODIFIER_NEEDSACTIVATION = 1 << 3, + + MODIFIER_RETURNFLAGS_OVERRIDEWEAPON = 1 << 0, + MODIFIER_RETURNFLAGS_OVERRIDEVELOCITY = 1 << 1, + MODIFIER_RETURNFLAGS_OVERRIDEPOSITION = 1 << 2, + MODIFIER_RETURNFLAGS_OVERRIDEGRAVITY = 1 << 3, + }; + class weapon + { + public: + entity* hitobjects[10]; + int numobjectshit; // for melee, so we don't hit the same object more than once per bash + int weapontype; + int equiptime; + int unequiptime; + int numammo; + int magsize; + int nummagazines; + int flags; + int firetime; + int reloadtime; + int projectileclass; + int damage; + int sound_fire; + int sound_equip; + int sound_impact; + int sound_impact_projectile; + int visualtimeattack; + float projectilevel; + float projectilespan; + float reach; // for melee + float force; + float recoilforce; + float projoffsety; + float projoffsetx; + + weapon() + { + weapontype = 0; + numammo = 0; + flags = 0; + reloadtime = 0; + projectileclass = 0; + numobjectshit = 0; + reach = 0.0f; + force = 5.0f; + damage = 1; + sound_fire = -1; + sound_equip = -1; + sound_impact = -1; + sound_impact_projectile = -1, + visualtimeattack = 3; + recoilforce = 0.0f; + projoffsety = 0.0f; + projoffsetx = 0.0f; + } + + void setgun(int ammo = 10) + { + weapontype = WEAPON_TYPE_GUN; + flags = 0;//WEAPON_DRAWSAMMO; + numammo = ammo; + projectileclass = WEAPON_PROJECTILETYPE_GUN; + firetime = SERVER_TICK_SPEED/10; + magsize = 0; + projectilevel = 30.0f; + projectilespan = 50.0f * 1.0f; + sound_fire = SOUND_FIRE_GUN; + sound_equip = SOUND_EQUIP_GUN; + sound_impact_projectile = SOUND_IMPACT_PROJECTILE_GUN; + projoffsety = -10.0f; + projoffsetx = 24.0f; + } + + void setrocket(int ammo = 10) + { + weapontype = WEAPON_TYPE_ROCKET; + flags = WEAPON_DRAWSAMMO; + numammo = ammo; + projectileclass = WEAPON_PROJECTILETYPE_ROCKET; + projectilevel = 15.0f; + projectilespan = 50.0f * 5.0f; + firetime = SERVER_TICK_SPEED * 4/5; + magsize = 0; + recoilforce = 5.0f; + sound_fire = SOUND_FIRE_ROCKET; + sound_equip = SOUND_EQUIP_ROCKET; + sound_impact_projectile = SOUND_IMPACT_PROJECTILE_ROCKET; + projoffsety = -17.0f; + projoffsetx = 24.0f; + } + + /*void setsniper(int ammo = 10) + { + weapontype = WEAPON_TYPE_SNIPER; + flags = WEAPON_DRAWSAMMO | WEAPON_HASSECONDARY | WEAPON_NEEDRELOAD; + numammo = ammo; + projectileclass = WEAPON_PROJECTILETYPE_SNIPER; + projectilevel = 30.0f; + projectilespan = 50.0f * 5.0f; + firetime = SERVER_TICK_SPEED; + reloadtime = SERVER_TICK_SPEED/2; + magsize = 2; + recoilforce = 20.0f; + }*/ + + void setshotgun(int ammo = 10) + { + weapontype = WEAPON_TYPE_SHOTGUN; + flags = WEAPON_DRAWSAMMO | WEAPON_NEEDRELOAD; + numammo = ammo; + projectileclass = WEAPON_PROJECTILETYPE_SHOTGUN; + projectilevel = 30.0f; + projectilespan = 50.0f * 5.0f; + firetime = SERVER_TICK_SPEED/2; + reloadtime = SERVER_TICK_SPEED/2; + magsize = 2; + damage = 3; + recoilforce = 5.0f; + sound_fire = SOUND_FIRE_SHOTGUN; + sound_equip = SOUND_EQUIP_SHOTGUN; + sound_impact_projectile = SOUND_IMPACT_PROJECTILE_SHOTGUN; + projoffsety = -17.0f; + projoffsetx = 24.0f; + } + + void setmelee(int ammo = 10) + { + weapontype = WEAPON_TYPE_MELEE; + flags = 0;//WEAPON_AUTOFIRE; + numammo = ammo; + projectileclass = -1; + firetime = SERVER_TICK_SPEED/5; + reloadtime = 0; + magsize = 2; + numobjectshit = 0; + reach = 15.0f; + damage = 1; + sound_fire = SOUND_FIRE_MELEE; + sound_equip = SOUND_EQUIP_MELEE; + sound_impact = SOUND_PLAYER_IMPACT; + } + + void settype() + { + switch(weapontype) + { + case WEAPON_TYPE_GUN: + { + setgun(); + break; + } + case WEAPON_TYPE_ROCKET: + { + setrocket(); + break; + } + /*case WEAPON_TYPE_SNIPER: + { + setsniper(); + break; + }*/ + case WEAPON_TYPE_SHOTGUN: + { + setshotgun(); + break; + } + case WEAPON_TYPE_MELEE: + { + setmelee(); + break; + } + default: + break; + } + } + + int activate(player* player) + { + // create sound event for fire + int projectileflags = 0; + create_sound(player->pos, sound_fire); + + switch (weapontype) + { + case WEAPON_TYPE_ROCKET: + projectileflags |= projectile::PROJECTILE_FLAGS_EXPLODE; + case WEAPON_TYPE_GUN: + //case WEAPON_TYPE_SNIPER: + case WEAPON_TYPE_SHOTGUN: + { + if (flags & WEAPON_DRAWSAMMO) + numammo--; + // Create projectile + new projectile(projectileclass, player->client_id, player->pos+vec2(0,projoffsety)+player->direction*projoffsetx, player->direction*projectilevel, projectilespan, player, damage, projectileflags, force, sound_impact_projectile); + // recoil force if any + if (recoilforce > 0.0f) + { + vec2 dir(player->direction.x,0.5); + if (dir.x == 0.0f) + dir.x = 0.5f; + else + dir = normalize(dir); + player->vel -= dir * recoilforce; + } + return firetime; + } + case WEAPON_TYPE_MELEE: + { + // Start bash sequence + numobjectshit = 0; + return firetime; + } + default: + return 0; + } + } + + void update(player* owner, int fire_timeout) + { + switch(weapontype) + { + case WEAPON_TYPE_MELEE: + { + // No more melee + if (fire_timeout <= 0) + return; + + // only one that needs update (for now) + // do selection for the weapon and bash anything in it + // check if we hit anything along the way + int type = OBJTYPE_PLAYER; + entity *ents[64]; + vec2 dir = owner->pos + owner->direction * reach; + float radius = length(dir * 0.5f); + vec2 center = owner->pos + dir * 0.5f; + int num = world.find_entities(center, radius, ents, 64, &type, 1); + + for (int i = 0; i < num; i++) + { + // Check if entity is a player + if (ents[i] == owner) + continue; + // make sure we haven't hit this object before + bool balreadyhit = false; + for (int j = 0; j < numobjectshit; j++) + { + if (hitobjects[j] == ents[i]) + balreadyhit = true; + } + if (balreadyhit) + continue; + + // check so we are sufficiently close + if (distance(ents[i]->pos, owner->pos) > (owner->phys_size * 2.0f)) + continue; + + // hit a player, give him damage and stuffs... + // create sound for bash + create_sound(ents[i]->pos, sound_impact); + + // set his velocity to fast upward (for now) + create_smoke(ents[i]->pos); + hitobjects[numobjectshit++] = ents[i]; + ents[i]->take_damage(vec2(0,10.0f), damage, owner->client_id); + player* target = (player*)ents[i]; + vec2 dir; + if (length(target->pos - owner->pos) > 0.0f) + dir = normalize(target->pos - owner->pos); + else + dir = vec2(0,-1); + target->vel += dir * 10.0f + vec2(0,-10.0f); + } + break; + } + default: + break; + } + } + }; + + class modifier + { + public: + vec2 activationdir; + entity* hitobjects[10]; + int numobjectshit; + float velocity; + int modifiertype; + int duration; + int numactivations; + int activationtime; + int cooldown; + int movetime; + int visualtimeattack; + int currentactivation; + int currentmovetime; + int currentcooldown; + int damage; + int flags; + int sound_impact; + int sound_activate; + + modifier() + { + modifiertype = 0; + duration = 0; + numobjectshit = 0; + numactivations = 0; + activationtime = 0; + cooldown = 0; + movetime = 0; + currentactivation = 0; + currentmovetime = 0; + currentcooldown =0; + damage = 0; + flags = 0; + activationdir = vec2(0.0f, 1.0f); + velocity = 0.0f; + visualtimeattack = 0; + sound_impact = -1; + } + + void setninja() + { + modifiertype = MODIFIER_TYPE_NINJA; + duration = SERVER_TICK_SPEED * 15; + numactivations = -1; + movetime = SERVER_TICK_SPEED / 5; + activationtime = SERVER_TICK_SPEED / 2; + cooldown = SERVER_TICK_SPEED; + currentactivation = 0; + currentmovetime = 0; + numobjectshit = 0; + damage = 3; + flags = MODIFIER_TIMELIMITED | MODIFIER_NEEDSACTIVATION; + velocity = 50.0f; + visualtimeattack = 3; + sound_impact = SOUND_PLAYER_IMPACT_NINJA; + sound_activate = SOUND_FIRE_NINJA; + } + + void settimefield() + { + modifiertype = MODIFIER_TYPE_TIMEFIELD; + duration = SERVER_TICK_SPEED * 10; + numactivations = -1; + activationtime = SERVER_TICK_SPEED; + numobjectshit = 0; + currentactivation = 0; + flags = MODIFIER_TIMELIMITED; + velocity = 0.0f; + } + + void settype() + { + switch (modifiertype) + { + case MODIFIER_TYPE_NINJA: + { + setninja(); + break; + } + case MODIFIER_TYPE_TIMEFIELD: + { + settimefield(); + break; + } + default: + break; + } + } + + int updateninja(player* player) + { + if (currentactivation <= 0) + return MODIFIER_RETURNFLAGS_OVERRIDEWEAPON; + currentactivation--; + currentmovetime--; + + if (currentmovetime == 0) + { + // reset player velocity + player->vel *= 0.2f; + //return MODIFIER_RETURNFLAGS_OVERRIDEWEAPON; + } + + if (currentmovetime > 0) + { + // Set player velocity + player->vel = activationdir * velocity; + vec2 oldpos = player->pos; + move_box(&player->pos, &player->vel, vec2(player->phys_size, player->phys_size), 0.0f); + // reset velocity so the client doesn't predict stuff + player->vel = vec2(0.0f,0.0f); + if ((currentmovetime % 2) == 0) + { + create_smoke(player->pos); + } + + // check if we hit anything along the way + { + int type = OBJTYPE_PLAYER; + entity *ents[64]; + vec2 dir = player->pos - oldpos; + float radius = length(dir * 0.5f); + vec2 center = oldpos + dir * 0.5f; + int num = world.find_entities(center, radius, ents, 64, &type, 1); + + for (int i = 0; i < num; i++) + { + // Check if entity is a player + if (ents[i] == player) + continue; + // make sure we haven't hit this object before + bool balreadyhit = false; + for (int j = 0; j < numobjectshit; j++) + { + if (hitobjects[j] == ents[i]) + balreadyhit = true; + } + if (balreadyhit) + continue; + + // check so we are sufficiently close + if (distance(ents[i]->pos, player->pos) > (player->phys_size * 2.0f)) + continue; + + // hit a player, give him damage and stuffs... + create_sound(ents[i]->pos, sound_impact); + // set his velocity to fast upward (for now) + hitobjects[numobjectshit++] = ents[i]; + ents[i]->take_damage(vec2(0,10.0f), damage, player->client_id); + } + } + return MODIFIER_RETURNFLAGS_OVERRIDEWEAPON | MODIFIER_RETURNFLAGS_OVERRIDEVELOCITY | MODIFIER_RETURNFLAGS_OVERRIDEPOSITION|MODIFIER_RETURNFLAGS_OVERRIDEGRAVITY; + } + + + // move char, and check intersection from us to target + return MODIFIER_RETURNFLAGS_OVERRIDEWEAPON | MODIFIER_RETURNFLAGS_OVERRIDEVELOCITY; + } + + int activateninja(player* player) + { + // ok then, activate ninja + activationdir = player->direction; + currentactivation = activationtime; + currentmovetime = movetime; + currentcooldown = cooldown; + // reset hit objects + numobjectshit = 0; + + create_sound(player->pos, SOUND_FIRE_NINJA); + + return MODIFIER_RETURNFLAGS_OVERRIDEWEAPON; + } + + int activate(player* player) + { + if (flags & MODIFIER_NEEDSACTIVATION) + { + if (!numactivations) + return 0; + numactivations--; + } + + switch (modifiertype) + { + case MODIFIER_TYPE_NINJA: + { + return activateninja(player); + } + /*case MODIFIER_TYPE_TIMEFIELD: + { + updatetimefield(); + break; + }*/ + default: + return 0; + } + } + int update(player* player) + { + switch (modifiertype) + { + case MODIFIER_TYPE_NINJA: + { + return updateninja(player); + } + /*case MODIFIER_TYPE_TIMEFIELD: + { + updatetimefield(); + break; + }*/ + default: + return 0; + } + } + }; + + enum + { + PLAYER_FLAGS_ISRELOADING = 1 << 0, + PLAYER_FLAGS_ISEQUIPPING = 1 << 1, + }; + + weapon lweapons[WEAPON_NUMWEAPONS]; + modifier modifiers[MODIFIER_NUMMODIFIERS]; + int iactiveweapon; + int inextweapon; + int equip_time; + + int client_id; + int flags; + + char name[32]; + player_input previnput; + player_input input; + int tick_count; + int damage_taken_tick; + + vec2 vel; + vec2 direction; + + int jumped; + int airjumped; + + //int firing; + int hooking; + + int fire_timeout; + int reload_timeout; + + int health; + int armor; + + int score; + + // sounds + int sound_player_jump; + int sound_player_land; + int sound_player_hurt_short; + int sound_player_hurt_long; + int sound_player_spawn; + int sound_player_chain_loop; + int sound_player_chain_impact; + int sound_player_impact; + int sound_player_impact_ninja; + int sound_player_die; + int sound_player_switchweapon; + + player* phookedplayer; + powerup* phookedpowerup; + int numhooked; + vec2 hook_pos; + vec2 hook_dir; + + player() : + entity(OBJTYPE_PLAYER) + { + reset(); + + //firing = 0; + // setup weaponflags and stuff + lweapons[WEAPON_TYPE_GUN].setgun(); + lweapons[WEAPON_TYPE_ROCKET].setrocket(); + //lweapons[WEAPON_TYPE_SNIPER].setsniper(); + lweapons[WEAPON_TYPE_SHOTGUN].setshotgun(); + lweapons[WEAPON_TYPE_MELEE].setmelee(); + + modifiers[MODIFIER_TYPE_NINJA].setninja(); + modifiers[MODIFIER_TYPE_TIMEFIELD].settimefield(); + //modifiers[MODIFIER_TYPE_NINJA].flags |= MODIFIER_ISACTIVE; + + sound_player_jump = SOUND_PLAYER_JUMP; + sound_player_hurt_short = SOUND_PLAYER_HURT_SHORT; + sound_player_hurt_long = SOUND_PLAYER_HURT_LONG; + sound_player_spawn = SOUND_PLAYER_SPAWN; + sound_player_chain_loop = SOUND_PLAYER_CHAIN_LOOP; + sound_player_chain_impact = SOUND_PLAYER_CHAIN_IMPACT; + sound_player_impact = SOUND_PLAYER_IMPACT; + sound_player_impact_ninja = SOUND_PLAYER_IMPACT_NINJA; + sound_player_die = SOUND_PLAYER_DIE; + sound_player_switchweapon = SOUND_PLAYER_SWITCHWEAPON; + sound_player_land = SOUND_PLAYER_LAND; + } + + void reset() + { + equip_time = 0; + phookedplayer = 0; + numhooked = 0; + proximity_radius = phys_size; + name[0] = 'n'; + name[1] = 'o'; + name[2] = 'o'; + name[3] = 'b'; + name[4] = 0; + + pos = vec2(100.0f, 0.0f); + vel = vec2(0.0f, 0.0f); + direction = vec2(0.0f, 1.0f); + client_id = -1; + tick_count = 0; + score = 0; + flags = 0; + } + + virtual void destroy() { flags = 0; } + + void respawn() + { + health = PLAYER_MAXHEALTH; + armor = 0; + + hooking = 0; + phookedplayer = 0; + phookedpowerup = 0; + numhooked = 0; + fire_timeout = 0; + reload_timeout = 0; + iactiveweapon = 0; + inextweapon = -1; + equip_time = 0; + jumped = 0; + airjumped = 0; + mem_zero(&input, sizeof(input)); + vel = vec2(0.0f, 0.0f); + + int start, num; + map_get_type(1, &start, &num); + + if(num) + { + mapres_spawnpoint *sp = (mapres_spawnpoint*)map_get_item(start + (rand()%num), NULL, NULL); + pos = vec2(sp->x, sp->y); + } + else + pos = vec2(100.0f, -60.0f); + + // reset active flags + for (int i = 0; i < WEAPON_NUMWEAPONS; i++) + { + // reset and remove + lweapons[i].settype(); + lweapons[i].flags &= ~WEAPON_ISACTIVE; + } + + + // TEMP REMOVE + + /*for (int i = 0; i < WEAPON_NUMWEAPONS; i++) + { + lweapons[i].settype(); + lweapons[i].flags |= WEAPON_ISACTIVE; + }*/ + lweapons[WEAPON_TYPE_MELEE].flags |= WEAPON_ISACTIVE; + // Add gun as default weapon + iactiveweapon = WEAPON_TYPE_GUN; + lweapons[WEAPON_TYPE_GUN].numammo = 10; + lweapons[WEAPON_TYPE_GUN].flags |= WEAPON_ISACTIVE; + + create_sound(pos, sound_player_spawn); + } + + bool is_grounded() + { + if(col_check_point((int)(pos.x+phys_size/2), (int)(pos.y+phys_size/2+5))) + return true; + if(col_check_point((int)(pos.x-phys_size/2), (int)(pos.y+phys_size/2+5))) + return true; + return false; + } + + // Disable weapon activation if this returns true + int handlemodifiers() + { + int returnflags = 0; + for (int i = 0; i < MODIFIER_NUMMODIFIERS; i++) + { + if (modifiers[i].flags & MODIFIER_ISACTIVE) + { + modifiers[i].duration--; + modifiers[i].currentcooldown--; + + // Check if it should activate + if (modifiers[i].currentcooldown <= 0 && + (modifiers[i].flags & MODIFIER_NEEDSACTIVATION) && + input.fire && !(previnput.fire)) + { + returnflags |= modifiers[i].activate(this); + } + + returnflags |= modifiers[i].update(this); + + // remove active if timed out + if (modifiers[i].duration <= 0 && modifiers[i].currentactivation <= 0) + modifiers[i].flags &= ~MODIFIER_ISACTIVE; + } + } + return returnflags; + } + + void handleweapon() + { + // handle weapon + if(input.fire && (!previnput.fire || lweapons[iactiveweapon].flags & WEAPON_AUTOFIRE) && + !(flags & PLAYER_FLAGS_ISEQUIPPING) && !reload_timeout) + { + if(fire_timeout == 0) + { + if (lweapons[iactiveweapon].numammo || !(lweapons[iactiveweapon].flags & WEAPON_DRAWSAMMO)) + { + // Decrease ammo + fire_timeout = lweapons[iactiveweapon].activate(this); + } + else if ((lweapons[iactiveweapon].flags & WEAPON_NEEDRELOAD) && lweapons[iactiveweapon].nummagazines) + { + // reload + reload_timeout = lweapons[iactiveweapon].reloadtime; + lweapons[iactiveweapon].nummagazines--; + lweapons[iactiveweapon].numammo = lweapons[iactiveweapon].magsize; + } + } + } + + // update active weapon + lweapons[iactiveweapon].update(this, fire_timeout); + } + + void handlehook() + { + // handle hook + if(input.hook) + { + if(hooking == 0) + { + hooking = 1; + hook_pos = pos; + hook_dir = direction; + // Sound + create_sound(pos, sound_player_chain_loop, SOUND_LOOPFLAG_STARTLOOP); + } + else if(hooking == 1) + { + vec2 new_pos = hook_pos+hook_dir*hook_fire_speed; + + // Check against other players and powerups first + player* targetplayer = 0; + powerup* targetpowerup = 0; + { + static const int typelist[2] = { OBJTYPE_PLAYER, OBJTYPE_POWERUP}; + entity *ents[64]; + vec2 dir = new_pos - hook_pos; + float radius = length(dir * 0.5f); + vec2 center = hook_pos + dir * 0.5f; + int num = world.find_entities(center, radius, ents, 64,typelist,2); + + for (int i = 0; i < num; i++) + { + // Check if entity is a player + if (ents[i] == this || (targetplayer && targetpowerup)) + continue; + + if (!targetplayer && ents[i]->objtype == OBJTYPE_PLAYER) + { + // temp, set hook pos to our position + if (((player*)ents[i])->phookedplayer != this) + targetplayer = (player*)ents[i]; + } + else if (!targetpowerup && ents[i]->objtype == OBJTYPE_POWERUP && + (((powerup*)ents[i])->flags & powerup::POWERUP_FLAG_HOOKABLE)) + { + targetpowerup = (powerup*)ents[i]; + } + } + } + + //player* targetplayer = intersect_player(hook_pos, hook_pos, new_pos, this); + if (targetplayer) + { + // So he can't move "normally" + new_pos = targetplayer->pos; + phookedplayer = targetplayer; + targetplayer->numhooked++; + hooking = 3; + create_sound(pos, sound_player_chain_impact); + + // stop looping chain sound + create_sound(pos, sound_player_chain_loop, SOUND_LOOPFLAG_STOPLOOP); + } + else if (targetpowerup) + { + new_pos = targetpowerup->pos; + phookedpowerup = targetpowerup; + phookedpowerup->playerhooked = this; + hooking = 4; + create_sound(pos, sound_player_chain_impact); + + // stop looping chain sound + create_sound(pos, sound_player_chain_loop, SOUND_LOOPFLAG_STOPLOOP); + } + else if(intersect_line(hook_pos, new_pos, &new_pos)) + { + hooking = 2; + create_sound(pos, sound_player_chain_impact); + + // stop looping chain sound + create_sound(pos, sound_player_chain_loop, SOUND_LOOPFLAG_STOPLOOP); + } + else if(distance(pos, new_pos) > hook_length) + { + hooking = -1; + create_sound(pos, sound_player_chain_loop, SOUND_LOOPFLAG_STOPLOOP); + } + + hook_pos = new_pos; + } + else if(hooking == 2) + { + vec2 hookvel = normalize(hook_pos-pos)*hook_drag_accel; + // the hook as more power to drag you up then down. + // this makes it easier to get on top of an platform + if(hookvel.y > 0) + hookvel.y *= 0.3f; + + // the hook will boost it's power if the player wants to move + // in that direction. otherwise it will dampen everything abit + if((hookvel.x < 0 && input.left) || (hookvel.x > 0 && input.right)) + hookvel.x *= 0.95f; + else + hookvel.x *= 0.75f; + vec2 new_vel = vel+hookvel; + + // check if we are under the legal limit for the hook + if(length(new_vel) < hook_drag_speed || length(new_vel) < length(vel)) + vel = new_vel; // no problem. apply + } + else if (hooking == 3) + { + // hmm, force the targetplayer towards us if possible, otherwise us towards them if they are already hooked + if (phookedplayer) + { + if (phookedplayer->hooking > 1) + { + // Drag us towards target player + vec2 hookvel = normalize(hook_pos-pos)*hook_drag_accel; + if((hookvel.x < 0 && input.left) || (hookvel.x > 0 && input.right)) + hookvel.x *= 0.95f; + else + hookvel.x *= 0.75f; + + // Apply the velocity + // the hook will boost it's power if the player wants to move + // in that direction. otherwise it will dampen everything abit + vec2 new_vel = vel+hookvel; + + // check if we are under the legal limit for the hook + if(length(new_vel) < hook_drag_speed || length(new_vel) < length(vel)) + vel = new_vel; // no problem. apply + } + else + { + // Drag targetplayer towards us + vec2 hookvel = normalize(pos-hook_pos)*hook_drag_accel; + + // Apply the velocity + // the hook will boost it's power if the player wants to move + // in that direction. otherwise it will dampen everything abit + vec2 new_vel = phookedplayer->vel+hookvel; + + // check if we are under the legal limit for the hook + if(length(new_vel) < hook_drag_speed || length(new_vel) < length(vel)) + phookedplayer->vel = new_vel; // no problem. apply + } + hook_pos = phookedplayer->pos; + // if hooked player dies, release the hook + } + else + { + hooking = -1; + phookedplayer = 0; + } + } + else if (hooking == 4) + { + // Have a powerup, drag it towards us + vec2 hookvel = normalize(pos-hook_pos)*hook_drag_accel; + + // Apply the velocity + // the hook will boost it's power if the player wants to move + // in that direction. otherwise it will dampen everything abit + vec2 new_vel = phookedpowerup->vel+hookvel; + + // check if we are under the legal limit for the hook + if(length(new_vel) < hook_drag_speed || length(new_vel) < length(vel)) + phookedpowerup->vel = new_vel; // no problem. apply + hook_pos = phookedpowerup->pos; + } + } + else + { + hooking = 0; + hook_pos = pos; + if (phookedplayer) + { + phookedplayer->numhooked--; + phookedplayer = 0; + } + } + } + + void getattackticks(int& curattack, int& attacklen, int& visualtimeattack) + { + // time left from current attack (if any) + // first check modifiers (ninja...) + for (int i = 0; i < MODIFIER_NUMMODIFIERS; i++) + { + if ((modifiers[i].flags & (MODIFIER_ISACTIVE | MODIFIER_NEEDSACTIVATION)) == (MODIFIER_ISACTIVE | MODIFIER_NEEDSACTIVATION)) + { + curattack = modifiers[i].currentactivation; + attacklen = modifiers[i].activationtime; + visualtimeattack = modifiers[i].visualtimeattack; + return; + } + } + + // otherwise current fire timeout + curattack = fire_timeout; + attacklen = lweapons[iactiveweapon].firetime; + visualtimeattack = lweapons[iactiveweapon].visualtimeattack; + } + + virtual void tick() + { + tick_count++; + + // fetch some info + bool grounded = is_grounded(); + direction = get_direction(input.angle); + + // decrease reload timer + if(fire_timeout) + fire_timeout--; + if (reload_timeout) + reload_timeout--; + + // Switch weapons + if (flags & PLAYER_FLAGS_ISEQUIPPING) + { + equip_time--; + if (equip_time <= 0) + { + if (inextweapon >= 0) + { + equip_time = SERVER_TICK_SPEED * lweapons[inextweapon].equiptime; + iactiveweapon = inextweapon; + inextweapon = -1; + + // Send switch weapon event to client? + } + else + { + flags &= ~PLAYER_FLAGS_ISEQUIPPING; + } + } + } + else if (input.activeweapon && iactiveweapon != (input.activeweapon & ~0x80000000)) + { + input.activeweapon &= ~0x80000000; + // check which weapon to activate + if (input.activeweapon >= 0 && input.activeweapon < WEAPON_NUMWEAPONS && + (lweapons[input.activeweapon].flags & WEAPON_ISACTIVE)) + { + inextweapon = input.activeweapon; + equip_time = SERVER_TICK_SPEED * lweapons[iactiveweapon].unequiptime; + // unequip current + flags |= PLAYER_FLAGS_ISEQUIPPING; + + create_sound(pos, sound_player_switchweapon); + } + } + + // don't do any weapon activations if modifier is currently overriding + int modifierflags = handlemodifiers(); + if (!(modifierflags & MODIFIER_RETURNFLAGS_OVERRIDEWEAPON)) + handleweapon(); + + handlehook(); + + // handle movement + if(grounded) + { + if (airjumped) + create_sound(pos, SOUND_PLAYER_LAND); + airjumped = 0; + } + + float elast = 0.0f; + + if (!numhooked) + { + // I'm hooked by someone, so don't do any movement plz (temp) + if (!(modifierflags & MODIFIER_RETURNFLAGS_OVERRIDEVELOCITY)) + { + if(grounded) + { + // ground movement + if(input.left) + { + if(vel.x > -ground_control_speed) + { + vel.x -= ground_control_accel; + if(vel.x < -ground_control_speed) + vel.x = -ground_control_speed; + } + } + else if(input.right) + { + if(vel.x < ground_control_speed) + { + vel.x += ground_control_accel; + if(vel.x > ground_control_speed) + vel.x = ground_control_speed; + } + } + else + vel.x *= ground_friction; // ground fiction + } + else + { + // air movement + if(input.left) + { + if(vel.x > -air_control_speed) + vel.x -= air_control_accel; + } + else if(input.right) + { + if(vel.x < air_control_speed) + vel.x += air_control_accel; + } + else + vel.x *= air_friction; // air fiction + } + + if(input.jump) + { + if(jumped == 0) + { + if(grounded) + { + create_sound(pos, sound_player_jump); + vel.y = -ground_jump_speed; + jumped++; + } + /* + else if(airjumped == 0) + { + vel.y = -12; + airjumped++; + jumped++; + }*/ + } + else if (!grounded) + { + airjumped++; + } + } + else + jumped = 0; + } + + // meh, go through all players and stop their hook on me + /* + for(entity *ent = world.first_entity; ent; ent = ent->next_entity) + { + if (ent && ent->objtype == OBJTYPE_PLAYER) + { + player *p = (player*)ent; + if(p != this) + { + float d = distance(pos, p->pos); + vec2 dir = normalize(pos - p->pos); + if(d < phys_size*1.5f) + { + float a = phys_size*1.5f - d; + vel = vel + dir*a; + } + + if(p->phookedplayer == this) + { + if(d > phys_size*2.5f) + { + elast = 0.0f; + vel = vel - dir*2.5f; + } + } + } + } + }*/ + + // gravity + if (!(modifierflags & MODIFIER_RETURNFLAGS_OVERRIDEGRAVITY)) + vel.y += gravity; + } + + if (!(modifierflags & MODIFIER_RETURNFLAGS_OVERRIDEPOSITION)) + move_box(&pos, &vel, vec2(phys_size, phys_size), elast); + } + + void die() + { + create_sound(pos, sound_player_die); + // release our hooked player + if (phookedplayer) + { + phookedplayer->numhooked--; + phookedplayer = 0; + hooking = -1; + numhooked = 0; + } + respawn(); + + // meh, go through all players and stop their hook on me + for(entity *ent = world.first_entity; ent; ent = ent->next_entity) + { + if (ent && ent->objtype == OBJTYPE_PLAYER) + { + player* p = (player*)ent; + if (p->phookedplayer == this) + { + p->phookedplayer = 0; + p->hooking = -1; + //p->numhooked--; + } + } + } + } + + virtual bool take_damage(vec2 force, int dmg, int from) + { + vel += force; + + if(armor) + { + armor -= 1; + dmg--; + } + + if(dmg > armor) + { + dmg -= armor; + armor = 0; + health -= dmg; + } + else + armor -= dmg; + /* + int armordmg = (dmg+1)/2; + int healthdmg = dmg-armordmg; + if(armor < armordmg) + { + healthdmg += armordmg - armor; + armor = 0; + } + else + armor -= armordmg; + + health -= healthdmg; + */ + + // create healthmod indicator + create_healthmod(pos, dmg); + + damage_taken_tick = tick_count+50; + + // check for death + if(health <= 0) + { + // apply score + if(from != -1) + { + if(from == client_id) + score--; + else + { + player *p = get_player(from); + p->score++; + } + } + + die(); + return false; + } + + if (dmg > 2) + create_sound(pos, sound_player_hurt_long); + else + create_sound(pos, sound_player_hurt_short); + + // spawn blood? + + return true; + } + + virtual void snap(int snaping_client) + { + obj_player *player = (obj_player *)snap_new_item(OBJTYPE_PLAYER, client_id, sizeof(obj_player)); + + client_info info; + if(server_getclientinfo(client_id, &info)) + snap_encode_string(info.name, player->name, strlen(info.name), 32); + + player->x = (int)pos.x; + player->y = (int)pos.y; + player->vx = (int)vel.x; + player->vy = (int)vel.y; + player->emote = EMOTE_NORMAL; + + player->ammocount = lweapons[iactiveweapon].numammo; + player->health = 0; + player->armor = 0; + player->local = 0; + player->clientid = client_id; + player->weapon = iactiveweapon; + player->modifier = 0; + for (int i = 0; i < MODIFIER_NUMMODIFIERS; i++) + { + // add active modifiers + if (modifiers[i].flags & MODIFIER_ISACTIVE) + player->modifier |= 1 << i; + } + // get current attack ticks + getattackticks(player->attackticks, player->attacklen, player->visualtimeattack); + + + if(client_id == snaping_client) + { + player->local = 1; + player->health = health; + player->armor = armor; + } + + if(length(vel) > 15.0f) + player->emote = EMOTE_HAPPY; + + if(damage_taken_tick > tick_count) + player->emote = EMOTE_PAIN; + + if(player->emote == EMOTE_NORMAL) + { + if((tick_count%(50*5)) < 10) + player->emote = EMOTE_BLINK; + } + + player->hook_active = hooking>0?1:0; + player->hook_x = (int)hook_pos.x; + player->hook_y = (int)hook_pos.y; + + player->angle = input.angle; + player->score = score; + } +}; + +// POWERUP /////////////////////// + +powerup::powerup(int _type, int _subtype, int _numitems, int _flags) : + entity(OBJTYPE_POWERUP) +{ + static int current_id = 0; + playerhooked = 0; + id = current_id++; + vel = vec2(0.0f,0.0f); + type = _type; + subtype = _subtype; + numitems = _numitems; + flags = _flags; + // set radius (so it can collide and be hooked and stuff) + proximity_radius = phys_size; + spawntick = -1; + world.insert_entity(this); +} + +void powerup::spawnrandom(int _lifespan) +{ + return; + /* + vec2 pos; + int start, num; + map_get_type(1, &start, &num); + + if(!num) + return; + + mapres_spawnpoint *sp = (mapres_spawnpoint*)map_get_item(start + (rand()%num), NULL, NULL); + pos = vec2(sp->x, sp->y); + + // Check if there already is a powerup at that location + { + int type = OBJTYPE_POWERUP; + entity *ents[64]; + int num = world.find_entities(pos, 5.0f, ents, 64,&type,1); + for (int i = 0; i < num; i++) + { + if (ents[i]->objtype == OBJTYPE_POWERUP) + { + // location busy + return; + } + } + } + + powerup* ppower = new powerup(rand() % POWERUP_TYPE_NUMPOWERUPS,_lifespan); + ppower->pos = pos; + if (ppower->type == POWERUP_TYPE_WEAPON) + { + ppower->subtype = rand() % WEAPON_NUMWEAPONS; + ppower->numitems = 10; + } + else if (ppower->type == POWERUP_TYPE_HEALTH || ppower->type == POWERUP_TYPE_ARMOR) + { + ppower->numitems = rand() % 5; + } + ppower->flags |= POWERUP_FLAG_HOOKABLE;*/ +} + +void powerup::tick() +{ + // wait for respawn + if(spawntick > 0) + { + if(server_tick() > spawntick) + spawntick = -1; + else + return; + } + + vec2 oldpos = pos; + //vel.y += 0.25f; + pos += vel; + move_box(&pos, &vel, vec2(phys_size, phys_size), 0.0f); + + // Check if a player intersected us + vec2 meh; + player* pplayer = intersect_player(pos, pos + vec2(0,16), meh, 0); + if (pplayer) + { + // player picked us up, is someone was hooking us, let them go + if (playerhooked) + playerhooked->hooking = -1; + int respawntime = -1; + switch (type) + { + case POWERUP_TYPE_HEALTH: + { + if(pplayer->health < PLAYER_MAXHEALTH) + { + pplayer->health = min(PLAYER_MAXHEALTH, pplayer->health + numitems); + respawntime = 20; + } + break; + } + case POWERUP_TYPE_ARMOR: + { + if(pplayer->armor < PLAYER_MAXARMOR) + { + pplayer->armor = min(PLAYER_MAXARMOR, pplayer->armor + numitems); + respawntime = 20; + } + break; + } + case POWERUP_TYPE_WEAPON: + { + if (pplayer->lweapons[subtype].flags & player::WEAPON_ISACTIVE) + { + // add ammo + /* + if (pplayer->lweapons[subtype].flags & player::WEAPON_NEEDRELOAD) + { + int numtoadd = min(numitems, pplayer->lweapons[subtype].magsize); + pplayer->lweapons[subtype].numammo = min(10, pplayer->lweapons[subtype].numammo + numtoadd); + pplayer->lweapons[subtype].nummagazines += (numitems - numtoadd) % pplayer->lweapons[subtype].magsize; + } + else*/ + if(pplayer->lweapons[subtype].numammo < 10) + { + respawntime = 20; + pplayer->lweapons[subtype].numammo = min(10, pplayer->lweapons[subtype].numammo + numitems); + } + } + else + { + pplayer->lweapons[subtype].settype(); + pplayer->lweapons[subtype].flags |= player::WEAPON_ISACTIVE; + respawntime = 20; + } + break; + } + case POWERUP_TYPE_NINJA: + { + respawntime = 60; + // reset and activate + pplayer->modifiers[MODIFIER_TYPE_NINJA].settype(); + pplayer->modifiers[MODIFIER_TYPE_NINJA].flags |= player::MODIFIER_ISACTIVE; + break; + } + //POWERUP_TYPE_TIMEFIELD = 4, + default: + break; + }; + + if(respawntime >= 0) + spawntick = server_tick() + server_tickspeed() * respawntime; + //world.destroy_entity(this); + } +} + +void powerup::snap(int snapping_client) +{ + if(spawntick != -1) + return; + + obj_powerup *powerup = (obj_powerup *)snap_new_item(OBJTYPE_POWERUP, id, sizeof(obj_powerup)); + powerup->x = (int)pos.x; + powerup->y = (int)pos.y; + powerup->vx = (int)vel.x; + powerup->vy = (int)vel.y; + powerup->type = type; + powerup->subtype = subtype; +} + +// POWERUP END /////////////////////// + +static player players[MAX_CLIENTS]; + +player *get_player(int index) +{ + return &players[index]; +} + +void create_healthmod(vec2 p, int amount) +{ + ev_healthmod *ev = (ev_healthmod *)events.create(EVENT_HEALTHMOD, sizeof(ev_healthmod)); + ev->x = (int)p.x; + ev->y = (int)p.y; + ev->amount = amount; +} + +void create_explosion(vec2 p, int owner, bool bnodamage) +{ + // create the event + ev_explosion *ev = (ev_explosion *)events.create(EVENT_EXPLOSION, sizeof(ev_explosion)); + ev->x = (int)p.x; + ev->y = (int)p.y; + + if (!bnodamage) + { + // deal damage + entity *ents[64]; + const float radius = 128.0f; + int num = world.find_entities(p, radius, ents, 64); + for(int i = 0; i < num; i++) + { + vec2 diff = ents[i]->pos - p; + vec2 forcedir(0,1); + if (length(diff)) + forcedir = normalize(diff); + float l = length(diff); + float dmg = 5 * (1 - (l/radius)); + if((int)dmg) + { + ents[i]->take_damage(forcedir*dmg*2, (int)dmg, owner);/* && + ents[i]->objtype == OBJTYPE_PLAYER && + owner >= 0) + { + player *p = (player*)ents[i]; + if(p->client_id == owner) + p->score--; + else + ((player*)ents[owner])->score++; + + }*/ + } + } + } +} + +void create_smoke(vec2 p) +{ + // create the event + ev_explosion *ev = (ev_explosion *)events.create(EVENT_SMOKE, sizeof(ev_explosion)); + ev->x = (int)p.x; + ev->y = (int)p.y; +} + +void create_sound(vec2 pos, int sound, int loopingflags) +{ + if (sound < 0) + return; + + // create a sound + ev_sound *ev = (ev_sound *)events.create(EVENT_SOUND, sizeof(ev_sound)); + ev->x = (int)pos.x; + ev->y = (int)pos.y; + ev->sound = sound | loopingflags; +} + +player* intersect_player(vec2 pos0, vec2 pos1, vec2& new_pos, entity* notthis) +{ + // Find other players + entity *ents[64]; + vec2 dir = pos1 - pos0; + float radius = length(dir * 0.5f); + vec2 center = pos0 + dir * 0.5f; + int num = world.find_entities(center, radius, ents, 64); + for (int i = 0; i < num; i++) + { + // Check if entity is a player + if (ents[i] != notthis && ents[i]->objtype == OBJTYPE_PLAYER) + { + // temp, set hook pos to our position + new_pos = ents[i]->pos; + return (player*)ents[i]; + } + } + + return 0; +} + +// Server hooks +static int addtick = SERVER_TICK_SPEED * 5; +void mods_tick() +{ + // clear all events + events.clear(); + world.tick(); + + if (addtick <= 0) + { + powerup::spawnrandom(SERVER_TICK_SPEED * 5); + addtick = SERVER_TICK_SPEED * 5; + } + addtick--; +} + +void mods_snap(int client_id) +{ + world.snap(client_id); + events.snap(client_id); +} + +void mods_client_input(int client_id, void *input) +{ + players[client_id].previnput = players[client_id].input; + players[client_id].input = *(player_input*)input; +} + +void mods_client_enter(int client_id) +{ + players[client_id].reset(); + players[client_id].client_id = client_id; + players[client_id].respawn(); + world.insert_entity(&players[client_id]); + +} + +void mods_client_drop(int client_id) +{ + players[client_id].client_id = -1; + world.remove_entity(&players[client_id]); +} + +void mods_init() +{ + col_init(32); + + int start, num; + map_get_type(MAPRES_ITEM, &start, &num); + + for(int i = 0; i < num; i++) + { + mapres_item *it = (mapres_item *)map_get_item(start+i, 0, 0); + + int type = -1; + int subtype = -1; + int numitems = 1; + + switch(it->type) + { + case ITEM_WEAPON_GUN: + type = POWERUP_TYPE_WEAPON; + subtype = WEAPON_TYPE_GUN; + break; + case ITEM_WEAPON_SHOTGUN: + type = POWERUP_TYPE_WEAPON; + subtype = WEAPON_TYPE_SHOTGUN; + numitems = 5; + break; + case ITEM_WEAPON_ROCKET: + type = POWERUP_TYPE_WEAPON; + subtype = WEAPON_TYPE_ROCKET; + numitems = 5; + break; + /*case ITEM_WEAPON_SNIPER: + type = POWERUP_TYPE_WEAPON; + subtype = WEAPON_TYPE_ROCKET; + break;*/ + case ITEM_WEAPON_HAMMER: + type = POWERUP_TYPE_WEAPON; + subtype = WEAPON_TYPE_MELEE; + break; + + case ITEM_HEALTH_1: + type = POWERUP_TYPE_HEALTH; + numitems = 1; + break; + case ITEM_HEALTH_5: + type = POWERUP_TYPE_HEALTH; + numitems = 5; + break; + case ITEM_HEALTH_10: + type = POWERUP_TYPE_HEALTH; + numitems = 10; + break; + + case ITEM_ARMOR_1: + type = POWERUP_TYPE_ARMOR; + numitems = 1; + break; + case ITEM_ARMOR_5: + type = POWERUP_TYPE_ARMOR; + numitems = 5; + break; + case ITEM_ARMOR_10: + type = POWERUP_TYPE_ARMOR; + numitems = 10; + break; + }; + + powerup* ppower = new powerup(type, subtype, numitems); + ppower->pos.x = it->x; + ppower->pos.y = it->y; + } + + + /* + powerup* ppower = new powerup(rand() % POWERUP_TYPE_NUMPOWERUPS,_lifespan); + ppower->pos = pos; + if (ppower->type == POWERUP_TYPE_WEAPON) + { + ppower->subtype = rand() % WEAPON_NUMWEAPONS; + ppower->numitems = 10; + } + else if (ppower->type == POWERUP_TYPE_HEALTH || ppower->type == POWERUP_TYPE_ARMOR) + { + ppower->numitems = rand() % 5; + } + ppower->flags |= POWERUP_FLAG_HOOKABLE; + */ + + //powerup::spawnrandom(SERVER_TICK_SPEED * 5); +} + +void mods_shutdown() {} +void mods_presnap() {} +void mods_postsnap() {} diff --git a/src/game/mapres.h b/src/game/mapres.h new file mode 100644 index 00000000..8d09e99c --- /dev/null +++ b/src/game/mapres.h @@ -0,0 +1,7 @@ +enum +{ + MAPRES_REGISTERED=0x8000, + MAPRES_IMAGE=0x8001, + MAPRES_TILEMAP=0x8002, + MAPRES_COLLISIONMAP=0x8003, +}; diff --git a/src/game/mapres_col.cpp b/src/game/mapres_col.cpp new file mode 100644 index 00000000..0cf71986 --- /dev/null +++ b/src/game/mapres_col.cpp @@ -0,0 +1,44 @@ +#include +#include "../interface.h" +#include "mapres_col.h" +#include "mapres.h" + +/* + Simple collision rutines! +*/ +struct collision +{ + int w, h; + unsigned char *data; +}; + +static collision col; +static int global_dividor; + +int col_init(int dividor) +{ + mapres_collision *c = (mapres_collision*)map_find_item(MAPRES_COLLISIONMAP,0); + if(!c) + { + dbg_msg("mapres_col", "failed!"); + return 0; + } + col.w = c->width; + col.h = c->height; + global_dividor = dividor; + col.data = (unsigned char *)map_get_data(c->data_index); + return col.data ? 1 : 0; +} + +int col_check_point(int x, int y) +{ + int nx = x/global_dividor; + int ny = y/global_dividor; + if(nx < 0 || nx >= col.w || ny >= col.h) + return 1; + + if(y < 0) + return 0; // up == sky == free + + return col.data[ny*col.w+nx]; +} diff --git a/src/game/mapres_col.h b/src/game/mapres_col.h new file mode 100644 index 00000000..2afad439 --- /dev/null +++ b/src/game/mapres_col.h @@ -0,0 +1,10 @@ + +struct mapres_collision +{ + int width; + int height; + int data_index; +}; + +int col_init(int dividor); +int col_check_point(int x, int y); diff --git a/src/game/mapres_image.cpp b/src/game/mapres_image.cpp new file mode 100644 index 00000000..baf7f09b --- /dev/null +++ b/src/game/mapres_image.cpp @@ -0,0 +1,41 @@ +#include +#include "../interface.h" +#include "mapres_image.h" +#include "mapres.h" + +static int map_textures[64] = {0}; +static int count = 0; + +int img_init() +{ + int start, count; + map_get_type(MAPRES_IMAGE, &start, &count); + dbg_msg("mapres_image", "start=%d count=%d", start, count); + for(int i = 0; i < 64; i++) + { + if(map_textures[i]) + { + gfx_unload_texture(map_textures[i]); + map_textures[i] = 0; + } + } + + for(int i = 0; i < count; i++) + { + mapres_image *img = (mapres_image *)map_get_item(start+i, 0, 0); + void *data = map_get_data(img->image_data); + map_textures[i] = gfx_load_texture_raw(img->width, img->height, data); + } + + return count; +} + +int img_num() +{ + return count; +} + +int img_get(int index) +{ + return map_textures[index]; +} diff --git a/src/game/mapres_image.h b/src/game/mapres_image.h new file mode 100644 index 00000000..eab1559a --- /dev/null +++ b/src/game/mapres_image.h @@ -0,0 +1,18 @@ + +// loads images from the map to textures +int img_init(); + +// returns the number of images in the map +int img_num(); + +// fetches the texture id for the image +int img_get(int index); + + +class mapres_image +{ +public: + int width; + int height; + int image_data; +}; diff --git a/src/game/mapres_tilemap.cpp b/src/game/mapres_tilemap.cpp new file mode 100644 index 00000000..0868d2e4 --- /dev/null +++ b/src/game/mapres_tilemap.cpp @@ -0,0 +1,54 @@ +#include "../interface.h" +#include "mapres_tilemap.h" +#include "mapres_image.h" +#include "mapres.h" + +int tilemap_init() +{ + return 0; +} + +void tilemap_render(float scale, int fg) +{ + if(!map_is_loaded()) + return; + + // fetch indecies + int start, num; + map_get_type(MAPRES_TILEMAP, &start, &num); + + // render tilemaps + int passed_main = 0; + for(int t = 0; t < num; t++) + { + mapres_tilemap *tmap = (mapres_tilemap *)map_get_item(start+t,0,0); + unsigned char *data = (unsigned char *)map_get_data(tmap->data); + + if(tmap->main) + passed_main = 1; + + if((fg && passed_main) || (!fg && !passed_main)) + { + gfx_texture_set(img_get(tmap->image)); + gfx_quads_begin(); + + int c = 0; + float frac = (1.0f/1024.0f); //2.0f; + for(int y = 0; y < tmap->height; y++) + for(int x = 0; x < tmap->width; x++, c++) + { + unsigned char d = data[c*2]; + if(d) + { + gfx_quads_setsubset( + (d%16)/16.0f+frac, + (d/16)/16.0f+frac, + (d%16)/16.0f+1.0f/16.0f-frac, + (d/16)/16.0f+1.0f/16.0f-frac); + gfx_quads_drawTL(x*scale, y*scale, scale, scale); + } + } + gfx_quads_end(); + } + } +} diff --git a/src/game/mapres_tilemap.h b/src/game/mapres_tilemap.h new file mode 100644 index 00000000..6e9d81be --- /dev/null +++ b/src/game/mapres_tilemap.h @@ -0,0 +1,19 @@ + +// dependencies: image + +// +int tilemap_init(); + +// renders the tilemaps +void tilemap_render(float scale, int fg); + +struct mapres_tilemap +{ + int image; + int width; + int height; + int x, y; + int scale; + int data; + int main; +}; diff --git a/src/gfx.cpp b/src/gfx.cpp new file mode 100644 index 00000000..c1e05c9c --- /dev/null +++ b/src/gfx.cpp @@ -0,0 +1,581 @@ +#include +#include +#include + +#include "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); + + 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 char 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 char c = *text++; + width += size * (current_font->m_CharEndTable[c] - current_font->m_CharStartTable[c] + spacing); + } + + return width; +} diff --git a/src/interface.h b/src/interface.h new file mode 100644 index 00000000..46d7ede7 --- /dev/null +++ b/src/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 + +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: + +*/ +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: + +*/ +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: + , + + 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: + +*/ +int gfx_screenwidth(); + +/* + Function: gfx_screenheight + Returns the screen height. + + See Also: + +*/ +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 . + This is equal to glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA). + + See Also: + +*/ +void gfx_blend_normal(); + +/* + Function: gfx_blend_additive + Set the active blending mode to additive (src, one). + + Remarks: + This must be used before calling . + This is equal to glBlendFunc(GL_SRC_ALPHA, GL_ONE). + + See Also: + +*/ +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 . + You can't change texture or blending mode during a session. + + See Also: + +*/ +void gfx_quads_begin(); + +/* + Function: gfx_quads_end + Ends a quad session. + + See Also: + +*/ +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 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 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 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 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 before calling this function. + + See Also: + +*/ +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 before calling this function. + + See Also: + +*/ +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 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 "teewars.com" +#define MASTER_SERVER_PORT 8300 + + + +#endif diff --git a/src/lzw.cpp b/src/lzw.cpp new file mode 100644 index 00000000..52ca569d --- /dev/null +++ b/src/lzw.cpp @@ -0,0 +1,213 @@ +#include + +// LZW Compressor +struct SYM +{ + unsigned char data[1024*2]; + 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_init() +{ + for(int i = 0; i < 256; i++) + { + symbols.syms[i].data[0] = (unsigned char)i; + symbols.syms[i].size = 1; + symbols.jumptable[i] = -1; + } + + for(int i = 0; i < 512; i++) + symbols.syms[i].next = -1; + + symbols.currentsym = 0; +} + +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; + + memcpy(symbols.syms[i].data, sym, len); + symbols.syms[i].size = len; + symbols.currentsym = (i+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_append(int sym, unsigned char extra) +{ + symbols.syms[sym].data[symbols.syms[sym].size] = extra; + symbols.syms[sym].size++; +} + +static int sym_find(unsigned char *data, int size) +{ + int best = data[0]; + int bestlen = 1; + int current = symbols.jumptable[data[0]]; + + while(current != -1) + { + if(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); + + // 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< symsize+1) // create new symbol + 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_; + int previtem = -1; // 0-255 = raw byte, 256+ = symbol + 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< +#include + +#include + +using namespace baselib; + +extern int client_main(int argc, char **argv); +extern int editor_main(int argc, char **argv); +extern int server_main(int argc, char **argv); + +int main(int argc, char **argv) +{ + // search for server or editor argument + for(int i = 1; i < argc; i++) + { + if(argv[i][0] == '-' && argv[i][1] == 's' && argv[i][2] == 0) + return server_main(argc, argv); + else if(argv[i][0] == '-' && argv[i][1] == 'e' && argv[i][2] == 0) + return editor_main(argc, argv); + } + + // no specific parameters, start the client + return client_main(argc, argv); +} diff --git a/src/map.cpp b/src/map.cpp new file mode 100644 index 00000000..3e76547e --- /dev/null +++ b/src/map.cpp @@ -0,0 +1,48 @@ +#include +#include + +#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/menu.cpp b/src/menu.cpp new file mode 100644 index 00000000..b5e6c0eb --- /dev/null +++ b/src/menu.cpp @@ -0,0 +1,597 @@ +#include +#include +#include +#include +#include +#include + +#include "interface.h" +#include "ui.h" + +#include "game/mapres_image.h" +#include "game/mapres_tilemap.h" +#include "game/mapres.h" + +using namespace baselib; + +/******************************************************** + MENU +*********************************************************/ + +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); +float gfx_pretty_text_width(float size, const char *text); + +void draw_scrolling_background(int id, float w, float h, float t) +{ + float tx = w/256.0f; + float ty = h/256.0f; + + float start_x = fmod(t, 1.0f); + float start_y = 1.0f - fmod(t*0.8f, 1.0f); + + gfx_blend_normal(); + gfx_texture_set(id); + gfx_quads_begin(); + gfx_quads_setcolor(1,1,1,1); + gfx_quads_setsubset( + start_x, // startx + start_y, // starty + start_x+tx, // endx + start_y+ty); // endy + gfx_quads_drawTL(0.0f,0.0f,w,h); + gfx_quads_end(); +} + +int background_texture; +int not_empty_item_texture; +int empty_item_texture; +int active_item_texture; +int selected_item_texture; +int join_button_texture; +int join_button_hot_texture; +int join_button_active_texture; +int join_button_grey_texture; +int quit_button_texture; +int quit_button_hot_texture; +int quit_button_active_texture; +int up_button_texture; +int up_button_active_texture; +int down_button_texture; +int down_button_active_texture; +int teewars_banner_texture; +int scroll_indicator_texture; +int connect_localhost_texture; +int refresh_button_texture; +int refresh_button_hot_texture; +int refresh_button_active_texture; +int input_box_texture; + +int music_menu; +int music_menu_id = -1; + +struct button_textures +{ + int *normal_texture; + int *hot_texture; + int *active_texture; +}; + +button_textures connect_localhost_button = { &connect_localhost_texture, &connect_localhost_texture, &connect_localhost_texture }; +button_textures join_button = { &join_button_texture, &join_button_hot_texture, &join_button_active_texture }; +button_textures quit_button = { &quit_button_texture, &quit_button_hot_texture, &quit_button_active_texture }; +button_textures scroll_up_button = { &up_button_texture, &up_button_texture, &up_button_active_texture }; +button_textures scroll_down_button = { &down_button_texture, &down_button_texture, &down_button_active_texture }; +button_textures list_item_button = { ¬_empty_item_texture, &active_item_texture, &active_item_texture }; +button_textures selected_item_button = { &selected_item_texture, &selected_item_texture, &selected_item_texture }; +button_textures refresh_button = { &refresh_button_texture, &refresh_button_hot_texture, &refresh_button_active_texture }; + +void draw_menu_button(void *id, const char *text, int checked, float x, float y, float w, float h, void *extra) +{ + button_textures *tx = (button_textures *)extra; + + gfx_blend_normal(); + + if (ui_active_item() == id && ui_hot_item() == id) + gfx_texture_set(*tx->active_texture); + else if (ui_hot_item() == id) + gfx_texture_set(*tx->hot_texture); + else + gfx_texture_set(*tx->normal_texture); + + gfx_quads_begin(); + + gfx_quads_setcolor(1,1,1,1); + + gfx_quads_drawTL(x,y,w,h); + gfx_quads_end(); + + gfx_texture_set(current_font->font_texture); + gfx_pretty_text(x + 4, y - 3.5f, 18.f, text); +} + +void draw_image_button(void *id, const char *text, int checked, float x, float y, float w, float h, void *extra) +{ + ui_do_image(*(int *)id, x, y, w, h); +} + +struct server_info +{ + int version; + int players; + int max_players; + netaddr4 address; + char name[129]; + char map[65]; +}; + +struct server_list +{ + server_info infos[10]; + int active_count, info_count; + int scroll_index; + int selected_index; +}; +#include + +int ui_do_edit_box(void *id, float x, float y, float w, float h, char *str, int str_size) +{ + int inside = ui_mouse_inside(x, y, w, h); + int r = 0; + + if(inside) + { + ui_set_hot_item(id); + + if(ui_mouse_button(0)) + ui_set_active_item(id); + } + + if (ui_active_item() == id) + { + char c = keys::last_char(); + int k = keys::last_key(); + int len = strlen(str); + + if (c >= 32 && c < 128) + { + if (len < str_size - 1) + { + str[len] = c; + str[len+1] = 0; + } + } + + if (k == keys::backspace) + { + if (len > 0) + str[len-1] = 0; + } + r = 1; + } + + ui_do_label(x + 4, y - 3.5f, str); + + if (ui_active_item() == id) + { + float w = gfx_pretty_text_width(18.0f, str); + ui_do_label(x + 4 + w, y - 3.5f, "_"); + } + + return r; +} + +int do_scroll_bar(void *id, float x, float y, float height, int steps, int last_index) +{ + int r = last_index; + + if (ui_do_button(&up_button_texture, "", 0, x, y, 8, 8, draw_menu_button, &scroll_up_button)) + { + if (r > 0) + --r; + } + else if (ui_do_button(&down_button_texture, "", 0, x, y + height - 8, 8, 8, draw_menu_button, &scroll_down_button)) + { + if (r < steps) + ++r; + } + else if (steps > 0) // only if there's actually stuff to scroll through + { + int inside = ui_mouse_inside(x, y + 8, 8, height - 16); + if (inside && (!ui_active_item() || ui_active_item() == id)) + ui_set_hot_item(id); + + if(ui_active_item() == id) + { + if (ui_mouse_button(0)) + { + float pos = ui_mouse_y() - y - 8; + float perc = pos / (height - 16); + + r = (steps + 1) * perc; + if (r < 0) + r = 0; + else if (r > steps) + r = steps; + } + else + ui_set_active_item(0); + } + else if (ui_hot_item() == id && ui_mouse_button(0)) + ui_set_active_item(id); + else if (inside && (!ui_active_item() || ui_active_item() == id)) + ui_set_hot_item(id); + } + + ui_do_image(scroll_indicator_texture, x, y + 8 + r * ((height - 32) / steps), 8, 16); + + return r; +} + +int do_server_list(server_list *list, float x, float y, int visible_items) +{ + const float spacing = 1.5f; + const float item_height = 14; + const float item_width = 364; + const float real_width = item_width + 10; + const float real_height = item_height * visible_items + spacing * (visible_items - 1); + + int r = -1; + + for (int i = 0; i < visible_items; i++) + { + int item_index = i + list->scroll_index; + if (item_index >= list->active_count) + ui_do_image(empty_item_texture, x, y + i * item_height + i * spacing, item_width, item_height); + else + { + server_info *item = &list->infos[item_index]; + + bool clicked = false; + if (list->selected_index == item_index) + clicked = ui_do_button(item, item->name, 0, x, y + i * item_height + i * spacing, item_width, item_height, draw_menu_button, &selected_item_button); + else + clicked = ui_do_button(item, item->name, 0, x, y + i * item_height + i * spacing, item_width, item_height, draw_menu_button, &list_item_button); + + char temp[64]; // plenty of extra room so we don't get sad :o + sprintf(temp, "%i/%i", item->players, item->max_players); + + gfx_texture_set(current_font->font_texture); + gfx_pretty_text(x + 300, y + i * item_height + i * spacing - 3.5f, 18.f, temp); + gfx_pretty_text(x + 180, y + i * item_height + i * spacing - 3.5f, 18.f, item->map); + + if (clicked) + { + r = item_index; + list->selected_index = item_index; + } + } + } + + list->scroll_index = do_scroll_bar(&list->scroll_index, x + real_width - 8, y, real_height, list->active_count - visible_items, list->scroll_index); + + return r; +} + +#include + +char *read_int(char *buffer, int *value) +{ + *value = buffer[0] << 24; + *value |= buffer[1] << 16; + *value |= buffer[2] << 8; + *value |= buffer[3]; + + return buffer + 4; +} + +char *read_netaddr(char *buffer, netaddr4 *addr) +{ + addr->ip[0] = *buffer++; + addr->ip[1] = *buffer++; + addr->ip[2] = *buffer++; + addr->ip[3] = *buffer++; + + int port; + buffer = read_int(buffer, &port); + + addr->port = port; + + return buffer; +} + +void refresh_list(server_list *list) +{ + netaddr4 addr; + netaddr4 me(0, 0, 0, 0, 0); + + list->selected_index = -1; + + if (net_host_lookup(MASTER_SERVER_ADDRESS, MASTER_SERVER_PORT, &addr) == 0) + { + socket_tcp4 sock; + sock.open(&me); + + //sock.set_non_blocking(); + + // try and connect with a timeout of 1 second + if (sock.connect_non_blocking(&addr)) + { + char data[256]; + int total_received = 0; + int pointer = 0; + int received; + + int master_server_version = -1; + int server_count = -1; + + // read header + while (total_received < 12 && (received = sock.recv(data + total_received, 12 - total_received)) > 0) + total_received += received; + + // see if we have the header + if (total_received == 12) + { + int signature; + read_int(data, &signature); + + // check signature + if (signature == 'TWSL') + { + read_int(data + 4, &master_server_version); + read_int(data + 8, &server_count); + + // TODO: handle master server version O.o + + const int server_info_size = 212; + const int wanted_data_count = server_count * server_info_size; + + list->active_count = 0; + + for (int i = 0; i < server_count; i++) + { + total_received = 0; + + // read data for a server + while (sock.is_connected() && total_received < server_info_size && (received = sock.recv(data + total_received, server_info_size - total_received)) > 0) + total_received += received; + + // check if we got enough data + if (total_received == server_info_size) + { + char *d = data; + + server_info *info = &list->infos[i]; + + d = read_int(d, &info->version); + d = read_netaddr(d, &info->address); + + //dbg_msg("menu/got_serverinfo", "IP: %i.%i.%i.%i:%i", (int)info->address.ip[0], (int)info->address.ip[1], (int)info->address.ip[2], (int)info->address.ip[3], info->address.port); + + d = read_int(d, &info->players); + d = read_int(d, &info->max_players); + memcpy(info->name, d, 128); + d += 128; + memcpy(info->map, d, 64); + + // let's be safe. + info->name[128] = 0; + info->map[64] = 0; + + ++list->active_count; + } + else + break; + } + + if (list->scroll_index >= list->active_count) + list->scroll_index = list->active_count - 1; + + if (list->scroll_index < 0) + list->scroll_index = 0; + } + } + + sock.close(); + } + } +} + +static int menu_render(netaddr4 *server_address, char *str, int max_len) +{ + // background color + gfx_clear(89/255.f,122/255.f,0.0); + + // GRADIENT: top to bottom + // top color: 60, 80, 0 + // bottom color: 90, 120, 0 + + // world coordsys + float zoom = 3.0f; + gfx_mapscreen(0,0,400.0f*zoom,300.0f*zoom); + + // GUI coordsys + gfx_mapscreen(0,0,400.0f,300.0f); + + static server_list list; + static bool inited = false; + + if (!inited) + { + list.info_count = 256; + + list.scroll_index = 0; + list.selected_index = -1; + + inited = true; + + refresh_list(&list); + } + + static int64 start = time_get(); + + float t = double(time_get() - start) / double(time_freq()); + draw_scrolling_background(background_texture, 400, 300, t * 0.01); + + ui_do_image(teewars_banner_texture, 70, 10, 256, 64); + + do_server_list(&list, 10, 80, 8); + + /* + if (ui_do_button(&connect_localhost_button, "", 0, 15, 250, 64, 24, draw_menu_button, &connect_localhost_button)) + { + *server_address = netaddr4(127, 0, 0, 1, 8303); + return 1; + }*/ + + if (ui_do_button(&refresh_button, "", 0, 220, 210, 64, 24, draw_menu_button, &refresh_button)) + { + refresh_list(&list); + } + + if (list.selected_index == -1) + { + ui_do_image(join_button_grey_texture, 290, 210, 64, 24); + } + else if (ui_do_button(&join_button, "", 0, 290, 210, 64, 24, draw_menu_button, &join_button)) + { + *server_address = list.infos[list.selected_index].address; + + dbg_msg("menu/join_button", "IP: %i.%i.%i.%i:%i", (int)server_address->ip[0], (int)server_address->ip[1], (int)server_address->ip[2], (int)server_address->ip[3], server_address->port); + + return 1; + } + + const float name_x = 10, name_y = 215; + + ui_do_label(name_x + 4, name_y - 3.5f, "Name:"); + ui_do_image(input_box_texture, name_x + 50 - 5, name_y - 5, 150 + 10, 14 + 10); + ui_do_edit_box(str, name_x + 50, name_y, 150, 14, str, max_len); + + if (ui_do_button(&quit_button, "", 0, 290, 250, 69, 25, draw_menu_button, &quit_button)) + return -1; + + return 0; +} + +void modmenu_init() +{ + keys::enable_char_cache(); + keys::enable_key_cache(); + + current_font->font_texture = gfx_load_texture_tga("data/big_font.tga"); + background_texture = gfx_load_texture_tga("data/gui_bg.tga"); + not_empty_item_texture = gfx_load_texture_tga("data/gui/game_list_item_not_empty.tga"); + empty_item_texture = gfx_load_texture_tga("data/gui/game_list_item_empty.tga"); + active_item_texture = gfx_load_texture_tga("data/gui/game_list_item_active.tga"); + selected_item_texture = gfx_load_texture_tga("data/gui/game_list_item_selected.tga"); + + join_button_texture = gfx_load_texture_tga("data/gui/join_button.tga"); + join_button_hot_texture = gfx_load_texture_tga("data/gui/join_button_hot.tga"); + join_button_active_texture = gfx_load_texture_tga("data/gui/join_button_active.tga"); + join_button_grey_texture = gfx_load_texture_tga("data/gui/join_button_greyed.tga"); + + +// button_not_hilighted_texture = gfx_load_texture_tga("data/gui/game_list_join_button.tga"); +// button_hilighted_texture = gfx_load_texture_tga("data/gui/button_hilighted.tga"); +// button_active_texture = gfx_load_texture_tga("data/gui/button_active.tga"); + + quit_button_texture = gfx_load_texture_tga("data/gui/quit_button.tga"); + quit_button_hot_texture = gfx_load_texture_tga("data/gui/quit_button_hot.tga"); + quit_button_active_texture = gfx_load_texture_tga("data/gui/quit_button_active.tga"); + + up_button_texture = gfx_load_texture_tga("data/gui/scroll_arrow_up.tga"); + up_button_active_texture = gfx_load_texture_tga("data/gui/scroll_arrow_up_active.tga"); + + down_button_texture = gfx_load_texture_tga("data/gui/scroll_arrow_down.tga"); + down_button_active_texture = gfx_load_texture_tga("data/gui/scroll_arrow_down_active.tga"); + + teewars_banner_texture = gfx_load_texture_tga("data/gui_logo.tga"); + scroll_indicator_texture = gfx_load_texture_tga("data/gui/scroll_drag.tga"); + connect_localhost_texture = gfx_load_texture_tga("data/gui/game_list_connect_localhost.tga"); + + refresh_button_texture = gfx_load_texture_tga("data/gui/refresh_button.tga"); + refresh_button_hot_texture = gfx_load_texture_tga("data/gui/refresh_button_hot.tga"); + refresh_button_active_texture = gfx_load_texture_tga("data/gui/refresh_button_active.tga"); + + input_box_texture = gfx_load_texture_tga("data/gui/input_box.tga"); + + music_menu = snd_load_wav("data/audio/Music_Menu.wav"); +} + +void modmenu_shutdown() +{ +} + +int modmenu_render(void *ptr, char *str, int max_len) +{ + static int mouse_x = 0; + static int mouse_y = 0; + + if (music_menu_id == -1) + { + dbg_msg("menu", "no music is playing, so let's play some tunes!"); + music_menu_id = snd_play(music_menu, SND_LOOP); + } + + netaddr4 *server_address = (netaddr4 *)ptr; + + // handle mouse movement + float mx, my, mwx, mwy; + { + int rx, ry; + inp_mouse_relative(&rx, &ry); + mouse_x += rx; + mouse_y += ry; + if(mouse_x < 0) mouse_x = 0; + if(mouse_y < 0) mouse_y = 0; + if(mouse_x > gfx_screenwidth()) mouse_x = gfx_screenwidth(); + if(mouse_y > gfx_screenheight()) mouse_y = gfx_screenheight(); + + // update the ui + mx = (mouse_x/(float)gfx_screenwidth())*400.0f; + my = (mouse_y/(float)gfx_screenheight())*300.0f; + mwx = mx*3.0f; // adjust to zoom and offset + mwy = mx*3.0f; // adjust to zoom and offset + + int buttons = 0; + if(inp_mouse_button_pressed(0)) buttons |= 1; + if(inp_mouse_button_pressed(1)) buttons |= 2; + if(inp_mouse_button_pressed(2)) buttons |= 4; + + ui_update(mx,my,mx*3.0f,my*3.0f,buttons); + } + + int r = menu_render(server_address, str, max_len); + + // render butt ugly mouse cursor + gfx_texture_set(-1); + gfx_quads_begin(); + gfx_quads_setcolor(0,0,0,1); + gfx_quads_draw_freeform(mx,my,mx,my, + mx+7,my, + mx,my+7); + gfx_quads_setcolor(1,1,1,1); + gfx_quads_draw_freeform(mx+1,my+1,mx+1,my+1, + mx+5,my+1, + mx+1,my+5); + gfx_quads_end(); + + if (r) + { + snd_stop(music_menu_id); + music_menu_id = -1; + } + + keys::clear_char(); + keys::clear_key(); + + return r; +} diff --git a/src/packet.h b/src/packet.h new file mode 100644 index 00000000..62784044 --- /dev/null +++ b/src/packet.h @@ -0,0 +1,297 @@ +#include +#include + +// 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-sizeof(header)]; + 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_SHIFT=24, + DEBUG_SIZE_MASK=0xffff, + 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: byteorder fix, should make sure that it writes big endian + // TODO: check for overflow + *(int*)current = i; + current += sizeof(int); + } + + // reads an int from the packet + void read_int_raw(int *i) + { + // TODO: byteorder fix, should make sure that it reads big endian + // TODO: check for overflow + *i = *(int*)current; + current += sizeof(int); + } + +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) + { + write_int_raw((DEBUG_TYPE_INT<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 + } + + //dbg_msg("network/connection", "sending packet. msg=%x size=%x seq=%x ", p->msg(), p->size(), seq); + 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/server.cpp b/src/server.cpp new file mode 100644 index 00000000..9783ac5e --- /dev/null +++ b/src/server.cpp @@ -0,0 +1,661 @@ +#include +#include + +#include +#include + +#include "interface.h" + +//#include "socket.h" +#include "packet.h" +#include "snapshot.h" + +#include "lzw.h" + +namespace baselib {} +using namespace baselib; + +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) + 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++; + + 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, 32); + 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) + { + char reason[128]; + p->read_str(reason, 128); + 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 + char version[32]; + char name[MAX_NAME_LENGTH]; + char clan[MAX_CLANNAME_LENGTH]; + char password[32]; + char skin[32]; + + p->read_str(version, 32); + p->read_str(name, MAX_NAME_LENGTH); + p->read_str(clan, MAX_CLANNAME_LENGTH); + p->read_str(password, 32); + p->read_str(skin, 32); + + if(p->is_good()) + { + // TODO: check version + + // 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 + 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 = 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 server_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; +} diff --git a/src/snapshot.h b/src/snapshot.h new file mode 100644 index 00000000..9d803486 --- /dev/null +++ b/src/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/snd.cpp b/src/snd.cpp new file mode 100644 index 00000000..42997897 --- /dev/null +++ b/src/snd.cpp @@ -0,0 +1,519 @@ +#include +#include +#include + +#include "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/ui.cpp b/src/ui.cpp new file mode 100644 index 00000000..7ef19b72 --- /dev/null +++ b/src/ui.cpp @@ -0,0 +1,115 @@ +#include "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/ui.h b/src/ui.h new file mode 100644 index 00000000..1a420906 --- /dev/null +++ b/src/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/wavpack/arm.S b/src/wavpack/arm.S new file mode 100644 index 00000000..ab882181 --- /dev/null +++ b/src/wavpack/arm.S @@ -0,0 +1,461 @@ +//////////////////////////////////////////////////////////////////////////// +// **** WAVPACK **** // +// Hybrid Lossless Wavefile Compressor // +// Copyright (c) 1998 - 2006 Conifer Software. // +// All Rights Reserved. // +// Distributed under the BSD Software License (see license.txt) // +//////////////////////////////////////////////////////////////////////////// + +/* This is an assembly optimized version of the following WavPack function: + * + * void decorr_stereo_pass_cont (struct decorr_pass *dpp, + * long *buffer, long sample_count); + * + * It performs a single pass of stereo decorrelation on the provided buffer. + * Note that this version of the function requires that the 8 previous stereo + * samples are visible and correct. In other words, it ignores the "samples_*" + * fields in the decorr_pass structure and gets the history data directly + * from the buffer. It does, however, return the appropriate history samples + * to the decorr_pass structure before returning. + * + * This is written to work on a ARM7TDMI processor. This version only uses the + * 32-bit multiply-accumulate instruction and so will overflow with 24-bit + * WavPack files. + */ + .text + .align + .global decorr_stereo_pass_cont_arm + +/* + * on entry: + * + * r0 = struct decorr_pass *dpp + * r1 = long *buffer + * r2 = long sample_count + */ + +decorr_stereo_pass_cont_arm: + + stmfd sp!, {r4 - r8, r10, r11, lr} + mov r5, r0 @ r5 = dpp + mov r11, #512 @ r11 = 512 for rounding + ldrsh r6, [r0, #2] @ r6 = dpp->delta + ldrsh r4, [r0, #4] @ r4 = dpp->weight_A + ldrsh r0, [r0, #6] @ r0 = dpp->weight_B + cmp r2, #0 @ exit if no samples to process + beq common_exit + + add r7, r1, r2, asl #3 @ r7 = buffer ending position + ldrsh r2, [r5, #0] @ r2 = dpp->term + cmp r2, #0 + bmi minus_term + + ldr lr, [r1, #-16] @ load 2 sample history from buffer + ldr r10, [r1, #-12] @ for terms 2, 17, and 18 + ldr r8, [r1, #-8] + ldr r3, [r1, #-4] + cmp r2, #17 + beq term_17_loop + cmp r2, #18 + beq term_18_loop + cmp r2, #2 + beq term_2_loop + b term_default_loop @ else handle default (1-8, except 2) + +minus_term: + mov r10, #1024 @ r10 = -1024 for weight clipping + rsb r10, r10, #0 @ (only used for negative terms) + cmn r2, #1 + beq term_minus_1 + cmn r2, #2 + beq term_minus_2 + cmn r2, #3 + beq term_minus_3 + b common_exit + +/* + ****************************************************************************** + * Loop to handle term = 17 condition + * + * r0 = dpp->weight_B r8 = previous left sample + * r1 = bptr r9 = + * r2 = current sample r10 = second previous left sample + * r3 = previous right sample r11 = 512 (for rounding) + * r4 = dpp->weight_A ip = current decorrelation value + * r5 = dpp sp = + * r6 = dpp->delta lr = second previous right sample + * r7 = eptr pc = + ******************************************************************************* + */ + +term_17_loop: + rsbs ip, lr, r8, asl #1 @ decorr value = (2 * prev) - 2nd prev + mov lr, r8 @ previous becomes 2nd previous + ldr r2, [r1], #4 @ get sample & update pointer + mla r8, ip, r4, r11 @ mult decorr value by weight, round, + add r8, r2, r8, asr #10 @ shift, and add to new sample + strne r8, [r1, #-4] @ if change possible, store sample back + cmpne r2, #0 + beq .L325 + teq ip, r2 @ update weight based on signs + submi r4, r4, r6 + addpl r4, r4, r6 + +.L325: rsbs ip, r10, r3, asl #1 @ do same thing for right channel + mov r10, r3 + ldr r2, [r1], #4 + mla r3, ip, r0, r11 + add r3, r2, r3, asr #10 + strne r3, [r1, #-4] + cmpne r2, #0 + beq .L329 + teq ip, r2 + submi r0, r0, r6 + addpl r0, r0, r6 + +.L329: cmp r7, r1 @ loop back if more samples to do + bhi term_17_loop + b store_1718 @ common exit for terms 17 & 18 + +/* + ****************************************************************************** + * Loop to handle term = 18 condition + * + * r0 = dpp->weight_B r8 = previous left sample + * r1 = bptr r9 = + * r2 = current sample r10 = second previous left sample + * r3 = previous right sample r11 = 512 (for rounding) + * r4 = dpp->weight_A ip = decorrelation value + * r5 = dpp sp = + * r6 = dpp->delta lr = second previous right sample + * r7 = eptr pc = + ******************************************************************************* + */ + +term_18_loop: + sub ip, r8, lr @ decorr value = + mov lr, r8 @ ((3 * prev) - 2nd prev) >> 1 + adds ip, r8, ip, asr #1 + ldr r2, [r1], #4 @ get sample & update pointer + mla r8, ip, r4, r11 @ mult decorr value by weight, round, + add r8, r2, r8, asr #10 @ shift, and add to new sample + strne r8, [r1, #-4] @ if change possible, store sample back + cmpne r2, #0 + beq .L337 + teq ip, r2 @ update weight based on signs + submi r4, r4, r6 + addpl r4, r4, r6 + +.L337: sub ip, r3, r10 @ do same thing for right channel + mov r10, r3 + adds ip, r3, ip, asr #1 + ldr r2, [r1], #4 + mla r3, ip, r0, r11 + add r3, r2, r3, asr #10 + strne r3, [r1, #-4] + cmpne r2, #0 + beq .L341 + teq ip, r2 + submi r0, r0, r6 + addpl r0, r0, r6 + +.L341: cmp r7, r1 @ loop back if more samples to do + bhi term_18_loop + +/* common exit for terms 17 & 18 */ + +store_1718: + str r3, [r5, #40] @ store sample history into struct + str r8, [r5, #8] + str r10, [r5, #44] + str lr, [r5, #12] + b common_exit @ and return + +/* + ****************************************************************************** + * Loop to handle term = 2 condition + * (note that this case can be handled by the default term handler (1-8), but + * this special case is faster because it doesn't have to read memory twice) + * + * r0 = dpp->weight_B r8 = previous left sample + * r1 = bptr r9 = + * r2 = current sample r10 = second previous left sample + * r3 = previous right sample r11 = 512 (for rounding) + * r4 = dpp->weight_A ip = decorrelation value + * r5 = dpp sp = + * r6 = dpp->delta lr = second previous right sample + * r7 = eptr pc = + ******************************************************************************* + */ + +term_2_loop: + movs ip, lr @ get decorrelation value & test + mov lr, r8 @ previous becomes 2nd previous + ldr r2, [r1], #4 @ get sample & update pointer + mla r8, ip, r4, r11 @ mult decorr value by weight, round, + add r8, r2, r8, asr #10 @ shift, and add to new sample + strne r8, [r1, #-4] @ if change possible, store sample back + cmpne r2, #0 + beq .L225 + teq ip, r2 @ update weight based on signs + submi r4, r4, r6 + addpl r4, r4, r6 + +.L225: movs ip, r10 @ do same thing for right channel + mov r10, r3 + ldr r2, [r1], #4 + mla r3, ip, r0, r11 + add r3, r2, r3, asr #10 + strne r3, [r1, #-4] + cmpne r2, #0 + beq .L229 + teq ip, r2 + submi r0, r0, r6 + addpl r0, r0, r6 + +.L229: cmp r7, r1 @ loop back if more samples to do + bhi term_2_loop + b default_term_exit @ this exit updates all dpp->samples + +/* + ****************************************************************************** + * Loop to handle default term condition + * + * r0 = dpp->weight_B r8 = result accumulator + * r1 = bptr r9 = + * r2 = dpp->term r10 = + * r3 = decorrelation value r11 = 512 (for rounding) + * r4 = dpp->weight_A ip = current sample + * r5 = dpp sp = + * r6 = dpp->delta lr = + * r7 = eptr pc = + ******************************************************************************* + */ + +term_default_loop: + ldr ip, [r1] @ get original sample + ldr r3, [r1, -r2, asl #3] @ get decorrelation value based on term + mla r8, r3, r4, r11 @ mult decorr value by weight, round, + add r8, ip, r8, asr #10 @ shift and add to new sample + str r8, [r1], #4 @ store update sample + cmp r3, #0 + cmpne ip, #0 + beq .L350 + teq ip, r3 @ update weight based on signs + submi r4, r4, r6 + addpl r4, r4, r6 + +.L350: ldr ip, [r1] @ do the same thing for right channel + ldr r3, [r1, -r2, asl #3] + mla r8, r3, r0, r11 + add r8, ip, r8, asr #10 + str r8, [r1], #4 + cmp r3, #0 + cmpne ip, #0 + beq .L354 + teq ip, r3 + submi r0, r0, r6 + addpl r0, r0, r6 + +.L354: cmp r7, r1 @ loop back if more samples to do + bhi term_default_loop + +/* + * This exit is used by terms 1-8 to store the previous 8 samples into the decorr + * structure (even if they are not all used for the given term) + */ + +default_term_exit: + ldrsh r3, [r5, #0] + sub ip, r3, #1 + mov lr, #7 + +.L358: and r3, ip, #7 + add r3, r5, r3, asl #2 + ldr r2, [r1, #-4] + str r2, [r3, #40] + ldr r2, [r1, #-8]! + str r2, [r3, #8] + sub ip, ip, #1 + sub lr, lr, #1 + cmn lr, #1 + bne .L358 + b common_exit + +/* + ****************************************************************************** + * Loop to handle term = -1 condition + * + * r0 = dpp->weight_B r8 = + * r1 = bptr r9 = + * r2 = intermediate result r10 = -1024 (for clipping) + * r3 = previous right sample r11 = 512 (for rounding) + * r4 = dpp->weight_A ip = current sample + * r5 = dpp sp = + * r6 = dpp->delta lr = updated left sample + * r7 = eptr pc = + ******************************************************************************* + */ + +term_minus_1: + ldr r3, [r1, #-4] + +term_minus_1_loop: + ldr ip, [r1] @ for left channel the decorrelation value + mla r2, r3, r4, r11 @ is the previous right sample (in r3) + add lr, ip, r2, asr #10 + str lr, [r1], #8 + cmp r3, #0 + cmpne ip, #0 + beq .L361 + teq ip, r3 @ update weight based on signs + submi r4, r4, r6 + addpl r4, r4, r6 + cmp r4, #1024 + movgt r4, #1024 + cmp r4, r10 + movlt r4, r10 + +.L361: ldr r2, [r1, #-4] @ for right channel the decorrelation value + mla r3, lr, r0, r11 @ is the just updated right sample (in lr) + add r3, r2, r3, asr #10 + str r3, [r1, #-4] + cmp lr, #0 + cmpne r2, #0 + beq .L369 + teq r2, lr + submi r0, r0, r6 + addpl r0, r0, r6 + cmp r0, #1024 @ then clip weight to +/-1024 + movgt r0, #1024 + cmp r0, r10 + movlt r0, r10 + +.L369: cmp r7, r1 @ loop back if more samples to do + bhi term_minus_1_loop + + str r3, [r5, #8] @ else store right sample and exit + b common_exit + +/* + ****************************************************************************** + * Loop to handle term = -2 condition + * (note that the channels are processed in the reverse order here) + * + * r0 = dpp->weight_B r8 = + * r1 = bptr r9 = + * r2 = intermediate result r10 = -1024 (for clipping) + * r3 = previous left sample r11 = 512 (for rounding) + * r4 = dpp->weight_A ip = current sample + * r5 = dpp sp = + * r6 = dpp->delta lr = updated right sample + * r7 = eptr pc = + ******************************************************************************* + */ + +term_minus_2: + ldr r3, [r1, #-8] + +term_minus_2_loop: + ldr ip, [r1, #4] @ for right channel the decorrelation value + mla r2, r3, r0, r11 @ is the previous left sample (in r3) + add lr, ip, r2, asr #10 + str lr, [r1, #4] + cmp r3, #0 + cmpne ip, #0 + beq .L380 + teq ip, r3 @ update weight based on signs + submi r0, r0, r6 + addpl r0, r0, r6 + cmp r0, #1024 @ then clip weight to +/-1024 + movgt r0, #1024 + cmp r0, r10 + movlt r0, r10 + +.L380: ldr r2, [r1, #0] @ for left channel the decorrelation value + mla r3, lr, r4, r11 @ is the just updated left sample (in lr) + add r3, r2, r3, asr #10 + str r3, [r1], #8 + cmp lr, #0 + cmpne r2, #0 + beq .L388 + teq r2, lr + submi r4, r4, r6 + addpl r4, r4, r6 + cmp r4, #1024 + movgt r4, #1024 + cmp r4, r10 + movlt r4, r10 + +.L388: cmp r7, r1 @ loop back if more samples to do + bhi term_minus_2_loop + + str r3, [r5, #40] @ else store left channel and exit + b common_exit + +/* + ****************************************************************************** + * Loop to handle term = -3 condition + * + * r0 = dpp->weight_B r8 = previous left sample + * r1 = bptr r9 = + * r2 = current left sample r10 = -1024 (for clipping) + * r3 = previous right sample r11 = 512 (for rounding) + * r4 = dpp->weight_A ip = intermediate result + * r5 = dpp sp = + * r6 = dpp->delta lr = + * r7 = eptr pc = + ******************************************************************************* + */ + +term_minus_3: + ldr r3, [r1, #-4] @ load previous samples + ldr r8, [r1, #-8] + +term_minus_3_loop: + ldr ip, [r1] + mla r2, r3, r4, r11 + add r2, ip, r2, asr #10 + str r2, [r1], #4 + cmp r3, #0 + cmpne ip, #0 + beq .L399 + teq ip, r3 @ update weight based on signs + submi r4, r4, r6 + addpl r4, r4, r6 + cmp r4, #1024 @ then clip weight to +/-1024 + movgt r4, #1024 + cmp r4, r10 + movlt r4, r10 + +.L399: movs ip, r8 @ ip = previous left we use now + mov r8, r2 @ r8 = current left we use next time + ldr r2, [r1], #4 + mla r3, ip, r0, r11 + add r3, r2, r3, asr #10 + strne r3, [r1, #-4] + cmpne r2, #0 + beq .L407 + teq ip, r2 + submi r0, r0, r6 + addpl r0, r0, r6 + cmp r0, #1024 + movgt r0, #1024 + cmp r0, r10 + movlt r0, r10 + +.L407: cmp r7, r1 @ loop back if more samples to do + bhi term_minus_3_loop + + str r3, [r5, #8] @ else store previous samples & exit + str r8, [r5, #40] + +/* + * Before finally exiting we must store weights back for next time + */ + +common_exit: + strh r4, [r5, #4] + strh r0, [r5, #6] + ldmfd sp!, {r4 - r8, r10, r11, pc} + diff --git a/src/wavpack/arml.S b/src/wavpack/arml.S new file mode 100644 index 00000000..39de5383 --- /dev/null +++ b/src/wavpack/arml.S @@ -0,0 +1,491 @@ +//////////////////////////////////////////////////////////////////////////// +// **** WAVPACK **** // +// Hybrid Lossless Wavefile Compressor // +// Copyright (c) 1998 - 2006 Conifer Software. // +// All Rights Reserved. // +// Distributed under the BSD Software License (see license.txt) // +//////////////////////////////////////////////////////////////////////////// + +/* This is an assembly optimized version of the following WavPack function: + * + * void decorr_stereo_pass_cont (struct decorr_pass *dpp, + * long *buffer, long sample_count); + * + * It performs a single pass of stereo decorrelation on the provided buffer. + * Note that this version of the function requires that the 8 previous stereo + * samples are visible and correct. In other words, it ignores the "samples_*" + * fields in the decorr_pass structure and gets the history data directly + * from the buffer. It does, however, return the appropriate history samples + * to the decorr_pass structure before returning. + * + * This is written to work on a ARM7TDMI processor. This version uses the + * 64-bit multiply-accumulate instruction and so can be used with all + * WavPack files. However, for optimum performance with 16-bit WavPack + * files, there is a faster version that only uses the 32-bit MLA + * instruction. + */ + + .text + .align + .global decorr_stereo_pass_cont_arml + +/* + * on entry: + * + * r0 = struct decorr_pass *dpp + * r1 = long *buffer + * r2 = long sample_count + */ + +decorr_stereo_pass_cont_arml: + + stmfd sp!, {r4 - r8, r10, r11, lr} + mov r5, r0 @ r5 = dpp + mov r11, #512 @ r11 = 512 for rounding + ldrsh r6, [r0, #2] @ r6 = dpp->delta + ldrsh r4, [r0, #4] @ r4 = dpp->weight_A + ldrsh r0, [r0, #6] @ r0 = dpp->weight_B + cmp r2, #0 @ exit if no samples to process + beq common_exit + + mov r0, r0, asl #18 @ for 64-bit math we use weights << 18 + mov r4, r4, asl #18 + mov r6, r6, asl #18 + add r7, r1, r2, asl #3 @ r7 = buffer ending position + ldrsh r2, [r5, #0] @ r2 = dpp->term + cmp r2, #0 + blt minus_term + + ldr lr, [r1, #-16] @ load 2 sample history from buffer + ldr r10, [r1, #-12] @ for terms 2, 17, and 18 + ldr r8, [r1, #-8] + ldr r3, [r1, #-4] + + cmp r2, #18 + beq term_18_loop + mov lr, lr, asl #4 + mov r10, r10, asl #4 + cmp r2, #2 + beq term_2_loop + cmp r2, #17 + beq term_17_loop + b term_default_loop + +minus_term: + mov r10, #(1024 << 18) @ r10 = -1024 << 18 for weight clipping + rsb r10, r10, #0 @ (only used for negative terms) + cmn r2, #1 + beq term_minus_1 + cmn r2, #2 + beq term_minus_2 + cmn r2, #3 + beq term_minus_3 + b common_exit + +/* + ****************************************************************************** + * Loop to handle term = 17 condition + * + * r0 = dpp->weight_B r8 = previous left sample + * r1 = bptr r9 = + * r2 = current sample r10 = second previous left sample << 4 + * r3 = previous right sample r11 = lo accumulator (for rounding) + * r4 = dpp->weight_A ip = current decorrelation value + * r5 = dpp sp = + * r6 = dpp->delta lr = second previous right sample << 4 + * r7 = eptr pc = + ******************************************************************************* + */ + +term_17_loop: + rsbs ip, lr, r8, asl #5 @ decorr value = (2 * prev) - 2nd prev + mov lr, r8, asl #4 @ previous becomes 2nd previous + ldr r2, [r1], #4 @ get sample & update pointer + mov r11, #0x80000000 + mov r8, r2 + smlalne r11, r8, r4, ip + strne r8, [r1, #-4] @ if change possible, store sample back + cmpne r2, #0 + beq .L325 + teq ip, r2 @ update weight based on signs + submi r4, r4, r6 + addpl r4, r4, r6 + +.L325: rsbs ip, r10, r3, asl #5 @ do same thing for right channel + mov r10, r3, asl #4 + ldr r2, [r1], #4 + mov r11, #0x80000000 + mov r3, r2 + smlalne r11, r3, r0, ip + strne r3, [r1, #-4] + cmpne r2, #0 + beq .L329 + teq ip, r2 + submi r0, r0, r6 + addpl r0, r0, r6 + +.L329: cmp r7, r1 @ loop back if more samples to do + bhi term_17_loop + mov lr, lr, asr #4 + mov r10, r10, asr #4 + b store_1718 @ common exit for terms 17 & 18 + +/* + ****************************************************************************** + * Loop to handle term = 18 condition + * + * r0 = dpp->weight_B r8 = previous left sample + * r1 = bptr r9 = + * r2 = current sample r10 = second previous left sample + * r3 = previous right sample r11 = lo accumulator (for rounding) + * r4 = dpp->weight_A ip = decorrelation value + * r5 = dpp sp = + * r6 = dpp->delta lr = second previous right sample + * r7 = eptr pc = + ******************************************************************************* + */ + +term_18_loop: + rsb ip, lr, r8 @ decorr value = + mov lr, r8 @ ((3 * prev) - 2nd prev) >> 1 + add ip, lr, ip, asr #1 + movs ip, ip, asl #4 + ldr r2, [r1], #4 @ get sample & update pointer + mov r11, #0x80000000 + mov r8, r2 + smlalne r11, r8, r4, ip + strne r8, [r1, #-4] @ if change possible, store sample back + cmpne r2, #0 + beq .L337 + teq ip, r2 @ update weight based on signs + submi r4, r4, r6 + addpl r4, r4, r6 + +.L337: rsb ip, r10, r3 @ do same thing for right channel + mov r10, r3 + add ip, r10, ip, asr #1 + movs ip, ip, asl #4 + ldr r2, [r1], #4 + mov r11, #0x80000000 + mov r3, r2 + smlalne r11, r3, r0, ip + strne r3, [r1, #-4] + cmpne r2, #0 + beq .L341 + teq ip, r2 + submi r0, r0, r6 + addpl r0, r0, r6 + +.L341: cmp r7, r1 @ loop back if more samples to do + bhi term_18_loop + +/* common exit for terms 17 & 18 */ + +store_1718: + str r3, [r5, #40] @ store sample history into struct + str r8, [r5, #8] + str r10, [r5, #44] + str lr, [r5, #12] + b common_exit @ and return + +/* + ****************************************************************************** + * Loop to handle term = 2 condition + * (note that this case can be handled by the default term handler (1-8), but + * this special case is faster because it doesn't have to read memory twice) + * + * r0 = dpp->weight_B r8 = previous left sample + * r1 = bptr r9 = + * r2 = current sample r10 = second previous left sample << 4 + * r3 = previous right sample r11 = lo accumulator (for rounding) + * r4 = dpp->weight_A ip = decorrelation value + * r5 = dpp sp = + * r6 = dpp->delta lr = second previous right sample << 4 + * r7 = eptr pc = + ******************************************************************************* + */ + +term_2_loop: + movs ip, lr @ get decorrelation value & test + ldr r2, [r1], #4 @ get sample & update pointer + mov lr, r8, asl #4 @ previous becomes 2nd previous + mov r11, #0x80000000 + mov r8, r2 + smlalne r11, r8, r4, ip + strne r8, [r1, #-4] @ if change possible, store sample back + cmpne r2, #0 + beq .L225 + teq ip, r2 @ update weight based on signs + submi r4, r4, r6 + addpl r4, r4, r6 + +.L225: movs ip, r10 @ do same thing for right channel + ldr r2, [r1], #4 + mov r10, r3, asl #4 + mov r11, #0x80000000 + mov r3, r2 + smlalne r11, r3, r0, ip + strne r3, [r1, #-4] + cmpne r2, #0 + beq .L229 + teq ip, r2 + submi r0, r0, r6 + addpl r0, r0, r6 + +.L229: cmp r7, r1 @ loop back if more samples to do + bhi term_2_loop + + b default_term_exit @ this exit updates all dpp->samples + +/* + ****************************************************************************** + * Loop to handle default term condition + * + * r0 = dpp->weight_B r8 = result accumulator + * r1 = bptr r9 = + * r2 = dpp->term r10 = + * r3 = decorrelation value r11 = lo accumulator (for rounding) + * r4 = dpp->weight_A ip = current sample + * r5 = dpp sp = + * r6 = dpp->delta lr = + * r7 = eptr pc = + ******************************************************************************* + */ + +term_default_loop: + ldr r3, [r1, -r2, asl #3] @ get decorrelation value based on term + ldr ip, [r1], #4 @ get original sample and bump ptr + movs r3, r3, asl #4 + mov r11, #0x80000000 + mov r8, ip + smlalne r11, r8, r4, r3 + strne r8, [r1, #-4] @ if possibly changed, store updated sample + cmpne ip, #0 + beq .L350 + teq ip, r3 @ update weight based on signs + submi r4, r4, r6 + addpl r4, r4, r6 + +.L350: ldr r3, [r1, -r2, asl #3] @ do the same thing for right channel + ldr ip, [r1], #4 + movs r3, r3, asl #4 + mov r11, #0x80000000 + mov r8, ip + smlalne r11, r8, r0, r3 + strne r8, [r1, #-4] + cmpne ip, #0 + beq .L354 + teq ip, r3 + submi r0, r0, r6 + addpl r0, r0, r6 + +.L354: cmp r7, r1 @ loop back if more samples to do + bhi term_default_loop + +/* + * This exit is used by terms 1-8 to store the previous 8 samples into the decorr + * structure (even if they are not all used for the given term) + */ + +default_term_exit: + ldrsh r3, [r5, #0] + sub ip, r3, #1 + mov lr, #7 + +.L358: and r3, ip, #7 + add r3, r5, r3, asl #2 + ldr r2, [r1, #-4] + str r2, [r3, #40] + ldr r2, [r1, #-8]! + str r2, [r3, #8] + sub ip, ip, #1 + sub lr, lr, #1 + cmn lr, #1 + bne .L358 + b common_exit + +/* + ****************************************************************************** + * Loop to handle term = -1 condition + * + * r0 = dpp->weight_B r8 = + * r1 = bptr r9 = + * r2 = intermediate result r10 = -1024 (for clipping) + * r3 = previous right sample r11 = lo accumulator (for rounding) + * r4 = dpp->weight_A ip = current sample + * r5 = dpp sp = + * r6 = dpp->delta lr = updated left sample + * r7 = eptr pc = + ******************************************************************************* + */ + +term_minus_1: + ldr r3, [r1, #-4] + +term_minus_1_loop: + ldr ip, [r1], #8 @ for left channel the decorrelation value + movs r3, r3, asl #4 @ is the previous right sample (in r3) + mov r11, #0x80000000 + mov lr, ip + smlalne r11, lr, r4, r3 + strne lr, [r1, #-8] + cmpne ip, #0 + beq .L361 + teq ip, r3 @ update weight based on signs + submi r4, r4, r6 + addpl r4, r4, r6 + cmp r4, #(1024 << 18) + movgt r4, #(1024 << 18) + cmp r4, r10 + movlt r4, r10 + +.L361: ldr r2, [r1, #-4] @ for right channel the decorrelation value + movs lr, lr, asl #4 + mov r11, #0x80000000 + mov r3, r2 + smlalne r11, r3, r0, lr + strne r3, [r1, #-4] + cmpne r2, #0 + beq .L369 + teq r2, lr + submi r0, r0, r6 + addpl r0, r0, r6 + cmp r0, #(1024 << 18) @ then clip weight to +/-1024 + movgt r0, #(1024 << 18) + cmp r0, r10 + movlt r0, r10 + +.L369: cmp r7, r1 @ loop back if more samples to do + bhi term_minus_1_loop + + str r3, [r5, #8] @ else store right sample and exit + b common_exit + +/* + ****************************************************************************** + * Loop to handle term = -2 condition + * (note that the channels are processed in the reverse order here) + * + * r0 = dpp->weight_B r8 = + * r1 = bptr r9 = + * r2 = intermediate result r10 = -1024 (for clipping) + * r3 = previous left sample r11 = lo accumulator (for rounding) + * r4 = dpp->weight_A ip = current sample + * r5 = dpp sp = + * r6 = dpp->delta lr = updated right sample + * r7 = eptr pc = + ******************************************************************************* + */ + +term_minus_2: + ldr r3, [r1, #-8] + +term_minus_2_loop: + ldr ip, [r1, #4] @ for right channel the decorrelation value + movs r3, r3, asl #4 @ is the previous left sample (in r3) + mov r11, #0x80000000 + mov lr, ip + smlalne r11, lr, r0, r3 + strne lr, [r1, #4] + cmpne ip, #0 + beq .L380 + teq ip, r3 @ update weight based on signs + submi r0, r0, r6 + addpl r0, r0, r6 + cmp r0, #(1024 << 18) @ then clip weight to +/-1024 + movgt r0, #(1024 << 18) + cmp r0, r10 + movlt r0, r10 + +.L380: ldr r2, [r1], #8 @ for left channel the decorrelation value + movs lr, lr, asl #4 + mov r11, #0x80000000 + mov r3, r2 + smlalne r11, r3, r4, lr + strne r3, [r1, #-8] + cmpne r2, #0 + beq .L388 + teq r2, lr + submi r4, r4, r6 + addpl r4, r4, r6 + cmp r4, #(1024 << 18) + movgt r4, #(1024 << 18) + cmp r4, r10 + movlt r4, r10 + +.L388: cmp r7, r1 @ loop back if more samples to do + bhi term_minus_2_loop + + str r3, [r5, #40] @ else store left channel and exit + b common_exit + +/* + ****************************************************************************** + * Loop to handle term = -3 condition + * + * r0 = dpp->weight_B r8 = previous left sample + * r1 = bptr r9 = + * r2 = current left sample r10 = -1024 (for clipping) + * r3 = previous right sample r11 = lo accumulator (for rounding) + * r4 = dpp->weight_A ip = intermediate result + * r5 = dpp sp = + * r6 = dpp->delta lr = + * r7 = eptr pc = + ******************************************************************************* + */ + +term_minus_3: + ldr r3, [r1, #-4] @ load previous samples + ldr r8, [r1, #-8] + +term_minus_3_loop: + ldr ip, [r1], #4 + movs r3, r3, asl #4 + mov r11, #0x80000000 + mov r2, ip + smlalne r11, r2, r4, r3 + strne r2, [r1, #-4] + cmpne ip, #0 + beq .L399 + teq ip, r3 @ update weight based on signs + submi r4, r4, r6 + addpl r4, r4, r6 + cmp r4, #(1024 << 18) @ then clip weight to +/-1024 + movgt r4, #(1024 << 18) + cmp r4, r10 + movlt r4, r10 + +.L399: movs ip, r8, asl #4 @ ip = previous left we use now + mov r8, r2 @ r8 = current left we use next time + ldr r2, [r1], #4 + mov r11, #0x80000000 + mov r3, r2 + smlalne r11, r3, r0, ip + strne r3, [r1, #-4] + cmpne r2, #0 + beq .L407 + teq ip, r2 + submi r0, r0, r6 + addpl r0, r0, r6 + cmp r0, #(1024 << 18) + movgt r0, #(1024 << 18) + cmp r0, r10 + movlt r0, r10 + +.L407: cmp r7, r1 @ loop back if more samples to do + bhi term_minus_3_loop + + str r3, [r5, #8] @ else store previous samples & exit + str r8, [r5, #40] + +/* + * Before finally exiting we must store weights back for next time + */ + +common_exit: + mov r0, r0, asr #18 @ restore weights to real magnitude + mov r4, r4, asr #18 + strh r4, [r5, #4] + strh r0, [r5, #6] + ldmfd sp!, {r4 - r8, r10, r11, pc} + diff --git a/src/wavpack/bits.c b/src/wavpack/bits.c new file mode 100644 index 00000000..dbfa0cae --- /dev/null +++ b/src/wavpack/bits.c @@ -0,0 +1,140 @@ +//////////////////////////////////////////////////////////////////////////// +// **** WAVPACK **** // +// Hybrid Lossless Wavefile Compressor // +// Copyright (c) 1998 - 2006 Conifer Software. // +// All Rights Reserved. // +// Distributed under the BSD Software License (see license.txt) // +//////////////////////////////////////////////////////////////////////////// + +// bits.c + +// This module provides utilities to support the BitStream structure which is +// used to read and write all WavPack audio data streams. It also contains a +// wrapper for the stream I/O functions and a set of functions dealing with +// endian-ness, both for enhancing portability. Finally, a debug wrapper for +// the malloc() system is provided. + +#include "wavpack.h" + +#include +#include + +////////////////////////// Bitstream functions //////////////////////////////// + +// Open the specified BitStream and associate with the specified buffer. + +static void bs_read (Bitstream *bs); + +void bs_open_read (Bitstream *bs, uchar *buffer_start, uchar *buffer_end, read_stream file, uint32_t file_bytes) +{ + CLEAR (*bs); + bs->buf = buffer_start; + bs->end = buffer_end; + + if (file) { + bs->ptr = bs->end - 1; + bs->file_bytes = file_bytes; + bs->file = file; + } + else + bs->ptr = bs->buf - 1; + + bs->wrap = bs_read; +} + +// This function is only called from the getbit() and getbits() macros when +// the BitStream has been exhausted and more data is required. Sinve these +// bistreams no longer access files, this function simple sets an error and +// resets the buffer. + +static void bs_read (Bitstream *bs) +{ + if (bs->file && bs->file_bytes) { + uint32_t bytes_read, bytes_to_read = bs->end - bs->buf; + + if (bytes_to_read > bs->file_bytes) + bytes_to_read = bs->file_bytes; + + bytes_read = bs->file (bs->buf, bytes_to_read); + + if (bytes_read) { + bs->end = bs->buf + bytes_read; + bs->file_bytes -= bytes_read; + } + else { + memset (bs->buf, -1, bs->end - bs->buf); + bs->error = 1; + } + } + else + bs->error = 1; + + if (bs->error) + memset (bs->buf, -1, bs->end - bs->buf); + + bs->ptr = bs->buf; +} + +/////////////////////// Endian Correction Routines //////////////////////////// + +void little_endian_to_native (void *data, char *format) +{ + uchar *cp = (uchar *) data; + int32_t temp; + + while (*format) { + switch (*format) { + case 'L': + temp = cp [0] + ((int32_t) cp [1] << 8) + ((int32_t) cp [2] << 16) + ((int32_t) cp [3] << 24); + * (int32_t *) cp = temp; + cp += 4; + break; + + case 'S': + temp = cp [0] + (cp [1] << 8); + * (short *) cp = (short) temp; + cp += 2; + break; + + default: + if (isdigit (*format)) + cp += *format - '0'; + + break; + } + + format++; + } +} + +void native_to_little_endian (void *data, char *format) +{ + uchar *cp = (uchar *) data; + int32_t temp; + + while (*format) { + switch (*format) { + case 'L': + temp = * (int32_t *) cp; + *cp++ = (uchar) temp; + *cp++ = (uchar) (temp >> 8); + *cp++ = (uchar) (temp >> 16); + *cp++ = (uchar) (temp >> 24); + break; + + case 'S': + temp = * (short *) cp; + *cp++ = (uchar) temp; + *cp++ = (uchar) (temp >> 8); + break; + + default: + if (isdigit (*format)) + cp += *format - '0'; + + break; + } + + format++; + } +} diff --git a/src/wavpack/coldfire.S b/src/wavpack/coldfire.S new file mode 100644 index 00000000..93df9d82 --- /dev/null +++ b/src/wavpack/coldfire.S @@ -0,0 +1,525 @@ +//////////////////////////////////////////////////////////////////////////// +// **** WAVPACK **** // +// Hybrid Lossless Wavefile Compressor // +// Copyright (c) 1998 - 2006 Conifer Software. // +// All Rights Reserved. // +// Distributed under the BSD Software License (see license.txt) // +//////////////////////////////////////////////////////////////////////////// + +/* This is an assembly optimized version of the following WavPack function: + * + * void decorr_stereo_pass_cont (struct decorr_pass *dpp, + * long *buffer, long sample_count); + * + * It performs a single pass of stereo decorrelation on the provided buffer. + * Note that this version of the function requires that the 8 previous stereo + * samples are visible and correct. In other words, it ignores the "samples_*" + * fields in the decorr_pass structure and gets the history data directly + * from the buffer. It does, however, return the appropriate history samples + * to the decorr_pass structure before returning. + * + * This is written to work on a MCF5249 processor, or any processor based on + * the ColdFire V2 core with an EMAC unit. The EMAC is perfectly suited for + * the "apply_weight" function of WavPack decorrelation because it provides + * the requires 40-bit product. The fractional rounding mode of the EMAC is not + * configurable and uses "round to even" while WavPack uses "round to larger", + * so the rounding has to be done manually. + */ + + .text + .align 2 + .global decorr_stereo_pass_cont_mcf5249 + +decorr_stereo_pass_cont_mcf5249: + + lea (-44, %sp), %sp + movem.l %d2-%d7/%a2-%a6, (%sp) + move.l 44+4(%sp), %a2 | a2 = dpp-> + move.l 44+8(%sp), %a1 | a1 = bptr + move.w 2(%a2), %a3 | a3 = dpp->delta + move.w 4(%a2), %d3 | d3 = dpp->weight_A (sign extended) + ext.l %d3 + move.w 6(%a2), %d4 | d4 = dpp->weight_B (sign extended) + ext.l %d4 + move.l 44+12(%sp), %d0 | d0 = sample_count + jbeq return_only | if zero, nothing to do + + lsl.l #3, %d0 | d5 = bptr + (sample_count * 8) + move.l %d0, %d5 + add.l %a1, %d5 + + moveq.l #17, %d0 | left shift weights & delta 17 places + asl.l %d0, %d3 + asl.l %d0, %d4 + move.l %a3, %d1 + asl.l %d0, %d1 + move.l %d1, %a3 + + moveq.l #0x20, %d6 + move.l %d6, %macsr | set fractional mode for MAC + move.l #0, %acc1 | acc1 = 0x00 0000 80 (for rounding) + move.l #0x800000, %accext01 + + move.l #1024<<17, %d6 | d6 & d7 are weight clipping limits + move.l #-1024<<17, %d7 | (only used by negative terms) + + move.w (%a2), %d0 | d0 = term + ext.l %d0 + cmp.l #17, %d0 + jbeq term_17 | term = 17 + cmp.l #18, %d0 + jbeq term_18 | term = 18 + addq.l #1, %d0 + jbeq term_minus_1 | term = -1 + addq.l #1, %d0 + jbeq term_minus_2 | term = -2 + addq.l #1, %d0 + jbeq term_minus_3 | term = -3 + jbra term_default | default term = 1 - 8 + +|------------------------------------------------------------------------------ +| Loop to handle term = 17 condition +| +| a0 = d0 = (2 * bptr [-1]) - bptr [-2] +| a1 = bptr d1 = initial bptr [0] +| a2 = dpp-> d2 = updated bptr [0] +| a3 = dpp->delta << 17 d3 = dpp->weight_A << 17 +| a4 = d4 = dpp->weight_B << 17 +| a5 = d5 = eptr +| macsr = 0x20 acc1 = 0x00 0000 80 +|------------------------------------------------------------------------------ + +term_17: + move.l -8(%a1), %d0 | d0 = 2 * bptr [-1] - bptr [-2] + add.l %d0, %d0 + sub.l -16(%a1), %d0 + beq .L251 | if zero, skip calculation + move.l %acc1, %acc0 + asl.l #4, %d0 | acc0 = acc1 + (d0 << 4) * weight_A + mac.l %d0, %d3, %acc0 + move.l (%a1), %d1 + beq .L255 + eor.l %d1, %d0 | else compare signs + bge .L256 | if same, add delta to weight + sub.l %a3, %d3 | else subtract delta from weight + sub.l %a3, %d3 | subtract again instead of branch +.L256: add.l %a3, %d3 | add delta to weight + +.L255: move.l %acc0, %d2 | d2 = rounded product + add.l %d1, %d2 | update bptr [0] and store + move.l %d2, (%a1)+ + +.L253: move.l -8(%a1), %d0 | d0 = 2 * bptr [-1] - bptr [-2] + add.l %d0, %d0 + sub.l -16(%a1), %d0 + beq .L257 | if zero, skip calculations + move.l %acc1, %acc0 + asl.l #4, %d0 | acc0 = acc1 + (d0 << 4) * weight_B + mac.l %d0, %d4, %acc0 + move.l (%a1), %d1 + beq .L254 + eor.l %d1, %d0 | else compare signs + bge .L259 | if same, add delta to weight + sub.l %a3, %d4 | else subtract delta from weight + sub.l %a3, %d4 | subtract again instead of branch +.L259: add.l %a3, %d4 | add delta to weight + +.L254: move.l %acc0, %d2 | d2 = rounded product + add.l %d1, %d2 | update bptr [0] and store + move.l %d2, (%a1)+ + +.L252: cmp.l %a1, %d5 | loop if bptr < eptr + jbhi term_17 + bra term_17_18_finish | exit through common path + +.L251: addq.l #4, %a1 | update point and jump back into loop + bra .L253 + +.L257: addq.l #4, %a1 | update point and jump back into loop + bra .L252 + +|------------------------------------------------------------------------------ +| Loop to handle term = 18 condition +| +| a0 = d0 = ((3 * bptr [-1]) - bptr [-2]) >> 1 +| a1 = bptr d1 = initial bptr [0] +| a2 = dpp-> d2 = updated bptr [0] +| a3 = dpp->delta << 17 d3 = dpp->weight_A << 17 +| a4 = d4 = dpp->weight_B << 17 +| a5 = d5 = eptr +| macsr = 0x20 acc1 = 0x00 0000 80 +|------------------------------------------------------------------------------ + +term_18: + move.l -8(%a1), %a0 | d0 = (3 * bptr [-1] - bptr [-2]) >> 1 + lea (%a0,%a0.l*2), %a0 + move.l %a0, %d0 + sub.l -16(%a1), %d0 + asr.l #1, %d0 + beq .L260 + move.l %acc1, %acc0 + asl.l #4, %d0 | acc0 = acc1 + (d0 << 4) * weight_A + mac.l %d0, %d3, %acc0 + move.l (%a1), %d1 + beq .L266 + eor.l %d1, %d0 | else compare signs + bge .L267 | if same, add delta to weight + sub.l %a3, %d3 | else subtract delta from weight + sub.l %a3, %d3 | subtract again instead of branch +.L267: add.l %a3, %d3 | add delta to weight + +.L266: move.l %acc0, %d2 | d2 = rounded product + add.l %d1, %d2 | add applied weight to bptr [0], store + move.l %d2, (%a1)+ + +.L268: move.l -8(%a1), %a0 | d0 = (3 * bptr [-1] - bptr [-2]) >> 1 + lea (%a0,%a0.l*2), %a0 + move.l %a0, %d0 + sub.l -16(%a1), %d0 + asr.l #1, %d0 + beq .L261 + move.l %acc1, %acc0 + asl.l #4, %d0 | acc0 = acc1 + (d0 << 4) * weight_B + mac.l %d0, %d4, %acc0 + move.l (%a1), %d1 + beq .L265 + eor.l %d1, %d0 | else compare signs + bge .L270 | if same, add delta to weight + sub.l %a3, %d4 | else subtract delta from weight + sub.l %a3, %d4 | subtract again instead of branch +.L270: add.l %a3, %d4 | add delta to weight + +.L265: move.l %acc0, %d2 | d2 = rounded product + add.l %d1, %d2 | add applied weight to bptr [0], store + move.l %d2, (%a1)+ + +.L269: cmp.l %a1, %d5 | loop if bptr < eptr + jbhi term_18 + bra term_17_18_finish | exit through common path + +.L260: addq.l #4, %a1 | bump pointer and jump back into loop + bra .L268 + +.L261: addq.l #4, %a1 | bump pointer and jump back into loop + bra .L269 + +term_17_18_finish: + move.l -4(%a1), 40(%a2) | restore dpp->samples_A [0-1], B [0-1] + move.l -8(%a1), 8(%a2) + move.l -12(%a1), 44(%a2) + move.l -16(%a1), 12(%a2) + jbra finish_up + +|------------------------------------------------------------------------------ +| Loop to handle default terms (i.e. 1 - 8) +| +| a0 = tptr d0 = tptr [0] +| a1 = bptr d1 = initial bptr [0] +| a2 = dpp-> d2 = updated bptr [0] +| a3 = dpp->delta << 17 d3 = dpp->weight_A << 17 +| a4 = d4 = dpp->weight_B << 17 +| a5 = d5 = eptr +| macsr = 0x20 acc1 = 0x00 0000 80 +|------------------------------------------------------------------------------ + +term_default: + move.w (%a2), %d0 | a0 = a1 - (dpp->term * 8) + ext.l %d0 + lsl.l #3, %d0 + move.l %a1, %a0 + sub.l %d0, %a0 + +term_default_loop: + move.l (%a0)+, %d0 | d0 = tptr [0], skip ahead if zero + beq .L271 + move.l %acc1, %acc0 + asl.l #4, %d0 | acc0 = acc1 + (d0 << 4) * weight_A + mac.l %d0, %d3, %acc0 + move.l (%a1), %d1 + beq .L277 + eor.l %d1, %d0 | else compare signs + bge .L278 | if same, add delta to weight + sub.l %a3, %d3 | else subtract delta from weight + sub.l %a3, %d3 | subtract again instead of branch +.L278: add.l %a3, %d3 | add delta to weight + +.L277: move.l %acc0, %d2 | d2 = rounded product + add.l %d1, %d2 | add applied weight to bptr [0], store + move.l %d2, (%a1)+ + +.L275: move.l (%a0)+, %d0 | d0 = tptr [0], skip ahead if zero + beq .L272 + move.l %acc1, %acc0 + asl.l #4, %d0 | acc0 = acc1 + (d0 << 4) * weight_B + mac.l %d0, %d4, %acc0 + move.l (%a1), %d1 + beq .L276 + eor.l %d1, %d0 | else compare signs + bge .L281 | if same, add delta to weight + sub.l %a3, %d4 | else subtract delta from weight + sub.l %a3, %d4 | subtract again instead of branch +.L281: add.l %a3, %d4 | add delta to weight + +.L276: move.l %acc0, %d2 | d2 = rounded product + add.l %d1, %d2 | add applied weight to bptr [0], store + move.l %d2, (%a1)+ + +.L274: cmp.l %a1, %d5 | loop back if bptr < eptr + jbhi term_default_loop + move.w (%a2), %d0 | d0 = term - 1 + moveq.l #8, %d1 | d1 = loop counter + +.L323: subq.l #1, %d0 | back up & mask index + and.l #7, %d0 + move.l -(%a1), 40(%a2,%d0.l*4) | store dpp->samples_B [d0] + move.l -(%a1), 8(%a2,%d0.l*4) | store dpp->samples_A [d0] + subq.l #1, %d1 | loop on count + jbne .L323 + jbra finish_up + +.L271: addq.l #4, %a1 | bump pointer and jump back into loop + bra .L275 + +.L272: addq.l #4, %a1 | bump pointer and jump back into loop + bra .L274 + + +|------------------------------------------------------------------------------ +| Loop to handle term = -1 condition +| +| a0 = d0 = decorrelation sample +| a1 = bptr d1 = initial bptr [0] +| a2 = dpp-> d2 = updated bptr [0] +| a3 = dpp->delta << 17 d3 = dpp->weight_A << 17 +| a4 = d4 = dpp->weight_B << 17 +| a5 = d5 = eptr +| a6 = d6 = 1024 << 17 +| a7 = d7 = -1024 << 17 +| macsr = 0x20 acc1 = 0x00 0000 80 +|------------------------------------------------------------------------------ + +term_minus_1: + move.l -4(%a1), %d0 | d0 = bptr [-1] + beq .L402 + move.l %acc1, %acc0 + asl.l #4, %d0 | acc0 = acc1 + ((d0 << 4) * weight_A) + mac.l %d0, %d3, %acc0 + move.l (%a1), %d1 + beq .L405 + eor.l %d1, %d0 | else compare signs + bge .L404 | if same, add delta to weight + sub.l %a3, %d3 | else subtract delta from weight + cmp.l %d7, %d3 | check for negative clip limit + bge .L405 + move.l %d7, %d3 + bra .L405 + +.L404: add.l %a3, %d3 | add delta to weight + cmp.l %d6, %d3 | check for positive clip limit + ble .L405 + move.l %d6, %d3 + +.L405: move.l %acc0, %d0 | d2 = rounded product + add.l %d1, %d0 | add applied weight to bptr [0], store + move.l %d0, (%a1)+ + beq .L401 + +.L410: move.l %acc1, %acc0 + asl.l #4, %d0 | acc0 = acc1 + ((d0 << 4) * weight_B) + mac.l %d0, %d4, %acc0 + move.l (%a1), %d1 + beq .L403 + eor.l %d1, %d0 | else compare signs + bge .L407 | if same, add delta to weight + sub.l %a3, %d4 | else subtract delta from weight + cmp.l %d7, %d4 | check for negative clip limit + bge .L403 + move.l %d7, %d4 + bra .L403 + +.L407: add.l %a3, %d4 | add delta to weight + cmp.l %d6, %d4 | check for positive clip limit + ble .L403 + move.l %d6, %d4 + +.L403: move.l %acc0, %d2 | d2 = rounded product + add.l %d1, %d2 | add applied weight to bptr [1], store + move.l %d2, (%a1)+ + +.L411: cmp.l %a1, %d5 | loop back if bptr < eptr + jbhi term_minus_1 + move.l -4(%a1), 8(%a2) | dpp->samples_A [0] = bptr [-1] + jbra finish_up + +.L402: move.l (%a1)+, %d0 + bne .L410 + +.L401: addq.l #4, %a1 + bra .L411 + + +|------------------------------------------------------------------------------ +| Loop to handle term = -2 condition +| +| a0 = d0 = decorrelation sample +| a1 = bptr d1 = initial bptr [0] +| a2 = dpp-> d2 = updated bptr [0] +| a3 = dpp->delta << 17 d3 = dpp->weight_A << 17 +| a4 = d4 = dpp->weight_B << 17 +| a5 = d5 = eptr +| a6 = d6 = 1024 << 17 +| a7 = d7 = -1024 << 17 +| macsr = 0x20 acc1 = 0x00 0000 80 +|------------------------------------------------------------------------------ + +term_minus_2: + move.l -8(%a1), %d0 | d0 = bptr [-2] + beq .L511 + move.l %acc1, %acc0 + asl.l #4, %d0 | acc0 = acc1 + ((d0 << 4) * weight_B) + mac.l %d0, %d4, %acc0 + move.l 4(%a1), %d1 + beq .L505 + eor.l %d1, %d0 | else compare signs + bge .L504 | if same, add delta to weight + sub.l %a3, %d4 | else subtract delta from weight + cmp.l %d7, %d4 | ckeck for negative clip limit + bge .L505 + move.l %d7, %d4 + bra .L505 + +.L504: add.l %a3, %d4 | add delta to weight + cmp.l %d6, %d4 | check for positive clip limit + ble .L505 + move.l %d6, %d4 + +.L505: move.l %acc0, %d0 | d2 = rounded product + add.l %d1, %d0 | add applied weight to bptr [0], store + move.l %d0, 4(%a1) + beq .L512 + +.L510: move.l %acc1, %acc0 + asl.l #4, %d0 | acc0 = acc1 + ((d0 << 4) * weight_A) + mac.l %d0, %d3, %acc0 + move.l (%a1), %d1 + beq .L503 + eor.l %d1, %d0 | else compare signs + bge .L507 | if same, add delta to weight + sub.l %a3, %d3 | else subtract delta from weight + cmp.l %d7, %d3 | check for negative clip limit + bge .L503 + move.l %d7, %d3 + bra .L503 + +.L507: add.l %a3, %d3 | add delta to weight + cmp.l %d6, %d3 | check for negative clip limit + ble .L503 + move.l %d6, %d3 + +.L503: move.l %acc0, %d2 | d2 = rounded product + add.l %d1, %d2 | add applied weight to bptr [1], store + move.l %d2, (%a1) + +.L512: addq.l #8, %a1 + cmp.l %a1, %d5 | loop if bptr < eptr + jbhi term_minus_2 + move.l -8(%a1), 40(%a2) | dpp->samples_B [0] = bptr [-4] + jbra finish_up + +.L511: move.l 4(%a1), %d0 + beq .L512 + bra .L510 + + +|------------------------------------------------------------------------------ +| Loop to handle term = -3 condition +| +| a0 = d0 = decorrelation sample +| a1 = bptr d1 = initial bptr [0] +| a2 = dpp-> d2 = updated bptr [0] +| a3 = dpp->delta << 17 d3 = dpp->weight_A << 17 +| a4 = d4 = dpp->weight_B << 17 +| a5 = d5 = eptr +| a6 = d6 = 1024 << 17 +| a7 = d7 = -1024 << 17 +| macsr = 0x20 acc1 = 0x00 0000 80 +|------------------------------------------------------------------------------ + +term_minus_3: + move.l -4(%a1), %d0 | d0 = bptr [-1] + beq .L301 + move.l %acc1, %acc0 + asl.l #4, %d0 | acc0 = acc1 + ((d0 << 4) * weight_A) + mac.l %d0, %d3, %acc0 + move.l (%a1), %d1 + beq .L320 + eor.l %d1, %d0 | else compare signs + bge .L319 | if same, add delta to weight + sub.l %a3, %d3 | else subtract delta from weight + cmp.l %d7, %d3 | check for negative clip limit + bge .L320 + move.l %d7, %d3 + bra .L320 + +.L319: add.l %a3, %d3 | add delta to weight + cmp.l %d6, %d3 | check for positive clip limit + ble .L320 + move.l %d6, %d3 + +.L320: move.l %acc0, %d2 | d2 = rounded product + add.l %d1, %d2 | add applied weight to bptr [0], store + move.l %d2, (%a1)+ + +.L330: move.l -12(%a1), %d0 | d0 = bptr [-2] + beq .L302 + move.l %acc1, %acc0 + asl.l #4, %d0 | acc0 = acc1 + ((d0 << 4) * weight_B) + mac.l %d0, %d4, %acc0 + move.l (%a1), %d1 + beq .L318 + eor.l %d1, %d0 | else compare signs + bge .L322 | if same, add delta to weight + sub.l %a3, %d4 | else subtract delta from weight + cmp.l %d7, %d4 | check for negative clip limit + bge .L318 + move.l %d7, %d4 + bra .L318 + +.L322: add.l %a3, %d4 | add delta to weight + cmp.l %d6, %d4 | check for positive clip limit + ble .L318 + move.l %d6, %d4 + +.L318: move.l %acc0, %d2 | d2 = rounded product + add.l %d1, %d2 | add applied weight to bptr [1], store + move.l %d2, (%a1)+ + +.L331: cmp.l %a1, %d5 | bptr, eptr + jbhi term_minus_3 + move.l -4(%a1), 8(%a2) | dpp->samples_A [0] = bptr [-1] + move.l -8(%a1), 40(%a2) | dpp->samples_B [0] = bptr [-2] + jbra finish_up + +.L301: addq.l #4, %a1 + bra .L330 + +.L302: addq.l #4, %a1 + bra .L331 + +| finish and return + +finish_up: + moveq.l #17, %d0 + asr.l %d0, %d3 + asr.l %d0, %d4 + move.w %d3, 4(%a2) | weight_A, dpp->weight_A + move.w %d4, 6(%a2) | weight_B, dpp->weight_B + + clr.l %d0 | clear up EMAC + move.l %d0, %acc0 + move.l %d0, %acc1 + +return_only: + movem.l (%sp), %d2-%d7/%a2-%a6 + lea (44,%sp), %sp + rts diff --git a/src/wavpack/float.c b/src/wavpack/float.c new file mode 100644 index 00000000..4b9b44ee --- /dev/null +++ b/src/wavpack/float.c @@ -0,0 +1,50 @@ +//////////////////////////////////////////////////////////////////////////// +// **** WAVPACK **** // +// Hybrid Lossless Wavefile Compressor // +// Copyright (c) 1998 - 2006 Conifer Software. // +// All Rights Reserved. // +// Distributed under the BSD Software License (see license.txt) // +//////////////////////////////////////////////////////////////////////////// + +// float.c + +#include "wavpack.h" + +int read_float_info (WavpackStream *wps, WavpackMetadata *wpmd) +{ + int bytecnt = wpmd->byte_length; + char *byteptr = wpmd->data; + + if (bytecnt != 4) + return FALSE; + + wps->float_flags = *byteptr++; + wps->float_shift = *byteptr++; + wps->float_max_exp = *byteptr++; + wps->float_norm_exp = *byteptr; + return TRUE; +} + +void float_values (WavpackStream *wps, int32_t *values, int32_t num_values) +{ + int shift = wps->float_max_exp - wps->float_norm_exp + wps->float_shift; + + if (shift > 32) + shift = 32; + else if (shift < -32) + shift = -32; + + while (num_values--) { + if (shift > 0) + *values <<= shift; + else if (shift < 0) + *values >>= -shift; + + if (*values > 8388607L) + *values = 8388607L; + else if (*values < -8388608L) + *values = -8388608L; + + values++; + } +} diff --git a/src/wavpack/license.txt b/src/wavpack/license.txt new file mode 100644 index 00000000..98f6e6b1 --- /dev/null +++ b/src/wavpack/license.txt @@ -0,0 +1,25 @@ + Copyright (c) 1998 - 2006 Conifer Software + All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Conifer Software nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/wavpack/metadata.c b/src/wavpack/metadata.c new file mode 100644 index 00000000..578b17f9 --- /dev/null +++ b/src/wavpack/metadata.c @@ -0,0 +1,105 @@ +//////////////////////////////////////////////////////////////////////////// +// **** WAVPACK **** // +// Hybrid Lossless Wavefile Compressor // +// Copyright (c) 1998 - 2006 Conifer Software. // +// All Rights Reserved. // +// Distributed under the BSD Software License (see license.txt) // +//////////////////////////////////////////////////////////////////////////// + +// metadata.c + +// This module handles the metadata structure introduced in WavPack 4.0 + +#include "wavpack.h" + +int read_metadata_buff (WavpackContext *wpc, WavpackMetadata *wpmd) +{ + uchar tchar; + + if (!wpc->infile (&wpmd->id, 1) || !wpc->infile (&tchar, 1)) + return FALSE; + + wpmd->byte_length = tchar << 1; + + if (wpmd->id & ID_LARGE) { + wpmd->id &= ~ID_LARGE; + + if (!wpc->infile (&tchar, 1)) + return FALSE; + + wpmd->byte_length += (int32_t) tchar << 9; + + if (!wpc->infile (&tchar, 1)) + return FALSE; + + wpmd->byte_length += (int32_t) tchar << 17; + } + + if (wpmd->id & ID_ODD_SIZE) { + wpmd->id &= ~ID_ODD_SIZE; + wpmd->byte_length--; + } + + if (wpmd->byte_length && wpmd->byte_length <= sizeof (wpc->read_buffer)) { + uint32_t bytes_to_read = wpmd->byte_length + (wpmd->byte_length & 1); + + if (wpc->infile (wpc->read_buffer, bytes_to_read) != (int32_t) bytes_to_read) { + wpmd->data = NULL; + return FALSE; + } + + wpmd->data = wpc->read_buffer; + } + else + wpmd->data = NULL; + + return TRUE; +} + +int process_metadata (WavpackContext *wpc, WavpackMetadata *wpmd) +{ + WavpackStream *wps = &wpc->stream; + + switch (wpmd->id) { + case ID_DUMMY: + return TRUE; + + case ID_DECORR_TERMS: + return read_decorr_terms (wps, wpmd); + + case ID_DECORR_WEIGHTS: + return read_decorr_weights (wps, wpmd); + + case ID_DECORR_SAMPLES: + return read_decorr_samples (wps, wpmd); + + case ID_ENTROPY_VARS: + return read_entropy_vars (wps, wpmd); + + case ID_HYBRID_PROFILE: + return read_hybrid_profile (wps, wpmd); + + case ID_FLOAT_INFO: + return read_float_info (wps, wpmd); + + case ID_INT32_INFO: + return read_int32_info (wps, wpmd); + + case ID_CHANNEL_INFO: + return read_channel_info (wpc, wpmd); + + case ID_CONFIG_BLOCK: + return read_config_info (wpc, wpmd); + + case ID_WV_BITSTREAM: + return init_wv_bitstream (wpc, wpmd); + + case ID_SHAPING_WEIGHTS: + case ID_WVC_BITSTREAM: + case ID_WVX_BITSTREAM: + return TRUE; + + default: + return (wpmd->id & ID_OPTIONAL_DATA) ? TRUE : FALSE; + } +} diff --git a/src/wavpack/readme.txt b/src/wavpack/readme.txt new file mode 100644 index 00000000..4ccbdf42 --- /dev/null +++ b/src/wavpack/readme.txt @@ -0,0 +1,68 @@ +//////////////////////////////////////////////////////////////////////////// +// **** WAVPACK **** // +// Hybrid Lossless Wavefile Compressor // +// Copyright (c) 1998 - 2006 Conifer Software. // +// All Rights Reserved. // +// Distributed under the BSD Software License (see license.txt) // +//////////////////////////////////////////////////////////////////////////// + +This package contains a tiny version of the WavPack 4.40 decoder that might +be used in a "resource limited" CPU environment or form the basis for a +hardware decoding implementation. It is packaged with a demo command-line +program that accepts a WavPack audio file on stdin and outputs a RIFF wav +file to stdout. The program is standard C, and a win32 executable is +included which was compiled under MS Visual C++ 6.0 using this command: + +cl /O1 /DWIN32 wvfilter.c wputils.c unpack.c float.c metadata.c words.c bits.c + +WavPack data is read with a stream reading callback. No direct seeking is +provided for, but it is possible to start decoding anywhere in a WavPack +stream. In this case, WavPack will be able to provide the sample-accurate +position when it synchs with the data and begins decoding. The WIN32 macro +is used for Windows to force the stdin and stdout streams to be binary mode. + +Compared to the previous version, this library has been optimized somewhat +for improved performance in exchange for slightly larger code size. The +library also now includes hand-optimized assembly language versions of the +decorrelation functions for both the ColdFire (w/EMAC) and ARM processors. + +For demonstration purposes this uses a single static copy of the +WavpackContext structure, so obviously it cannot be used for more than one +file at a time. Also, this decoder will not handle "correction" files, plays +only the first two channels of multi-channel files, and is limited in +resolution in some large integer or floating point files (but always +provides at least 24 bits of resolution). It also will not accept WavPack +files from before version 4.0. + +The previous version of this library would handle float files by returning +32-bit floating-point data (even though no floating point math was used). +Because this library would normally be used for simply playing WavPack +files where lossless performance (beyond 24-bits) is not relevant, I have +changed this behavior. Now, these files will generate clipped 24-bit data. +The MODE_FLOAT flag will still be returned by WavpackGetMode(), but the +BitsPerSample and BytesPerSample queries will be 24 and 3, respectfully. +What this means is that an application that can handle 24-bit data will +now be able to handle floating point data (assuming that the MODE_FLOAT +flag is ignored). + +To make this code viable on the greatest number of hardware platforms, the +following are true: + + speed is about 5x realtime on an AMD K6 300 MHz + ("high" mode 16/44 stereo; normal mode is about twice that fast) + + no floating-point math required; just 32b * 32b = 32b int multiply + + large data areas are static and less than 4K total + executable code and tables are less than 40K + no malloc / free usage + +To maintain compatibility on various platforms, the following conventions +are used: + + a "char" must be exactly 8-bits + a "short" must be exactly 16-bits + an "int" must be at least 16-bits, but may be larger + the "long" type is not used to avoid problems with 64-bit compilers + +Questions or comments should be directed to david@wavpack.com diff --git a/src/wavpack/unpack.c b/src/wavpack/unpack.c new file mode 100644 index 00000000..2bed5a0c --- /dev/null +++ b/src/wavpack/unpack.c @@ -0,0 +1,785 @@ +//////////////////////////////////////////////////////////////////////////// +// **** WAVPACK **** // +// Hybrid Lossless Wavefile Compressor // +// Copyright (c) 1998 - 2006 Conifer Software. // +// All Rights Reserved. // +// Distributed under the BSD Software License (see license.txt) // +//////////////////////////////////////////////////////////////////////////// + +// unpack.c + +// This module actually handles the decompression of the audio data, except +// for the entropy decoding which is handled by the words.c module. For +// maximum efficiency, the conversion is isolated to tight loops that handle +// an entire buffer. + +#include "wavpack.h" + +#include +#include + +#define LOSSY_MUTE + +///////////////////////////// executable code //////////////////////////////// + +// This function initializes everything required to unpack a WavPack block +// and must be called before unpack_samples() is called to obtain audio data. +// It is assumed that the WavpackHeader has been read into the wps->wphdr +// (in the current WavpackStream). This is where all the metadata blocks are +// scanned up to the one containing the audio bitstream. + +int unpack_init (WavpackContext *wpc) +{ + WavpackStream *wps = &wpc->stream; + WavpackMetadata wpmd; + + if (wps->wphdr.block_samples && wps->wphdr.block_index != (uint32_t) -1) + wps->sample_index = wps->wphdr.block_index; + + wps->mute_error = FALSE; + wps->crc = 0xffffffff; + CLEAR (wps->wvbits); + CLEAR (wps->decorr_passes); + CLEAR (wps->w); + + while (read_metadata_buff (wpc, &wpmd)) { + if (!process_metadata (wpc, &wpmd)) { + strcpy (wpc->error_message, "invalid metadata!"); + return FALSE; + } + + if (wpmd.id == ID_WV_BITSTREAM) + break; + } + + if (wps->wphdr.block_samples && !bs_is_open (&wps->wvbits)) { + strcpy (wpc->error_message, "invalid WavPack file!"); + return FALSE; + } + + if (wps->wphdr.block_samples) { + if ((wps->wphdr.flags & INT32_DATA) && wps->int32_sent_bits) + wpc->lossy_blocks = TRUE; + + if ((wps->wphdr.flags & FLOAT_DATA) && + wps->float_flags & (FLOAT_EXCEPTIONS | FLOAT_ZEROS_SENT | FLOAT_SHIFT_SENT | FLOAT_SHIFT_SAME)) + wpc->lossy_blocks = TRUE; + } + + return TRUE; +} + +// This function initialzes the main bitstream for audio samples, which must +// be in the "wv" file. + +int init_wv_bitstream (WavpackContext *wpc, WavpackMetadata *wpmd) +{ + WavpackStream *wps = &wpc->stream; + + if (wpmd->data) + bs_open_read (&wps->wvbits, wpmd->data, (unsigned char *) wpmd->data + wpmd->byte_length, NULL, 0); + else if (wpmd->byte_length) + bs_open_read (&wps->wvbits, wpc->read_buffer, wpc->read_buffer + sizeof (wpc->read_buffer), + wpc->infile, wpmd->byte_length + (wpmd->byte_length & 1)); + + return TRUE; +} + +// Read decorrelation terms from specified metadata block into the +// decorr_passes array. The terms range from -3 to 8, plus 17 & 18; +// other values are reserved and generate errors for now. The delta +// ranges from 0 to 7 with all values valid. Note that the terms are +// stored in the opposite order in the decorr_passes array compared +// to packing. + +int read_decorr_terms (WavpackStream *wps, WavpackMetadata *wpmd) +{ + int termcnt = wpmd->byte_length; + uchar *byteptr = wpmd->data; + struct decorr_pass *dpp; + + if (termcnt > MAX_NTERMS) + return FALSE; + + wps->num_terms = termcnt; + + for (dpp = wps->decorr_passes + termcnt - 1; termcnt--; dpp--) { + dpp->term = (int)(*byteptr & 0x1f) - 5; + dpp->delta = (*byteptr++ >> 5) & 0x7; + + if (!dpp->term || dpp->term < -3 || (dpp->term > MAX_TERM && dpp->term < 17) || dpp->term > 18) + return FALSE; + } + + return TRUE; +} + +// Read decorrelation weights from specified metadata block into the +// decorr_passes array. The weights range +/-1024, but are rounded and +// truncated to fit in signed chars for metadata storage. Weights are +// separate for the two channels and are specified from the "last" term +// (first during encode). Unspecified weights are set to zero. + +int read_decorr_weights (WavpackStream *wps, WavpackMetadata *wpmd) +{ + int termcnt = wpmd->byte_length, tcount; + signed char *byteptr = wpmd->data; + struct decorr_pass *dpp; + + if (!(wps->wphdr.flags & MONO_DATA)) + termcnt /= 2; + + if (termcnt > wps->num_terms) + return FALSE; + + for (tcount = wps->num_terms, dpp = wps->decorr_passes; tcount--; dpp++) + dpp->weight_A = dpp->weight_B = 0; + + while (--dpp >= wps->decorr_passes && termcnt--) { + dpp->weight_A = restore_weight (*byteptr++); + + if (!(wps->wphdr.flags & MONO_DATA)) + dpp->weight_B = restore_weight (*byteptr++); + } + + return TRUE; +} + +// Read decorrelation samples from specified metadata block into the +// decorr_passes array. The samples are signed 32-bit values, but are +// converted to signed log2 values for storage in metadata. Values are +// stored for both channels and are specified from the "last" term +// (first during encode) with unspecified samples set to zero. The +// number of samples stored varies with the actual term value, so +// those must obviously come first in the metadata. + +int read_decorr_samples (WavpackStream *wps, WavpackMetadata *wpmd) +{ + uchar *byteptr = wpmd->data; + uchar *endptr = byteptr + wpmd->byte_length; + struct decorr_pass *dpp; + int tcount; + + for (tcount = wps->num_terms, dpp = wps->decorr_passes; tcount--; dpp++) { + CLEAR (dpp->samples_A); + CLEAR (dpp->samples_B); + } + + if (wps->wphdr.version == 0x402 && (wps->wphdr.flags & HYBRID_FLAG)) { + byteptr += 2; + + if (!(wps->wphdr.flags & MONO_DATA)) + byteptr += 2; + } + + while (dpp-- > wps->decorr_passes && byteptr < endptr) + if (dpp->term > MAX_TERM) { + dpp->samples_A [0] = exp2s ((short)(byteptr [0] + (byteptr [1] << 8))); + dpp->samples_A [1] = exp2s ((short)(byteptr [2] + (byteptr [3] << 8))); + byteptr += 4; + + if (!(wps->wphdr.flags & MONO_DATA)) { + dpp->samples_B [0] = exp2s ((short)(byteptr [0] + (byteptr [1] << 8))); + dpp->samples_B [1] = exp2s ((short)(byteptr [2] + (byteptr [3] << 8))); + byteptr += 4; + } + } + else if (dpp->term < 0) { + dpp->samples_A [0] = exp2s ((short)(byteptr [0] + (byteptr [1] << 8))); + dpp->samples_B [0] = exp2s ((short)(byteptr [2] + (byteptr [3] << 8))); + byteptr += 4; + } + else { + int m = 0, cnt = dpp->term; + + while (cnt--) { + dpp->samples_A [m] = exp2s ((short)(byteptr [0] + (byteptr [1] << 8))); + byteptr += 2; + + if (!(wps->wphdr.flags & MONO_DATA)) { + dpp->samples_B [m] = exp2s ((short)(byteptr [0] + (byteptr [1] << 8))); + byteptr += 2; + } + + m++; + } + } + + return byteptr == endptr; +} + +// Read the int32 data from the specified metadata into the specified stream. +// This data is used for integer data that has more than 24 bits of magnitude +// or, in some cases, used to eliminate redundant bits from any audio stream. + +int read_int32_info (WavpackStream *wps, WavpackMetadata *wpmd) +{ + int bytecnt = wpmd->byte_length; + char *byteptr = wpmd->data; + + if (bytecnt != 4) + return FALSE; + + wps->int32_sent_bits = *byteptr++; + wps->int32_zeros = *byteptr++; + wps->int32_ones = *byteptr++; + wps->int32_dups = *byteptr; + return TRUE; +} + +// Read multichannel information from metadata. The first byte is the total +// number of channels and the following bytes represent the channel_mask +// as described for Microsoft WAVEFORMATEX. + +int read_channel_info (WavpackContext *wpc, WavpackMetadata *wpmd) +{ + int bytecnt = wpmd->byte_length, shift = 0; + char *byteptr = wpmd->data; + uint32_t mask = 0; + + if (!bytecnt || bytecnt > 5) + return FALSE; + + wpc->config.num_channels = *byteptr++; + + while (--bytecnt) { + mask |= (uint32_t) *byteptr++ << shift; + shift += 8; + } + + wpc->config.channel_mask = mask; + return TRUE; +} + +// Read configuration information from metadata. + +int read_config_info (WavpackContext *wpc, WavpackMetadata *wpmd) +{ + int bytecnt = wpmd->byte_length; + uchar *byteptr = wpmd->data; + + if (bytecnt >= 3) { + wpc->config.flags &= 0xff; + wpc->config.flags |= (int32_t) *byteptr++ << 8; + wpc->config.flags |= (int32_t) *byteptr++ << 16; + wpc->config.flags |= (int32_t) *byteptr << 24; + } + + return TRUE; +} + +// This monster actually unpacks the WavPack bitstream(s) into the specified +// buffer as 32-bit integers or floats (depending on orignal data). Lossy +// samples will be clipped to their original limits (i.e. 8-bit samples are +// clipped to -128/+127) but are still returned in int32_ts. It is up to the +// caller to potentially reformat this for the final output including any +// multichannel distribution, block alignment or endian compensation. The +// function unpack_init() must have been called and the entire WavPack block +// must still be visible (although wps->blockbuff will not be accessed again). +// For maximum clarity, the function is broken up into segments that handle +// various modes. This makes for a few extra infrequent flag checks, but +// makes the code easier to follow because the nesting does not become so +// deep. For maximum efficiency, the conversion is isolated to tight loops +// that handle an entire buffer. The function returns the total number of +// samples unpacked, which can be less than the number requested if an error +// occurs or the end of the block is reached. + +#if defined(CPU_COLDFIRE) && !defined(SIMULATOR) +extern void decorr_stereo_pass_cont_mcf5249 (struct decorr_pass *dpp, int32_t *buffer, int32_t sample_count); +#elif defined(CPU_ARM) && !defined(SIMULATOR) +extern void decorr_stereo_pass_cont_arm (struct decorr_pass *dpp, int32_t *buffer, int32_t sample_count); +extern void decorr_stereo_pass_cont_arml (struct decorr_pass *dpp, int32_t *buffer, int32_t sample_count); +#else +static void decorr_stereo_pass_cont (struct decorr_pass *dpp, int32_t *buffer, int32_t sample_count); +#endif + +static void decorr_mono_pass (struct decorr_pass *dpp, int32_t *buffer, int32_t sample_count); +static void decorr_stereo_pass (struct decorr_pass *dpp, int32_t *buffer, int32_t sample_count); +static void fixup_samples (WavpackStream *wps, int32_t *buffer, uint32_t sample_count); + +int32_t unpack_samples (WavpackContext *wpc, int32_t *buffer, uint32_t sample_count) +{ + WavpackStream *wps = &wpc->stream; + uint32_t flags = wps->wphdr.flags, crc = wps->crc, i; + int32_t mute_limit = (1L << ((flags & MAG_MASK) >> MAG_LSB)) + 2; + struct decorr_pass *dpp; + int32_t *bptr, *eptr; + int tcount; + + if (wps->sample_index + sample_count > wps->wphdr.block_index + wps->wphdr.block_samples) + sample_count = wps->wphdr.block_index + wps->wphdr.block_samples - wps->sample_index; + + if (wps->mute_error) { + memset (buffer, 0, sample_count * (flags & MONO_FLAG ? 4 : 8)); + wps->sample_index += sample_count; + return sample_count; + } + + if (flags & HYBRID_FLAG) + mute_limit *= 2; + + ///////////////////// handle version 4 mono data ///////////////////////// + + if (flags & MONO_DATA) { + eptr = buffer + sample_count; + i = get_words (buffer, sample_count, flags, &wps->w, &wps->wvbits); + + for (tcount = wps->num_terms, dpp = wps->decorr_passes; tcount--; dpp++) + decorr_mono_pass (dpp, buffer, sample_count); + + for (bptr = buffer; bptr < eptr; ++bptr) { + if (labs (bptr [0]) > mute_limit) { + i = bptr - buffer; + break; + } + + crc = crc * 3 + bptr [0]; + } + } + + //////////////////// handle version 4 stereo data //////////////////////// + + else { + eptr = buffer + (sample_count * 2); + i = get_words (buffer, sample_count, flags, &wps->w, &wps->wvbits); + + if (sample_count < 16) + for (tcount = wps->num_terms, dpp = wps->decorr_passes; tcount--; dpp++) + decorr_stereo_pass (dpp, buffer, sample_count); + else + for (tcount = wps->num_terms, dpp = wps->decorr_passes; tcount--; dpp++) { + decorr_stereo_pass (dpp, buffer, 8); +#if defined(CPU_COLDFIRE) && !defined(SIMULATOR) + decorr_stereo_pass_cont_mcf5249 (dpp, buffer + 16, sample_count - 8); +#elif defined(CPU_ARM) && !defined(SIMULATOR) + if (((flags & MAG_MASK) >> MAG_LSB) > 15) + decorr_stereo_pass_cont_arml (dpp, buffer + 16, sample_count - 8); + else + decorr_stereo_pass_cont_arm (dpp, buffer + 16, sample_count - 8); +#else + decorr_stereo_pass_cont (dpp, buffer + 16, sample_count - 8); +#endif + } + + if (flags & JOINT_STEREO) + for (bptr = buffer; bptr < eptr; bptr += 2) { + bptr [0] += (bptr [1] -= (bptr [0] >> 1)); + + if (labs (bptr [0]) > mute_limit || labs (bptr [1]) > mute_limit) { + i = (bptr - buffer) / 2; + break; + } + + crc = (crc * 3 + bptr [0]) * 3 + bptr [1]; + } + else + for (bptr = buffer; bptr < eptr; bptr += 2) { + if (labs (bptr [0]) > mute_limit || labs (bptr [1]) > mute_limit) { + i = (bptr - buffer) / 2; + break; + } + + crc = (crc * 3 + bptr [0]) * 3 + bptr [1]; + } + } + + if (i != sample_count) { + memset (buffer, 0, sample_count * (flags & MONO_FLAG ? 4 : 8)); + wps->mute_error = TRUE; + i = sample_count; + } + + fixup_samples (wps, buffer, i); + + if (flags & FALSE_STEREO) { + int32_t *dptr = buffer + i * 2; + int32_t *sptr = buffer + i; + int32_t c = i; + + while (c--) { + *--dptr = *--sptr; + *--dptr = *sptr; + } + } + + wps->sample_index += i; + wps->crc = crc; + + return i; +} + +static void decorr_stereo_pass (struct decorr_pass *dpp, int32_t *buffer, int32_t sample_count) +{ + int32_t delta = dpp->delta, weight_A = dpp->weight_A, weight_B = dpp->weight_B; + int32_t *bptr, *eptr = buffer + (sample_count * 2), sam_A, sam_B; + int m, k; + + switch (dpp->term) { + + case 17: + for (bptr = buffer; bptr < eptr; bptr += 2) { + sam_A = 2 * dpp->samples_A [0] - dpp->samples_A [1]; + dpp->samples_A [1] = dpp->samples_A [0]; + dpp->samples_A [0] = apply_weight (weight_A, sam_A) + bptr [0]; + update_weight (weight_A, delta, sam_A, bptr [0]); + bptr [0] = dpp->samples_A [0]; + + sam_A = 2 * dpp->samples_B [0] - dpp->samples_B [1]; + dpp->samples_B [1] = dpp->samples_B [0]; + dpp->samples_B [0] = apply_weight (weight_B, sam_A) + bptr [1]; + update_weight (weight_B, delta, sam_A, bptr [1]); + bptr [1] = dpp->samples_B [0]; + } + + break; + + case 18: + for (bptr = buffer; bptr < eptr; bptr += 2) { + sam_A = (3 * dpp->samples_A [0] - dpp->samples_A [1]) >> 1; + dpp->samples_A [1] = dpp->samples_A [0]; + dpp->samples_A [0] = apply_weight (weight_A, sam_A) + bptr [0]; + update_weight (weight_A, delta, sam_A, bptr [0]); + bptr [0] = dpp->samples_A [0]; + + sam_A = (3 * dpp->samples_B [0] - dpp->samples_B [1]) >> 1; + dpp->samples_B [1] = dpp->samples_B [0]; + dpp->samples_B [0] = apply_weight (weight_B, sam_A) + bptr [1]; + update_weight (weight_B, delta, sam_A, bptr [1]); + bptr [1] = dpp->samples_B [0]; + } + + break; + + default: + for (m = 0, k = dpp->term & (MAX_TERM - 1), bptr = buffer; bptr < eptr; bptr += 2) { + sam_A = dpp->samples_A [m]; + dpp->samples_A [k] = apply_weight (weight_A, sam_A) + bptr [0]; + update_weight (weight_A, delta, sam_A, bptr [0]); + bptr [0] = dpp->samples_A [k]; + + sam_A = dpp->samples_B [m]; + dpp->samples_B [k] = apply_weight (weight_B, sam_A) + bptr [1]; + update_weight (weight_B, delta, sam_A, bptr [1]); + bptr [1] = dpp->samples_B [k]; + + m = (m + 1) & (MAX_TERM - 1); + k = (k + 1) & (MAX_TERM - 1); + } + + if (m) { + int32_t temp_samples [MAX_TERM]; + + memcpy (temp_samples, dpp->samples_A, sizeof (dpp->samples_A)); + + for (k = 0; k < MAX_TERM; k++, m++) + dpp->samples_A [k] = temp_samples [m & (MAX_TERM - 1)]; + + memcpy (temp_samples, dpp->samples_B, sizeof (dpp->samples_B)); + + for (k = 0; k < MAX_TERM; k++, m++) + dpp->samples_B [k] = temp_samples [m & (MAX_TERM - 1)]; + } + + break; + + case -1: + for (bptr = buffer; bptr < eptr; bptr += 2) { + sam_A = bptr [0] + apply_weight (weight_A, dpp->samples_A [0]); + update_weight_clip (weight_A, delta, dpp->samples_A [0], bptr [0]); + bptr [0] = sam_A; + dpp->samples_A [0] = bptr [1] + apply_weight (weight_B, sam_A); + update_weight_clip (weight_B, delta, sam_A, bptr [1]); + bptr [1] = dpp->samples_A [0]; + } + + break; + + case -2: + for (bptr = buffer; bptr < eptr; bptr += 2) { + sam_B = bptr [1] + apply_weight (weight_B, dpp->samples_B [0]); + update_weight_clip (weight_B, delta, dpp->samples_B [0], bptr [1]); + bptr [1] = sam_B; + dpp->samples_B [0] = bptr [0] + apply_weight (weight_A, sam_B); + update_weight_clip (weight_A, delta, sam_B, bptr [0]); + bptr [0] = dpp->samples_B [0]; + } + + break; + + case -3: + for (bptr = buffer; bptr < eptr; bptr += 2) { + sam_A = bptr [0] + apply_weight (weight_A, dpp->samples_A [0]); + update_weight_clip (weight_A, delta, dpp->samples_A [0], bptr [0]); + sam_B = bptr [1] + apply_weight (weight_B, dpp->samples_B [0]); + update_weight_clip (weight_B, delta, dpp->samples_B [0], bptr [1]); + bptr [0] = dpp->samples_B [0] = sam_A; + bptr [1] = dpp->samples_A [0] = sam_B; + } + + break; + } + + dpp->weight_A = weight_A; + dpp->weight_B = weight_B; +} + +#if (!defined(CPU_COLDFIRE) && !defined(CPU_ARM)) || defined(SIMULATOR) + +static void decorr_stereo_pass_cont (struct decorr_pass *dpp, int32_t *buffer, int32_t sample_count) +{ + int32_t delta = dpp->delta, weight_A = dpp->weight_A, weight_B = dpp->weight_B; + int32_t *bptr, *tptr, *eptr = buffer + (sample_count * 2), sam_A, sam_B; + int k, i; + + switch (dpp->term) { + + case 17: + for (bptr = buffer; bptr < eptr; bptr += 2) { + sam_A = 2 * bptr [-2] - bptr [-4]; + bptr [0] = apply_weight (weight_A, sam_A) + (sam_B = bptr [0]); + update_weight (weight_A, delta, sam_A, sam_B); + + sam_A = 2 * bptr [-1] - bptr [-3]; + bptr [1] = apply_weight (weight_B, sam_A) + (sam_B = bptr [1]); + update_weight (weight_B, delta, sam_A, sam_B); + } + + dpp->samples_B [0] = bptr [-1]; + dpp->samples_A [0] = bptr [-2]; + dpp->samples_B [1] = bptr [-3]; + dpp->samples_A [1] = bptr [-4]; + break; + + case 18: + for (bptr = buffer; bptr < eptr; bptr += 2) { + sam_A = (3 * bptr [-2] - bptr [-4]) >> 1; + bptr [0] = apply_weight (weight_A, sam_A) + (sam_B = bptr [0]); + update_weight (weight_A, delta, sam_A, sam_B); + + sam_A = (3 * bptr [-1] - bptr [-3]) >> 1; + bptr [1] = apply_weight (weight_B, sam_A) + (sam_B = bptr [1]); + update_weight (weight_B, delta, sam_A, sam_B); + } + + dpp->samples_B [0] = bptr [-1]; + dpp->samples_A [0] = bptr [-2]; + dpp->samples_B [1] = bptr [-3]; + dpp->samples_A [1] = bptr [-4]; + break; + + default: + for (bptr = buffer, tptr = buffer - (dpp->term * 2); bptr < eptr; bptr += 2, tptr += 2) { + bptr [0] = apply_weight (weight_A, tptr [0]) + (sam_A = bptr [0]); + update_weight (weight_A, delta, tptr [0], sam_A); + + bptr [1] = apply_weight (weight_B, tptr [1]) + (sam_A = bptr [1]); + update_weight (weight_B, delta, tptr [1], sam_A); + } + + for (k = dpp->term - 1, i = 8; i--; k--) { + dpp->samples_B [k & (MAX_TERM - 1)] = *--bptr; + dpp->samples_A [k & (MAX_TERM - 1)] = *--bptr; + } + + break; + + case -1: + for (bptr = buffer; bptr < eptr; bptr += 2) { + bptr [0] = apply_weight (weight_A, bptr [-1]) + (sam_A = bptr [0]); + update_weight_clip (weight_A, delta, bptr [-1], sam_A); + bptr [1] = apply_weight (weight_B, bptr [0]) + (sam_A = bptr [1]); + update_weight_clip (weight_B, delta, bptr [0], sam_A); + } + + dpp->samples_A [0] = bptr [-1]; + break; + + case -2: + for (bptr = buffer; bptr < eptr; bptr += 2) { + bptr [1] = apply_weight (weight_B, bptr [-2]) + (sam_A = bptr [1]); + update_weight_clip (weight_B, delta, bptr [-2], sam_A); + bptr [0] = apply_weight (weight_A, bptr [1]) + (sam_A = bptr [0]); + update_weight_clip (weight_A, delta, bptr [1], sam_A); + } + + dpp->samples_B [0] = bptr [-2]; + break; + + case -3: + for (bptr = buffer; bptr < eptr; bptr += 2) { + bptr [0] = apply_weight (weight_A, bptr [-1]) + (sam_A = bptr [0]); + update_weight_clip (weight_A, delta, bptr [-1], sam_A); + bptr [1] = apply_weight (weight_B, bptr [-2]) + (sam_A = bptr [1]); + update_weight_clip (weight_B, delta, bptr [-2], sam_A); + } + + dpp->samples_A [0] = bptr [-1]; + dpp->samples_B [0] = bptr [-2]; + break; + } + + dpp->weight_A = weight_A; + dpp->weight_B = weight_B; +} + +#endif + +static void decorr_mono_pass (struct decorr_pass *dpp, int32_t *buffer, int32_t sample_count) +{ + int32_t delta = dpp->delta, weight_A = dpp->weight_A; + int32_t *bptr, *eptr = buffer + sample_count, sam_A; + int m, k; + + switch (dpp->term) { + + case 17: + for (bptr = buffer; bptr < eptr; bptr++) { + sam_A = 2 * dpp->samples_A [0] - dpp->samples_A [1]; + dpp->samples_A [1] = dpp->samples_A [0]; + dpp->samples_A [0] = apply_weight (weight_A, sam_A) + bptr [0]; + update_weight (weight_A, delta, sam_A, bptr [0]); + bptr [0] = dpp->samples_A [0]; + } + + break; + + case 18: + for (bptr = buffer; bptr < eptr; bptr++) { + sam_A = (3 * dpp->samples_A [0] - dpp->samples_A [1]) >> 1; + dpp->samples_A [1] = dpp->samples_A [0]; + dpp->samples_A [0] = apply_weight (weight_A, sam_A) + bptr [0]; + update_weight (weight_A, delta, sam_A, bptr [0]); + bptr [0] = dpp->samples_A [0]; + } + + break; + + default: + for (m = 0, k = dpp->term & (MAX_TERM - 1), bptr = buffer; bptr < eptr; bptr++) { + sam_A = dpp->samples_A [m]; + dpp->samples_A [k] = apply_weight (weight_A, sam_A) + bptr [0]; + update_weight (weight_A, delta, sam_A, bptr [0]); + bptr [0] = dpp->samples_A [k]; + m = (m + 1) & (MAX_TERM - 1); + k = (k + 1) & (MAX_TERM - 1); + } + + if (m) { + int32_t temp_samples [MAX_TERM]; + + memcpy (temp_samples, dpp->samples_A, sizeof (dpp->samples_A)); + + for (k = 0; k < MAX_TERM; k++, m++) + dpp->samples_A [k] = temp_samples [m & (MAX_TERM - 1)]; + } + + break; + } + + dpp->weight_A = weight_A; +} + + +// This is a helper function for unpack_samples() that applies several final +// operations. First, if the data is 32-bit float data, then that conversion +// is done in the float.c module (whether lossy or lossless) and we return. +// Otherwise, if the extended integer data applies, then that operation is +// executed first. If the unpacked data is lossy (and not corrected) then +// it is clipped and shifted in a single operation. Otherwise, if it's +// lossless then the last step is to apply the final shift (if any). + +static void fixup_samples (WavpackStream *wps, int32_t *buffer, uint32_t sample_count) +{ + uint32_t flags = wps->wphdr.flags; + int shift = (flags & SHIFT_MASK) >> SHIFT_LSB; + + if (flags & FLOAT_DATA) { + float_values (wps, buffer, (flags & MONO_FLAG) ? sample_count : sample_count * 2); + return; + } + + if (flags & INT32_DATA) { + uint32_t count = (flags & MONO_FLAG) ? sample_count : sample_count * 2; + int sent_bits = wps->int32_sent_bits, zeros = wps->int32_zeros; + int ones = wps->int32_ones, dups = wps->int32_dups; + int32_t *dptr = buffer; + + if (!(flags & HYBRID_FLAG) && !sent_bits && (zeros + ones + dups)) + while (count--) { + if (zeros) + *dptr <<= zeros; + else if (ones) + *dptr = ((*dptr + 1) << ones) - 1; + else if (dups) + *dptr = ((*dptr + (*dptr & 1)) << dups) - (*dptr & 1); + + dptr++; + } + else + shift += zeros + sent_bits + ones + dups; + } + + if (flags & HYBRID_FLAG) { + int32_t min_value, max_value, min_shifted, max_shifted; + + switch (flags & BYTES_STORED) { + case 0: + min_shifted = (min_value = -128 >> shift) << shift; + max_shifted = (max_value = 127 >> shift) << shift; + break; + + case 1: + min_shifted = (min_value = -32768 >> shift) << shift; + max_shifted = (max_value = 32767 >> shift) << shift; + break; + + case 2: + min_shifted = (min_value = -8388608 >> shift) << shift; + max_shifted = (max_value = 8388607 >> shift) << shift; + break; + + case 3: + default: + min_shifted = (min_value = (int32_t) 0x80000000 >> shift) << shift; + max_shifted = (max_value = (int32_t) 0x7FFFFFFF >> shift) << shift; + break; + } + + if (!(flags & MONO_FLAG)) + sample_count *= 2; + + while (sample_count--) { + if (*buffer < min_value) + *buffer++ = min_shifted; + else if (*buffer > max_value) + *buffer++ = max_shifted; + else + *buffer++ <<= shift; + } + } + else if (shift) { + if (!(flags & MONO_FLAG)) + sample_count *= 2; + + while (sample_count--) + *buffer++ <<= shift; + } +} + +// This function checks the crc value(s) for an unpacked block, returning the +// number of actual crc errors detected for the block. The block must be +// completely unpacked before this test is valid. For losslessly unpacked +// blocks of float or extended integer data the extended crc is also checked. +// Note that WavPack's crc is not a CCITT approved polynomial algorithm, but +// is a much simpler method that is virtually as robust for real world data. + +int check_crc_error (WavpackContext *wpc) +{ + WavpackStream *wps = &wpc->stream; + int result = 0; + + if (wps->crc != wps->wphdr.crc) + ++result; + + return result; +} diff --git a/src/wavpack/wavpack.h b/src/wavpack/wavpack.h new file mode 100644 index 00000000..7c260586 --- /dev/null +++ b/src/wavpack/wavpack.h @@ -0,0 +1,384 @@ +//////////////////////////////////////////////////////////////////////////// +// **** WAVPACK **** // +// Hybrid Lossless Wavefile Compressor // +// Copyright (c) 1998 - 2004 Conifer Software. // +// All Rights Reserved. // +// Distributed under the BSD Software License (see license.txt) // +//////////////////////////////////////////////////////////////////////////// + +// wavpack.h + +#include + +// This header file contains all the definitions required by WavPack. + +#ifdef __BORLANDC__ +typedef unsigned long uint32_t; +typedef long int32_t; +#elif defined(_WIN32) && !defined(__MINGW32__) +#include +typedef unsigned __int64 uint64_t; +typedef unsigned __int32 uint32_t; +typedef __int64 int64_t; +typedef __int32 int32_t; +#else +#include +#endif + +typedef unsigned char uchar; + +#if !defined(__GNUC__) || defined(WIN32) +typedef unsigned short ushort; +typedef unsigned int uint; +#endif + +#include + +#define FALSE 0 +#define TRUE 1 + +////////////////////////////// WavPack Header ///////////////////////////////// + +// Note that this is the ONLY structure that is written to (or read from) +// WavPack 4.0 files, and is the preamble to every block in both the .wv +// and .wvc files. + +typedef struct { + char ckID [4]; + uint32_t ckSize; + short version; + uchar track_no, index_no; + uint32_t total_samples, block_index, block_samples, flags, crc; +} WavpackHeader; + +#define WavpackHeaderFormat "4LS2LLLLL" + +// or-values for "flags" + +#define BYTES_STORED 3 // 1-4 bytes/sample +#define MONO_FLAG 4 // not stereo +#define HYBRID_FLAG 8 // hybrid mode +#define JOINT_STEREO 0x10 // joint stereo +#define CROSS_DECORR 0x20 // no-delay cross decorrelation +#define HYBRID_SHAPE 0x40 // noise shape (hybrid mode only) +#define FLOAT_DATA 0x80 // ieee 32-bit floating point data + +#define INT32_DATA 0x100 // special extended int handling +#define HYBRID_BITRATE 0x200 // bitrate noise (hybrid mode only) +#define HYBRID_BALANCE 0x400 // balance noise (hybrid stereo mode only) + +#define INITIAL_BLOCK 0x800 // initial block of multichannel segment +#define FINAL_BLOCK 0x1000 // final block of multichannel segment + +#define SHIFT_LSB 13 +#define SHIFT_MASK (0x1fL << SHIFT_LSB) + +#define MAG_LSB 18 +#define MAG_MASK (0x1fL << MAG_LSB) + +#define SRATE_LSB 23 +#define SRATE_MASK (0xfL << SRATE_LSB) + +#define FALSE_STEREO 0x40000000 // block is stereo, but data is mono + +#define IGNORED_FLAGS 0x18000000 // reserved, but ignore if encountered +#define NEW_SHAPING 0x20000000 // use IIR filter for negative shaping +#define UNKNOWN_FLAGS 0x80000000 // also reserved, but refuse decode if + // encountered + +#define MONO_DATA (MONO_FLAG | FALSE_STEREO) + +#define MIN_STREAM_VERS 0x402 // lowest stream version we'll decode +#define MAX_STREAM_VERS 0x410 // highest stream version we'll decode + +//////////////////////////// WavPack Metadata ///////////////////////////////// + +// This is an internal representation of metadata. + +typedef struct { + int32_t byte_length; + void *data; + uchar id; +} WavpackMetadata; + +#define ID_OPTIONAL_DATA 0x20 +#define ID_ODD_SIZE 0x40 +#define ID_LARGE 0x80 + +#define ID_DUMMY 0x0 +#define ID_ENCODER_INFO 0x1 +#define ID_DECORR_TERMS 0x2 +#define ID_DECORR_WEIGHTS 0x3 +#define ID_DECORR_SAMPLES 0x4 +#define ID_ENTROPY_VARS 0x5 +#define ID_HYBRID_PROFILE 0x6 +#define ID_SHAPING_WEIGHTS 0x7 +#define ID_FLOAT_INFO 0x8 +#define ID_INT32_INFO 0x9 +#define ID_WV_BITSTREAM 0xa +#define ID_WVC_BITSTREAM 0xb +#define ID_WVX_BITSTREAM 0xc +#define ID_CHANNEL_INFO 0xd + +#define ID_RIFF_HEADER (ID_OPTIONAL_DATA | 0x1) +#define ID_RIFF_TRAILER (ID_OPTIONAL_DATA | 0x2) +#define ID_REPLAY_GAIN (ID_OPTIONAL_DATA | 0x3) +#define ID_CUESHEET (ID_OPTIONAL_DATA | 0x4) +#define ID_CONFIG_BLOCK (ID_OPTIONAL_DATA | 0x5) +#define ID_MD5_CHECKSUM (ID_OPTIONAL_DATA | 0x6) + +///////////////////////// WavPack Configuration /////////////////////////////// + +// This internal structure is used during encode to provide configuration to +// the encoding engine and during decoding to provide fle information back to +// the higher level functions. Not all fields are used in both modes. + +typedef struct { + int bits_per_sample, bytes_per_sample; + int num_channels, float_norm_exp; + uint32_t flags, sample_rate, channel_mask; +} WavpackConfig; + +#define CONFIG_BYTES_STORED 3 // 1-4 bytes/sample +#define CONFIG_MONO_FLAG 4 // not stereo +#define CONFIG_HYBRID_FLAG 8 // hybrid mode +#define CONFIG_JOINT_STEREO 0x10 // joint stereo +#define CONFIG_CROSS_DECORR 0x20 // no-delay cross decorrelation +#define CONFIG_HYBRID_SHAPE 0x40 // noise shape (hybrid mode only) +#define CONFIG_FLOAT_DATA 0x80 // ieee 32-bit floating point data + +#define CONFIG_FAST_FLAG 0x200 // fast mode +#define CONFIG_HIGH_FLAG 0x800 // high quality mode +#define CONFIG_VERY_HIGH_FLAG 0x1000 // very high +#define CONFIG_BITRATE_KBPS 0x2000 // bitrate is kbps, not bits / sample +#define CONFIG_AUTO_SHAPING 0x4000 // automatic noise shaping +#define CONFIG_SHAPE_OVERRIDE 0x8000 // shaping mode specified +#define CONFIG_JOINT_OVERRIDE 0x10000 // joint-stereo mode specified +#define CONFIG_CREATE_EXE 0x40000 // create executable +#define CONFIG_CREATE_WVC 0x80000 // create correction file +#define CONFIG_OPTIMIZE_WVC 0x100000 // maximize bybrid compression +#define CONFIG_CALC_NOISE 0x800000 // calc noise in hybrid mode +#define CONFIG_LOSSY_MODE 0x1000000 // obsolete (for information) +#define CONFIG_EXTRA_MODE 0x2000000 // extra processing mode +#define CONFIG_SKIP_WVX 0x4000000 // no wvx stream w/ floats & big ints +#define CONFIG_MD5_CHECKSUM 0x8000000 // compute & store MD5 signature +#define CONFIG_OPTIMIZE_MONO 0x80000000 // optimize for mono streams posing as stereo + +//////////////////////////////// WavPack Stream /////////////////////////////// + +// This internal structure contains everything required to handle a WavPack +// "stream", which is defined as a stereo or mono stream of audio samples. For +// multichannel audio several of these would be required. Each stream contains +// pointers to hold a complete allocated block of WavPack data, although it's +// possible to decode WavPack blocks without buffering an entire block. + +typedef int32_t (*read_stream)(void *, int32_t); + +typedef struct bs { + uchar *buf, *end, *ptr; + void (*wrap)(struct bs *bs); + uint32_t file_bytes, sr; + int error, bc; + read_stream file; +} Bitstream; + +#define MAX_NTERMS 16 +#define MAX_TERM 8 + +struct decorr_pass { + short term, delta, weight_A, weight_B; + int32_t samples_A [MAX_TERM], samples_B [MAX_TERM]; +}; + +struct entropy_data { + uint32_t median [3], slow_level, error_limit; +}; + +struct words_data { + uint32_t bitrate_delta [2], bitrate_acc [2]; + uint32_t pend_data, holding_one, zeros_acc; + int holding_zero, pend_count; + struct entropy_data c [2]; +}; + +typedef struct { + WavpackHeader wphdr; + Bitstream wvbits; + + struct words_data w; + + int num_terms, mute_error; + uint32_t sample_index, crc; + + uchar int32_sent_bits, int32_zeros, int32_ones, int32_dups; + uchar float_flags, float_shift, float_max_exp, float_norm_exp; + + struct decorr_pass decorr_passes [MAX_NTERMS]; + +} WavpackStream; + +// flags for float_flags: + +#define FLOAT_SHIFT_ONES 1 // bits left-shifted into float = '1' +#define FLOAT_SHIFT_SAME 2 // bits left-shifted into float are the same +#define FLOAT_SHIFT_SENT 4 // bits shifted into float are sent literally +#define FLOAT_ZEROS_SENT 8 // "zeros" are not all real zeros +#define FLOAT_NEG_ZEROS 0x10 // contains negative zeros +#define FLOAT_EXCEPTIONS 0x20 // contains exceptions (inf, nan, etc.) + +/////////////////////////////// WavPack Context /////////////////////////////// + +// This internal structure holds everything required to encode or decode WavPack +// files. It is recommended that direct access to this structure be minimized +// and the provided utilities used instead. + +typedef struct { + WavpackConfig config; + WavpackStream stream; + + uchar read_buffer [1024]; + char error_message [80]; + + read_stream infile; + uint32_t total_samples, crc_errors, first_flags; + int open_flags, norm_offset, reduced_channels, lossy_blocks; + +} WavpackContext; + +//////////////////////// function prototypes and macros ////////////////////// + +#define CLEAR(destin) memset (&destin, 0, sizeof (destin)); + +// bits.c + +void bs_open_read (Bitstream *bs, uchar *buffer_start, uchar *buffer_end, read_stream file, uint32_t file_bytes); + +#define bs_is_open(bs) ((bs)->ptr != NULL) + +#define getbit(bs) ( \ + (((bs)->bc) ? \ + ((bs)->bc--, (bs)->sr & 1) : \ + (((++((bs)->ptr) != (bs)->end) ? (void) 0 : (bs)->wrap (bs)), (bs)->bc = 7, ((bs)->sr = *((bs)->ptr)) & 1) \ + ) ? \ + ((bs)->sr >>= 1, 1) : \ + ((bs)->sr >>= 1, 0) \ +) + +#define getbits(value, nbits, bs) { \ + while ((nbits) > (bs)->bc) { \ + if (++((bs)->ptr) == (bs)->end) (bs)->wrap (bs); \ + (bs)->sr |= (int32_t)*((bs)->ptr) << (bs)->bc; \ + (bs)->bc += 8; \ + } \ + *(value) = (bs)->sr; \ + if ((bs)->bc > 32) { \ + (bs)->bc -= (nbits); \ + (bs)->sr = *((bs)->ptr) >> (8 - (bs)->bc); \ + } \ + else { \ + (bs)->bc -= (nbits); \ + (bs)->sr >>= (nbits); \ + } \ +} + +void little_endian_to_native (void *data, char *format); +void native_to_little_endian (void *data, char *format); + +// These macros implement the weight application and update operations +// that are at the heart of the decorrelation loops. Note that when there +// are several alternative versions of the same macro (marked with PERFCOND) +// then the versions are functionally equivalent with respect to WavPack +// decoding and the user should choose the one that provides the best +// performance. This may be easier to check when NOT using the assembly +// language optimizations. + +#if 1 // PERFCOND +#define apply_weight_i(weight, sample) ((weight * sample + 512) >> 10) +#else +#define apply_weight_i(weight, sample) ((((weight * sample) >> 8) + 2) >> 2) +#endif + +#define apply_weight_f(weight, sample) (((((sample & 0xffffL) * weight) >> 9) + \ + (((sample & ~0xffffL) >> 9) * weight) + 1) >> 1) + +#if 1 // PERFCOND +#define apply_weight(weight, sample) (sample != (short) sample ? \ + apply_weight_f (weight, sample) : apply_weight_i (weight, sample)) +#else +#define apply_weight(weight, sample) ((int32_t)((weight * (int64_t) sample + 512) >> 10)) +#endif + +#if 0 // PERFCOND +#define update_weight(weight, delta, source, result) \ + if (source && result) { int32_t s = (int32_t) (source ^ result) >> 31; weight = (delta ^ s) + (weight - s); } +#elif 1 +#define update_weight(weight, delta, source, result) \ + if (source && result) weight += (((source ^ result) >> 30) | 1) * delta +#else +#define update_weight(weight, delta, source, result) \ + if (source && result) (source ^ result) < 0 ? (weight -= delta) : (weight += delta) +#endif + +#define update_weight_clip(weight, delta, source, result) \ + if (source && result && ((source ^ result) < 0 ? (weight -= delta) < -1024 : (weight += delta) > 1024)) \ + weight = weight < 0 ? -1024 : 1024 + +// unpack.c + +int unpack_init (WavpackContext *wpc); +int init_wv_bitstream (WavpackContext *wpc, WavpackMetadata *wpmd); +int read_decorr_terms (WavpackStream *wps, WavpackMetadata *wpmd); +int read_decorr_weights (WavpackStream *wps, WavpackMetadata *wpmd); +int read_decorr_samples (WavpackStream *wps, WavpackMetadata *wpmd); +int read_float_info (WavpackStream *wps, WavpackMetadata *wpmd); +int read_int32_info (WavpackStream *wps, WavpackMetadata *wpmd); +int read_channel_info (WavpackContext *wpc, WavpackMetadata *wpmd); +int read_config_info (WavpackContext *wpc, WavpackMetadata *wpmd); +int32_t unpack_samples (WavpackContext *wpc, int32_t *buffer, uint32_t sample_count); +int check_crc_error (WavpackContext *wpc); + +// metadata.c stuff + +int read_metadata_buff (WavpackContext *wpc, WavpackMetadata *wpmd); +int process_metadata (WavpackContext *wpc, WavpackMetadata *wpmd); + +// words.c stuff + +int read_entropy_vars (WavpackStream *wps, WavpackMetadata *wpmd); +int read_hybrid_profile (WavpackStream *wps, WavpackMetadata *wpmd); +int32_t get_words (int32_t *buffer, int nsamples, uint32_t flags, + struct words_data *w, Bitstream *bs); +int32_t exp2s (int log); +int restore_weight (signed char weight); + +#define WORD_EOF (1L << 31) + +// float.c + +int read_float_info (WavpackStream *wps, WavpackMetadata *wpmd); +void float_values (WavpackStream *wps, int32_t *values, int32_t num_values); + +// wputils.c + +WavpackContext *WavpackOpenFileInput (read_stream infile, char *error); + +int WavpackGetMode (WavpackContext *wpc); + +#define MODE_WVC 0x1 +#define MODE_LOSSLESS 0x2 +#define MODE_HYBRID 0x4 +#define MODE_FLOAT 0x8 +#define MODE_VALID_TAG 0x10 +#define MODE_HIGH 0x20 +#define MODE_FAST 0x40 + +uint32_t WavpackUnpackSamples (WavpackContext *wpc, int32_t *buffer, uint32_t samples); +uint32_t WavpackGetNumSamples (WavpackContext *wpc); +uint32_t WavpackGetSampleIndex (WavpackContext *wpc); +int WavpackGetNumErrors (WavpackContext *wpc); +int WavpackLossyBlocks (WavpackContext *wpc); +uint32_t WavpackGetSampleRate (WavpackContext *wpc); +int WavpackGetBitsPerSample (WavpackContext *wpc); +int WavpackGetBytesPerSample (WavpackContext *wpc); +int WavpackGetNumChannels (WavpackContext *wpc); +int WavpackGetReducedChannels (WavpackContext *wpc); diff --git a/src/wavpack/words.c b/src/wavpack/words.c new file mode 100644 index 00000000..0e5a3db7 --- /dev/null +++ b/src/wavpack/words.c @@ -0,0 +1,560 @@ +//////////////////////////////////////////////////////////////////////////// +// **** WAVPACK **** // +// Hybrid Lossless Wavefile Compressor // +// Copyright (c) 1998 - 2006 Conifer Software. // +// All Rights Reserved. // +// Distributed under the BSD Software License (see license.txt) // +//////////////////////////////////////////////////////////////////////////// + +// words.c + +// This module provides entropy word encoding and decoding functions using +// a variation on the Rice method. This was introduced in version 3.93 +// because it allows splitting the data into a "lossy" stream and a +// "correction" stream in a very efficient manner and is therefore ideal +// for the "hybrid" mode. For 4.0, the efficiency of this method was +// significantly improved by moving away from the normal Rice restriction of +// using powers of two for the modulus divisions and now the method can be +// used for both hybrid and pure lossless encoding. + +// Samples are divided by median probabilities at 5/7 (71.43%), 10/49 (20.41%), +// and 20/343 (5.83%). Each zone has 3.5 times fewer samples than the +// previous. Using standard Rice coding on this data would result in 1.4 +// bits per sample average (not counting sign bit). However, there is a +// very simple encoding that is over 99% efficient with this data and +// results in about 1.22 bits per sample. + +#include "wavpack.h" + +#include + +//////////////////////////////// local macros ///////////////////////////////// + +#define LIMIT_ONES 16 // maximum consecutive 1s sent for "div" data + +// these control the time constant "slow_level" which is used for hybrid mode +// that controls bitrate as a function of residual level (HYBRID_BITRATE). +#define SLS 8 +#define SLO ((1 << (SLS - 1))) + +// these control the time constant of the 3 median level breakpoints +#define DIV0 128 // 5/7 of samples +#define DIV1 64 // 10/49 of samples +#define DIV2 32 // 20/343 of samples + +// this macro retrieves the specified median breakpoint (without frac; min = 1) +#define GET_MED(med) (((c->median [med]) >> 4) + 1) + +// These macros update the specified median breakpoints. Note that the median +// is incremented when the sample is higher than the median, else decremented. +// They are designed so that the median will never drop below 1 and the value +// is essentially stationary if there are 2 increments for every 5 decrements. + +#define INC_MED0() (c->median [0] += ((c->median [0] + DIV0) / DIV0) * 5) +#define DEC_MED0() (c->median [0] -= ((c->median [0] + (DIV0-2)) / DIV0) * 2) +#define INC_MED1() (c->median [1] += ((c->median [1] + DIV1) / DIV1) * 5) +#define DEC_MED1() (c->median [1] -= ((c->median [1] + (DIV1-2)) / DIV1) * 2) +#define INC_MED2() (c->median [2] += ((c->median [2] + DIV2) / DIV2) * 5) +#define DEC_MED2() (c->median [2] -= ((c->median [2] + (DIV2-2)) / DIV2) * 2) + +#define count_bits(av) ( \ + (av) < (1 << 8) ? nbits_table [av] : \ + ( \ + (av) < (1L << 16) ? nbits_table [(av) >> 8] + 8 : \ + ((av) < (1L << 24) ? nbits_table [(av) >> 16] + 16 : nbits_table [(av) >> 24] + 24) \ + ) \ +) + +///////////////////////////// local table storage //////////////////////////// + +const char nbits_table [] = { + 0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, // 0 - 15 + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, // 16 - 31 + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, // 32 - 47 + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, // 48 - 63 + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // 64 - 79 + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // 80 - 95 + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // 96 - 111 + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // 112 - 127 + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, // 128 - 143 + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, // 144 - 159 + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, // 160 - 175 + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, // 176 - 191 + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, // 192 - 207 + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, // 208 - 223 + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, // 224 - 239 + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 // 240 - 255 +}; + +static const uchar log2_table [] = { + 0x00, 0x01, 0x03, 0x04, 0x06, 0x07, 0x09, 0x0a, 0x0b, 0x0d, 0x0e, 0x10, 0x11, 0x12, 0x14, 0x15, + 0x16, 0x18, 0x19, 0x1a, 0x1c, 0x1d, 0x1e, 0x20, 0x21, 0x22, 0x24, 0x25, 0x26, 0x28, 0x29, 0x2a, + 0x2c, 0x2d, 0x2e, 0x2f, 0x31, 0x32, 0x33, 0x34, 0x36, 0x37, 0x38, 0x39, 0x3b, 0x3c, 0x3d, 0x3e, + 0x3f, 0x41, 0x42, 0x43, 0x44, 0x45, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4d, 0x4e, 0x4f, 0x50, 0x51, + 0x52, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, + 0x64, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x74, 0x75, + 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, + 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, + 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, + 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb2, + 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 0xc0, 0xc0, + 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcb, 0xcc, 0xcd, 0xce, + 0xcf, 0xd0, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd8, 0xd9, 0xda, 0xdb, + 0xdc, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe4, 0xe5, 0xe6, 0xe7, 0xe7, + 0xe8, 0xe9, 0xea, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xee, 0xef, 0xf0, 0xf1, 0xf1, 0xf2, 0xf3, 0xf4, + 0xf4, 0xf5, 0xf6, 0xf7, 0xf7, 0xf8, 0xf9, 0xf9, 0xfa, 0xfb, 0xfc, 0xfc, 0xfd, 0xfe, 0xff, 0xff +}; + +static const uchar exp2_table [] = { + 0x00, 0x01, 0x01, 0x02, 0x03, 0x03, 0x04, 0x05, 0x06, 0x06, 0x07, 0x08, 0x08, 0x09, 0x0a, 0x0b, + 0x0b, 0x0c, 0x0d, 0x0e, 0x0e, 0x0f, 0x10, 0x10, 0x11, 0x12, 0x13, 0x13, 0x14, 0x15, 0x16, 0x16, + 0x17, 0x18, 0x19, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1d, 0x1e, 0x1f, 0x20, 0x20, 0x21, 0x22, 0x23, + 0x24, 0x24, 0x25, 0x26, 0x27, 0x28, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3a, 0x3b, 0x3c, 0x3d, + 0x3e, 0x3f, 0x40, 0x41, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x48, 0x49, 0x4a, 0x4b, + 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, + 0x5b, 0x5c, 0x5d, 0x5e, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, + 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, + 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x87, 0x88, 0x89, 0x8a, + 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, + 0x9c, 0x9d, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, + 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbc, 0xbd, 0xbe, 0xbf, 0xc0, + 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc8, 0xc9, 0xca, 0xcb, 0xcd, 0xce, 0xcf, 0xd0, 0xd2, 0xd3, 0xd4, + 0xd6, 0xd7, 0xd8, 0xd9, 0xdb, 0xdc, 0xdd, 0xde, 0xe0, 0xe1, 0xe2, 0xe4, 0xe5, 0xe6, 0xe8, 0xe9, + 0xea, 0xec, 0xed, 0xee, 0xf0, 0xf1, 0xf2, 0xf4, 0xf5, 0xf6, 0xf8, 0xf9, 0xfa, 0xfc, 0xfd, 0xff +}; + +static const char ones_count_table [] = { + 0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,5, + 0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,6, + 0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,5, + 0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,7, + 0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,5, + 0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,6, + 0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,5, + 0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,8 +}; + +///////////////////////////// executable code //////////////////////////////// + +void init_words (WavpackStream *wps) +{ + CLEAR (wps->w); +} + +static int mylog2 (uint32_t avalue); + +// Read the median log2 values from the specifed metadata structure, convert +// them back to 32-bit unsigned values and store them. If length is not +// exactly correct then we flag and return an error. + +int read_entropy_vars (WavpackStream *wps, WavpackMetadata *wpmd) +{ + uchar *byteptr = wpmd->data; + + if (wpmd->byte_length != ((wps->wphdr.flags & MONO_DATA) ? 6 : 12)) + return FALSE; + + wps->w.c [0].median [0] = exp2s (byteptr [0] + (byteptr [1] << 8)); + wps->w.c [0].median [1] = exp2s (byteptr [2] + (byteptr [3] << 8)); + wps->w.c [0].median [2] = exp2s (byteptr [4] + (byteptr [5] << 8)); + + if (!(wps->wphdr.flags & MONO_DATA)) { + wps->w.c [1].median [0] = exp2s (byteptr [6] + (byteptr [7] << 8)); + wps->w.c [1].median [1] = exp2s (byteptr [8] + (byteptr [9] << 8)); + wps->w.c [1].median [2] = exp2s (byteptr [10] + (byteptr [11] << 8)); + } + + return TRUE; +} + +// Read the hybrid related values from the specifed metadata structure, convert +// them back to their internal formats and store them. The extended profile +// stuff is not implemented yet, so return an error if we get more data than +// we know what to do with. + +int read_hybrid_profile (WavpackStream *wps, WavpackMetadata *wpmd) +{ + uchar *byteptr = wpmd->data; + uchar *endptr = byteptr + wpmd->byte_length; + + if (wps->wphdr.flags & HYBRID_BITRATE) { + wps->w.c [0].slow_level = exp2s (byteptr [0] + (byteptr [1] << 8)); + byteptr += 2; + + if (!(wps->wphdr.flags & MONO_DATA)) { + wps->w.c [1].slow_level = exp2s (byteptr [0] + (byteptr [1] << 8)); + byteptr += 2; + } + } + + wps->w.bitrate_acc [0] = (int32_t)(byteptr [0] + (byteptr [1] << 8)) << 16; + byteptr += 2; + + if (!(wps->wphdr.flags & MONO_DATA)) { + wps->w.bitrate_acc [1] = (int32_t)(byteptr [0] + (byteptr [1] << 8)) << 16; + byteptr += 2; + } + + if (byteptr < endptr) { + wps->w.bitrate_delta [0] = exp2s ((short)(byteptr [0] + (byteptr [1] << 8))); + byteptr += 2; + + if (!(wps->wphdr.flags & MONO_DATA)) { + wps->w.bitrate_delta [1] = exp2s ((short)(byteptr [0] + (byteptr [1] << 8))); + byteptr += 2; + } + + if (byteptr < endptr) + return FALSE; + } + else + wps->w.bitrate_delta [0] = wps->w.bitrate_delta [1] = 0; + + return TRUE; +} + +// This function is called during both encoding and decoding of hybrid data to +// update the "error_limit" variable which determines the maximum sample error +// allowed in the main bitstream. In the HYBRID_BITRATE mode (which is the only +// currently implemented) this is calculated from the slow_level values and the +// bitrate accumulators. Note that the bitrate accumulators can be changing. + +void update_error_limit (struct words_data *w, uint32_t flags) +{ + int bitrate_0 = (w->bitrate_acc [0] += w->bitrate_delta [0]) >> 16; + + if (flags & MONO_DATA) { + if (flags & HYBRID_BITRATE) { + int slow_log_0 = (w->c [0].slow_level + SLO) >> SLS; + + if (slow_log_0 - bitrate_0 > -0x100) + w->c [0].error_limit = exp2s (slow_log_0 - bitrate_0 + 0x100); + else + w->c [0].error_limit = 0; + } + else + w->c [0].error_limit = exp2s (bitrate_0); + } + else { + int bitrate_1 = (w->bitrate_acc [1] += w->bitrate_delta [1]) >> 16; + + if (flags & HYBRID_BITRATE) { + int slow_log_0 = (w->c [0].slow_level + SLO) >> SLS; + int slow_log_1 = (w->c [1].slow_level + SLO) >> SLS; + + if (flags & HYBRID_BALANCE) { + int balance = (slow_log_1 - slow_log_0 + bitrate_1 + 1) >> 1; + + if (balance > bitrate_0) { + bitrate_1 = bitrate_0 * 2; + bitrate_0 = 0; + } + else if (-balance > bitrate_0) { + bitrate_0 = bitrate_0 * 2; + bitrate_1 = 0; + } + else { + bitrate_1 = bitrate_0 + balance; + bitrate_0 = bitrate_0 - balance; + } + } + + if (slow_log_0 - bitrate_0 > -0x100) + w->c [0].error_limit = exp2s (slow_log_0 - bitrate_0 + 0x100); + else + w->c [0].error_limit = 0; + + if (slow_log_1 - bitrate_1 > -0x100) + w->c [1].error_limit = exp2s (slow_log_1 - bitrate_1 + 0x100); + else + w->c [1].error_limit = 0; + } + else { + w->c [0].error_limit = exp2s (bitrate_0); + w->c [1].error_limit = exp2s (bitrate_1); + } + } +} + +static uint32_t read_code (Bitstream *bs, uint32_t maxcode); + +// Read the next word from the bitstream "wvbits" and return the value. This +// function can be used for hybrid or lossless streams, but since an +// optimized version is available for lossless this function would normally +// be used for hybrid only. If a hybrid lossless stream is being read then +// the "correction" offset is written at the specified pointer. A return value +// of WORD_EOF indicates that the end of the bitstream was reached (all 1s) or +// some other error occurred. + +int32_t get_words (int32_t *buffer, int nsamples, uint32_t flags, + struct words_data *w, Bitstream *bs) +{ + register struct entropy_data *c = w->c; + int csamples; + + if (!(flags & MONO_DATA)) + nsamples *= 2; + + for (csamples = 0; csamples < nsamples; ++csamples) { + uint32_t ones_count, low, mid, high; + + if (!(flags & MONO_DATA)) + c = w->c + (csamples & 1); + + if (!(w->c [0].median [0] & ~1) && !w->holding_zero && !w->holding_one && !(w->c [1].median [0] & ~1)) { + uint32_t mask; + int cbits; + + if (w->zeros_acc) { + if (--w->zeros_acc) { + c->slow_level -= (c->slow_level + SLO) >> SLS; + *buffer++ = 0; + continue; + } + } + else { + for (cbits = 0; cbits < 33 && getbit (bs); ++cbits); + + if (cbits == 33) + break; + + if (cbits < 2) + w->zeros_acc = cbits; + else { + for (mask = 1, w->zeros_acc = 0; --cbits; mask <<= 1) + if (getbit (bs)) + w->zeros_acc |= mask; + + w->zeros_acc |= mask; + } + + if (w->zeros_acc) { + c->slow_level -= (c->slow_level + SLO) >> SLS; + CLEAR (w->c [0].median); + CLEAR (w->c [1].median); + *buffer++ = 0; + continue; + } + } + } + + if (w->holding_zero) + ones_count = w->holding_zero = 0; + else { + int next8; + + if (bs->bc < 8) { + if (++(bs->ptr) == bs->end) + bs->wrap (bs); + + next8 = (bs->sr |= *(bs->ptr) << bs->bc) & 0xff; + bs->bc += 8; + } + else + next8 = bs->sr & 0xff; + + if (next8 == 0xff) { + bs->bc -= 8; + bs->sr >>= 8; + + for (ones_count = 8; ones_count < (LIMIT_ONES + 1) && getbit (bs); ++ones_count); + + if (ones_count == (LIMIT_ONES + 1)) + break; + + if (ones_count == LIMIT_ONES) { + uint32_t mask; + int cbits; + + for (cbits = 0; cbits < 33 && getbit (bs); ++cbits); + + if (cbits == 33) + break; + + if (cbits < 2) + ones_count = cbits; + else { + for (mask = 1, ones_count = 0; --cbits; mask <<= 1) + if (getbit (bs)) + ones_count |= mask; + + ones_count |= mask; + } + + ones_count += LIMIT_ONES; + } + } + else { + bs->bc -= (ones_count = ones_count_table [next8]) + 1; + bs->sr >>= ones_count + 1; + } + + if (w->holding_one) { + w->holding_one = ones_count & 1; + ones_count = (ones_count >> 1) + 1; + } + else { + w->holding_one = ones_count & 1; + ones_count >>= 1; + } + + w->holding_zero = ~w->holding_one & 1; + } + + if ((flags & HYBRID_FLAG) && ((flags & MONO_DATA) || !(csamples & 1))) + update_error_limit (w, flags); + + if (ones_count == 0) { + low = 0; + high = GET_MED (0) - 1; + DEC_MED0 (); + } + else { + low = GET_MED (0); + INC_MED0 (); + + if (ones_count == 1) { + high = low + GET_MED (1) - 1; + DEC_MED1 (); + } + else { + low += GET_MED (1); + INC_MED1 (); + + if (ones_count == 2) { + high = low + GET_MED (2) - 1; + DEC_MED2 (); + } + else { + low += (ones_count - 2) * GET_MED (2); + high = low + GET_MED (2) - 1; + INC_MED2 (); + } + } + } + + mid = (high + low + 1) >> 1; + + if (!c->error_limit) + mid = read_code (bs, high - low) + low; + else while (high - low > c->error_limit) { + if (getbit (bs)) + mid = (high + (low = mid) + 1) >> 1; + else + mid = ((high = mid - 1) + low + 1) >> 1; + } + + *buffer++ = getbit (bs) ? ~mid : mid; + + if (flags & HYBRID_BITRATE) + c->slow_level = c->slow_level - ((c->slow_level + SLO) >> SLS) + mylog2 (mid); + } + + return (flags & MONO_DATA) ? csamples : (csamples / 2); +} + +// Read a single unsigned value from the specified bitstream with a value +// from 0 to maxcode. If there are exactly a power of two number of possible +// codes then this will read a fixed number of bits; otherwise it reads the +// minimum number of bits and then determines whether another bit is needed +// to define the code. + +static uint32_t read_code (Bitstream *bs, uint32_t maxcode) +{ + int bitcount = count_bits (maxcode); + uint32_t extras = (1L << bitcount) - maxcode - 1, code; + + if (!bitcount) + return 0; + + getbits (&code, bitcount - 1, bs); + code &= (1L << (bitcount - 1)) - 1; + + if (code >= extras) { + code = (code << 1) - extras; + + if (getbit (bs)) + ++code; + } + + return code; +} + +// The concept of a base 2 logarithm is used in many parts of WavPack. It is +// a way of sufficiently accurately representing 32-bit signed and unsigned +// values storing only 16 bits (actually fewer). It is also used in the hybrid +// mode for quickly comparing the relative magnitude of large values (i.e. +// division) and providing smooth exponentials using only addition. + +// These are not strict logarithms in that they become linear around zero and +// can therefore represent both zero and negative values. They have 8 bits +// of precision and in "roundtrip" conversions the total error never exceeds 1 +// part in 225 except for the cases of +/-115 and +/-195 (which error by 1). + + +// This function returns the log2 for the specified 32-bit unsigned value. +// The maximum value allowed is about 0xff800000 and returns 8447. + +static int mylog2 (uint32_t avalue) +{ + int dbits; + + if ((avalue += avalue >> 9) < (1 << 8)) { + dbits = nbits_table [avalue]; + return (dbits << 8) + log2_table [(avalue << (9 - dbits)) & 0xff]; + } + else { + if (avalue < (1L << 16)) + dbits = nbits_table [avalue >> 8] + 8; + else if (avalue < (1L << 24)) + dbits = nbits_table [avalue >> 16] + 16; + else + dbits = nbits_table [avalue >> 24] + 24; + + return (dbits << 8) + log2_table [(avalue >> (dbits - 9)) & 0xff]; + } +} + +// This function returns the log2 for the specified 32-bit signed value. +// All input values are valid and the return values are in the range of +// +/- 8192. + +int log2s (int32_t value) +{ + return (value < 0) ? -mylog2 (-value) : mylog2 (value); +} + +// This function returns the original integer represented by the supplied +// logarithm (at least within the provided accuracy). The log is signed, +// but since a full 32-bit value is returned this can be used for unsigned +// conversions as well (i.e. the input range is -8192 to +8447). + +int32_t exp2s (int log) +{ + uint32_t value; + + if (log < 0) + return -exp2s (-log); + + value = exp2_table [log & 0xff] | 0x100; + + if ((log >>= 8) <= 9) + return value >> (9 - log); + else + return value << (log - 9); +} + +// These two functions convert internal weights (which are normally +/-1024) +// to and from an 8-bit signed character version for storage in metadata. The +// weights are clipped here in the case that they are outside that range. + +int restore_weight (signed char weight) +{ + int result; + + if ((result = (int) weight << 3) > 0) + result += (result + 64) >> 7; + + return result; +} diff --git a/src/wavpack/wputils.c b/src/wavpack/wputils.c new file mode 100644 index 00000000..0d71af53 --- /dev/null +++ b/src/wavpack/wputils.c @@ -0,0 +1,351 @@ +//////////////////////////////////////////////////////////////////////////// +// **** WAVPACK **** // +// Hybrid Lossless Wavefile Compressor // +// Copyright (c) 1998 - 2006 Conifer Software. // +// All Rights Reserved. // +// Distributed under the BSD Software License (see license.txt) // +//////////////////////////////////////////////////////////////////////////// + +// wputils.c + +// This module provides a high-level interface for decoding WavPack 4.0 audio +// streams and files. WavPack data is read with a stream reading callback. No +// direct seeking is provided for, but it is possible to start decoding +// anywhere in a WavPack stream. In this case, WavPack will be able to provide +// the sample-accurate position when it synchs with the data and begins +// decoding. + +#include "wavpack.h" + +#include + +///////////////////////////// local table storage //////////////////////////// + +const uint32_t sample_rates [] = { 6000, 8000, 9600, 11025, 12000, 16000, 22050, + 24000, 32000, 44100, 48000, 64000, 88200, 96000, 192000 }; + +///////////////////////////// executable code //////////////////////////////// + +static uint32_t read_next_header (read_stream infile, WavpackHeader *wphdr); + +// This function reads data from the specified stream in search of a valid +// WavPack 4.0 audio block. If this fails in 1 megabyte (or an invalid or +// unsupported WavPack block is encountered) then an appropriate message is +// copied to "error" and NULL is returned, otherwise a pointer to a +// WavpackContext structure is returned (which is used to call all other +// functions in this module). This can be initiated at the beginning of a +// WavPack file, or anywhere inside a WavPack file. To determine the exact +// position within the file use WavpackGetSampleIndex(). For demonstration +// purposes this uses a single static copy of the WavpackContext structure, +// so obviously it cannot be used for more than one file at a time. Also, +// this function will not handle "correction" files, plays only the first +// two channels of multi-channel files, and is limited in resolution in some +// large integer or floating point files (but always provides at least 24 bits +// of resolution). + +static WavpackContext wpc; + +WavpackContext *WavpackOpenFileInput (read_stream infile, char *error) +{ + WavpackStream *wps = &wpc.stream; + uint32_t bcount; + + CLEAR (wpc); + wpc.infile = infile; + wpc.total_samples = (uint32_t) -1; + wpc.norm_offset = 0; + wpc.open_flags = 0; + + // open the source file for reading and store the size + + while (!wps->wphdr.block_samples) { + + bcount = read_next_header (wpc.infile, &wps->wphdr); + + if (bcount == (uint32_t) -1) { + strcpy (error, "not compatible with this version of WavPack file!"); + return NULL; + } + + if (wps->wphdr.block_samples && wps->wphdr.total_samples != (uint32_t) -1) + wpc.total_samples = wps->wphdr.total_samples; + + if (!unpack_init (&wpc)) { + strcpy (error, wpc.error_message [0] ? wpc.error_message : + "not compatible with this version of WavPack file!"); + + return NULL; + } + } + + wpc.config.flags &= ~0xff; + wpc.config.flags |= wps->wphdr.flags & 0xff; + wpc.config.bytes_per_sample = (wps->wphdr.flags & BYTES_STORED) + 1; + wpc.config.float_norm_exp = wps->float_norm_exp; + + wpc.config.bits_per_sample = (wpc.config.bytes_per_sample * 8) - + ((wps->wphdr.flags & SHIFT_MASK) >> SHIFT_LSB); + + if (wpc.config.flags & FLOAT_DATA) { + wpc.config.bytes_per_sample = 3; + wpc.config.bits_per_sample = 24; + } + + if (!wpc.config.sample_rate) { + if (!wps || !wps->wphdr.block_samples || (wps->wphdr.flags & SRATE_MASK) == SRATE_MASK) + wpc.config.sample_rate = 44100; + else + wpc.config.sample_rate = sample_rates [(wps->wphdr.flags & SRATE_MASK) >> SRATE_LSB]; + } + + if (!wpc.config.num_channels) { + wpc.config.num_channels = (wps->wphdr.flags & MONO_FLAG) ? 1 : 2; + wpc.config.channel_mask = 0x5 - wpc.config.num_channels; + } + + if (!(wps->wphdr.flags & FINAL_BLOCK)) + wpc.reduced_channels = (wps->wphdr.flags & MONO_FLAG) ? 1 : 2; + + return &wpc; +} + +// This function obtains general information about an open file and returns +// a mask with the following bit values: + +// MODE_LOSSLESS: file is lossless (pure lossless only) +// MODE_HYBRID: file is hybrid mode (lossy part only) +// MODE_FLOAT: audio data is 32-bit ieee floating point (but will provided +// in 24-bit integers for convenience) +// MODE_HIGH: file was created in "high" mode (information only) +// MODE_FAST: file was created in "fast" mode (information only) + +int WavpackGetMode (WavpackContext *wpc) +{ + int mode = 0; + + if (wpc) { + if (wpc->config.flags & CONFIG_HYBRID_FLAG) + mode |= MODE_HYBRID; + else if (!(wpc->config.flags & CONFIG_LOSSY_MODE)) + mode |= MODE_LOSSLESS; + + if (wpc->lossy_blocks) + mode &= ~MODE_LOSSLESS; + + if (wpc->config.flags & CONFIG_FLOAT_DATA) + mode |= MODE_FLOAT; + + if (wpc->config.flags & CONFIG_HIGH_FLAG) + mode |= MODE_HIGH; + + if (wpc->config.flags & CONFIG_FAST_FLAG) + mode |= MODE_FAST; + } + + return mode; +} + +// Unpack the specified number of samples from the current file position. +// Note that "samples" here refers to "complete" samples, which would be +// 2 longs for stereo files. The audio data is returned right-justified in +// 32-bit longs in the endian mode native to the executing processor. So, +// if the original data was 16-bit, then the values returned would be +// +/-32k. Floating point data will be returned as 24-bit integers (and may +// also be clipped). The actual number of samples unpacked is returned, +// which should be equal to the number requested unless the end of fle is +// encountered or an error occurs. + +uint32_t WavpackUnpackSamples (WavpackContext *wpc, int32_t *buffer, uint32_t samples) +{ + WavpackStream *wps = &wpc->stream; + uint32_t bcount, samples_unpacked = 0, samples_to_unpack; + int num_channels = wpc->config.num_channels; + + while (samples) { + if (!wps->wphdr.block_samples || !(wps->wphdr.flags & INITIAL_BLOCK) || + wps->sample_index >= wps->wphdr.block_index + wps->wphdr.block_samples) { + bcount = read_next_header (wpc->infile, &wps->wphdr); + + if (bcount == (uint32_t) -1) + break; + + if (!wps->wphdr.block_samples || wps->sample_index == wps->wphdr.block_index) + if (!unpack_init (wpc)) + break; + } + + if (!wps->wphdr.block_samples || !(wps->wphdr.flags & INITIAL_BLOCK) || + wps->sample_index >= wps->wphdr.block_index + wps->wphdr.block_samples) + continue; + + if (wps->sample_index < wps->wphdr.block_index) { + samples_to_unpack = wps->wphdr.block_index - wps->sample_index; + + if (samples_to_unpack > samples) + samples_to_unpack = samples; + + wps->sample_index += samples_to_unpack; + samples_unpacked += samples_to_unpack; + samples -= samples_to_unpack; + + if (wpc->reduced_channels) + samples_to_unpack *= wpc->reduced_channels; + else + samples_to_unpack *= num_channels; + + while (samples_to_unpack--) + *buffer++ = 0; + + continue; + } + + samples_to_unpack = wps->wphdr.block_index + wps->wphdr.block_samples - wps->sample_index; + + if (samples_to_unpack > samples) + samples_to_unpack = samples; + + unpack_samples (wpc, buffer, samples_to_unpack); + + if (wpc->reduced_channels) + buffer += samples_to_unpack * wpc->reduced_channels; + else + buffer += samples_to_unpack * num_channels; + + samples_unpacked += samples_to_unpack; + samples -= samples_to_unpack; + + if (wps->sample_index == wps->wphdr.block_index + wps->wphdr.block_samples) { + if (check_crc_error (wpc)) + wpc->crc_errors++; + } + + if (wps->sample_index == wpc->total_samples) + break; + } + + return samples_unpacked; +} + +// Get total number of samples contained in the WavPack file, or -1 if unknown + +uint32_t WavpackGetNumSamples (WavpackContext *wpc) +{ + return wpc ? wpc->total_samples : (uint32_t) -1; +} + +// Get the current sample index position, or -1 if unknown + +uint32_t WavpackGetSampleIndex (WavpackContext *wpc) +{ + if (wpc) + return wpc->stream.sample_index; + + return (uint32_t) -1; +} + +// Get the number of errors encountered so far + +int WavpackGetNumErrors (WavpackContext *wpc) +{ + return wpc ? wpc->crc_errors : 0; +} + +// return TRUE if any uncorrected lossy blocks were actually written or read + +int WavpackLossyBlocks (WavpackContext *wpc) +{ + return wpc ? wpc->lossy_blocks : 0; +} + +// Returns the sample rate of the specified WavPack file + +uint32_t WavpackGetSampleRate (WavpackContext *wpc) +{ + return wpc ? wpc->config.sample_rate : 44100; +} + +// Returns the number of channels of the specified WavPack file. Note that +// this is the actual number of channels contained in the file, but this +// version can only decode the first two. + +int WavpackGetNumChannels (WavpackContext *wpc) +{ + return wpc ? wpc->config.num_channels : 2; +} + +// Returns the actual number of valid bits per sample contained in the +// original file, which may or may not be a multiple of 8. Floating data +// always has 32 bits, integers may be from 1 to 32 bits each. When this +// value is not a multiple of 8, then the "extra" bits are located in the +// LSBs of the results. That is, values are right justified when unpacked +// into longs, but are left justified in the number of bytes used by the +// original data. + +int WavpackGetBitsPerSample (WavpackContext *wpc) +{ + return wpc ? wpc->config.bits_per_sample : 16; +} + +// Returns the number of bytes used for each sample (1 to 4) in the original +// file. This is required information for the user of this module because the +// audio data is returned in the LOWER bytes of the long buffer and must be +// left-shifted 8, 16, or 24 bits if normalized longs are required. + +int WavpackGetBytesPerSample (WavpackContext *wpc) +{ + return wpc ? wpc->config.bytes_per_sample : 2; +} + +// This function will return the actual number of channels decoded from the +// file (which may or may not be less than the actual number of channels, but +// will always be 1 or 2). Normally, this will be the front left and right +// channels of a multi-channel file. + +int WavpackGetReducedChannels (WavpackContext *wpc) +{ + if (wpc) + return wpc->reduced_channels ? wpc->reduced_channels : wpc->config.num_channels; + else + return 2; +} + +// Read from current file position until a valid 32-byte WavPack 4.0 header is +// found and read into the specified pointer. The number of bytes skipped is +// returned. If no WavPack header is found within 1 meg, then a -1 is returned +// to indicate the error. No additional bytes are read past the header and it +// is returned in the processor's native endian mode. Seeking is not required. + +static uint32_t read_next_header (read_stream infile, WavpackHeader *wphdr) +{ + char buffer [sizeof (*wphdr)], *sp = buffer + sizeof (*wphdr), *ep = sp; + uint32_t bytes_skipped = 0; + int bleft; + + while (1) { + if (sp < ep) { + bleft = ep - sp; + memcpy (buffer, sp, bleft); + } + else + bleft = 0; + + if (infile (buffer + bleft, sizeof (*wphdr) - bleft) != (int32_t) sizeof (*wphdr) - bleft) + return -1; + + sp = buffer; + + if (*sp++ == 'w' && *sp == 'v' && *++sp == 'p' && *++sp == 'k' && + !(*++sp & 1) && sp [2] < 16 && !sp [3] && sp [5] == 4 && + sp [4] >= (MIN_STREAM_VERS & 0xff) && sp [4] <= (MAX_STREAM_VERS & 0xff)) { + memcpy (wphdr, buffer, sizeof (*wphdr)); + little_endian_to_native (wphdr, WavpackHeaderFormat); + return bytes_skipped; + } + + while (sp < ep && *sp != 'w') + sp++; + + if ((bytes_skipped += sp - buffer) > 1048576L) + return -1; + } +} diff --git a/src/wavpack/wvfilter.c.no_compile b/src/wavpack/wvfilter.c.no_compile new file mode 100644 index 00000000..f80d73dd --- /dev/null +++ b/src/wavpack/wvfilter.c.no_compile @@ -0,0 +1,200 @@ +//////////////////////////////////////////////////////////////////////////// +// **** WAVPACK **** // +// Hybrid Lossless Wavefile Compressor // +// Copyright (c) 1998 - 2006 Conifer Software. // +// All Rights Reserved. // +// Distributed under the BSD Software License (see license.txt) // +//////////////////////////////////////////////////////////////////////////// + +// wv_filter.c + +// This is the main module for the demonstration WavPack command-line +// decoder filter. It uses the tiny "hardware" version of the decoder and +// accepts WavPack files on stdin and outputs a standard MS wav file to +// stdout. Note that this involves converting the data to little-endian +// (if the executing processor is not), possibly packing the data into +// fewer bytes per sample, and generating an appropriate riff wav header. +// Note that this is NOT the copy of the RIFF header that might be stored +// in the file, and any additional RIFF information and tags are lost. +// See wputils.c for further limitations. + +#include "wavpack.h" + +#if defined(WIN32) +#include +#include +#endif + +#include + +// These structures are used to place a wav riff header at the beginning of +// the output. + +typedef struct { + char ckID [4]; + uint32_t ckSize; + char formType [4]; +} RiffChunkHeader; + +typedef struct { + char ckID [4]; + uint32_t ckSize; +} ChunkHeader; + +#define ChunkHeaderFormat "4L" + +typedef struct { + ushort FormatTag, NumChannels; + uint32_t SampleRate, BytesPerSecond; + ushort BlockAlign, BitsPerSample; +} WaveHeader; + +#define WaveHeaderFormat "SSLLSS" + +static uchar *format_samples (int bps, uchar *dst, int32_t *src, uint32_t samcnt); +static int32_t read_bytes (void *buff, int32_t bcount); +static int32_t temp_buffer [256]; + +int main () +{ + ChunkHeader FormatChunkHeader, DataChunkHeader; + RiffChunkHeader RiffChunkHeader; + WaveHeader WaveHeader; + + uint32_t total_unpacked_samples = 0, total_samples; + int num_channels, bps; + WavpackContext *wpc; + char error [80]; + +#if defined(WIN32) + setmode (fileno (stdin), O_BINARY); + setmode (fileno (stdout), O_BINARY); +#endif + + wpc = WavpackOpenFileInput (read_bytes, error); + + if (!wpc) { + fputs (error, stderr); + fputs ("\n", stderr); + return 1; + } + + num_channels = WavpackGetReducedChannels (wpc); + total_samples = WavpackGetNumSamples (wpc); + bps = WavpackGetBytesPerSample (wpc); + + strncpy (RiffChunkHeader.ckID, "RIFF", sizeof (RiffChunkHeader.ckID)); + RiffChunkHeader.ckSize = total_samples * num_channels * bps + sizeof (ChunkHeader) * 2 + sizeof (WaveHeader) + 4; + strncpy (RiffChunkHeader.formType, "WAVE", sizeof (RiffChunkHeader.formType)); + + strncpy (FormatChunkHeader.ckID, "fmt ", sizeof (FormatChunkHeader.ckID)); + FormatChunkHeader.ckSize = sizeof (WaveHeader); + + WaveHeader.FormatTag = 1; + WaveHeader.NumChannels = num_channels; + WaveHeader.SampleRate = WavpackGetSampleRate (wpc); + WaveHeader.BlockAlign = num_channels * bps; + WaveHeader.BytesPerSecond = WaveHeader.SampleRate * WaveHeader.BlockAlign; + WaveHeader.BitsPerSample = WavpackGetBitsPerSample (wpc); + + strncpy (DataChunkHeader.ckID, "data", sizeof (DataChunkHeader.ckID)); + DataChunkHeader.ckSize = total_samples * num_channels * bps; + + native_to_little_endian (&RiffChunkHeader, ChunkHeaderFormat); + native_to_little_endian (&FormatChunkHeader, ChunkHeaderFormat); + native_to_little_endian (&WaveHeader, WaveHeaderFormat); + native_to_little_endian (&DataChunkHeader, ChunkHeaderFormat); + + if (!fwrite (&RiffChunkHeader, sizeof (RiffChunkHeader), 1, stdout) || + !fwrite (&FormatChunkHeader, sizeof (FormatChunkHeader), 1, stdout) || + !fwrite (&WaveHeader, sizeof (WaveHeader), 1, stdout) || + !fwrite (&DataChunkHeader, sizeof (DataChunkHeader), 1, stdout)) { + fputs ("can't write .WAV data, disk probably full!\n", stderr); + return 1; + } + + while (1) { + uint32_t samples_unpacked; + + samples_unpacked = WavpackUnpackSamples (wpc, temp_buffer, 256 / num_channels); + total_unpacked_samples += samples_unpacked; + + if (samples_unpacked) { + format_samples (bps, (uchar *) temp_buffer, temp_buffer, samples_unpacked *= num_channels); + + if (fwrite (temp_buffer, bps, samples_unpacked, stdout) != samples_unpacked) { + fputs ("can't write .WAV data, disk probably full!\n", stderr); + return 1; + } + } + + if (!samples_unpacked) + break; + } + + fflush (stdout); + + if (WavpackGetNumSamples (wpc) != (uint32_t) -1 && + total_unpacked_samples != WavpackGetNumSamples (wpc)) { + fputs ("incorrect number of samples!\n", stderr); + return 1; + } + + if (WavpackGetNumErrors (wpc)) { + fputs ("crc errors detected!\n", stderr); + return 1; + } + + return 0; +} + +static int32_t read_bytes (void *buff, int32_t bcount) +{ + return fread (buff, 1, bcount, stdin); +} + +// Reformat samples from longs in processor's native endian mode to +// little-endian data with (possibly) less than 4 bytes / sample. + +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; +} -- cgit 1.4.1