diff options
Diffstat (limited to 'src/game/client/components')
44 files changed, 6493 insertions, 0 deletions
diff --git a/src/game/client/components/binds.cpp b/src/game/client/components/binds.cpp new file mode 100644 index 00000000..5d033ad4 --- /dev/null +++ b/src/game/client/components/binds.cpp @@ -0,0 +1,181 @@ +extern "C" { + #include <engine/e_console.h> +} + +#include "binds.hpp" + +BINDS::BINDS() +{ + mem_zero(keybindings, sizeof(keybindings)); +} + +void BINDS::bind(int keyid, const char *str) +{ + if(keyid < 0 && keyid >= KEY_LAST) + return; + + str_copy(keybindings[keyid], str, sizeof(keybindings[keyid])); + if(!keybindings[keyid][0]) + dbg_msg("binds", "unbound %s (%d)", inp_key_name(keyid), keyid); + else + dbg_msg("binds", "bound %s (%d) = %s", inp_key_name(keyid), keyid, keybindings[keyid]); +} + + +bool BINDS::on_input(INPUT_EVENT e) +{ + // don't handle invalid events and keys that arn't set to anything + if(e.key <= 0 || e.key >= KEY_LAST || keybindings[e.key][0] == 0) + return false; + + int stroke = 0; + if(e.flags&INPFLAG_PRESS) + stroke = 1; + console_execute_line_stroked(stroke, keybindings[e.key]); + return true; +} + +void BINDS::unbindall() +{ + for(int i = 0; i < KEY_LAST; i++) + keybindings[i][0] = 0; +} + +const char *BINDS::get(int keyid) +{ + if(keyid > 0 && keyid < KEY_LAST) + return keybindings[keyid]; + return ""; +} + +void BINDS::set_defaults() +{ + unbindall(); + + // set default key bindings + bind(KEY_F1, "toggle_local_console"); + bind(KEY_F2, "toggle_remote_console"); + bind(KEY_TAB, "+scoreboard"); + bind(KEY_F10, "screenshot"); + + bind('A', "+left"); + bind('D', "+right"); + bind(KEY_SPACE, "+jump"); + bind(KEY_MOUSE_1, "+fire"); + bind(KEY_MOUSE_2, "+hook"); + bind(KEY_LSHIFT, "+emote"); + + bind('1', "+weapon1"); + bind('2', "+weapon2"); + bind('3', "+weapon3"); + bind('4', "+weapon4"); + bind('5', "+weapon5"); + + bind(KEY_MOUSE_WHEEL_UP, "+prevweapon"); + bind(KEY_MOUSE_WHEEL_DOWN, "+nextweapon"); + + bind('T', "chat all"); + bind('Y', "chat team"); +} + +void BINDS::on_init() +{ + set_defaults(); +} + +/* +static int get_key_id(const char *key_name) +{ + // check for numeric + if(key_name[0] == '#') + { + int i = atoi(key_name+1); + if(i > 0 && i < KEY_LAST) + return i; // numeric + } + + // search for key + for(int i = 0; i < KEY_LAST; i++) + { + if(strcmp(key_name, inp_key_name(i)) == 0) + return i; + } + + return 0; +} + +static void con_bind(void *result, void *user_data) +{ + const char *key_name = console_arg_string(result, 0); + int id = get_key_id(key_name); + + if(!id) + { + dbg_msg("binds", "key %s not found", key_name); + return; + } + + binds_set(id, console_arg_string(result, 1)); +} + + +static void con_unbind(void *result, void *user_data) +{ + const char *key_name = console_arg_string(result, 0); + int id = get_key_id(key_name); + + if(!id) + { + dbg_msg("binds", "key %s not found", key_name); + return; + } + + binds_set(id, ""); +} + + +static void con_unbindall(void *result, void *user_data) +{ + binds_unbindall(); +} + + +static void con_dump_binds(void *result, void *user_data) +{ + for(int i = 0; i < KEY_LAST; i++) + { + if(keybindings[i][0] == 0) + continue; + dbg_msg("binds", "%s (%d) = %s", inp_key_name(i), i, keybindings[i]); + } +} + +void binds_save() +{ + char buffer[256]; + char *end = buffer+sizeof(buffer)-8; + client_save_line("unbindall"); + for(int i = 0; i < KEY_LAST; i++) + { + if(keybindings[i][0] == 0) + continue; + str_format(buffer, sizeof(buffer), "bind %s ", inp_key_name(i)); + + // process the string. we need to escape some characters + const char *src = keybindings[i]; + char *dst = buffer + strlen(buffer); + *dst++ = '"'; + while(*src && dst < end) + { + if(*src == '"' || *src == '\\') // escape \ and " + *dst++ = '\\'; + *dst++ = *src++; + } + *dst++ = '"'; + *dst++ = 0; + + client_save_line(buffer); + } +} + +*/ diff --git a/src/game/client/components/binds.hpp b/src/game/client/components/binds.hpp new file mode 100644 index 00000000..304b4a8d --- /dev/null +++ b/src/game/client/components/binds.hpp @@ -0,0 +1,19 @@ +#include <game/client/component.hpp> + +class BINDS : public COMPONENT +{ + char keybindings[KEY_LAST][128]; +public: + BINDS(); + + void bind(int keyid, const char *str); + void set_defaults(); + void unbindall(); + const char *get(int keyid); + + /*virtual void on_reset(); + virtual void on_render(); + virtual void on_message(int msgtype, void *rawmsg);*/ + virtual void on_init(); + virtual bool on_input(INPUT_EVENT e); +}; diff --git a/src/game/client/components/broadcast.cpp b/src/game/client/components/broadcast.cpp new file mode 100644 index 00000000..31ff6a71 --- /dev/null +++ b/src/game/client/components/broadcast.cpp @@ -0,0 +1,37 @@ +#include <engine/e_client_interface.h> +#include <engine/e_config.h> +#include <game/generated/g_protocol.hpp> +#include <game/generated/gc_data.hpp> + +#include <game/client/gameclient.hpp> +//#include <game/client/gc_anim.hpp> +#include <game/client/gc_client.hpp> + +#include "broadcast.hpp" + +void BROADCAST::on_reset() +{ + broadcast_time = 0; +} + +void BROADCAST::on_render() +{ + gfx_mapscreen(0, 0, 300*gfx_screenaspect(), 300); + + if(time_get() < broadcast_time) + { + float w = gfx_text_width(0, 14, broadcast_text, -1); + gfx_text(0, 150*gfx_screenaspect()-w/2, 35, 14, broadcast_text, -1); + } +} + +void BROADCAST::on_message(int msgtype, void *rawmsg) +{ + if(msgtype == NETMSGTYPE_SV_BROADCAST) + { + NETMSG_SV_BROADCAST *msg = (NETMSG_SV_BROADCAST *)rawmsg; + str_copy(broadcast_text, msg->message, sizeof(broadcast_text)); + broadcast_time = time_get()+time_freq()*10; + } +} + diff --git a/src/game/client/components/broadcast.hpp b/src/game/client/components/broadcast.hpp new file mode 100644 index 00000000..102201cc --- /dev/null +++ b/src/game/client/components/broadcast.hpp @@ -0,0 +1,14 @@ +#include <game/client/component.hpp> + +class BROADCAST : public COMPONENT +{ +public: + // broadcasts + char broadcast_text[1024]; + int64 broadcast_time; + + virtual void on_reset(); + virtual void on_render(); + virtual void on_message(int msgtype, void *rawmsg); +}; + diff --git a/src/game/client/components/camera.cpp b/src/game/client/components/camera.cpp new file mode 100644 index 00000000..32442031 --- /dev/null +++ b/src/game/client/components/camera.cpp @@ -0,0 +1,45 @@ +extern "C" { + #include <engine/e_config.h> + #include <engine/e_client_interface.h> +} + +#include <base/math.hpp> +#include <game/collision.hpp> +#include <game/client/gameclient.hpp> +#include <game/client/component.hpp> + +#include "camera.hpp" +#include "controls.hpp" + +CAMERA::CAMERA() +{ +} + +void CAMERA::on_render() +{ + //vec2 center; + zoom = 1.0f; + + bool spectate = false; + + if(spectate) + center = gameclient.controls->mouse_pos; + else + { + + float l = length(gameclient.controls->mouse_pos); + float deadzone = config.cl_mouse_deadzone; + float follow_factor = config.cl_mouse_followfactor/100.0f; + vec2 camera_offset(0, 0); + + float offset_amount = max(l-deadzone, 0.0f) * follow_factor; + if(l > 0.0001f) // make sure that this isn't 0 + camera_offset = normalize(gameclient.controls->mouse_pos)*offset_amount; + + center = gameclient.local_character_pos + camera_offset; + } + + // set listner pos + snd_set_listener_pos(center.x, center.y); +} + diff --git a/src/game/client/components/camera.hpp b/src/game/client/components/camera.hpp new file mode 100644 index 00000000..1cb05f5b --- /dev/null +++ b/src/game/client/components/camera.hpp @@ -0,0 +1,13 @@ +#include <base/vmath.hpp> +#include <game/client/component.hpp> + +class CAMERA : public COMPONENT +{ +public: + vec2 center; + float zoom; + + CAMERA(); + virtual void on_render(); +}; + diff --git a/src/game/client/components/chat.cpp b/src/game/client/components/chat.cpp new file mode 100644 index 00000000..ecd29ab8 --- /dev/null +++ b/src/game/client/components/chat.cpp @@ -0,0 +1,201 @@ +#include <engine/e_client_interface.h> +#include <game/generated/g_protocol.hpp> +#include <game/generated/gc_data.hpp> + +#include <game/client/gameclient.hpp> +//#include <game/client/gc_anim.hpp> +#include <game/client/gc_client.hpp> + +#include "chat.hpp" + +void CHAT::on_reset() +{ + mode = MODE_NONE; + for(int i = 0; i < MAX_LINES; i++) + lines[i].tick = -1000000; + current_line = 0; +} + + +bool CHAT::on_input(INPUT_EVENT e) +{ + if(mode == MODE_NONE) + return false; + + if(e.flags&INPFLAG_PRESS && (e.key == KEY_ENTER || e.key == KEY_KP_ENTER)) + { + if(input.get_string()[0]) + gameclient.chat->say(mode == MODE_ALL ? 0 : 1, input.get_string()); + mode = MODE_NONE; + } + else + input.process_input(e); + + return true; +} + + +void CHAT::enable_mode(int team) +{ + if(mode == MODE_NONE) + { + if(team) + mode = MODE_TEAM; + else + mode = MODE_ALL; + + input.clear(); + inp_clear_events(); + } +} + +void CHAT::on_message(int msgtype, void *rawmsg) +{ + if(msgtype == NETMSGTYPE_SV_CHAT) + { + NETMSG_SV_CHAT *msg = (NETMSG_SV_CHAT *)rawmsg; + add_line(msg->cid, msg->team, msg->message); + + if(msg->cid >= 0) // TODO: repair me + snd_play(CHN_GUI, data->sounds[SOUND_CHAT_CLIENT].sounds[0].id, 0); + else + snd_play(CHN_GUI, data->sounds[SOUND_CHAT_SERVER].sounds[0].id, 0); + } +} + + + +void CHAT::add_line(int client_id, int team, const char *line) +{ + current_line = (current_line+1)%MAX_LINES; + lines[current_line].tick = client_tick(); + lines[current_line].client_id = client_id; + lines[current_line].team = team; + lines[current_line].name_color = -2; + + if(client_id == -1) // server message + { + str_copy(lines[current_line].name, "*** ", sizeof(lines[current_line].name)); + str_format(lines[current_line].text, sizeof(lines[current_line].text), "%s", line); + } + else + { + if(gameclient.clients[client_id].team == -1) + lines[current_line].name_color = -1; + + if(gameclient.snap.gameobj && gameclient.snap.gameobj->flags&GAMEFLAG_TEAMS) + { + if(gameclient.clients[client_id].team == 0) + lines[current_line].name_color = 0; + else if(gameclient.clients[client_id].team == 1) + lines[current_line].name_color = 1; + } + + str_copy(lines[current_line].name, gameclient.clients[client_id].name, sizeof(lines[current_line].name)); + str_format(lines[current_line].text, sizeof(lines[current_line].text), ": %s", line); + } + + dbg_msg("chat", "%s%s", lines[current_line].name, lines[current_line].text); +} + +void CHAT::on_render() +{ + gfx_mapscreen(0,0,300*gfx_screenaspect(),300); + float x = 10.0f; + float y = 300.0f-30.0f; + if(mode != MODE_NONE) + { + // render chat input + TEXT_CURSOR cursor; + gfx_text_set_cursor(&cursor, x, y, 8.0f, TEXTFLAG_RENDER); + cursor.line_width = 300.0f; + + if(mode == MODE_ALL) + gfx_text_ex(&cursor, "All: ", -1); + else if(mode == MODE_TEAM) + gfx_text_ex(&cursor, "Team: ", -1); + else + gfx_text_ex(&cursor, "Chat: ", -1); + + gfx_text_ex(&cursor, input.get_string(), input.cursor_offset()); + TEXT_CURSOR marker = cursor; + gfx_text_ex(&marker, "|", -1); + gfx_text_ex(&cursor, input.get_string()+input.cursor_offset(), -1); + } + + y -= 8; + + int i; + for(i = 0; i < MAX_LINES; i++) + { + int r = ((current_line-i)+MAX_LINES)%MAX_LINES; + if(client_tick() > lines[r].tick+50*15) + break; + + float begin = x; + float fontsize = 8.0f; + + // get the y offset + TEXT_CURSOR cursor; + gfx_text_set_cursor(&cursor, begin, 0, fontsize, 0); + cursor.line_width = 300.0f; + gfx_text_ex(&cursor, lines[r].name, -1); + gfx_text_ex(&cursor, lines[r].text, -1); + y -= cursor.y + cursor.font_size; + + // reset the cursor + gfx_text_set_cursor(&cursor, begin, y, fontsize, TEXTFLAG_RENDER); + cursor.line_width = 300.0f; + + // render name + gfx_text_color(0.8f,0.8f,0.8f,1); + if(lines[r].client_id == -1) + gfx_text_color(1,1,0.5f,1); // system + else if(lines[r].team) + gfx_text_color(0.45f,0.9f,0.45f,1); // team message + else if(lines[r].name_color == 0) + gfx_text_color(1.0f,0.5f,0.5f,1); // red + else if(lines[r].name_color == 1) + gfx_text_color(0.7f,0.7f,1.0f,1); // blue + else if(lines[r].name_color == -1) + gfx_text_color(0.75f,0.5f,0.75f, 1); // spectator + + // render name + gfx_text_ex(&cursor, lines[r].name, -1); + + // render line + gfx_text_color(1,1,1,1); + if(lines[r].client_id == -1) + gfx_text_color(1,1,0.5f,1); // system + else if(lines[r].team) + gfx_text_color(0.65f,1,0.65f,1); // team message + + gfx_text_ex(&cursor, lines[r].text, -1); + } + + gfx_text_color(1,1,1,1); +} + +void con_chat(void *result, void *user_data) +{ + /* + const char *mode = console_arg_string(result, 0); + if(strcmp(mode, "all") == 0) + chat_enable_mode(0); + else if(strcmp(mode, "team") == 0) + chat_enable_mode(1); + else + dbg_msg("console", "expected all or team as mode"); + */ +} + + +void CHAT::say(int team, const char *line) +{ + // send chat message + NETMSG_CL_SAY msg; + msg.team = team; + msg.message = line; + msg.pack(MSGFLAG_VITAL); + client_send_msg(); +} diff --git a/src/game/client/components/chat.hpp b/src/game/client/components/chat.hpp new file mode 100644 index 00000000..caec18b2 --- /dev/null +++ b/src/game/client/components/chat.hpp @@ -0,0 +1,49 @@ +#include <game/client/component.hpp> +#include <game/client/lineinput.hpp> + +class CHAT : public COMPONENT +{ +public: + LINEINPUT input; + + enum + { + MAX_LINES = 10, + }; + + struct LINE + { + int tick; + int client_id; + int team; + int name_color; + char name[64]; + char text[512]; + }; + + LINE lines[MAX_LINES]; + int current_line; + + // chat + enum + { + MODE_NONE=0, + MODE_ALL, + MODE_TEAM, + }; + + int mode; + + void add_line(int client_id, int team, const char *line); + //void chat_reset(); + //bool chat_input_handle(INPUT_EVENT e, void *user_data); + + void enable_mode(int team); + + void say(int team, const char *line); + + virtual void on_reset(); + virtual void on_render(); + virtual void on_message(int msgtype, void *rawmsg); + virtual bool on_input(INPUT_EVENT e); +}; diff --git a/src/game/client/components/console.cpp b/src/game/client/components/console.cpp new file mode 100644 index 00000000..6437b718 --- /dev/null +++ b/src/game/client/components/console.cpp @@ -0,0 +1,503 @@ +//#include "gc_console.hpp" +#include <game/generated/gc_data.hpp> + +#include <base/system.h> + +extern "C" { + #include <engine/e_client_interface.h> + #include <engine/e_config.h> + #include <engine/e_console.h> + #include <engine/e_ringbuffer.h> + #include <engine/client/ec_font.h> +} + +#include <cstring> +#include <cstdio> + +#include <game/client/gc_ui.hpp> +#include <game/client/gc_client.hpp> + +#include <game/version.hpp> + +#include <game/client/lineinput.hpp> + +#include "console.hpp" + +enum +{ + CONSOLE_CLOSED, + CONSOLE_OPENING, + CONSOLE_OPEN, + CONSOLE_CLOSING, +}; + +CONSOLE::INSTANCE::INSTANCE(int t) +{ + // init ringbuffers + history = ringbuf_init(history_data, sizeof(history_data)); + backlog = ringbuf_init(backlog_data, sizeof(backlog_data)); + + history_entry = 0x0; + + type = t; +} + +void CONSOLE::INSTANCE::execute_line(const char *line) +{ + if(type == 0) + console_execute_line(line); + else + { + if(client_rcon_authed()) + client_rcon(line); + else + client_rcon_auth("", line); + } +} + +void CONSOLE::INSTANCE::on_input(INPUT_EVENT e) +{ + bool handled = false; + + if(e.flags&INPFLAG_PRESS) + { + if(e.key == KEY_ENTER || e.key == KEY_KP_ENTER) + { + if(input.get_string()[0]) + { + char *entry = (char *)ringbuf_allocate(history, input.get_length()+1); + mem_copy(entry, input.get_string(), input.get_length()+1); + + execute_line(input.get_string()); + input.clear(); + history_entry = 0x0; + } + + handled = true; + } + else if (e.key == KEY_UP) + { + if (history_entry) + { + char *test = (char *)ringbuf_prev(history, history_entry); + + if (test) + history_entry = test; + } + else + history_entry = (char *)ringbuf_last(history); + + if (history_entry) + { + unsigned int len = strlen(history_entry); + if (len < sizeof(input) - 1) + input.set(history_entry); + } + handled = true; + } + else if (e.key == KEY_DOWN) + { + if (history_entry) + history_entry = (char *)ringbuf_next(history, history_entry); + + if (history_entry) + { + unsigned int len = strlen(history_entry); + if (len < sizeof(input) - 1) + input.set(history_entry); + } + else + input.clear(); + handled = true; + } + } + + if(!handled) + input.process_input(e); +} + +void CONSOLE::INSTANCE::print_line(const char *line) +{ + int len = strlen(line); + + if (len > 255) + len = 255; + + char *entry = (char *)ringbuf_allocate(backlog, len+1); + mem_copy(entry, line, len+1); +} + +CONSOLE::CONSOLE() +: local_console(0), remote_console(1) +{ + console_type = 0; + console_state = CONSOLE_CLOSED; + state_change_end = 0.0f; + state_change_duration = 0.1f; +} + +float CONSOLE::time_now() +{ + static long long time_start = time_get(); + return float(time_get()-time_start)/float(time_freq()); +} + +CONSOLE::INSTANCE *CONSOLE::current_console() +{ + if(console_type != 0) + return &remote_console; + return &local_console; +} + +void CONSOLE::on_reset() +{ +} + +// only defined for 0<=t<=1 +static float console_scale_func(float t) +{ + //return t; + return sinf(acosf(1.0f-t)); +} + +void CONSOLE::on_render() +{ + + RECT screen = *ui_screen(); + float console_max_height = screen.h*3/5.0f; + float console_height; + + float progress = (time_now()-(state_change_end-state_change_duration))/float(state_change_duration); + + if (progress >= 1.0f) + { + if (console_state == CONSOLE_CLOSING) + console_state = CONSOLE_CLOSED; + else if (console_state == CONSOLE_OPENING) + console_state = CONSOLE_OPEN; + + progress = 1.0f; + } + + if (console_state == CONSOLE_CLOSED) + return; + + float console_height_scale; + + if (console_state == CONSOLE_OPENING) + console_height_scale = console_scale_func(progress); + else if (console_state == CONSOLE_CLOSING) + console_height_scale = console_scale_func(1.0f-progress); + else //if (console_state == CONSOLE_OPEN) + console_height_scale = console_scale_func(1.0f); + + console_height = console_height_scale*console_max_height; + + gfx_mapscreen(screen.x, screen.y, screen.w, screen.h); + + // do console shadow + gfx_texture_set(-1); + gfx_quads_begin(); + gfx_setcolorvertex(0, 0,0,0, 0.5f); + gfx_setcolorvertex(1, 0,0,0, 0.5f); + gfx_setcolorvertex(2, 0,0,0, 0.0f); + gfx_setcolorvertex(3, 0,0,0, 0.0f); + gfx_quads_drawTL(0,console_height,screen.w,10.0f); + gfx_quads_end(); + + // do background + gfx_texture_set(data->images[IMAGE_CONSOLE_BG].id); + gfx_quads_begin(); + gfx_setcolor(0.2f, 0.2f, 0.2f,0.9f); + if(console_type != 0) + gfx_setcolor(0.4f, 0.2f, 0.2f,0.9f); + gfx_quads_setsubset(0,-console_height*0.075f,screen.w*0.075f*0.5f,0); + gfx_quads_drawTL(0,0,screen.w,console_height); + gfx_quads_end(); + + // do small bar shadow + gfx_texture_set(-1); + gfx_quads_begin(); + gfx_setcolorvertex(0, 0,0,0, 0.0f); + gfx_setcolorvertex(1, 0,0,0, 0.0f); + gfx_setcolorvertex(2, 0,0,0, 0.25f); + gfx_setcolorvertex(3, 0,0,0, 0.25f); + gfx_quads_drawTL(0,console_height-20,screen.w,10); + gfx_quads_end(); + + // do the lower bar + gfx_texture_set(data->images[IMAGE_CONSOLE_BAR].id); + gfx_quads_begin(); + gfx_setcolor(1.0f, 1.0f, 1.0f, 0.9f); + gfx_quads_setsubset(0,0.1f,screen.w*0.015f,1-0.1f); + gfx_quads_drawTL(0,console_height-10.0f,screen.w,10.0f); + gfx_quads_end(); + + console_height -= 10.0f; + + INSTANCE *console = current_console(); + + { + float font_size = 10.0f; + float row_height = font_size*1.25f; + float x = 3; + float y = console_height - row_height - 2; + + // render prompt + TEXT_CURSOR cursor; + gfx_text_set_cursor(&cursor, x, y, font_size, TEXTFLAG_RENDER); + + const char *prompt = "> "; + if(console_type) + { + if(client_state() == CLIENTSTATE_ONLINE) + { + if(client_rcon_authed()) + prompt = "rcon> "; + else + prompt = "ENTER PASSWORD> "; + } + else + prompt = "NOT CONNECTED> "; + } + + gfx_text_ex(&cursor, prompt, -1); + + // render console input + gfx_text_ex(&cursor, console->input.get_string(), console->input.cursor_offset()); + TEXT_CURSOR marker = cursor; + gfx_text_ex(&marker, "|", -1); + gfx_text_ex(&cursor, console->input.get_string()+console->input.cursor_offset(), -1); + + // render version + char buf[128]; + str_format(buf, sizeof(buf), "v%s", GAME_VERSION); + float version_width = gfx_text_width(0, font_size, buf, -1); + gfx_text(0, screen.w-version_width-5, y, font_size, buf, -1); + + // render log + y -= row_height; + char *entry = (char *)ringbuf_last(console->backlog); + while (y > 0.0f && entry) + { + gfx_text(0, x, y, font_size, entry, -1); + y -= row_height; + + entry = (char *)ringbuf_prev(console->backlog, entry); + } + } +} + +void CONSOLE::on_message(int msgtype, void *rawmsg) +{ +} + +bool CONSOLE::on_input(INPUT_EVENT e) +{ + return false; +} + + +/* +static void client_console_print(const char *str) +{ + // TODO: repair me + //local_console.print_line(str); +} + +void console_rcon_print(const char *line) +{ + // TODO: repair me + //remote_console.print_line(line); +}*/ + +/* +static void con_team(void *result, void *user_data) +{ + send_switch_team(console_arg_int(result, 0)); +} + +static void con_say(void *result, void *user_data) +{ + chat_say(0, console_arg_string(result, 0)); +} + +static void con_sayteam(void *result, void *user_data) +{ + chat_say(1, console_arg_string(result, 0)); +} + +void send_kill(int client_id); + +static void con_kill(void *result, void *user_data) +{ + send_kill(-1); +} + +static void con_key_input_state(void *result, void *user_data) +{ + ((int *)user_data)[0] = console_arg_int(result, 0); +} + +static void con_key_input_counter(void *result, void *user_data) +{ + int *v = (int *)user_data; + if(((*v)&1) != console_arg_int(result, 0)) + (*v)++; + *v &= INPUT_STATE_MASK; +} + +static void con_key_input_weapon(void *result, void *user_data) +{ + int w = (char *)user_data - (char *)0; + if(console_arg_int(result, 0)) + input_data.wanted_weapon = w; +} + +static void con_key_input_nextprev_weapon(void *result, void *user_data) +{ + con_key_input_counter(result, user_data); + input_data.wanted_weapon = 0; +} + +static void con_toggle_local_console(void *result, void *user_data) +{ + console_toggle(0); +} + +static void con_toggle_remote_console(void *result, void *user_data) +{ + console_toggle(1); +} + +static void con_emote(void *result, void *user_data) +{ + send_emoticon(console_arg_int(result, 0)); +} + +extern void con_chat(void *result, void *user_data); + +void client_console_init() +{ + console_register_print_callback(client_console_print); + + // + MACRO_REGISTER_COMMAND("toggle_local_console", "", con_toggle_local_console, 0x0); + MACRO_REGISTER_COMMAND("toggle_remote_console", "", con_toggle_remote_console, 0x0); + + // + MACRO_REGISTER_COMMAND("team", "i", con_team, 0x0); + MACRO_REGISTER_COMMAND("kill", "", con_kill, 0x0); + + // bindings + MACRO_REGISTER_COMMAND("bind", "sr", con_bind, 0x0); + MACRO_REGISTER_COMMAND("unbind", "s", con_unbind, 0x0); + MACRO_REGISTER_COMMAND("unbindall", "", con_unbindall, 0x0); + + MACRO_REGISTER_COMMAND("dump_binds", "", con_dump_binds, 0x0); + + // chatting + MACRO_REGISTER_COMMAND("say", "r", con_say, 0x0); + MACRO_REGISTER_COMMAND("say_team", "r", con_sayteam, 0x0); + MACRO_REGISTER_COMMAND("chat", "s", con_chat, 0x0); + MACRO_REGISTER_COMMAND("emote", "i", con_emote, 0); + + // game commands + MACRO_REGISTER_COMMAND("+left", "", con_key_input_state, &input_direction_left); + MACRO_REGISTER_COMMAND("+right", "", con_key_input_state, &input_direction_right); + MACRO_REGISTER_COMMAND("+jump", "", con_key_input_state, &input_data.jump); + MACRO_REGISTER_COMMAND("+hook", "", con_key_input_state, &input_data.hook); + MACRO_REGISTER_COMMAND("+fire", "", con_key_input_counter, &input_data.fire); + MACRO_REGISTER_COMMAND("+weapon1", "", con_key_input_weapon, (void *)1); + MACRO_REGISTER_COMMAND("+weapon2", "", con_key_input_weapon, (void *)2); + MACRO_REGISTER_COMMAND("+weapon3", "", con_key_input_weapon, (void *)3); + MACRO_REGISTER_COMMAND("+weapon4", "", con_key_input_weapon, (void *)4); + MACRO_REGISTER_COMMAND("+weapon5", "", con_key_input_weapon, (void *)5); + + MACRO_REGISTER_COMMAND("+nextweapon", "", con_key_input_nextprev_weapon, &input_data.next_weapon); + MACRO_REGISTER_COMMAND("+prevweapon", "", con_key_input_nextprev_weapon, &input_data.prev_weapon); + + MACRO_REGISTER_COMMAND("+emote", "", con_key_input_state, &emoticon_selector_active); + MACRO_REGISTER_COMMAND("+scoreboard", "", con_key_input_state, &scoreboard_active); + + binds_default(); +} + +bool console_input_cli(INPUT_EVENT e, void *user_data) +{ + if(!console_active()) + return false; + + if(e.key == KEY_ESC && (e.flags&INPFLAG_PRESS)) + console_toggle(console_type); + else + current_console()->handle_event(e); + return true; +} + +static bool console_execute_event(INPUT_EVENT e) +{ + // don't handle invalid events and keys that arn't set to anything + if(e.key <= 0 || e.key >= KEY_LAST || keybindings[e.key][0] == 0) + return false; + + int stroke = 0; + if(e.flags&INPFLAG_PRESS) + stroke = 1; + console_execute_line_stroked(stroke, keybindings[e.key]); + return true; +} + +bool console_input_special_binds(INPUT_EVENT e, void *user_data) +{ + // only handle function keys + if(e.key < KEY_F1 || e.key > KEY_F25) + return false; + return console_execute_event(e); +} + +bool console_input_normal_binds(INPUT_EVENT e, void *user_data) +{ + // need to be ingame for these binds + if(client_state() != CLIENTSTATE_ONLINE) + return false; + return console_execute_event(e); +} + +void console_toggle(int type) +{ + if(console_type != type && (console_state == CONSOLE_OPEN || console_state == CONSOLE_OPENING)) + { + // don't toggle console, just switch what console to use + } + else + { + if (console_state == CONSOLE_CLOSED || console_state == CONSOLE_OPEN) + { + state_change_end = time_now()+state_change_duration; + } + else + { + float progress = state_change_end-time_now(); + float reversed_progress = state_change_duration-progress; + + state_change_end = time_now()+reversed_progress; + } + + if (console_state == CONSOLE_CLOSED || console_state == CONSOLE_CLOSING) + console_state = CONSOLE_OPENING; + else + console_state = CONSOLE_CLOSING; + } + + console_type = type; +} + + + +int console_active() +{ + return console_state != CONSOLE_CLOSED; +} + +*/ diff --git a/src/game/client/components/console.hpp b/src/game/client/components/console.hpp new file mode 100644 index 00000000..988e4ea3 --- /dev/null +++ b/src/game/client/components/console.hpp @@ -0,0 +1,54 @@ +extern "C" { + #include <engine/e_client_interface.h> + #include <engine/e_config.h> + #include <engine/e_console.h> + #include <engine/e_ringbuffer.h> + #include <engine/client/ec_font.h> +} + +#include <game/client/component.hpp> + +class CONSOLE : public COMPONENT +{ + class INSTANCE + { + public: + char history_data[65536]; + RINGBUFFER *history; + char *history_entry; + + char backlog_data[65536]; + RINGBUFFER *backlog; + + LINEINPUT input; + + int type; + + public: + INSTANCE(int t); + + void execute_line(const char *line); + + void on_input(INPUT_EVENT e); + void print_line(const char *line); + }; + + INSTANCE local_console; + INSTANCE remote_console; + + INSTANCE *current_console(); + float time_now(); + + int console_type; + int console_state; + float state_change_end; + float state_change_duration; + +public: + CONSOLE(); + + virtual void on_reset(); + virtual void on_render(); + virtual void on_message(int msgtype, void *rawmsg); + virtual bool on_input(INPUT_EVENT e); +}; diff --git a/src/game/client/components/controls.cpp b/src/game/client/components/controls.cpp new file mode 100644 index 00000000..9f0ba30a --- /dev/null +++ b/src/game/client/components/controls.cpp @@ -0,0 +1,184 @@ +extern "C" { + #include <engine/e_config.h> + #include <engine/e_console.h> + #include <engine/e_client_interface.h> +} + +#include <base/math.hpp> +#include <game/collision.hpp> +#include <game/client/gameclient.hpp> +#include <game/client/component.hpp> + +#include "controls.hpp" + +CONTROLS::CONTROLS() +{ +} + +static void con_key_input_state(void *result, void *user_data) +{ + ((int *)user_data)[0] = console_arg_int(result, 0); +} + +static void con_key_input_counter(void *result, void *user_data) +{ + int *v = (int *)user_data; + if(((*v)&1) != console_arg_int(result, 0)) + (*v)++; + *v &= INPUT_STATE_MASK; +} +/* +static void con_key_input_weapon(void *result, void *user_data) +{ + int w = (char *)user_data - (char *)0; + if(console_arg_int(result, 0)) + input_data.wanted_weapon = w; +} + +static void con_key_input_nextprev_weapon(void *result, void *user_data) +{ + con_key_input_counter(result, user_data); + input_data.wanted_weapon = 0; +}*/ + +void CONTROLS::on_init() +{ + // game commands + MACRO_REGISTER_COMMAND("+left", "", con_key_input_state, &input_direction_left); + MACRO_REGISTER_COMMAND("+right", "", con_key_input_state, &input_direction_right); + MACRO_REGISTER_COMMAND("+jump", "", con_key_input_state, &input_data.jump); + MACRO_REGISTER_COMMAND("+hook", "", con_key_input_state, &input_data.hook); + MACRO_REGISTER_COMMAND("+fire", "", con_key_input_counter, &input_data.fire); + /* + MACRO_REGISTER_COMMAND("+weapon1", "", con_key_input_weapon, (void *)1); + MACRO_REGISTER_COMMAND("+weapon2", "", con_key_input_weapon, (void *)2); + MACRO_REGISTER_COMMAND("+weapon3", "", con_key_input_weapon, (void *)3); + MACRO_REGISTER_COMMAND("+weapon4", "", con_key_input_weapon, (void *)4); + MACRO_REGISTER_COMMAND("+weapon5", "", con_key_input_weapon, (void *)5); + + MACRO_REGISTER_COMMAND("+nextweapon", "", con_key_input_nextprev_weapon, &input_data.next_weapon); + MACRO_REGISTER_COMMAND("+prevweapon", "", con_key_input_nextprev_weapon, &input_data.prev_weapon); + */ +} + +int CONTROLS::snapinput(int *data) +{ + static NETOBJ_PLAYER_INPUT last_data = {0}; + static int64 last_send_time = 0; + + // update player state + /*if(chat_mode != CHATMODE_NONE) // TODO: repair me + input_data.player_state = PLAYERSTATE_CHATTING; + else if(menu_active) + input_data.player_state = PLAYERSTATE_IN_MENU; + else + input_data.player_state = PLAYERSTATE_PLAYING;*/ + last_data.player_state = input_data.player_state; + + // we freeze the input if chat or menu is activated + /* repair me + if(menu_active || chat_mode != CHATMODE_NONE || console_active()) + { + last_data.direction = 0; + last_data.hook = 0; + last_data.jump = 0; + + input_data = last_data; + + mem_copy(data, &input_data, sizeof(input_data)); + return sizeof(input_data); + }*/ + + input_data.target_x = (int)mouse_pos.x; + input_data.target_y = (int)mouse_pos.y; + if(!input_data.target_x && !input_data.target_y) + input_data.target_y = 1; + + // set direction + input_data.direction = 0; + if(input_direction_left && !input_direction_right) + input_data.direction = -1; + if(!input_direction_left && input_direction_right) + input_data.direction = 1; + + // stress testing + if(config.dbg_stress) + { + float t = client_localtime(); + mem_zero(&input_data, sizeof(input_data)); + + input_data.direction = ((int)t/2)&1; + input_data.jump = ((int)t); + input_data.fire = ((int)(t*10)); + input_data.hook = ((int)(t*2))&1; + input_data.wanted_weapon = ((int)t)%NUM_WEAPONS; + input_data.target_x = (int)(sinf(t*3)*100.0f); + input_data.target_y = (int)(cosf(t*3)*100.0f); + } + + // check if we need to send input + bool send = false; + if(input_data.direction != last_data.direction) send = true; + else if(input_data.jump != last_data.jump) send = true; + else if(input_data.fire != last_data.fire) send = true; + else if(input_data.hook != last_data.hook) send = true; + else if(input_data.player_state != last_data.player_state) send = true; + else if(input_data.wanted_weapon != last_data.wanted_weapon) send = true; + else if(input_data.next_weapon != last_data.next_weapon) send = true; + else if(input_data.prev_weapon != last_data.prev_weapon) send = true; + + if(time_get() > last_send_time + time_freq()/5) + send = true; + + last_data = input_data; + if(!send) + return 0; + + // copy and return size + last_send_time = time_get(); + mem_copy(data, &input_data, sizeof(input_data)); + return sizeof(input_data); +} + +bool CONTROLS::on_mousemove(float x, float y) +{ + mouse_pos += vec2(x, y); // TODO: ugly + + bool spectate = false; + + // + float camera_max_distance = 200.0f; + float follow_factor = config.cl_mouse_followfactor/100.0f; + float deadzone = config.cl_mouse_deadzone; + float mouse_max = min(camera_max_distance/follow_factor + deadzone, (float)config.cl_mouse_max_distance); + + //vec2 camera_offset(0, 0); + + if(spectate) + { + if(mouse_pos.x < 200.0f) mouse_pos.x = 200.0f; + if(mouse_pos.y < 200.0f) mouse_pos.y = 200.0f; + if(mouse_pos.x > col_width()*32-200.0f) mouse_pos.x = col_width()*32-200.0f; + if(mouse_pos.y > col_height()*32-200.0f) mouse_pos.y = col_height()*32-200.0f; + + target_pos = mouse_pos; + } + else + { + float l = length(mouse_pos); + + if(l > mouse_max) + { + mouse_pos = normalize(mouse_pos)*mouse_max; + l = mouse_max; + } + + target_pos = gameclient.local_character_pos + mouse_pos; + + //float offset_amount = max(l-deadzone, 0.0f) * follow_factor; + //if(l > 0.0001f) // make sure that this isn't 0 + //camera_offset = normalize(mouse_pos)*offset_amount; + } + + return true; +} diff --git a/src/game/client/components/controls.hpp b/src/game/client/components/controls.hpp new file mode 100644 index 00000000..d875522a --- /dev/null +++ b/src/game/client/components/controls.hpp @@ -0,0 +1,19 @@ +#include <base/vmath.hpp> +#include <game/client/component.hpp> + +class CONTROLS : public COMPONENT +{ +public: + vec2 mouse_pos; + vec2 target_pos; + + NETOBJ_PLAYER_INPUT input_data; + int input_direction_left; + int input_direction_right; + + CONTROLS(); + virtual bool on_mousemove(float x, float y); + virtual void on_init(); + + int snapinput(int *data); +}; diff --git a/src/game/client/components/damageind.cpp b/src/game/client/components/damageind.cpp new file mode 100644 index 00000000..15ca4b18 --- /dev/null +++ b/src/game/client/components/damageind.cpp @@ -0,0 +1,66 @@ +#include <engine/e_client_interface.h> +#include <game/generated/g_protocol.hpp> +#include <game/generated/gc_data.hpp> + +#include <game/gamecore.hpp> // get_angle +#include <game/client/gc_ui.hpp> +#include <game/client/gc_render.hpp> +#include "damageind.hpp" + +DAMAGEIND::DAMAGEIND() +{ + lastupdate = 0; + num_items = 0; +} + +DAMAGEIND::ITEM *DAMAGEIND::create_i() +{ + if (num_items < MAX_ITEMS) + { + ITEM *p = &items[num_items]; + num_items++; + return p; + } + return 0; +} + +void DAMAGEIND::destroy_i(DAMAGEIND::ITEM *i) +{ + num_items--; + *i = items[num_items]; +} + +void DAMAGEIND::create(vec2 pos, vec2 dir) +{ + ITEM *i = create_i(); + if (i) + { + i->pos = pos; + i->life = 0.75f; + i->dir = dir*-1; + i->startangle = (( (float)rand()/(float)RAND_MAX) - 1.0f) * 2.0f * pi; + } +} + +void DAMAGEIND::on_render() +{ + gfx_texture_set(data->images[IMAGE_GAME].id); + gfx_quads_begin(); + for(int i = 0; i < num_items;) + { + vec2 pos = mix(items[i].pos+items[i].dir*75.0f, items[i].pos, clamp((items[i].life-0.60f)/0.15f, 0.0f, 1.0f)); + + items[i].life -= client_frametime(); + if(items[i].life < 0.0f) + destroy_i(&items[i]); + else + { + gfx_setcolor(1.0f,1.0f,1.0f, items[i].life/0.1f); + gfx_quads_setrotation(items[i].startangle + items[i].life * 2.0f); + select_sprite(SPRITE_STAR1); + draw_sprite(pos.x, pos.y, 48.0f); + i++; + } + } + gfx_quads_end(); +} diff --git a/src/game/client/components/damageind.hpp b/src/game/client/components/damageind.hpp new file mode 100644 index 00000000..c74af9ca --- /dev/null +++ b/src/game/client/components/damageind.hpp @@ -0,0 +1,31 @@ +#include <base/vmath.hpp> +#include <game/client/component.hpp> + +class DAMAGEIND : public COMPONENT +{ + int64 lastupdate; + struct ITEM + { + vec2 pos; + vec2 dir; + float life; + float startangle; + }; + + enum + { + MAX_ITEMS=64, + }; + + ITEM items[MAX_ITEMS]; + int num_items; + + ITEM *create_i(); + void destroy_i(ITEM *i); + +public: + DAMAGEIND(); + + void create(vec2 pos, vec2 dir); + virtual void on_render(); +}; diff --git a/src/game/client/components/debughud.cpp b/src/game/client/components/debughud.cpp new file mode 100644 index 00000000..7ec7b88f --- /dev/null +++ b/src/game/client/components/debughud.cpp @@ -0,0 +1,113 @@ +#include <memory.h> // memcmp + +extern "C" { + #include <engine/e_config.h> +} + +#include <engine/e_client_interface.h> +#include <game/generated/g_protocol.hpp> +#include <game/generated/gc_data.hpp> + +#include <game/layers.hpp> + +#include <game/client/gameclient.hpp> +#include <game/client/animstate.hpp> +#include <game/client/gc_client.hpp> +#include <game/client/gc_render.hpp> + +//#include "controls.hpp" +//#include "camera.hpp" +#include "debughud.hpp" + +void DEBUGHUD::render_netcorrections() +{ + if(!config.debug || !gameclient.snap.local_character || !gameclient.snap.local_prev_character) + return; + + gfx_mapscreen(0, 0, 300*gfx_screenaspect(), 300); + + /*float speed = distance(vec2(netobjects.local_prev_character->x, netobjects.local_prev_character->y), + vec2(netobjects.local_character->x, netobjects.local_character->y));*/ + + float velspeed = length(vec2(gameclient.snap.local_character->vx/256.0f, gameclient.snap.local_character->vy/256.0f))*50; + + float ramp = velocity_ramp(velspeed, tuning.velramp_start, tuning.velramp_range, tuning.velramp_curvature); + + char buf[512]; + str_format(buf, sizeof(buf), "%.0f\n%.0f\n%.2f\n%d %s\n%d %d", + velspeed, velspeed*ramp, ramp, + netobj_num_corrections(), netobj_corrected_on(), + gameclient.snap.local_character->x, + gameclient.snap.local_character->y + ); + gfx_text(0, 150, 50, 12, buf, -1); +} + +void DEBUGHUD::render_tuning() +{ + // render tuning debugging + if(!config.dbg_tuning) + return; + + TUNING_PARAMS standard_tuning; + + gfx_mapscreen(0, 0, 300*gfx_screenaspect(), 300); + + float y = 50.0f; + int count = 0; + for(int i = 0; i < tuning.num(); i++) + { + char buf[128]; + float current, standard; + tuning.get(i, ¤t); + standard_tuning.get(i, &standard); + + if(standard == current) + gfx_text_color(1,1,1,1.0f); + else + gfx_text_color(1,0.25f,0.25f,1.0f); + + float w; + float x = 5.0f; + + str_format(buf, sizeof(buf), "%.2f", standard); + x += 20.0f; + w = gfx_text_width(0, 5, buf, -1); + gfx_text(0x0, x-w, y+count*6, 5, buf, -1); + + str_format(buf, sizeof(buf), "%.2f", current); + x += 20.0f; + w = gfx_text_width(0, 5, buf, -1); + gfx_text(0x0, x-w, y+count*6, 5, buf, -1); + + x += 5.0f; + gfx_text(0x0, x, y+count*6, 5, tuning.names[i], -1); + + count++; + } + + y = y+count*6; + + gfx_texture_set(-1); + gfx_blend_normal(); + gfx_lines_begin(); + float height = 50.0f; + float pv = 1; + for(int i = 0; i < 100; i++) + { + float speed = i/100.0f * 3000; + float ramp = velocity_ramp(speed, tuning.velramp_start, tuning.velramp_range, tuning.velramp_curvature); + float rampedspeed = (speed * ramp)/1000.0f; + gfx_lines_draw((i-1)*2, y+height-pv*height, i*2, y+height-rampedspeed*height); + //gfx_lines_draw((i-1)*2, 200, i*2, 200); + pv = rampedspeed; + } + gfx_lines_end(); + gfx_text_color(1,1,1,1); +} + +void DEBUGHUD::on_render() +{ + render_tuning(); + render_netcorrections(); +} diff --git a/src/game/client/components/debughud.hpp b/src/game/client/components/debughud.hpp new file mode 100644 index 00000000..473b2ce2 --- /dev/null +++ b/src/game/client/components/debughud.hpp @@ -0,0 +1,10 @@ +#include <game/client/component.hpp> + +class DEBUGHUD : public COMPONENT +{ + void render_netcorrections(); + void render_tuning(); +public: + virtual void on_render(); +}; + diff --git a/src/game/client/components/effects.cpp b/src/game/client/components/effects.cpp new file mode 100644 index 00000000..000d0f3a --- /dev/null +++ b/src/game/client/components/effects.cpp @@ -0,0 +1,240 @@ +#include <engine/e_client_interface.h> +//#include <gc_client.hpp> +#include <game/generated/gc_data.hpp> + +#include <game/client/components/particles.hpp> +#include <game/client/components/skins.hpp> +#include <game/client/components/flow.hpp> +#include <game/client/components/damageind.hpp> +#include <game/client/gameclient.hpp> +#include <game/client/gc_client.hpp> + +#include "effects.hpp" + +EFFECTS::EFFECTS() +{ + add_50hz = false; + add_100hz = false; +} + +void EFFECTS::air_jump(vec2 pos) +{ + PARTICLE p; + p.set_default(); + p.spr = SPRITE_PART_AIRJUMP; + p.pos = pos + vec2(-6.0f, 16.0f); + p.vel = vec2(0, -200); + p.life_span = 0.5f; + p.start_size = 48.0f; + p.end_size = 0; + p.rot = frandom()*pi*2; + p.rotspeed = pi*2; + p.gravity = 500; + p.friction = 0.7f; + p.flow_affected = 0.0f; + gameclient.particles->add(PARTICLES::GROUP_GENERAL, &p); + + p.pos = pos + vec2(6.0f, 16.0f); + gameclient.particles->add(PARTICLES::GROUP_GENERAL, &p); +} + +void EFFECTS::damage_indicator(vec2 pos, vec2 dir) +{ + gameclient.damageind->create(pos, dir); +} + +void EFFECTS::powerupshine(vec2 pos, vec2 size) +{ + if(!add_50hz) + return; + + PARTICLE p; + p.set_default(); + p.spr = SPRITE_PART_SLICE; + p.pos = pos + vec2((frandom()-0.5f)*size.x, (frandom()-0.5f)*size.y); + p.vel = vec2(0, 0); + p.life_span = 0.5f; + p.start_size = 16.0f; + p.end_size = 0; + p.rot = frandom()*pi*2; + p.rotspeed = pi*2; + p.gravity = 500; + p.friction = 0.9f; + p.flow_affected = 0.0f; + gameclient.particles->add(PARTICLES::GROUP_GENERAL, &p); +} + +void EFFECTS::smoketrail(vec2 pos, vec2 vel) +{ + if(!add_50hz) + return; + + PARTICLE p; + p.set_default(); + p.spr = SPRITE_PART_SMOKE; + p.pos = pos; + p.vel = vel + random_dir()*50.0f; + p.life_span = 0.5f + frandom()*0.5f; + p.start_size = 12.0f + frandom()*8; + p.end_size = 0; + p.friction = 0.7; + p.gravity = frandom()*-500.0f; + gameclient.particles->add(PARTICLES::GROUP_PROJECTILE_TRAIL, &p); +} + + +void EFFECTS::skidtrail(vec2 pos, vec2 vel) +{ + if(!add_100hz) + return; + + PARTICLE p; + p.set_default(); + p.spr = SPRITE_PART_SMOKE; + p.pos = pos; + p.vel = vel + random_dir()*50.0f; + p.life_span = 0.5f + frandom()*0.5f; + p.start_size = 24.0f + frandom()*12; + p.end_size = 0; + p.friction = 0.7f; + p.gravity = frandom()*-500.0f; + p.color = vec4(0.75f,0.75f,0.75f,1.0f); + gameclient.particles->add(PARTICLES::GROUP_GENERAL, &p); +} + +void EFFECTS::bullettrail(vec2 pos) +{ + if(!add_100hz) + return; + + PARTICLE p; + p.set_default(); + p.spr = SPRITE_PART_BALL; + p.pos = pos; + p.life_span = 0.25f + frandom()*0.25f; + p.start_size = 8.0f; + p.end_size = 0; + p.friction = 0.7f; + gameclient.particles->add(PARTICLES::GROUP_PROJECTILE_TRAIL, &p); +} + +void EFFECTS::playerspawn(vec2 pos) +{ + for(int i = 0; i < 32; i++) + { + PARTICLE p; + p.set_default(); + p.spr = SPRITE_PART_SHELL; + p.pos = pos; + p.vel = random_dir() * (pow(frandom(), 3)*600.0f); + p.life_span = 0.3f + frandom()*0.3f; + p.start_size = 64.0f + frandom()*32; + p.end_size = 0; + p.rot = frandom()*pi*2; + p.rotspeed = frandom(); + p.gravity = frandom()*-400.0f; + p.friction = 0.7f; + p.color = vec4(0xb5/255.0f, 0x50/255.0f, 0xcb/255.0f, 1.0f); + gameclient.particles->add(PARTICLES::GROUP_GENERAL, &p); + + } +} + +void EFFECTS::playerdeath(vec2 pos, int cid) +{ + vec3 blood_color(1.0f,1.0f,1.0f); + + if(cid >= 0) + { + const SKINS::SKIN *s = gameclient.skins->get(gameclient.clients[cid].skin_id); + if(s) + blood_color = s->blood_color; + } + + for(int i = 0; i < 64; i++) + { + PARTICLE p; + p.set_default(); + p.spr = SPRITE_PART_SPLAT01 + (rand()%3); + p.pos = pos; + p.vel = random_dir() * ((frandom()+0.1f)*900.0f); + p.life_span = 0.3f + frandom()*0.3f; + p.start_size = 24.0f + frandom()*16; + p.end_size = 0; + p.rot = frandom()*pi*2; + p.rotspeed = (frandom()-0.5f) * pi; + p.gravity = 800.0f; + p.friction = 0.8f; + vec3 c = blood_color * (0.75f + frandom()*0.25f); + p.color = vec4(c.r, c.g, c.b, 0.75f); + gameclient.particles->add(PARTICLES::GROUP_GENERAL, &p); + } +} + + +void EFFECTS::explosion(vec2 pos) +{ + // add to flow + for(int y = -8; y <= 8; y++) + for(int x = -8; x <= 8; x++) + { + if(x == 0 && y == 0) + continue; + + float a = 1 - (length(vec2(x,y)) / length(vec2(8,8))); + gameclient.flow->add(pos+vec2(x,y)*16, normalize(vec2(x,y))*5000.0f*a, 10.0f); + } + + // add the explosion + PARTICLE p; + p.set_default(); + p.spr = SPRITE_PART_EXPL01; + p.pos = pos; + p.life_span = 0.4f; + p.start_size = 150.0f; + p.end_size = 0; + p.rot = frandom()*pi*2; + gameclient.particles->add(PARTICLES::GROUP_EXPLOSIONS, &p); + + // add the smoke + for(int i = 0; i < 24; i++) + { + PARTICLE p; + p.set_default(); + p.spr = SPRITE_PART_SMOKE; + p.pos = pos; + p.vel = random_dir() * ((1.0f + frandom()*0.2f) * 1000.0f); + p.life_span = 0.5f + frandom()*0.4f; + p.start_size = 32.0f + frandom()*8; + p.end_size = 0; + p.gravity = frandom()*-800.0f; + p.friction = 0.4f; + p.color = mix(vec4(0.75f,0.75f,0.75f,1.0f), vec4(0.5f,0.5f,0.5f,1.0f), frandom()); + gameclient.particles->add(PARTICLES::GROUP_GENERAL, &p); + } +} + +void EFFECTS::on_render() +{ + static int64 last_update_100hz = 0; + static int64 last_update_50hz = 0; + + if(time_get()-last_update_100hz > time_freq()/100) + { + add_100hz = true; + last_update_100hz = time_get(); + } + else + add_100hz = false; + + if(time_get()-last_update_50hz > time_freq()/100) + { + add_50hz = true; + last_update_50hz = time_get(); + } + else + add_50hz = false; + + if(add_50hz) + gameclient.flow->update(); +} diff --git a/src/game/client/components/effects.hpp b/src/game/client/components/effects.hpp new file mode 100644 index 00000000..13af8947 --- /dev/null +++ b/src/game/client/components/effects.hpp @@ -0,0 +1,23 @@ +#include <game/client/component.hpp> + +class EFFECTS : public COMPONENT +{ + bool add_50hz; + bool add_100hz; +public: + EFFECTS(); + + virtual void on_render(); + + void bullettrail(vec2 pos); + void smoketrail(vec2 pos, vec2 vel); + void skidtrail(vec2 pos, vec2 vel); + void explosion(vec2 pos); + void air_jump(vec2 pos); + void damage_indicator(vec2 pos, vec2 dir); + void playerspawn(vec2 pos); + void playerdeath(vec2 pos, int cid); + void powerupshine(vec2 pos, vec2 size); + + void update(); +}; diff --git a/src/game/client/components/emoticon.cpp b/src/game/client/components/emoticon.cpp new file mode 100644 index 00000000..f359530a --- /dev/null +++ b/src/game/client/components/emoticon.cpp @@ -0,0 +1,121 @@ +#include <engine/e_client_interface.h> +#include <game/generated/g_protocol.hpp> +#include <game/generated/gc_data.hpp> + +#include <game/gamecore.hpp> // get_angle +#include <game/client/gc_ui.hpp> +#include <game/client/gc_render.hpp> +#include "emoticon.hpp" + +EMOTICON::EMOTICON() +{ + on_reset(); +} + +void EMOTICON::on_reset() +{ + selector_active = 0; + selected_emote = -1; +} + +void EMOTICON::on_message(int msgtype, void *rawmsg) +{ +} + +bool EMOTICON::on_input(INPUT_EVENT e) +{ + return false; +} + + +void EMOTICON::draw_circle(float x, float y, float r, int segments) +{ + float f_segments = (float)segments; + for(int i = 0; i < segments; i+=2) + { + float a1 = i/f_segments * 2*pi; + float a2 = (i+1)/f_segments * 2*pi; + float a3 = (i+2)/f_segments * 2*pi; + float ca1 = cosf(a1); + float ca2 = cosf(a2); + float ca3 = cosf(a3); + float sa1 = sinf(a1); + float sa2 = sinf(a2); + float sa3 = sinf(a3); + + gfx_quads_draw_freeform( + x, y, + x+ca1*r, y+sa1*r, + x+ca3*r, y+sa3*r, + x+ca2*r, y+sa2*r); + } +} + + +void EMOTICON::on_render() +{ + int x, y; + inp_mouse_relative(&x, &y); + + selector_mouse.x += x; + selector_mouse.y += y; + + if (length(selector_mouse) > 140) + selector_mouse = normalize(selector_mouse) * 140; + + float selected_angle = get_angle(selector_mouse) + 2*pi/24; + if (selected_angle < 0) + selected_angle += 2*pi; + + if (length(selector_mouse) > 100) + selected_emote = (int)(selected_angle / (2*pi) * 12.0f); + + RECT screen = *ui_screen(); + + gfx_mapscreen(screen.x, screen.y, screen.w, screen.h); + + gfx_blend_normal(); + + gfx_texture_set(-1); + gfx_quads_begin(); + gfx_setcolor(0,0,0,0.3f); + draw_circle(screen.w/2, screen.h/2, 160, 64); + gfx_quads_end(); + + gfx_texture_set(data->images[IMAGE_EMOTICONS].id); + gfx_quads_begin(); + + for (int i = 0; i < 12; i++) + { + float angle = 2*pi*i/12.0; + if (angle > pi) + angle -= 2*pi; + + bool selected = selected_emote == i; + + float size = selected ? 96 : 64; + + float nudge_x = 120 * cos(angle); + float nudge_y = 120 * sin(angle); + select_sprite(SPRITE_OOP + i); + gfx_quads_draw(screen.w/2 + nudge_x, screen.h/2 + nudge_y, size, size); + } + + gfx_quads_end(); + + gfx_texture_set(data->images[IMAGE_CURSOR].id); + gfx_quads_begin(); + gfx_setcolor(1,1,1,1); + gfx_quads_drawTL(selector_mouse.x+screen.w/2,selector_mouse.y+screen.h/2,24,24); + gfx_quads_end(); +} + + + +void EMOTICON::emote(int emoticon) +{ + NETMSG_CL_EMOTICON msg; + msg.emoticon = emoticon; + msg.pack(MSGFLAG_VITAL); + client_send_msg(); +} diff --git a/src/game/client/components/emoticon.hpp b/src/game/client/components/emoticon.hpp new file mode 100644 index 00000000..17e977ab --- /dev/null +++ b/src/game/client/components/emoticon.hpp @@ -0,0 +1,22 @@ +#include <base/vmath.hpp> +#include <game/client/component.hpp> + +class EMOTICON : public COMPONENT +{ + void draw_circle(float x, float y, float r, int segments); + + vec2 selector_mouse; + int selector_active; + int selected_emote; + +public: + EMOTICON(); + + virtual void on_reset(); + virtual void on_render(); + virtual void on_message(int msgtype, void *rawmsg); + virtual bool on_input(INPUT_EVENT e); + + void emote(int emoticon); +}; + diff --git a/src/game/client/components/flow.cpp b/src/game/client/components/flow.cpp new file mode 100644 index 00000000..b2f983e6 --- /dev/null +++ b/src/game/client/components/flow.cpp @@ -0,0 +1,84 @@ +#include <game/mapitems.hpp> +#include <game/layers.hpp> +#include "flow.hpp" + +FLOW::FLOW() +{ + cells = 0; + height = 0; + width = 0; + spacing = 16; +} + +void FLOW::dbg_render() +{ + if(!cells) + return; + + gfx_texture_set(-1); + gfx_lines_begin(); + for(int y = 0; y < height; y++) + for(int x = 0; x < width; x++) + { + vec2 pos(x*spacing, y*spacing); + vec2 vel = cells[y*width+x].vel * 0.01f; + gfx_lines_draw(pos.x, pos.y, pos.x+vel.x, pos.y+vel.y); + } + + gfx_lines_end(); +} + +void FLOW::init() +{ + if(cells) + { + mem_free(cells); + cells = 0; + } + + MAPITEM_LAYER_TILEMAP *tilemap = layers_game_layer(); + width = tilemap->width*32/spacing; + height = tilemap->height*32/spacing; + + // allocate and clear + cells = (CELL *)mem_alloc(sizeof(CELL)*width*height, 1); + for(int y = 0; y < height; y++) + for(int x = 0; x < width; x++) + cells[y*width+x].vel = vec2(0.0f, 0.0f); +} + +void FLOW::update() +{ + if(!cells) + return; + + for(int y = 0; y < height; y++) + for(int x = 0; x < width; x++) + cells[y*width+x].vel *= 0.85f; +} + +vec2 FLOW::get(vec2 pos) +{ + if(!cells) + return vec2(0,0); + + int x = (int)(pos.x / spacing); + int y = (int)(pos.y / spacing); + if(x < 0 || y < 0 || x >= width || y >= height) + return vec2(0,0); + + return cells[y*width+x].vel; +} + +void FLOW::add(vec2 pos, vec2 vel, float size) +{ + if(!cells) + return; + + int x = (int)(pos.x / spacing); + int y = (int)(pos.y / spacing); + if(x < 0 || y < 0 || x >= width || y >= height) + return; + + cells[y*width+x].vel += vel; +} diff --git a/src/game/client/components/flow.hpp b/src/game/client/components/flow.hpp new file mode 100644 index 00000000..351b1f69 --- /dev/null +++ b/src/game/client/components/flow.hpp @@ -0,0 +1,25 @@ +#include <base/vmath.hpp> +#include <game/client/component.hpp> + +class FLOW : public COMPONENT +{ + struct CELL + { + vec2 vel; + }; + + CELL *cells; + int height; + int width; + int spacing; + + void dbg_render(); + void init(); +public: + FLOW(); + + vec2 get(vec2 pos); + void add(vec2 pos, vec2 vel, float size); + void update(); +}; + diff --git a/src/game/client/components/hud.cpp b/src/game/client/components/hud.cpp new file mode 100644 index 00000000..0e8d371c --- /dev/null +++ b/src/game/client/components/hud.cpp @@ -0,0 +1,273 @@ +#include <memory.h> // memcmp + +extern "C" { + #include <engine/e_config.h> +} + +#include <engine/e_client_interface.h> +#include <game/generated/g_protocol.hpp> +#include <game/generated/gc_data.hpp> + +#include <game/layers.hpp> + +#include <game/client/gameclient.hpp> +#include <game/client/animstate.hpp> +#include <game/client/gc_client.hpp> +#include <game/client/gc_render.hpp> + +#include "controls.hpp" +#include "camera.hpp" +#include "hud.hpp" + +HUD::HUD() +{ + +} + +void HUD::on_reset() +{ +} + +void HUD::render_goals() +{ + // TODO: split this up into these: + // render_gametimer + // render_suddendeath + // render_scorehud + // render_warmuptimer + + int gametype = gameclient.snap.gameobj->gametype; + int gameflags = gameclient.snap.gameobj->flags; + + float whole = 300*gfx_screenaspect(); + float half = whole/2.0f; + + + gfx_mapscreen(0,0,300*gfx_screenaspect(),300); + if(!gameclient.snap.gameobj->sudden_death) + { + char buf[32]; + int time = 0; + if(gameclient.snap.gameobj->time_limit) + { + time = gameclient.snap.gameobj->time_limit*60 - ((client_tick()-gameclient.snap.gameobj->round_start_tick)/client_tickspeed()); + + if(gameclient.snap.gameobj->game_over) + time = 0; + } + else + time = (client_tick()-gameclient.snap.gameobj->round_start_tick)/client_tickspeed(); + + str_format(buf, sizeof(buf), "%d:%02d", time /60, time %60); + float w = gfx_text_width(0, 16, buf, -1); + gfx_text(0, half-w/2, 2, 16, buf, -1); + } + + if(gameclient.snap.gameobj->sudden_death) + { + const char *text = "Sudden Death"; + float w = gfx_text_width(0, 16, text, -1); + gfx_text(0, half-w/2, 2, 16, text, -1); + } + + // render small score hud + if(!(gameclient.snap.gameobj && gameclient.snap.gameobj->game_over) && (gameflags&GAMEFLAG_TEAMS)) + { + for(int t = 0; t < 2; t++) + { + gfx_blend_normal(); + gfx_texture_set(-1); + gfx_quads_begin(); + if(t == 0) + gfx_setcolor(1,0,0,0.25f); + else + gfx_setcolor(0,0,1,0.25f); + draw_round_rect(whole-40, 300-40-15+t*20, 50, 18, 5.0f); + gfx_quads_end(); + + char buf[32]; + str_format(buf, sizeof(buf), "%d", t?gameclient.snap.gameobj->teamscore_blue:gameclient.snap.gameobj->teamscore_red); + float w = gfx_text_width(0, 14, buf, -1); + + if(gametype == GAMETYPE_CTF) + { + gfx_text(0, whole-20-w/2+5, 300-40-15+t*20, 14, buf, -1); + if(gameclient.snap.flags[t]) + { + if(gameclient.snap.flags[t]->carried_by == -2 || (gameclient.snap.flags[t]->carried_by == -1 && ((client_tick()/10)&1))) + { + gfx_blend_normal(); + gfx_texture_set(data->images[IMAGE_GAME].id); + gfx_quads_begin(); + + if(t == 0) select_sprite(SPRITE_FLAG_RED); + else select_sprite(SPRITE_FLAG_BLUE); + + float size = 16; + gfx_quads_drawTL(whole-40+5, 300-40-15+t*20+1, size/2, size); + gfx_quads_end(); + } + else if(gameclient.snap.flags[t]->carried_by >= 0) + { + int id = gameclient.snap.flags[t]->carried_by%MAX_CLIENTS; + const char *name = gameclient.clients[id].name; + float w = gfx_text_width(0, 10, name, -1); + gfx_text(0, whole-40-5-w, 300-40-15+t*20+2, 10, name, -1); + TEE_RENDER_INFO info = gameclient.clients[id].render_info; + info.size = 18.0f; + + render_tee(ANIMSTATE::get_idle(), &info, EMOTE_NORMAL, vec2(1,0), + vec2(whole-40+10, 300-40-15+9+t*20+1)); + } + } + } + else + gfx_text(0, whole-20-w/2, 300-40-15+t*20, 14, buf, -1); + } + } + + // render warmup timer + if(gameclient.snap.gameobj->warmup) + { + char buf[256]; + float w = gfx_text_width(0, 24, "Warmup", -1); + gfx_text(0, 150*gfx_screenaspect()+-w/2, 50, 24, "Warmup", -1); + + int seconds = gameclient.snap.gameobj->warmup/SERVER_TICK_SPEED; + if(seconds < 5) + str_format(buf, sizeof(buf), "%d.%d", seconds, (gameclient.snap.gameobj->warmup*10/SERVER_TICK_SPEED)%10); + else + str_format(buf, sizeof(buf), "%d", seconds); + w = gfx_text_width(0, 24, buf, -1); + gfx_text(0, 150*gfx_screenaspect()+-w/2, 75, 24, buf, -1); + } +} + +static void mapscreen_to_group(float center_x, float center_y, MAPITEM_GROUP *group) +{ + float points[4]; + mapscreen_to_world(center_x, center_y, group->parallax_x/100.0f, group->parallax_y/100.0f, + group->offset_x, group->offset_y, gfx_screenaspect(), 1.0f, points); + gfx_mapscreen(points[0], points[1], points[2], points[3]); +} + +void HUD::render_fps() +{ + if(config.cl_showfps) + { + char buf[512]; + str_format(buf, sizeof(buf), "%d", (int)(1.0f/client_frametime())); + gfx_text(0, width-10-gfx_text_width(0,12,buf,-1), 10, 12, buf, -1); + } +} + +void HUD::render_connectionwarning() +{ + if(client_connection_problems()) + { + const char *text = "Connection Problems..."; + float w = gfx_text_width(0, 24, text, -1); + gfx_text(0, 150*gfx_screenaspect()-w/2, 50, 24, text, -1); + } +} + +void HUD::render_tunewarning() +{ + TUNING_PARAMS standard_tuning; + + // render warning about non standard tuning + bool flash = time_get()/(time_freq()/2)%2 == 0; + if(config.cl_warning_tuning && memcmp(&standard_tuning, &tuning, sizeof(TUNING_PARAMS)) != 0) + { + const char *text = "Warning! Server is running non-standard tuning."; + if(flash) + gfx_text_color(1,0.4f,0.4f,1.0f); + else + gfx_text_color(0.75f,0.2f,0.2f,1.0f); + gfx_text(0x0, 5, 40, 6, text, -1); + gfx_text_color(1,1,1,1); + } +} + +void HUD::render_cursor() +{ + mapscreen_to_group(gameclient.camera->center.x, gameclient.camera->center.y, layers_game_group()); + gfx_texture_set(data->images[IMAGE_GAME].id); + gfx_quads_begin(); + + // render cursor + // TODO: repair me + //if (!menu_active) + { + dbg_msg("", "%f %f", gameclient.controls->target_pos.x, gameclient.controls->target_pos.y); + select_sprite(data->weapons.id[gameclient.snap.local_character->weapon%NUM_WEAPONS].sprite_cursor); + float cursorsize = 64; + draw_sprite(gameclient.controls->target_pos.x, gameclient.controls->target_pos.y, cursorsize); + } + gfx_quads_end(); +} + +void HUD::render_healthandammo() +{ + //mapscreen_to_group(gacenter_x, center_y, layers_game_group()); + + float x = 5; + float y = 5; + + // render ammo count + // render gui stuff + gfx_quads_begin(); + gfx_mapscreen(0,0,width,300); + + // if weaponstage is active, put a "glow" around the stage ammo + select_sprite(data->weapons.id[gameclient.snap.local_character->weapon%NUM_WEAPONS].sprite_proj); + for (int i = 0; i < min(gameclient.snap.local_character->ammocount, 10); i++) + gfx_quads_drawTL(x+i*12,y+24,10,10); + + gfx_quads_end(); + + gfx_texture_set(data->images[IMAGE_GAME].id); + gfx_quads_begin(); + int h = 0; + + // render health + select_sprite(SPRITE_HEALTH_FULL); + for(; h < gameclient.snap.local_character->health; h++) + gfx_quads_drawTL(x+h*12,y,10,10); + + select_sprite(SPRITE_HEALTH_EMPTY); + for(; h < 10; h++) + gfx_quads_drawTL(x+h*12,y,10,10); + + // render armor meter + h = 0; + select_sprite(SPRITE_ARMOR_FULL); + for(; h < gameclient.snap.local_character->armor; h++) + gfx_quads_drawTL(x+h*12,y+12,10,10); + + select_sprite(SPRITE_ARMOR_EMPTY); + for(; h < 10; h++) + gfx_quads_drawTL(x+h*12,y+12,10,10); + gfx_quads_end(); +} + +void HUD::on_render() +{ + if(!gameclient.snap.gameobj) + return; + + width = 300*gfx_screenaspect(); + + bool spectate = false; + if(gameclient.snap.local_info && gameclient.snap.local_info->team == -1) + spectate = true; + + if(gameclient.snap.local_character && !spectate && !(gameclient.snap.gameobj && gameclient.snap.gameobj->game_over)) + render_healthandammo(); + + render_goals(); + render_fps(); + render_connectionwarning(); + render_tunewarning(); + render_cursor(); +} diff --git a/src/game/client/components/hud.hpp b/src/game/client/components/hud.hpp new file mode 100644 index 00000000..f8eb5e8f --- /dev/null +++ b/src/game/client/components/hud.hpp @@ -0,0 +1,21 @@ +#include <game/client/component.hpp> + +class HUD : public COMPONENT +{ + float width; + + void render_cursor(); + + void render_fps(); + void render_connectionwarning(); + void render_tunewarning(); + void render_healthandammo(); + void render_goals(); + +public: + HUD(); + + virtual void on_reset(); + virtual void on_render(); +}; + diff --git a/src/game/client/components/items.cpp b/src/game/client/components/items.cpp new file mode 100644 index 00000000..15cc7524 --- /dev/null +++ b/src/game/client/components/items.cpp @@ -0,0 +1,254 @@ +#include <engine/e_client_interface.h> +#include <game/generated/g_protocol.hpp> +#include <game/generated/gc_data.hpp> + +#include <game/gamecore.hpp> // get_angle +#include <game/client/gameclient.hpp> +#include <game/client/gc_client.hpp> +#include <game/client/gc_ui.hpp> +#include <game/client/gc_render.hpp> + +#include <game/client/components/flow.hpp> +#include <game/client/components/effects.hpp> + +#include "items.hpp" + +void ITEMS::render_projectile(const NETOBJ_PROJECTILE *current, int itemid) +{ + gfx_texture_set(data->images[IMAGE_GAME].id); + gfx_quads_begin(); + + // get positions + float curvature = 0; + float speed = 0; + if(current->type == WEAPON_GRENADE) + { + curvature = tuning.grenade_curvature; + speed = tuning.grenade_speed; + } + else if(current->type == WEAPON_SHOTGUN) + { + curvature = tuning.shotgun_curvature; + speed = tuning.shotgun_speed; + } + else if(current->type == WEAPON_GUN) + { + curvature = tuning.gun_curvature; + speed = tuning.gun_speed; + } + + float ct = (client_tick()-current->start_tick)/(float)SERVER_TICK_SPEED + client_ticktime()*1/(float)SERVER_TICK_SPEED; + vec2 startpos(current->x, current->y); + vec2 startvel(current->vx/100.0f, current->vy/100.0f); + vec2 pos = calc_pos(startpos, startvel, curvature, speed, ct); + vec2 prevpos = calc_pos(startpos, startvel, curvature, speed, ct-0.001f); + + select_sprite(data->weapons.id[clamp(current->type, 0, NUM_WEAPONS-1)].sprite_proj); + vec2 vel = pos-prevpos; + //vec2 pos = mix(vec2(prev->x, prev->y), vec2(current->x, current->y), client_intratick()); + + + // add particle for this projectile + if(current->type == WEAPON_GRENADE) + { + gameclient.effects->smoketrail(pos, vel*-1); + gameclient.flow->add(pos, vel*1000*client_frametime(), 10.0f); + gfx_quads_setrotation(client_localtime()*pi*2*2 + itemid); + } + else + { + gameclient.effects->bullettrail(pos); + gameclient.flow->add(pos, vel*1000*client_frametime(), 10.0f); + + if(length(vel) > 0.00001f) + gfx_quads_setrotation(get_angle(vel)); + else + gfx_quads_setrotation(0); + + } + + gfx_quads_draw(pos.x, pos.y, 32, 32); + gfx_quads_setrotation(0); + gfx_quads_end(); +} + +void ITEMS::render_pickup(const NETOBJ_PICKUP *prev, const NETOBJ_PICKUP *current) +{ + gfx_texture_set(data->images[IMAGE_GAME].id); + gfx_quads_begin(); + vec2 pos = mix(vec2(prev->x, prev->y), vec2(current->x, current->y), client_intratick()); + float angle = 0.0f; + float size = 64.0f; + if (current->type == POWERUP_WEAPON) + { + angle = 0; //-pi/6;//-0.25f * pi * 2.0f; + select_sprite(data->weapons.id[clamp(current->subtype, 0, NUM_WEAPONS-1)].sprite_body); + size = data->weapons.id[clamp(current->subtype, 0, NUM_WEAPONS-1)].visual_size; + } + else + { + const int c[] = { + SPRITE_PICKUP_HEALTH, + SPRITE_PICKUP_ARMOR, + SPRITE_PICKUP_WEAPON, + SPRITE_PICKUP_NINJA + }; + select_sprite(c[current->type]); + + if(c[current->type] == SPRITE_PICKUP_NINJA) + { + gameclient.effects->powerupshine(pos, vec2(96,18)); + size *= 2.0f; + pos.x += 10.0f; + } + } + + gfx_quads_setrotation(angle); + + float offset = pos.y/32.0f + pos.x/32.0f; + pos.x += cosf(client_localtime()*2.0f+offset)*2.5f; + pos.y += sinf(client_localtime()*2.0f+offset)*2.5f; + draw_sprite(pos.x, pos.y, size); + gfx_quads_end(); +} + +void ITEMS::render_flag(const NETOBJ_FLAG *prev, const NETOBJ_FLAG *current) +{ + float angle = 0.0f; + float size = 42.0f; + + gfx_blend_normal(); + gfx_texture_set(data->images[IMAGE_GAME].id); + gfx_quads_begin(); + + if(current->team == 0) // red team + select_sprite(SPRITE_FLAG_RED); + else + select_sprite(SPRITE_FLAG_BLUE); + + gfx_quads_setrotation(angle); + + vec2 pos = mix(vec2(prev->x, prev->y), vec2(current->x, current->y), client_intratick()); + + // make sure that the flag isn't interpolated between capture and return + if(prev->carried_by != current->carried_by) + pos = vec2(current->x, current->y); + + // make sure to use predicted position if we are the carrier + if(gameclient.snap.local_info && current->carried_by == gameclient.snap.local_info->cid) + pos = gameclient.local_character_pos; + + gfx_quads_draw(pos.x, pos.y-size*0.75f, size, size*2); + gfx_quads_end(); +} + + +void ITEMS::render_laser(const struct NETOBJ_LASER *current) +{ + vec2 pos = vec2(current->x, current->y); + vec2 from = vec2(current->from_x, current->from_y); + vec2 dir = normalize(pos-from); + + float ticks = client_tick() + client_intratick() - current->start_tick; + float ms = (ticks/50.0f) * 1000.0f; + float a = ms / tuning.laser_bounce_delay; + a = clamp(a, 0.0f, 1.0f); + float ia = 1-a; + + vec2 out, border; + + gfx_blend_normal(); + gfx_texture_set(-1); + gfx_quads_begin(); + + //vec4 inner_color(0.15f,0.35f,0.75f,1.0f); + //vec4 outer_color(0.65f,0.85f,1.0f,1.0f); + + // do outline + vec4 outer_color(0.075f,0.075f,0.25f,1.0f); + gfx_setcolor(outer_color.r,outer_color.g,outer_color.b,1.0f); + out = vec2(dir.y, -dir.x) * (7.0f*ia); + + gfx_quads_draw_freeform( + from.x-out.x, from.y-out.y, + from.x+out.x, from.y+out.y, + pos.x-out.x, pos.y-out.y, + pos.x+out.x, pos.y+out.y + ); + + // do inner + vec4 inner_color(0.5f,0.5f,1.0f,1.0f); + out = vec2(dir.y, -dir.x) * (5.0f*ia); + gfx_setcolor(inner_color.r, inner_color.g, inner_color.b, 1.0f); // center + + gfx_quads_draw_freeform( + from.x-out.x, from.y-out.y, + from.x+out.x, from.y+out.y, + pos.x-out.x, pos.y-out.y, + pos.x+out.x, pos.y+out.y + ); + + gfx_quads_end(); + + // render head + { + gfx_blend_normal(); + gfx_texture_set(data->images[IMAGE_PARTICLES].id); + gfx_quads_begin(); + + int sprites[] = {SPRITE_PART_SPLAT01, SPRITE_PART_SPLAT02, SPRITE_PART_SPLAT03}; + select_sprite(sprites[client_tick()%3]); + gfx_quads_setrotation(client_tick()); + gfx_setcolor(outer_color.r,outer_color.g,outer_color.b,1.0f); + gfx_quads_draw(pos.x, pos.y, 24,24); + gfx_setcolor(inner_color.r, inner_color.g, inner_color.b, 1.0f); + gfx_quads_draw(pos.x, pos.y, 20,20); + gfx_quads_end(); + } + + gfx_blend_normal(); +} + +void ITEMS::on_render() +{ + int num = snap_num_items(SNAP_CURRENT); + for(int i = 0; i < num; i++) + { + SNAP_ITEM item; + const void *data = snap_get_item(SNAP_CURRENT, i, &item); + + if(item.type == NETOBJTYPE_PROJECTILE) + { + render_projectile((const NETOBJ_PROJECTILE *)data, item.id); + } + else if(item.type == NETOBJTYPE_PICKUP) + { + const void *prev = snap_find_item(SNAP_PREV, item.type, item.id); + if(prev) + render_pickup((const NETOBJ_PICKUP *)prev, (const NETOBJ_PICKUP *)data); + } + else if(item.type == NETOBJTYPE_LASER) + { + render_laser((const NETOBJ_LASER *)data); + } + else if(item.type == NETOBJTYPE_FLAG) + { + const void *prev = snap_find_item(SNAP_PREV, item.type, item.id); + if (prev) + render_flag((const NETOBJ_FLAG *)prev, (const NETOBJ_FLAG *)data); + } + } + + // render extra projectiles + /* + for(int i = 0; i < extraproj_num; i++) + { + if(extraproj_projectiles[i].start_tick < client_tick()) + { + extraproj_projectiles[i] = extraproj_projectiles[extraproj_num-1]; + extraproj_num--; + } + else + render_projectile(&extraproj_projectiles[i], 0); + }*/ +} diff --git a/src/game/client/components/items.hpp b/src/game/client/components/items.hpp new file mode 100644 index 00000000..2f33c8c4 --- /dev/null +++ b/src/game/client/components/items.hpp @@ -0,0 +1,13 @@ +#include <game/client/component.hpp> + +class ITEMS : public COMPONENT +{ + void render_projectile(const NETOBJ_PROJECTILE *current, int itemid); + void render_pickup(const NETOBJ_PICKUP *prev, const NETOBJ_PICKUP *current); + void render_flag(const NETOBJ_FLAG *prev, const NETOBJ_FLAG *current); + void render_laser(const struct NETOBJ_LASER *current); + +public: + virtual void on_render(); +}; + diff --git a/src/game/client/components/killmessages.cpp b/src/game/client/components/killmessages.cpp new file mode 100644 index 00000000..41ba2e48 --- /dev/null +++ b/src/game/client/components/killmessages.cpp @@ -0,0 +1,130 @@ +#include <engine/e_client_interface.h> +#include <game/generated/g_protocol.hpp> +#include <game/generated/gc_data.hpp> + +#include <game/client/gameclient.hpp> +#include <game/client/animstate.hpp> +#include <game/client/gc_client.hpp> + +#include "killmessages.hpp" + +void KILLMESSAGES::on_reset() +{ + killmsg_current = 0; + for(int i = 0; i < killmsg_max; i++) + killmsgs[i].tick = -100000; +} + +void KILLMESSAGES::on_message(int msgtype, void *rawmsg) +{ + if(msgtype == NETMSGTYPE_SV_KILLMSG) + { + NETMSG_SV_KILLMSG *msg = (NETMSG_SV_KILLMSG *)rawmsg; + + // unpack messages + KILLMSG kill; + kill.killer = msg->killer; + kill.victim = msg->victim; + kill.weapon = msg->weapon; + kill.mode_special = msg->mode_special; + kill.tick = client_tick(); + + // add the message + killmsg_current = (killmsg_current+1)%killmsg_max; + killmsgs[killmsg_current] = kill; + } +} + +void KILLMESSAGES::on_render() +{ + float width = 400*3.0f*gfx_screenaspect(); + float height = 400*3.0f; + + gfx_mapscreen(0, 0, width*1.5f, height*1.5f); + float startx = width*1.5f-10.0f; + float y = 20.0f; + + for(int i = 0; i < killmsg_max; i++) + { + + int r = (killmsg_current+i+1)%killmsg_max; + if(client_tick() > killmsgs[r].tick+50*10) + continue; + + float font_size = 36.0f; + float killername_w = gfx_text_width(0, font_size, gameclient.clients[killmsgs[r].killer].name, -1); + float victimname_w = gfx_text_width(0, font_size, gameclient.clients[killmsgs[r].victim].name, -1); + + float x = startx; + + // render victim name + x -= victimname_w; + gfx_text(0, x, y, font_size, gameclient.clients[killmsgs[r].victim].name, -1); + + // render victim tee + x -= 24.0f; + + if(gameclient.snap.gameobj && gameclient.snap.gameobj->gametype == GAMETYPE_CTF) + { + if(killmsgs[r].mode_special&1) + { + gfx_blend_normal(); + gfx_texture_set(data->images[IMAGE_GAME].id); + gfx_quads_begin(); + + if(gameclient.clients[killmsgs[r].victim].team == 0) select_sprite(SPRITE_FLAG_BLUE); + else select_sprite(SPRITE_FLAG_RED); + + float size = 56.0f; + gfx_quads_drawTL(x, y-16, size/2, size); + gfx_quads_end(); + } + } + + render_tee(ANIMSTATE::get_idle(), &gameclient.clients[killmsgs[r].victim].render_info, EMOTE_PAIN, vec2(-1,0), vec2(x, y+28)); + x -= 32.0f; + + // render weapon + x -= 44.0f; + if (killmsgs[r].weapon >= 0) + { + gfx_texture_set(data->images[IMAGE_GAME].id); + gfx_quads_begin(); + select_sprite(data->weapons.id[killmsgs[r].weapon].sprite_body); + draw_sprite(x, y+28, 96); + gfx_quads_end(); + } + x -= 52.0f; + + if(killmsgs[r].victim != killmsgs[r].killer) + { + if(gameclient.snap.gameobj && gameclient.snap.gameobj->gametype == GAMETYPE_CTF) + { + if(killmsgs[r].mode_special&2) + { + gfx_blend_normal(); + gfx_texture_set(data->images[IMAGE_GAME].id); + gfx_quads_begin(); + + if(gameclient.clients[killmsgs[r].killer].team == 0) select_sprite(SPRITE_FLAG_BLUE, SPRITE_FLAG_FLIP_X); + else select_sprite(SPRITE_FLAG_RED, SPRITE_FLAG_FLIP_X); + + float size = 56.0f; + gfx_quads_drawTL(x-56, y-16, size/2, size); + gfx_quads_end(); + } + } + + // render killer tee + x -= 24.0f; + render_tee(ANIMSTATE::get_idle(), &gameclient.clients[killmsgs[r].killer].render_info, EMOTE_ANGRY, vec2(1,0), vec2(x, y+28)); + x -= 32.0f; + + // render killer name + x -= killername_w; + gfx_text(0, x, y, font_size, gameclient.clients[killmsgs[r].killer].name, -1); + } + + y += 44; + } +} diff --git a/src/game/client/components/killmessages.hpp b/src/game/client/components/killmessages.hpp new file mode 100644 index 00000000..f29e0bdf --- /dev/null +++ b/src/game/client/components/killmessages.hpp @@ -0,0 +1,24 @@ +#include <game/client/component.hpp> + +class KILLMESSAGES : public COMPONENT +{ +public: + // kill messages + struct KILLMSG + { + int weapon; + int victim; + int killer; + int mode_special; // for CTF, if the guy is carrying a flag for example + int tick; + }; + + static const int killmsg_max = 5; + KILLMSG killmsgs[killmsg_max]; + int killmsg_current; + + virtual void on_reset(); + virtual void on_render(); + virtual void on_message(int msgtype, void *rawmsg); +}; + diff --git a/src/game/client/components/maplayers.cpp b/src/game/client/components/maplayers.cpp new file mode 100644 index 00000000..8ed6264b --- /dev/null +++ b/src/game/client/components/maplayers.cpp @@ -0,0 +1,159 @@ + +extern "C" { + #include <engine/e_config.h> +} + +#include <game/layers.hpp> +#include <game/client/gameclient.hpp> +#include <game/client/component.hpp> +#include <game/client/gc_render.hpp> +#include <game/client/gc_map_image.hpp> + +#include <game/client/components/camera.hpp> + +#include "maplayers.hpp" + +MAPLAYERS::MAPLAYERS(int t) +{ + type = t; +} + + +static void mapscreen_to_group(float center_x, float center_y, MAPITEM_GROUP *group) +{ + float points[4]; + mapscreen_to_world(center_x, center_y, group->parallax_x/100.0f, group->parallax_y/100.0f, + group->offset_x, group->offset_y, gfx_screenaspect(), 1.0f, points); + gfx_mapscreen(points[0], points[1], points[2], points[3]); +} + + +static void envelope_eval(float time_offset, int env, float *channels) +{ + channels[0] = 0; + channels[1] = 0; + channels[2] = 0; + channels[3] = 0; + + ENVPOINT *points; + + { + int start, num; + map_get_type(MAPITEMTYPE_ENVPOINTS, &start, &num); + if(num) + points = (ENVPOINT *)map_get_item(start, 0, 0); + } + + int start, num; + map_get_type(MAPITEMTYPE_ENVELOPE, &start, &num); + + if(env >= num) + return; + + MAPITEM_ENVELOPE *item = (MAPITEM_ENVELOPE *)map_get_item(start+env, 0, 0); + render_eval_envelope(points+item->start_point, item->num_points, 4, client_localtime()+time_offset, channels); +} + +void MAPLAYERS::on_render() +{ + vec2 center = gameclient.camera->center; + //float center_x = gameclient.camera->center.x; + //float center_y = gameclient.camera->center.y; + + bool passed_gamelayer = false; + + for(int g = 0; g < layers_num_groups(); g++) + { + MAPITEM_GROUP *group = layers_get_group(g); + + if(group->version >= 2 && group->use_clipping) + { + // set clipping + float points[4]; + mapscreen_to_group(center.x, center.y, layers_game_group()); + gfx_getscreen(&points[0], &points[1], &points[2], &points[3]); + float x0 = (group->clip_x - points[0]) / (points[2]-points[0]); + float y0 = (group->clip_y - points[1]) / (points[3]-points[1]); + float x1 = ((group->clip_x+group->clip_w) - points[0]) / (points[2]-points[0]); + float y1 = ((group->clip_y+group->clip_h) - points[1]) / (points[3]-points[1]); + + gfx_clip_enable((int)(x0*gfx_screenwidth()), (int)(y0*gfx_screenheight()), + (int)((x1-x0)*gfx_screenwidth()), (int)((y1-y0)*gfx_screenheight())); + } + + mapscreen_to_group(center.x, center.y, group); + + for(int l = 0; l < group->num_layers; l++) + { + MAPITEM_LAYER *layer = layers_get_layer(group->start_layer+l); + bool render = false; + bool is_game_layer = false; + + // skip rendering if detail layers if not wanted + if(layer->flags&LAYERFLAG_DETAIL && !config.gfx_high_detail) + continue; + + if(layer == (MAPITEM_LAYER*)layers_game_layer()) + { + is_game_layer = true; + passed_gamelayer = 1; + } + + if(type == -1) + render = true; + else if(type == 0) + { + if(passed_gamelayer) + return; + render = true; + } + else + { + if(passed_gamelayer && !is_game_layer) + render = true; + } + + if(render && !is_game_layer) + { + //layershot_begin(); + + if(layer->type == LAYERTYPE_TILES) + { + MAPITEM_LAYER_TILEMAP *tmap = (MAPITEM_LAYER_TILEMAP *)layer; + if(tmap->image == -1) + gfx_texture_set(-1); + else + gfx_texture_set(img_get(tmap->image)); + + TILE *tiles = (TILE *)map_get_data(tmap->data); + gfx_blend_none(); + render_tilemap(tiles, tmap->width, tmap->height, 32.0f, vec4(1,1,1,1), TILERENDERFLAG_EXTEND|LAYERRENDERFLAG_OPAQUE); + gfx_blend_normal(); + render_tilemap(tiles, tmap->width, tmap->height, 32.0f, vec4(1,1,1,1), TILERENDERFLAG_EXTEND|LAYERRENDERFLAG_TRANSPARENT); + } + else if(layer->type == LAYERTYPE_QUADS) + { + MAPITEM_LAYER_QUADS *qlayer = (MAPITEM_LAYER_QUADS *)layer; + if(qlayer->image == -1) + gfx_texture_set(-1); + else + gfx_texture_set(img_get(qlayer->image)); + + QUAD *quads = (QUAD *)map_get_data_swapped(qlayer->data); + + gfx_blend_none(); + render_quads(quads, qlayer->num_quads, envelope_eval, LAYERRENDERFLAG_OPAQUE); + gfx_blend_normal(); + render_quads(quads, qlayer->num_quads, envelope_eval, LAYERRENDERFLAG_TRANSPARENT); + } + + //layershot_end(); + } + } + + gfx_clip_disable(); + } + + gfx_clip_disable(); +} + diff --git a/src/game/client/components/maplayers.hpp b/src/game/client/components/maplayers.hpp new file mode 100644 index 00000000..c2919f08 --- /dev/null +++ b/src/game/client/components/maplayers.hpp @@ -0,0 +1,16 @@ +#include <game/client/component.hpp> + +class MAPLAYERS : public COMPONENT +{ + int type; +public: + enum + { + TYPE_BACKGROUND=0, + TYPE_FOREGROUND, + }; + + MAPLAYERS(int type); + virtual void on_render(); +}; + diff --git a/src/game/client/components/menus.cpp b/src/game/client/components/menus.cpp new file mode 100644 index 00000000..fc409821 --- /dev/null +++ b/src/game/client/components/menus.cpp @@ -0,0 +1,1051 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +#include <stdio.h> +#include <math.h> +#include <string.h> +#include <stdlib.h> + +#include <base/system.h> +#include <base/math.hpp> +#include <base/vmath.hpp> + +#include "menus.hpp" +#include "skins.hpp" + +extern "C" { + #include <engine/e_client_interface.h> + #include <engine/e_config.h> + #include <engine/client/ec_font.h> +} + +#include <game/version.hpp> +#include <game/generated/g_protocol.hpp> + +#include <game/generated/gc_data.hpp> +#include <game/client/components/binds.hpp> +#include <game/client/gameclient.hpp> +#include <game/client/animstate.hpp> +#include <game/client/gc_render.hpp> +#include <game/client/gc_ui.hpp> +#include <game/client/gc_client.hpp> +#include <mastersrv/mastersrv.h> + +vec4 MENUS::gui_color; +vec4 MENUS::color_tabbar_inactive_outgame; +vec4 MENUS::color_tabbar_active_outgame; +vec4 MENUS::color_tabbar_inactive; +vec4 MENUS::color_tabbar_active; +vec4 MENUS::color_tabbar_inactive_ingame; +vec4 MENUS::color_tabbar_active_ingame; + + +MENUS::MENUS() +{ + popup = POPUP_NONE; + active_page = PAGE_INTERNET; + game_page = PAGE_GAME; + + need_restart = false; + menu_active = true; +} + +vec4 MENUS::button_color_mul(const void *id) +{ + if(ui_active_item() == id) + return vec4(1,1,1,0.5f); + else if(ui_hot_item() == id) + return vec4(1,1,1,1.5f); + return vec4(1,1,1,1); +} + +void MENUS::ui_draw_browse_icon(int what, const RECT *r) +{ + gfx_texture_set(data->images[IMAGE_BROWSEICONS].id); + gfx_quads_begin(); + select_sprite(SPRITE_BROWSE_PROGRESS1); // default + if(what == -1) + { + } + else if(what <= 100) + { + if(what < 66) + select_sprite(SPRITE_BROWSE_PROGRESS2); + else + select_sprite(SPRITE_BROWSE_PROGRESS3); + } + else if(what&0x100) + { + select_sprite(SPRITE_BROWSE_LOCK); + } + gfx_quads_drawTL(r->x,r->y,r->w,r->h); + gfx_quads_end(); +} + + +void MENUS::ui_draw_menu_button(const void *id, const char *text, int checked, const RECT *r, const void *extra) +{ + ui_draw_rect(r, vec4(1,1,1,0.5f)*button_color_mul(id), CORNER_ALL, 5.0f); + ui_do_label(r, text, 18.0f, 0); +} + +void MENUS::ui_draw_keyselect_button(const void *id, const char *text, int checked, const RECT *r, const void *extra) +{ + ui_draw_rect(r, vec4(1,1,1,0.5f)*button_color_mul(id), CORNER_ALL, 5.0f); + ui_do_label(r, text, 14.0f, 0); +} + +void MENUS::ui_draw_menu_tab_button(const void *id, const char *text, int checked, const RECT *r, const void *extra) +{ + if(checked) + ui_draw_rect(r, color_tabbar_active, CORNER_T, 10.0f); + else + ui_draw_rect(r, color_tabbar_inactive, CORNER_T, 10.0f); + ui_do_label(r, text, 22.0f, 0); +} + + +void MENUS::ui_draw_settings_tab_button(const void *id, const char *text, int checked, const RECT *r, const void *extra) +{ + if(checked) + ui_draw_rect(r, color_tabbar_active, CORNER_R, 10.0f); + else + ui_draw_rect(r, color_tabbar_inactive, CORNER_R, 10.0f); + ui_do_label(r, text, 20.0f, 0); +} + +void MENUS::ui_draw_grid_header(const void *id, const char *text, int checked, const RECT *r, const void *extra) +{ + if(checked) + ui_draw_rect(r, vec4(1,1,1,0.5f), CORNER_T, 5.0f); + RECT t; + ui_vsplit_l(r, 5.0f, 0, &t); + ui_do_label(&t, text, 14.0f, -1); +} + +void MENUS::ui_draw_list_row(const void *id, const char *text, int checked, const RECT *r, const void *extra) +{ + if(checked) + { + RECT sr = *r; + ui_margin(&sr, 1.5f, &sr); + ui_draw_rect(&sr, vec4(1,1,1,0.5f), CORNER_ALL, 4.0f); + } + ui_do_label(r, text, 14.0f, -1); +} + +void MENUS::ui_draw_checkbox_common(const void *id, const char *text, const char *boxtext, const RECT *r) +{ + RECT c = *r; + RECT t = *r; + c.w = c.h; + t.x += c.w; + t.w -= c.w; + ui_vsplit_l(&t, 5.0f, 0, &t); + + ui_margin(&c, 2.0f, &c); + ui_draw_rect(&c, vec4(1,1,1,0.25f)*button_color_mul(id), CORNER_ALL, 3.0f); + c.y += 2; + ui_do_label(&c, boxtext, 12.0f, 0); + ui_do_label(&t, text, 14.0f, -1); +} + +void MENUS::ui_draw_checkbox(const void *id, const char *text, int checked, const RECT *r, const void *extra) +{ + ui_draw_checkbox_common(id, text, checked?"X":"", r); +} + + +void MENUS::ui_draw_checkbox_number(const void *id, const char *text, int checked, const RECT *r, const void *extra) +{ + char buf[16]; + str_format(buf, sizeof(buf), "%d", checked); + ui_draw_checkbox_common(id, text, buf, r); +} + +int MENUS::ui_do_edit_box(void *id, const RECT *rect, char *str, int str_size, float font_size, bool hidden) +{ + int inside = ui_mouse_inside(rect); + int r = 0; + static int at_index = 0; + + if(ui_last_active_item() == id) + { + int len = strlen(str); + + if (inside && ui_mouse_button(0)) + { + int mx_rel = (int)(ui_mouse_x() - rect->x); + + for (int i = 1; i <= len; i++) + { + if (gfx_text_width(0, font_size, str, i) + 10 > mx_rel) + { + at_index = i - 1; + break; + } + + if (i == len) + at_index = len; + } + } + + for(int i = 0; i < inp_num_events(); i++) + { + INPUT_EVENT e = inp_get_event(i); + char c = e.ch; + int k = e.key; + + if (at_index > len) + at_index = len; + + if (!(c >= 0 && c < 32)) + { + if (len < str_size - 1 && at_index < str_size - 1) + { + memmove(str + at_index + 1, str + at_index, len - at_index + 1); + str[at_index] = c; + at_index++; + } + } + + if(e.flags&INPFLAG_PRESS) + { + if (k == KEY_BACKSPACE && at_index > 0) + { + memmove(str + at_index - 1, str + at_index, len - at_index + 1); + at_index--; + } + else if (k == KEY_DEL && at_index < len) + memmove(str + at_index, str + at_index + 1, len - at_index); + else if (k == KEY_ENTER) + ui_clear_last_active_item(); + else if (k == KEY_LEFT && at_index > 0) + at_index--; + else if (k == KEY_RIGHT && at_index < len) + at_index++; + else if (k == KEY_HOME) + at_index = 0; + else if (k == KEY_END) + at_index = len; + } + } + + r = 1; + } + + bool just_got_active = false; + + if(ui_active_item() == id) + { + if(!ui_mouse_button(0)) + ui_set_active_item(0); + } + else if(ui_hot_item() == id) + { + if(ui_mouse_button(0)) + { + if (ui_last_active_item() != id) + just_got_active = true; + ui_set_active_item(id); + } + } + + if(inside) + ui_set_hot_item(id); + + RECT textbox = *rect; + ui_draw_rect(&textbox, vec4(1,1,1,0.5f), CORNER_ALL, 5.0f); + ui_vmargin(&textbox, 5.0f, &textbox); + + const char *display_str = str; + char stars[128]; + + if(hidden) + { + unsigned s = strlen(str); + if(s >= sizeof(stars)) + s = sizeof(stars)-1; + memset(stars, '*', s); + stars[s] = 0; + display_str = stars; + } + + ui_do_label(&textbox, display_str, font_size, -1); + + if (ui_last_active_item() == id && !just_got_active) + { + float w = gfx_text_width(0, font_size, display_str, at_index); + textbox.x += w*ui_scale(); + ui_do_label(&textbox, "_", font_size, -1); + } + + return r; +} + +float MENUS::ui_do_scrollbar_v(const void *id, const RECT *rect, float current) +{ + RECT handle; + static float offset_y; + ui_hsplit_t(rect, 33, &handle, 0); + + handle.y += (rect->h-handle.h)*current; + + /* logic */ + float ret = current; + int inside = ui_mouse_inside(&handle); + + if(ui_active_item() == id) + { + if(!ui_mouse_button(0)) + ui_set_active_item(0); + + float min = rect->y; + float max = rect->h-handle.h; + float cur = ui_mouse_y()-offset_y; + ret = (cur-min)/max; + if(ret < 0.0f) ret = 0.0f; + if(ret > 1.0f) ret = 1.0f; + } + else if(ui_hot_item() == id) + { + if(ui_mouse_button(0)) + { + ui_set_active_item(id); + offset_y = ui_mouse_y()-handle.y; + } + } + + if(inside) + ui_set_hot_item(id); + + // render + RECT rail; + ui_vmargin(rect, 5.0f, &rail); + ui_draw_rect(&rail, vec4(1,1,1,0.25f), 0, 0.0f); + + RECT slider = handle; + slider.w = rail.x-slider.x; + ui_draw_rect(&slider, vec4(1,1,1,0.25f), CORNER_L, 2.5f); + slider.x = rail.x+rail.w; + ui_draw_rect(&slider, vec4(1,1,1,0.25f), CORNER_R, 2.5f); + + slider = handle; + ui_margin(&slider, 5.0f, &slider); + ui_draw_rect(&slider, vec4(1,1,1,0.25f)*button_color_mul(id), CORNER_ALL, 2.5f); + + return ret; +} + + + +float MENUS::ui_do_scrollbar_h(const void *id, const RECT *rect, float current) +{ + RECT handle; + static float offset_x; + ui_vsplit_l(rect, 33, &handle, 0); + + handle.x += (rect->w-handle.w)*current; + + /* logic */ + float ret = current; + int inside = ui_mouse_inside(&handle); + + if(ui_active_item() == id) + { + if(!ui_mouse_button(0)) + ui_set_active_item(0); + + float min = rect->x; + float max = rect->w-handle.w; + float cur = ui_mouse_x()-offset_x; + ret = (cur-min)/max; + if(ret < 0.0f) ret = 0.0f; + if(ret > 1.0f) ret = 1.0f; + } + else if(ui_hot_item() == id) + { + if(ui_mouse_button(0)) + { + ui_set_active_item(id); + offset_x = ui_mouse_x()-handle.x; + } + } + + if(inside) + ui_set_hot_item(id); + + // render + RECT rail; + ui_hmargin(rect, 5.0f, &rail); + ui_draw_rect(&rail, vec4(1,1,1,0.25f), 0, 0.0f); + + RECT slider = handle; + slider.h = rail.y-slider.y; + ui_draw_rect(&slider, vec4(1,1,1,0.25f), CORNER_T, 2.5f); + slider.y = rail.y+rail.h; + ui_draw_rect(&slider, vec4(1,1,1,0.25f), CORNER_B, 2.5f); + + slider = handle; + ui_margin(&slider, 5.0f, &slider); + ui_draw_rect(&slider, vec4(1,1,1,0.25f)*button_color_mul(id), CORNER_ALL, 2.5f); + + return ret; +} + +int MENUS::ui_do_key_reader(void *id, const RECT *rect, int key) +{ + // process + static bool mouse_released = true; + int inside = ui_mouse_inside(rect); + int new_key = key; + + if(!ui_mouse_button(0)) + mouse_released = true; + + if(ui_active_item() == id) + { + for(int i = 0; i < inp_num_events(); i++) + { + INPUT_EVENT e = inp_get_event(i); + if(e.flags&INPFLAG_PRESS && e.key && e.key != KEY_ESC) + { + new_key = e.key; + ui_set_active_item(0); + mouse_released = false; + inp_clear_events(); + break; + } + } + } + else if(ui_hot_item() == id) + { + if(ui_mouse_button(0) && mouse_released) + ui_set_active_item(id); + } + + if(inside) + ui_set_hot_item(id); + + // draw + if (ui_active_item() == id) + ui_draw_keyselect_button(id, "???", 0, rect, 0); + else + { + if(key == 0) + ui_draw_keyselect_button(id, "", 0, rect, 0); + else + ui_draw_keyselect_button(id, inp_key_name(key), 0, rect, 0); + } + return new_key; +} + + +int MENUS::render_menubar(RECT r) +{ + RECT box = r; + RECT button; + + int active_page = config.ui_page; + int new_page = -1; + + if(client_state() != CLIENTSTATE_OFFLINE) + active_page = game_page; + + if(client_state() == CLIENTSTATE_OFFLINE) + { + /* offline menus */ + if(0) // this is not done yet + { + ui_vsplit_l(&box, 90.0f, &button, &box); + static int news_button=0; + if (ui_do_button(&news_button, "News", active_page==PAGE_NEWS, &button, ui_draw_menu_tab_button, 0)) + new_page = PAGE_NEWS; + ui_vsplit_l(&box, 30.0f, 0, &box); + } + + ui_vsplit_l(&box, 110.0f, &button, &box); + static int internet_button=0; + if (ui_do_button(&internet_button, "Internet", active_page==PAGE_INTERNET, &button, ui_draw_menu_tab_button, 0)) + { + client_serverbrowse_refresh(0); + new_page = PAGE_INTERNET; + } + + ui_vsplit_l(&box, 4.0f, 0, &box); + ui_vsplit_l(&box, 90.0f, &button, &box); + static int lan_button=0; + if (ui_do_button(&lan_button, "LAN", active_page==PAGE_LAN, &button, ui_draw_menu_tab_button, 0)) + { + client_serverbrowse_refresh(1); + new_page = PAGE_LAN; + } + + if(0) // this one is not done yet + { + ui_vsplit_l(&box, 4.0f, 0, &box); + ui_vsplit_l(&box, 120.0f, &button, &box); + static int favorites_button=0; + if (ui_do_button(&favorites_button, "Favorites", active_page==PAGE_FAVORITES, &button, ui_draw_menu_tab_button, 0)) + new_page = PAGE_FAVORITES; + } + + + } + else + { + /* online menus */ + ui_vsplit_l(&box, 90.0f, &button, &box); + static int game_button=0; + if (ui_do_button(&game_button, "Game", active_page==PAGE_GAME, &button, ui_draw_menu_tab_button, 0)) + new_page = PAGE_GAME; + + ui_vsplit_l(&box, 4.0f, 0, &box); + ui_vsplit_l(&box, 140.0f, &button, &box); + static int server_info_button=0; + if (ui_do_button(&server_info_button, "Server Info", active_page==PAGE_SERVER_INFO, &button, ui_draw_menu_tab_button, 0)) + new_page = PAGE_SERVER_INFO; + + ui_vsplit_l(&box, 30.0f, 0, &box); + } + + /* + ui_vsplit_r(&box, 110.0f, &box, &button); + static int system_button=0; + if (ui_do_button(&system_button, "System", config.ui_page==PAGE_SYSTEM, &button, ui_draw_menu_tab_button, 0)) + config.ui_page = PAGE_SYSTEM; + + ui_vsplit_r(&box, 30.0f, &box, 0); + */ + + ui_vsplit_r(&box, 110.0f, &box, &button); + static int quit_button=0; + if (ui_do_button(&quit_button, "Quit", 0, &button, ui_draw_menu_tab_button, 0)) + popup = POPUP_QUIT; + + ui_vsplit_r(&box, 10.0f, &box, &button); + ui_vsplit_r(&box, 110.0f, &box, &button); + static int settings_button=0; + if (ui_do_button(&settings_button, "Settings", active_page==PAGE_SETTINGS, &button, ui_draw_menu_tab_button, 0)) + new_page = PAGE_SETTINGS; + + if(new_page != -1) + { + if(client_state() == CLIENTSTATE_OFFLINE) + config.ui_page = new_page; + else + game_page = new_page; + } + + return 0; +} + +void MENUS::render_background() +{ + RECT s = *ui_screen(); + + gfx_texture_set(-1); + gfx_quads_begin(); + vec4 bottom(gui_color.r*0.6f, gui_color.g*0.6f, gui_color.b*0.6f, 1.0f); + vec4 top(gui_color.r, gui_color.g, gui_color.b, 1.0f); + gfx_setcolorvertex(0, top.r, top.g, top.b, top.a); + gfx_setcolorvertex(1, top.r, top.g, top.b, top.a); + gfx_setcolorvertex(2, bottom.r, bottom.g, bottom.b, bottom.a); + gfx_setcolorvertex(3, bottom.r, bottom.g, bottom.b, bottom.a); + gfx_quads_drawTL(0, 0, s.w, s.h); + gfx_quads_end(); + + if(data->images[IMAGE_BANNER].id != 0) + { + gfx_texture_set(data->images[IMAGE_BANNER].id); + gfx_quads_begin(); + gfx_setcolor(0,0,0,0.05f); + gfx_quads_setrotation(-pi/4+0.15f); + gfx_quads_draw(400, 300, 1000, 250); + gfx_quads_end(); + } +} + +void MENUS::render_loading(float percent) +{ + // need up date this here to get correct + vec3 rgb = hsl_to_rgb(vec3(config.ui_color_hue/255.0f, config.ui_color_sat/255.0f, config.ui_color_lht/255.0f)); + gui_color = vec4(rgb.r, rgb.g, rgb.b, config.ui_color_alpha/255.0f); + + RECT screen = *ui_screen(); + gfx_mapscreen(screen.x, screen.y, screen.w, screen.h); + + render_background(); + + float tw; + + float w = 700; + float h = 200; + float x = screen.w/2-w/2; + float y = screen.h/2-h/2; + + gfx_blend_normal(); + + gfx_texture_set(-1); + gfx_quads_begin(); + gfx_setcolor(0,0,0,0.50f); + draw_round_rect(x, y, w, h, 40.0f); + gfx_quads_end(); + + + const char *caption = "Loading"; + + tw = gfx_text_width(0, 48.0f, caption, -1); + RECT r; + r.x = x; + r.y = y+20; + r.w = w; + r.h = h; + ui_do_label(&r, caption, 48.0f, 0, -1); + + gfx_texture_set(-1); + gfx_quads_begin(); + gfx_setcolor(1,1,1,0.75f); + draw_round_rect(x+40, y+h-75, (w-80)*percent, 25, 5.0f); + gfx_quads_end(); + + gfx_swap(); +} + +void MENUS::render_news(RECT main_view) +{ + ui_draw_rect(&main_view, color_tabbar_active, CORNER_ALL, 10.0f); +} + +void MENUS::render_game(RECT main_view) +{ + RECT button; + ui_hsplit_t(&main_view, 45.0f, &main_view, 0); + ui_draw_rect(&main_view, color_tabbar_active, CORNER_ALL, 10.0f); + + ui_hsplit_t(&main_view, 10.0f, 0, &main_view); + ui_hsplit_t(&main_view, 25.0f, &main_view, 0); + ui_vmargin(&main_view, 10.0f, &main_view); + + ui_vsplit_r(&main_view, 120.0f, &main_view, &button); + static int disconnect_button = 0; + if(ui_do_button(&disconnect_button, "Disconnect", 0, &button, ui_draw_menu_button, 0)) + client_disconnect(); + + if(gameclient.snap.local_info && gameclient.snap.gameobj) + { + if(gameclient.snap.local_info->team != -1) + { + ui_vsplit_l(&main_view, 10.0f, &button, &main_view); + ui_vsplit_l(&main_view, 120.0f, &button, &main_view); + static int spectate_button = 0; + if(ui_do_button(&spectate_button, "Spectate", 0, &button, ui_draw_menu_button, 0)) + { + gameclient.send_switch_team(-1); + menu_active = false; + } + } + + if(gameclient.snap.gameobj->flags & GAMEFLAG_TEAMS) + { + if(gameclient.snap.local_info->team != 0) + { + ui_vsplit_l(&main_view, 10.0f, &button, &main_view); + ui_vsplit_l(&main_view, 120.0f, &button, &main_view); + static int spectate_button = 0; + if(ui_do_button(&spectate_button, "Join Red", 0, &button, ui_draw_menu_button, 0)) + { + gameclient.send_switch_team(0); + menu_active = false; + } + } + + if(gameclient.snap.local_info->team != 1) + { + ui_vsplit_l(&main_view, 10.0f, &button, &main_view); + ui_vsplit_l(&main_view, 120.0f, &button, &main_view); + static int spectate_button = 0; + if(ui_do_button(&spectate_button, "Join Blue", 0, &button, ui_draw_menu_button, 0)) + { + gameclient.send_switch_team(1); + menu_active = false; + } + } + } + else + { + if(gameclient.snap.local_info->team != 0) + { + ui_vsplit_l(&main_view, 10.0f, &button, &main_view); + ui_vsplit_l(&main_view, 120.0f, &button, &main_view); + static int spectate_button = 0; + if(ui_do_button(&spectate_button, "Join Game", 0, &button, ui_draw_menu_button, 0)) + { + gameclient.send_switch_team(0); + menu_active = false; + } + } + } + } +} + +void MENUS::render_serverinfo(RECT main_view) +{ + // render background + ui_draw_rect(&main_view, color_tabbar_active, CORNER_ALL, 10.0f); + + // render motd + RECT view; + ui_margin(&main_view, 10.0f, &view); + //void gfx_text(void *font, float x, float y, float size, const char *text, int max_width); + // TODO: repair me + //gfx_text(0, view.x, view.y, 16, server_motd, -1); +} + +void MENUS::init() +{ + if(config.cl_show_welcome) + popup = POPUP_FIRST_LAUNCH; + config.cl_show_welcome = 0; +} + +int MENUS::render() +{ + RECT screen = *ui_screen(); + gfx_mapscreen(screen.x, screen.y, screen.w, screen.h); + + static bool first = true; + if(first) + { + if(config.ui_page == PAGE_INTERNET) + client_serverbrowse_refresh(0); + else if(config.ui_page == PAGE_LAN) + client_serverbrowse_refresh(1); + first = false; + } + + if(client_state() == CLIENTSTATE_ONLINE) + { + color_tabbar_inactive = color_tabbar_inactive_ingame; + color_tabbar_active = color_tabbar_active_ingame; + } + else + { + render_background(); + color_tabbar_inactive = color_tabbar_inactive_outgame; + color_tabbar_active = color_tabbar_active_outgame; + } + + RECT tab_bar; + RECT main_view; + + // some margin around the screen + ui_margin(&screen, 10.0f, &screen); + + if(popup == POPUP_NONE) + { + // do tab bar + ui_hsplit_t(&screen, 26.0f, &tab_bar, &main_view); + ui_vmargin(&tab_bar, 20.0f, &tab_bar); + render_menubar(tab_bar); + + // render current page + if(client_state() != CLIENTSTATE_OFFLINE) + { + if(game_page == PAGE_GAME) + render_game(main_view); + else if(game_page == PAGE_SERVER_INFO) + render_serverinfo(main_view); + else if(game_page == PAGE_SETTINGS) + render_settings(main_view); + } + else if(config.ui_page == PAGE_NEWS) + render_news(main_view); + else if(config.ui_page == PAGE_INTERNET) + render_serverbrowser(main_view); + else if(config.ui_page == PAGE_LAN) + render_serverbrowser(main_view); + else if(config.ui_page == PAGE_FAVORITES) + render_serverbrowser(main_view); + else if(config.ui_page == PAGE_SETTINGS) + render_settings(main_view); + } + else + { + // make sure that other windows doesn't do anything funnay! + //ui_set_hot_item(0); + //ui_set_active_item(0); + char buf[128]; + const char *title = ""; + const char *extra_text = ""; + const char *button_text = ""; + int extra_align = 0; + + if(popup == POPUP_CONNECTING) + { + title = "Connecting to"; + extra_text = config.ui_server_address; // TODO: query the client about the address + button_text = "Abort"; + if(client_mapdownload_totalsize() > 0) + { + title = "Downloading map"; + str_format(buf, sizeof(buf), "%d/%d KiB", client_mapdownload_amount()/1024, client_mapdownload_totalsize()/1024); + extra_text = buf; + } + } + else if(popup == POPUP_DISCONNECTED) + { + title = "Disconnected"; + extra_text = client_error_string(); + button_text = "Ok"; + extra_align = -1; + } + else if(popup == POPUP_PASSWORD) + { + title = "Password Error"; + extra_text = client_error_string(); + button_text = "Try Again"; + } + else if(popup == POPUP_QUIT) + { + title = "Quit"; + extra_text = "Are you sure that you want to quit?"; + } + else if(popup == POPUP_FIRST_LAUNCH) + { + title = "Welcome to Teeworlds"; + extra_text = + "As this is the first time you launch the game, please enter your nick name below. " + "It's recommended that you check the settings to adjust them to your liking " + "before joining a server."; + button_text = "Ok"; + extra_align = -1; + } + + RECT box, part; + box = screen; + ui_vmargin(&box, 150.0f, &box); + ui_hmargin(&box, 150.0f, &box); + + // render the box + ui_draw_rect(&box, vec4(0,0,0,0.5f), CORNER_ALL, 15.0f); + + ui_hsplit_t(&box, 20.f, &part, &box); + ui_hsplit_t(&box, 24.f, &part, &box); + ui_do_label(&part, title, 24.f, 0); + ui_hsplit_t(&box, 20.f, &part, &box); + ui_hsplit_t(&box, 24.f, &part, &box); + ui_vmargin(&part, 20.f, &part); + + if(extra_align == -1) + ui_do_label(&part, extra_text, 20.f, -1, (int)part.w); + else + ui_do_label(&part, extra_text, 20.f, 0, -1); + + if(popup == POPUP_QUIT) + { + RECT yes, no; + ui_hsplit_b(&box, 20.f, &box, &part); + ui_hsplit_b(&box, 24.f, &box, &part); + ui_vmargin(&part, 80.0f, &part); + + ui_vsplit_mid(&part, &no, &yes); + + ui_vmargin(&yes, 20.0f, &yes); + ui_vmargin(&no, 20.0f, &no); + + static int button_abort = 0; + if(ui_do_button(&button_abort, "No", 0, &no, ui_draw_menu_button, 0) || inp_key_down(KEY_ESC)) + popup = POPUP_NONE; + + static int button_tryagain = 0; + if(ui_do_button(&button_tryagain, "Yes", 0, &yes, ui_draw_menu_button, 0) || inp_key_down(KEY_ENTER)) + client_quit(); + } + else if(popup == POPUP_PASSWORD) + { + RECT label, textbox, tryagain, abort; + + ui_hsplit_b(&box, 20.f, &box, &part); + ui_hsplit_b(&box, 24.f, &box, &part); + ui_vmargin(&part, 80.0f, &part); + + ui_vsplit_mid(&part, &abort, &tryagain); + + ui_vmargin(&tryagain, 20.0f, &tryagain); + ui_vmargin(&abort, 20.0f, &abort); + + static int button_abort = 0; + if(ui_do_button(&button_abort, "Abort", 0, &abort, ui_draw_menu_button, 0) || inp_key_down(KEY_ESC)) + popup = POPUP_NONE; + + static int button_tryagain = 0; + if(ui_do_button(&button_tryagain, "Try again", 0, &tryagain, ui_draw_menu_button, 0) || inp_key_down(KEY_ENTER)) + { + client_connect(config.ui_server_address); + } + + ui_hsplit_b(&box, 60.f, &box, &part); + ui_hsplit_b(&box, 24.f, &box, &part); + + ui_vsplit_l(&part, 60.0f, 0, &label); + ui_vsplit_l(&label, 100.0f, 0, &textbox); + ui_vsplit_l(&textbox, 20.0f, 0, &textbox); + ui_vsplit_r(&textbox, 60.0f, &textbox, 0); + ui_do_label(&label, "Password:", 20, -1); + ui_do_edit_box(&config.password, &textbox, config.password, sizeof(config.password), 14.0f, true); + } + else if(popup == POPUP_FIRST_LAUNCH) + { + RECT label, textbox; + + ui_hsplit_b(&box, 20.f, &box, &part); + ui_hsplit_b(&box, 24.f, &box, &part); + ui_vmargin(&part, 80.0f, &part); + + static int enter_button = 0; + if(ui_do_button(&enter_button, "Enter", 0, &part, ui_draw_menu_button, 0) || inp_key_down(KEY_ENTER)) + popup = POPUP_NONE; + + ui_hsplit_b(&box, 40.f, &box, &part); + ui_hsplit_b(&box, 24.f, &box, &part); + + ui_vsplit_l(&part, 60.0f, 0, &label); + ui_vsplit_l(&label, 100.0f, 0, &textbox); + ui_vsplit_l(&textbox, 20.0f, 0, &textbox); + ui_vsplit_r(&textbox, 60.0f, &textbox, 0); + ui_do_label(&label, "Nickname:", 20, -1); + ui_do_edit_box(&config.player_name, &textbox, config.player_name, sizeof(config.player_name), 14.0f); + } + else + { + ui_hsplit_b(&box, 20.f, &box, &part); + ui_hsplit_b(&box, 24.f, &box, &part); + ui_vmargin(&part, 120.0f, &part); + + static int button = 0; + if(ui_do_button(&button, button_text, 0, &part, ui_draw_menu_button, 0) || inp_key_down(KEY_ESC) || inp_key_down(KEY_ENTER)) + { + if(popup == POPUP_CONNECTING) + client_disconnect(); + popup = POPUP_NONE; + } + } + } + + return 0; +} + +void MENUS::on_reset() +{ +} + +bool MENUS::on_mousemove(float x, float y) +{ + if(!menu_active) + return false; + + mouse_pos.x += x; + mouse_pos.y += y; + if(mouse_pos.x < 0) mouse_pos.x = 0; + if(mouse_pos.y < 0) mouse_pos.y = 0; + if(mouse_pos.x > gfx_screenwidth()) mouse_pos.x = gfx_screenwidth(); + if(mouse_pos.y > gfx_screenheight()) mouse_pos.y = gfx_screenheight(); + + return true; +} + +bool MENUS::on_input(INPUT_EVENT e) +{ + return false; +} + +void MENUS::on_statechange(int new_state, int old_state) +{ + if(new_state == CLIENTSTATE_OFFLINE) + { + popup = POPUP_NONE; + if(client_error_string() && client_error_string()[0] != 0) + { + if(strstr(client_error_string(), "password")) + { + popup = POPUP_PASSWORD; + ui_set_hot_item(&config.password); + ui_set_active_item(&config.password); + } + else + popup = POPUP_DISCONNECTED; + } } + else if(new_state == CLIENTSTATE_LOADING) + popup = POPUP_CONNECTING; + else if(new_state == CLIENTSTATE_CONNECTING) + popup = POPUP_CONNECTING; + else if (new_state == CLIENTSTATE_ONLINE) + { + popup = POPUP_NONE; + menu_active = false; + //menu_game_active = true; + //snapshot_count = 0; + } +} + +void MENUS::on_render() +{ + if(!menu_active) + return; + + // update colors + vec3 rgb = hsl_to_rgb(vec3(config.ui_color_hue/255.0f, config.ui_color_sat/255.0f, config.ui_color_lht/255.0f)); + gui_color = vec4(rgb.r, rgb.g, rgb.b, config.ui_color_alpha/255.0f); + + color_tabbar_inactive_outgame = vec4(0,0,0,0.25f); + color_tabbar_active_outgame = vec4(0,0,0,0.5f); + + float color_ingame_scale_i = 0.5f; + float color_ingame_scale_a = 0.2f; + color_tabbar_inactive_ingame = vec4( + gui_color.r*color_ingame_scale_i, + gui_color.g*color_ingame_scale_i, + gui_color.b*color_ingame_scale_i, + gui_color.a*0.8f); + + color_tabbar_active_ingame = vec4( + gui_color.r*color_ingame_scale_a, + gui_color.g*color_ingame_scale_a, + gui_color.b*color_ingame_scale_a, + gui_color.a); + + // update the ui + RECT *screen = ui_screen(); + float mx = (mouse_pos.x/(float)gfx_screenwidth())*screen->w; + float my = (mouse_pos.y/(float)gfx_screenheight())*screen->h; + + int buttons = 0; + if(inp_key_pressed(KEY_MOUSE_1)) buttons |= 1; + if(inp_key_pressed(KEY_MOUSE_2)) buttons |= 2; + if(inp_key_pressed(KEY_MOUSE_3)) buttons |= 4; + + ui_update(mx,my,mx*3.0f,my*3.0f,buttons); + + // render + render(); + + // render cursor + gfx_texture_set(data->images[IMAGE_CURSOR].id); + gfx_quads_begin(); + gfx_setcolor(1,1,1,1); + gfx_quads_drawTL(mx,my,24,24); + gfx_quads_end(); + + // render debug information + if(config.debug) + { + RECT screen = *ui_screen(); + gfx_mapscreen(screen.x, screen.y, screen.w, screen.h); + + char buf[512]; + str_format(buf, sizeof(buf), "%p %p %p", ui_hot_item(), ui_active_item(), ui_last_active_item()); + TEXT_CURSOR cursor; + gfx_text_set_cursor(&cursor, 10, 10, 10, TEXTFLAG_RENDER); + gfx_text_ex(&cursor, buf, -1); + } + +} diff --git a/src/game/client/components/menus.hpp b/src/game/client/components/menus.hpp new file mode 100644 index 00000000..4d45c65a --- /dev/null +++ b/src/game/client/components/menus.hpp @@ -0,0 +1,96 @@ +#include <base/vmath.hpp> + +#include <game/client/component.hpp> +#include <game/client/gc_ui.hpp> + +class MENUS : public COMPONENT +{ + static vec4 gui_color; + static vec4 color_tabbar_inactive_outgame; + static vec4 color_tabbar_active_outgame; + static vec4 color_tabbar_inactive_ingame; + static vec4 color_tabbar_active_ingame; + static vec4 color_tabbar_inactive; + static vec4 color_tabbar_active; + + static vec4 button_color_mul(const void *id); + + static void ui_draw_browse_icon(int what, const RECT *r); + static void ui_draw_menu_button(const void *id, const char *text, int checked, const RECT *r, const void *extra); + static void ui_draw_keyselect_button(const void *id, const char *text, int checked, const RECT *r, const void *extra); + static void ui_draw_menu_tab_button(const void *id, const char *text, int checked, const RECT *r, const void *extra); + static void ui_draw_settings_tab_button(const void *id, const char *text, int checked, const RECT *r, const void *extra); + static void ui_draw_grid_header(const void *id, const char *text, int checked, const RECT *r, const void *extra); + static void ui_draw_list_row(const void *id, const char *text, int checked, const RECT *r, const void *extra); + static void ui_draw_checkbox_common(const void *id, const char *text, const char *boxtext, const RECT *r); + static void ui_draw_checkbox(const void *id, const char *text, int checked, const RECT *r, const void *extra); + static void ui_draw_checkbox_number(const void *id, const char *text, int checked, const RECT *r, const void *extra); + static int ui_do_edit_box(void *id, const RECT *rect, char *str, int str_size, float font_size, bool hidden=false); + + static float ui_do_scrollbar_v(const void *id, const RECT *rect, float current); + static float ui_do_scrollbar_h(const void *id, const RECT *rect, float current); + + static int ui_do_key_reader(void *id, const RECT *rect, int key); + + + enum + { + POPUP_NONE=0, + POPUP_FIRST_LAUNCH, + POPUP_CONNECTING, + POPUP_DISCONNECTED, + POPUP_PASSWORD, + POPUP_QUIT, + }; + + enum + { + PAGE_NEWS=0, + PAGE_GAME, + PAGE_SERVER_INFO, + PAGE_INTERNET, + PAGE_LAN, + PAGE_FAVORITES, + PAGE_SETTINGS, + PAGE_SYSTEM, + }; + + int game_page; + int popup; + int active_page; + bool menu_active; + vec2 mouse_pos; + + // for graphic settings + bool need_restart; + + // found in menus.cpp + int render(); + void render_background(); + void render_loading(float percent); + int render_menubar(RECT r); + void render_news(RECT main_view); + void render_game(RECT main_view); + void render_serverinfo(RECT main_view); + + // found in menus_browser.cpp + void render_serverbrowser(RECT main_view); + + // found in menus_settings.cpp + void render_settings_player(RECT main_view); + void render_settings_controls(RECT main_view); + void render_settings_graphics(RECT main_view); + void render_settings_sound(RECT main_view); + void render_settings(RECT main_view); + +public: + MENUS(); + + void init(); + + virtual void on_statechange(int new_state, int old_state); + virtual void on_reset(); + virtual void on_render(); + virtual bool on_input(INPUT_EVENT e); + virtual bool on_mousemove(float x, float y); +}; diff --git a/src/game/client/components/menus_browser.cpp b/src/game/client/components/menus_browser.cpp new file mode 100644 index 00000000..05ca620b --- /dev/null +++ b/src/game/client/components/menus_browser.cpp @@ -0,0 +1,531 @@ + +#include <string.h> // strcmp, strlen, strncpy +#include <stdlib.h> // atoi + +extern "C" { + #include <engine/e_client_interface.h> + #include <engine/e_config.h> + #include <engine/client/ec_font.h> +} + +#include <game/generated/g_protocol.hpp> +#include <game/generated/gc_data.hpp> + +#include <game/client/gc_ui.hpp> +#include <game/client/gc_render.hpp> +#include "menus.hpp" + +void MENUS::render_serverbrowser(RECT main_view) +{ + ui_draw_rect(&main_view, color_tabbar_active, CORNER_ALL, 10.0f); + + RECT view; + ui_margin(&main_view, 10.0f, &view); + + RECT headers; + RECT filters; + RECT status; + RECT toolbox; + RECT server_details; + RECT server_scoreboard; + + //ui_hsplit_t(&view, 20.0f, &status, &view); + ui_hsplit_b(&view, 110.0f, &view, &filters); + + // split off a piece for details and scoreboard + ui_vsplit_r(&view, 200.0f, &view, &server_details); + + // server list + ui_hsplit_t(&view, 16.0f, &headers, &view); + //ui_hsplit_b(&view, 110.0f, &view, &filters); + ui_hsplit_b(&view, 5.0f, &view, 0); + ui_hsplit_b(&view, 20.0f, &view, &status); + + //ui_vsplit_r(&filters, 300.0f, &filters, &toolbox); + //ui_vsplit_r(&filters, 150.0f, &filters, 0); + + ui_vsplit_mid(&filters, &filters, &toolbox); + ui_vsplit_r(&filters, 50.0f, &filters, 0); + + // split of the scrollbar + ui_draw_rect(&headers, vec4(1,1,1,0.25f), CORNER_T, 5.0f); + ui_vsplit_r(&headers, 20.0f, &headers, 0); + + struct column + { + int id; + int sort; + const char *caption; + int direction; + float width; + int flags; + RECT rect; + RECT spacer; + }; + + enum + { + FIXED=1, + SPACER=2, + + COL_FLAGS=0, + COL_NAME, + COL_GAMETYPE, + COL_MAP, + COL_PLAYERS, + COL_PING, + COL_PROGRESS, + COL_VERSION, + }; + + static column cols[] = { + {-1, -1, " ", -1, 10.0f, 0, {0}, {0}}, + {COL_FLAGS, -1, " ", -1, 20.0f, 0, {0}, {0}}, + {COL_NAME, BROWSESORT_NAME, "Name", 0, 300.0f, 0, {0}, {0}}, + {COL_GAMETYPE, BROWSESORT_GAMETYPE, "Type", 1, 50.0f, 0, {0}, {0}}, + {COL_MAP, BROWSESORT_MAP, "Map", 1, 100.0f, 0, {0}, {0}}, + {COL_PLAYERS, BROWSESORT_NUMPLAYERS, "Players", 1, 60.0f, 0, {0}, {0}}, + {-1, -1, " ", 1, 10.0f, 0, {0}, {0}}, + {COL_PING, BROWSESORT_PING, "Ping", 1, 40.0f, FIXED, {0}, {0}}, + }; + + int num_cols = sizeof(cols)/sizeof(column); + + // do layout + for(int i = 0; i < num_cols; i++) + { + if(cols[i].direction == -1) + { + ui_vsplit_l(&headers, cols[i].width, &cols[i].rect, &headers); + + if(i+1 < num_cols) + { + //cols[i].flags |= SPACER; + ui_vsplit_l(&headers, 2, &cols[i].spacer, &headers); + } + } + } + + for(int i = num_cols-1; i >= 0; i--) + { + if(cols[i].direction == 1) + { + ui_vsplit_r(&headers, cols[i].width, &headers, &cols[i].rect); + ui_vsplit_r(&headers, 2, &headers, &cols[i].spacer); + } + } + + for(int i = 0; i < num_cols; i++) + { + if(cols[i].direction == 0) + cols[i].rect = headers; + } + + // do headers + for(int i = 0; i < num_cols; i++) + { + if(ui_do_button(cols[i].caption, cols[i].caption, config.b_sort == cols[i].sort, &cols[i].rect, ui_draw_grid_header, 0)) + { + if(cols[i].sort != -1) + { + if(config.b_sort == cols[i].sort) + config.b_sort_order ^= 1; + else + config.b_sort_order = 0; + config.b_sort = cols[i].sort; + } + } + } + + ui_draw_rect(&view, vec4(0,0,0,0.15f), 0, 0); + + RECT scroll; + ui_vsplit_r(&view, 15, &view, &scroll); + + int num_servers = client_serverbrowse_sorted_num(); + + int num = (int)(view.h/cols[0].rect.h); + static int scrollbar = 0; + static float scrollvalue = 0; + //static int selected_index = -1; + ui_hmargin(&scroll, 5.0f, &scroll); + scrollvalue = ui_do_scrollbar_v(&scrollbar, &scroll, scrollvalue); + + int scrollnum = num_servers-num+10; + if(scrollnum > 0) + { + if(inp_key_presses(KEY_MOUSE_WHEEL_UP)) + scrollvalue -= 1.0f/scrollnum; + if(inp_key_presses(KEY_MOUSE_WHEEL_DOWN)) + scrollvalue += 1.0f/scrollnum; + + if(scrollvalue < 0) scrollvalue = 0; + if(scrollvalue > 1) scrollvalue = 1; + } + else + scrollnum = 0; + + // set clipping + ui_clip_enable(&view); + + int start = (int)(scrollnum*scrollvalue); + if(start < 0) + start = 0; + + RECT original_view = view; + view.y -= scrollvalue*scrollnum*cols[0].rect.h; + + int new_selected = -1; + int selected_index = -1; + int num_players = 0; + + for (int i = 0; i < num_servers; i++) + { + SERVER_INFO *item = client_serverbrowse_sorted_get(i); + num_players += item->num_players; + } + + for (int i = 0; i < num_servers; i++) + { + int item_index = i; + SERVER_INFO *item = client_serverbrowse_sorted_get(item_index); + RECT row; + RECT select_hit_box; + + int selected = strcmp(item->address, config.ui_server_address) == 0; //selected_index==item_index; + + ui_hsplit_t(&view, 17.0f, &row, &view); + select_hit_box = row; + + if(selected) + { + selected_index = i; + RECT r = row; + ui_margin(&r, 1.5f, &r); + ui_draw_rect(&r, vec4(1,1,1,0.5f), CORNER_ALL, 4.0f); + } + + + // make sure that only those in view can be selected + if(row.y+row.h > original_view.y) + { + if(select_hit_box.y < original_view.y) // clip the selection + { + select_hit_box.h -= original_view.y-select_hit_box.y; + select_hit_box.y = original_view.y; + } + + if(ui_do_button(item, "", selected, &select_hit_box, 0, 0)) + { + new_selected = item_index; + } + } + + // check if we need to do more + if(row.y > original_view.y+original_view.h) + break; + + for(int c = 0; c < num_cols; c++) + { + RECT button; + char temp[64]; + button.x = cols[c].rect.x; + button.y = row.y; + button.h = row.h; + button.w = cols[c].rect.w; + + //int s = 0; + int id = cols[c].id; + + //s = ui_do_button(item, "L", l, &button, ui_draw_browse_icon, 0); + + if(id == COL_FLAGS) + { + if(item->flags&1) + ui_draw_browse_icon(0x100, &button); + } + else if(id == COL_NAME) + { + TEXT_CURSOR cursor; + gfx_text_set_cursor(&cursor, button.x, button.y, 12.0f, TEXTFLAG_RENDER); + + if(config.b_filter_string[0] && (item->quicksearch_hit&BROWSEQUICK_SERVERNAME)) + { + // highlight the parts that matches + const char *s = str_find_nocase(item->name, config.b_filter_string); + if(s) + { + gfx_text_ex(&cursor, item->name, (int)(s-item->name)); + gfx_text_color(0.4f,0.4f,1.0f,1); + gfx_text_ex(&cursor, s, strlen(config.b_filter_string)); + gfx_text_color(1,1,1,1); + gfx_text_ex(&cursor, s+strlen(config.b_filter_string), -1); + } + else + gfx_text_ex(&cursor, item->name, -1); + } + else + gfx_text_ex(&cursor, item->name, -1); + } + else if(id == COL_MAP) + ui_do_label(&button, item->map, 12.0f, -1); + else if(id == COL_PLAYERS) + { + str_format(temp, sizeof(temp), "%i/%i", item->num_players, item->max_players); + if(config.b_filter_string[0] && (item->quicksearch_hit&BROWSEQUICK_PLAYERNAME)) + gfx_text_color(0.4f,0.4f,1.0f,1); + ui_do_label(&button, temp, 12.0f, 1); + gfx_text_color(1,1,1,1); + } + else if(id == COL_PING) + { + str_format(temp, sizeof(temp), "%i", item->latency); + ui_do_label(&button, temp, 12.0f, 1); + } + else if(id == COL_PROGRESS) + { + if(item->progression > 100) + item->progression = 100; + ui_draw_browse_icon(item->progression, &button); + } + else if(id == COL_VERSION) + { + const char *version = item->version; + if(strcmp(version, "0.3 e2d7973c6647a13c") == 0) // TODO: remove me later on + version = "0.3.0"; + ui_do_label(&button, version, 12.0f, 1); + } + else if(id == COL_GAMETYPE) + ui_do_label(&button, item->gametype, 12.0f, 0); + } + } + + ui_clip_disable(); + + if(new_selected != -1) + { + // select the new server + SERVER_INFO *item = client_serverbrowse_sorted_get(new_selected); + strncpy(config.ui_server_address, item->address, sizeof(config.ui_server_address)); + if(inp_mouse_doubleclick()) + client_connect(config.ui_server_address); + } + + SERVER_INFO *selected_server = client_serverbrowse_sorted_get(selected_index); + RECT server_header; + + ui_vsplit_l(&server_details, 10.0f, 0x0, &server_details); + + // split off a piece to use for scoreboard + ui_hsplit_t(&server_details, 140.0f, &server_details, &server_scoreboard); + ui_hsplit_b(&server_details, 10.0f, &server_details, 0x0); + + // server details + const float font_size = 12.0f; + ui_hsplit_t(&server_details, 20.0f, &server_header, &server_details); + ui_draw_rect(&server_header, vec4(1,1,1,0.25f), CORNER_T, 4.0f); + ui_draw_rect(&server_details, vec4(0,0,0,0.15f), CORNER_B, 4.0f); + ui_vsplit_l(&server_header, 8.0f, 0x0, &server_header); + ui_do_label(&server_header, "Server Details: ", font_size+2.0f, -1); + + ui_vsplit_l(&server_details, 5.0f, 0x0, &server_details); + + ui_margin(&server_details, 3.0f, &server_details); + + if (selected_server) + { + RECT row; + static const char *labels[] = { "Version:", "Game Type:", "Progression:", "Ping:" }; + + RECT left_column; + RECT right_column; + + ui_vsplit_l(&server_details, 5.0f, 0x0, &server_details); + ui_vsplit_l(&server_details, 80.0f, &left_column, &right_column); + + for (int i = 0; i < 4; i++) + { + ui_hsplit_t(&left_column, 15.0f, &row, &left_column); + ui_do_label(&row, labels[i], font_size, -1); + } + + ui_hsplit_t(&right_column, 15.0f, &row, &right_column); + ui_do_label(&row, selected_server->version, font_size, -1); + + ui_hsplit_t(&right_column, 15.0f, &row, &right_column); + ui_do_label(&row, selected_server->gametype, font_size, -1); + + char temp[16]; + + if(selected_server->progression < 0) + str_format(temp, sizeof(temp), "N/A"); + else + str_format(temp, sizeof(temp), "%d%%", selected_server->progression); + ui_hsplit_t(&right_column, 15.0f, &row, &right_column); + ui_do_label(&row, temp, font_size, -1); + + str_format(temp, sizeof(temp), "%d", selected_server->latency); + ui_hsplit_t(&right_column, 15.0f, &row, &right_column); + ui_do_label(&row, temp, font_size, -1); + } + + // server scoreboard + ui_hsplit_b(&server_scoreboard, 10.0f, &server_scoreboard, 0x0); + ui_hsplit_t(&server_scoreboard, 20.0f, &server_header, &server_scoreboard); + ui_draw_rect(&server_header, vec4(1,1,1,0.25f), CORNER_T, 4.0f); + ui_draw_rect(&server_scoreboard, vec4(0,0,0,0.15f), CORNER_B, 4.0f); + ui_vsplit_l(&server_header, 8.0f, 0x0, &server_header); + ui_do_label(&server_header, "Scoreboard: ", font_size+2.0f, -1); + + ui_vsplit_l(&server_scoreboard, 5.0f, 0x0, &server_scoreboard); + + ui_margin(&server_scoreboard, 3.0f, &server_scoreboard); + + if (selected_server) + { + for (int i = 0; i < selected_server->num_players; i++) + { + RECT row; + char temp[16]; + ui_hsplit_t(&server_scoreboard, 16.0f, &row, &server_scoreboard); + + str_format(temp, sizeof(temp), "%d", selected_server->players[i].score); + ui_do_label(&row, temp, font_size, -1); + + ui_vsplit_l(&row, 25.0f, 0x0, &row); + + TEXT_CURSOR cursor; + gfx_text_set_cursor(&cursor, row.x, row.y, 12.0f, TEXTFLAG_RENDER); + + const char *name = selected_server->players[i].name; + if(config.b_filter_string[0]) + { + // highlight the parts that matches + const char *s = str_find_nocase(name, config.b_filter_string); + if(s) + { + gfx_text_ex(&cursor, name, (int)(s-name)); + gfx_text_color(0.4f,0.4f,1,1); + gfx_text_ex(&cursor, s, strlen(config.b_filter_string)); + gfx_text_color(1,1,1,1); + gfx_text_ex(&cursor, s+strlen(config.b_filter_string), -1); + } + else + gfx_text_ex(&cursor, name, -1); + } + else + gfx_text_ex(&cursor, name, -1); + + /*ui_do_label(&row, selected_server->player_names[i], font_size, -1);*/ + } + } + + RECT button; + RECT types; + ui_hsplit_t(&filters, 20.0f, &button, &filters); + ui_do_label(&button, "Quick search: ", 14.0f, -1); + ui_vsplit_l(&button, 95.0f, 0, &button); + ui_do_edit_box(&config.b_filter_string, &button, config.b_filter_string, sizeof(config.b_filter_string), 14.0f); + + ui_vsplit_l(&filters, 180.0f, &filters, &types); + + // render filters + ui_hsplit_t(&filters, 20.0f, &button, &filters); + if (ui_do_button(&config.b_filter_empty, "Has people playing", config.b_filter_empty, &button, ui_draw_checkbox, 0)) + config.b_filter_empty ^= 1; + + ui_hsplit_t(&filters, 20.0f, &button, &filters); + if (ui_do_button(&config.b_filter_full, "Server not full", config.b_filter_full, &button, ui_draw_checkbox, 0)) + config.b_filter_full ^= 1; + + ui_hsplit_t(&filters, 20.0f, &button, &filters); + if (ui_do_button(&config.b_filter_pw, "No password", config.b_filter_pw, &button, ui_draw_checkbox, 0)) + config.b_filter_pw ^= 1; + + ui_hsplit_t(&filters, 20.0f, &button, &filters); + if (ui_do_button((char *)&config.b_filter_compatversion, "Compatible Version", config.b_filter_compatversion, &button, ui_draw_checkbox, 0)) + config.b_filter_compatversion ^= 1; + + // game types + /* + ui_hsplit_t(&types, 20.0f, &button, &types); + if (ui_do_button(&config.b_filter_gametype, "DM", config.b_filter_gametype&(1<<GAME_TYPE_DM), &button, ui_draw_checkbox, 0)) + config.b_filter_gametype ^= (1<<GAME_TYPE_DM); + + ui_hsplit_t(&types, 20.0f, &button, &types); + if (ui_do_button((char *)&config.b_filter_gametype + 1, "TDM", config.b_filter_gametype&(1<<GAME_TYPE_TDM), &button, ui_draw_checkbox, 0)) + config.b_filter_gametype ^= (1<<GAME_TYPE_TDM); + + ui_hsplit_t(&types, 20.0f, &button, &types); + if (ui_do_button((char *)&config.b_filter_gametype + 2, "CTF", config.b_filter_gametype&(1<<GAME_TYPE_CTF), &button, ui_draw_checkbox, 0)) + config.b_filter_gametype ^= (1<<GAME_TYPE_CTF); + */ + + // ping + ui_hsplit_t(&types, 2.0f, &button, &types); + ui_hsplit_t(&types, 20.0f, &button, &types); + { + RECT editbox; + ui_vsplit_l(&button, 40.0f, &editbox, &button); + ui_vsplit_l(&button, 5.0f, &button, &button); + + char buf[8]; + str_format(buf, sizeof(buf), "%d", config.b_filter_ping); + ui_do_edit_box(&config.b_filter_ping, &editbox, buf, sizeof(buf), 14.0f); + config.b_filter_ping = atoi(buf); + + ui_do_label(&button, "Maximum ping", 14.0f, -1); + } + + + // render status + ui_draw_rect(&status, vec4(1,1,1,0.25f), CORNER_B, 5.0f); + ui_vmargin(&status, 50.0f, &status); + char buf[128]; + str_format(buf, sizeof(buf), "%d of %d servers, %d players", client_serverbrowse_sorted_num(), client_serverbrowse_num(), num_players); + ui_do_label(&status, buf, 14.0f, -1); + + // render toolbox + { + RECT buttons, button; + ui_hsplit_b(&toolbox, 25.0f, &toolbox, &buttons); + + ui_vsplit_r(&buttons, 100.0f, &buttons, &button); + ui_vmargin(&button, 2.0f, &button); + static int join_button = 0; + if(ui_do_button(&join_button, "Connect", 0, &button, ui_draw_menu_button, 0)) + client_connect(config.ui_server_address); + + ui_vsplit_r(&buttons, 20.0f, &buttons, &button); + ui_vsplit_r(&buttons, 100.0f, &buttons, &button); + ui_vmargin(&button, 2.0f, &button); + static int refresh_button = 0; + if(ui_do_button(&refresh_button, "Refresh", 0, &button, ui_draw_menu_button, 0)) + { + if(config.ui_page == PAGE_INTERNET) + client_serverbrowse_refresh(0); + else if(config.ui_page == PAGE_LAN) + client_serverbrowse_refresh(1); + } + + //ui_vsplit_r(&buttons, 30.0f, &buttons, &button); + ui_vsplit_l(&buttons, 120.0f, &button, &buttons); + static int clear_button = 0; + if(ui_do_button(&clear_button, "Reset Filter", 0, &button, ui_draw_menu_button, 0)) + { + config.b_filter_full = 0; + config.b_filter_empty = 0; + config.b_filter_pw = 0; + config.b_filter_ping = 999; + config.b_filter_gametype = 0xf; + config.b_filter_compatversion = 1; + config.b_filter_string[0] = 0; + } + + + ui_hsplit_t(&toolbox, 20.0f, &button, &toolbox); + ui_do_label(&button, "Host address:", 14.0f, -1); + ui_vsplit_l(&button, 100.0f, 0, &button); + ui_do_edit_box(&config.ui_server_address, &button, config.ui_server_address, sizeof(config.ui_server_address), 14.0f); + } +} diff --git a/src/game/client/components/menus_settings.cpp b/src/game/client/components/menus_settings.cpp new file mode 100644 index 00000000..f031c20f --- /dev/null +++ b/src/game/client/components/menus_settings.cpp @@ -0,0 +1,567 @@ + +#include <base/math.hpp> + +#include <string.h> // strcmp, strlen, strncpy +#include <stdlib.h> // atoi + +extern "C" { + #include <engine/e_client_interface.h> + #include <engine/e_config.h> + #include <engine/client/ec_font.h> +} + +#include <game/generated/g_protocol.hpp> +#include <game/generated/gc_data.hpp> + +#include <game/client/gc_ui.hpp> +#include <game/client/gc_render.hpp> +#include <game/client/gameclient.hpp> +#include <game/client/animstate.hpp> + +#include "binds.hpp" +#include "menus.hpp" +#include "skins.hpp" + +void MENUS::render_settings_player(RECT main_view) +{ + RECT button; + RECT skinselection; + ui_vsplit_l(&main_view, 300.0f, &main_view, &skinselection); + + + ui_hsplit_t(&main_view, 20.0f, &button, &main_view); + + // render settings + { + ui_hsplit_t(&main_view, 20.0f, &button, &main_view); + ui_do_label(&button, "Name:", 14.0, -1); + ui_vsplit_l(&button, 80.0f, 0, &button); + ui_vsplit_l(&button, 180.0f, &button, 0); + ui_do_edit_box(config.player_name, &button, config.player_name, sizeof(config.player_name), 14.0f); + + static int dynamic_camera_button = 0; + ui_hsplit_t(&main_view, 20.0f, &button, &main_view); + if(ui_do_button(&dynamic_camera_button, "Dynamic Camera", config.cl_mouse_deadzone != 0, &button, ui_draw_checkbox, 0)) + { + + if(config.cl_mouse_deadzone) + { + config.cl_mouse_followfactor = 0; + config.cl_mouse_max_distance = 400; + config.cl_mouse_deadzone = 0; + } + else + { + config.cl_mouse_followfactor = 60; + config.cl_mouse_max_distance = 1000; + config.cl_mouse_deadzone = 300; + } + } + + ui_hsplit_t(&main_view, 20.0f, &button, &main_view); + if (ui_do_button(&config.cl_autoswitch_weapons, "Switch weapon on pickup", config.cl_autoswitch_weapons, &button, ui_draw_checkbox, 0)) + config.cl_autoswitch_weapons ^= 1; + + ui_hsplit_t(&main_view, 20.0f, &button, &main_view); + if (ui_do_button(&config.cl_nameplates, "Show name plates", config.cl_nameplates, &button, ui_draw_checkbox, 0)) + config.cl_nameplates ^= 1; + + //if(config.cl_nameplates) + { + ui_hsplit_t(&main_view, 20.0f, &button, &main_view); + ui_vsplit_l(&button, 15.0f, 0, &button); + if (ui_do_button(&config.cl_nameplates_always, "Always show name plates", config.cl_nameplates_always, &button, ui_draw_checkbox, 0)) + config.cl_nameplates_always ^= 1; + } + + ui_hsplit_t(&main_view, 20.0f, &button, &main_view); + + ui_hsplit_t(&main_view, 20.0f, &button, &main_view); + if (ui_do_button(&config.player_color_body, "Custom colors", config.player_use_custom_color, &button, ui_draw_checkbox, 0)) + config.player_use_custom_color = config.player_use_custom_color?0:1; + + if(config.player_use_custom_color) + { + int *colors[2]; + colors[0] = &config.player_color_body; + colors[1] = &config.player_color_feet; + + const char *parts[] = {"Body", "Feet"}; + const char *labels[] = {"Hue", "Sat.", "Lht."}; + static int color_slider[2][3] = {{0}}; + //static float v[2][3] = {{0, 0.5f, 0.25f}, {0, 0.5f, 0.25f}}; + + for(int i = 0; i < 2; i++) + { + RECT text; + ui_hsplit_t(&main_view, 20.0f, &text, &main_view); + ui_vsplit_l(&text, 15.0f, 0, &text); + ui_do_label(&text, parts[i], 14.0f, -1); + + int prevcolor = *colors[i]; + int color = 0; + for(int s = 0; s < 3; s++) + { + RECT text; + ui_hsplit_t(&main_view, 19.0f, &button, &main_view); + ui_vsplit_l(&button, 30.0f, 0, &button); + ui_vsplit_l(&button, 30.0f, &text, &button); + ui_vsplit_r(&button, 5.0f, &button, 0); + ui_hsplit_t(&button, 4.0f, 0, &button); + + float k = ((prevcolor>>((2-s)*8))&0xff) / 255.0f; + k = ui_do_scrollbar_h(&color_slider[i][s], &button, k); + color <<= 8; + color += clamp((int)(k*255), 0, 255); + ui_do_label(&text, labels[s], 15.0f, -1); + + } + + *colors[i] = color; + ui_hsplit_t(&main_view, 5.0f, 0, &main_view); + } + } + } + + // draw header + RECT header, footer; + ui_hsplit_t(&skinselection, 20, &header, &skinselection); + ui_draw_rect(&header, vec4(1,1,1,0.25f), CORNER_T, 5.0f); + ui_do_label(&header, "Skins", 18.0f, 0); + + // draw footers + ui_hsplit_b(&skinselection, 20, &skinselection, &footer); + ui_draw_rect(&footer, vec4(1,1,1,0.25f), CORNER_B, 5.0f); + ui_vsplit_l(&footer, 10.0f, 0, &footer); + + // modes + ui_draw_rect(&skinselection, vec4(0,0,0,0.15f), 0, 0); + + RECT scroll; + ui_vsplit_r(&skinselection, 15, &skinselection, &scroll); + + RECT list = skinselection; + ui_hsplit_t(&list, 50, &button, &list); + + int num = (int)(skinselection.h/button.h); + static float scrollvalue = 0; + static int scrollbar = 0; + ui_hmargin(&scroll, 5.0f, &scroll); + scrollvalue = ui_do_scrollbar_v(&scrollbar, &scroll, scrollvalue); + + int start = (int)((gameclient.skins->num()-num)*scrollvalue); + if(start < 0) + start = 0; + + for(int i = start; i < start+num && i < gameclient.skins->num(); i++) + { + const SKINS::SKIN *s = gameclient.skins->get(i); + + // no special skins + if(s->name[0] == 'x' && s->name[1] == '_') + { + num++; + continue; + } + + char buf[128]; + str_format(buf, sizeof(buf), "%s", s->name); + int selected = 0; + if(strcmp(s->name, config.player_skin) == 0) + selected = 1; + + TEE_RENDER_INFO info; + info.texture = s->org_texture; + info.color_body = vec4(1,1,1,1); + info.color_feet = vec4(1,1,1,1); + if(config.player_use_custom_color) + { + info.color_body = gameclient.skins->get_color(config.player_color_body); + info.color_feet = gameclient.skins->get_color(config.player_color_feet); + info.texture = s->color_texture; + } + + info.size = ui_scale()*50.0f; + + RECT icon; + RECT text; + ui_vsplit_l(&button, 50.0f, &icon, &text); + + if(ui_do_button(s, "", selected, &button, ui_draw_list_row, 0)) + config_set_player_skin(&config, s->name); + + ui_hsplit_t(&text, 12.0f, 0, &text); // some margin from the top + ui_do_label(&text, buf, 18.0f, 0); + + ui_hsplit_t(&icon, 5.0f, 0, &icon); // some margin from the top + render_tee(ANIMSTATE::get_idle(), &info, 0, vec2(1, 0), vec2(icon.x+icon.w/2, icon.y+icon.h/2)); + + if(config.debug) + { + gfx_texture_set(-1); + gfx_quads_begin(); + gfx_setcolor(s->blood_color.r, s->blood_color.g, s->blood_color.b, 1.0f); + gfx_quads_drawTL(icon.x, icon.y, 12, 12); + gfx_quads_end(); + } + + ui_hsplit_t(&list, 50, &button, &list); + } +} + +typedef void (*assign_func_callback)(CONFIGURATION *config, int value); + +void MENUS::render_settings_controls(RECT main_view) +{ + RECT right_part; + ui_vsplit_l(&main_view, 300.0f, &main_view, &right_part); + + { + RECT button, label; + ui_hsplit_t(&main_view, 20.0f, &button, &main_view); + ui_vsplit_l(&button, 110.0f, &label, &button); + ui_do_label(&label, "Mouse sens.", 14.0f, -1); + ui_hmargin(&button, 2.0f, &button); + config.inp_mousesens = (int)(ui_do_scrollbar_h(&config.inp_mousesens, &button, (config.inp_mousesens-5)/500.0f)*500.0f)+5; + //*key.key = ui_do_key_reader(key.key, &button, *key.key); + ui_hsplit_t(&main_view, 20.0f, 0, &main_view); + } + + typedef struct + { + const char *name; + const char *command; + int keyid; + } KEYINFO; + + KEYINFO keys[] = + { + { "Move Left:", "+left", 0}, + { "Move Right:", "+right", 0 }, + { "Jump:", "+jump", 0 }, + { "Fire:", "+fire", 0 }, + { "Hook:", "+hook", 0 }, + { "Hammer:", "+weapon1", 0 }, + { "Pistol:", "+weapon2", 0 }, + { "Shotgun:", "+weapon3", 0 }, + { "Grenade:", "+weapon4", 0 }, + { "Rifle:", "+weapon5", 0 }, + { "Next Weapon:", "+nextweapon", 0 }, + { "Prev. Weapon:", "+prevweapon", 0 }, + { "Emoticon:", "+emote", 0 }, + { "Chat:", "chat all", 0 }, + { "Team Chat:", "chat team", 0 }, + { "Console:", "toggle_local_console", 0 }, + { "Remote Console:", "toggle_remote_console", 0 }, + { "Screenshot:", "screenshot", 0 }, + }; + + const int key_count = sizeof(keys) / sizeof(KEYINFO); + + // this is kinda slow, but whatever + for(int keyid = 0; keyid < KEY_LAST; keyid++) + { + const char *bind = gameclient.binds->get(keyid); + if(!bind[0]) + continue; + + for(int i = 0; i < key_count; i++) + if(strcmp(bind, keys[i].command) == 0) + { + keys[i].keyid = keyid; + break; + } + } + + for (int i = 0; i < key_count; i++) + { + KEYINFO key = keys[i]; + RECT button, label; + ui_hsplit_t(&main_view, 20.0f, &button, &main_view); + ui_vsplit_l(&button, 110.0f, &label, &button); + + ui_do_label(&label, key.name, 14.0f, -1); + int oldid = key.keyid; + int newid = ui_do_key_reader((void *)keys[i].name, &button, oldid); + if(newid != oldid) + { + gameclient.binds->bind(oldid, ""); + gameclient.binds->bind(newid, keys[i].command); + } + ui_hsplit_t(&main_view, 5.0f, 0, &main_view); + } + + // defaults + RECT button; + ui_hsplit_b(&right_part, 25.0f, &right_part, &button); + ui_vsplit_l(&button, 50.0f, 0, &button); + static int default_button = 0; + if (ui_do_button((void*)&default_button, "Reset to defaults", 0, &button, ui_draw_menu_button, 0)) + gameclient.binds->set_defaults(); +} + +void MENUS::render_settings_graphics(RECT main_view) +{ + RECT button; + char buf[128]; + + static const int MAX_RESOLUTIONS = 256; + static VIDEO_MODE modes[MAX_RESOLUTIONS]; + static int num_modes = -1; + + if(num_modes == -1) + num_modes = gfx_get_video_modes(modes, MAX_RESOLUTIONS); + + RECT modelist; + ui_vsplit_l(&main_view, 300.0f, &main_view, &modelist); + + // draw allmodes switch + RECT header, footer; + ui_hsplit_t(&modelist, 20, &button, &modelist); + if (ui_do_button(&config.gfx_display_all_modes, "Show only supported", config.gfx_display_all_modes^1, &button, ui_draw_checkbox, 0)) + { + config.gfx_display_all_modes ^= 1; + num_modes = gfx_get_video_modes(modes, MAX_RESOLUTIONS); + } + + // draw header + ui_hsplit_t(&modelist, 20, &header, &modelist); + ui_draw_rect(&header, vec4(1,1,1,0.25f), CORNER_T, 5.0f); + ui_do_label(&header, "Display Modes", 14.0f, 0); + + // draw footers + ui_hsplit_b(&modelist, 20, &modelist, &footer); + str_format(buf, sizeof(buf), "Current: %dx%d %d bit", config.gfx_screen_width, config.gfx_screen_height, config.gfx_color_depth); + ui_draw_rect(&footer, vec4(1,1,1,0.25f), CORNER_B, 5.0f); + ui_vsplit_l(&footer, 10.0f, 0, &footer); + ui_do_label(&footer, buf, 14.0f, -1); + + // modes + ui_draw_rect(&modelist, vec4(0,0,0,0.15f), 0, 0); + + RECT scroll; + ui_vsplit_r(&modelist, 15, &modelist, &scroll); + + RECT list = modelist; + ui_hsplit_t(&list, 20, &button, &list); + + int num = (int)(modelist.h/button.h); + static float scrollvalue = 0; + static int scrollbar = 0; + ui_hmargin(&scroll, 5.0f, &scroll); + scrollvalue = ui_do_scrollbar_v(&scrollbar, &scroll, scrollvalue); + + int start = (int)((num_modes-num)*scrollvalue); + if(start < 0) + start = 0; + + for(int i = start; i < start+num && i < num_modes; i++) + { + int depth = modes[i].red+modes[i].green+modes[i].blue; + if(depth < 16) + depth = 16; + else if(depth > 16) + depth = 24; + + int selected = 0; + if(config.gfx_color_depth == depth && + config.gfx_screen_width == modes[i].width && + config.gfx_screen_height == modes[i].height) + { + selected = 1; + } + + str_format(buf, sizeof(buf), " %dx%d %d bit", modes[i].width, modes[i].height, depth); + if(ui_do_button(&modes[i], buf, selected, &button, ui_draw_list_row, 0)) + { + config.gfx_color_depth = depth; + config.gfx_screen_width = modes[i].width; + config.gfx_screen_height = modes[i].height; + if(!selected) + need_restart = true; + } + + ui_hsplit_t(&list, 20, &button, &list); + } + + + // switches + ui_hsplit_t(&main_view, 20.0f, &button, &main_view); + if (ui_do_button(&config.gfx_fullscreen, "Fullscreen", config.gfx_fullscreen, &button, ui_draw_checkbox, 0)) + { + config.gfx_fullscreen ^= 1; + need_restart = true; + } + + ui_hsplit_t(&main_view, 20.0f, &button, &main_view); + if (ui_do_button(&config.gfx_vsync, "V-Sync", config.gfx_vsync, &button, ui_draw_checkbox, 0)) + config.gfx_vsync ^= 1; + + ui_hsplit_t(&main_view, 20.0f, &button, &main_view); + if (ui_do_button(&config.gfx_fsaa_samples, "FSAA samples", config.gfx_fsaa_samples, &button, ui_draw_checkbox_number, 0)) + { + if(config.gfx_fsaa_samples < 2) config.gfx_fsaa_samples = 2; + else if(config.gfx_fsaa_samples < 4) config.gfx_fsaa_samples = 4; + else if(config.gfx_fsaa_samples < 6) config.gfx_fsaa_samples = 6; + else if(config.gfx_fsaa_samples < 8) config.gfx_fsaa_samples = 8; + else if(config.gfx_fsaa_samples < 16) config.gfx_fsaa_samples = 16; + else if(config.gfx_fsaa_samples >= 16) config.gfx_fsaa_samples = 0; + need_restart = true; + } + + ui_hsplit_t(&main_view, 40.0f, &button, &main_view); + ui_hsplit_t(&main_view, 20.0f, &button, &main_view); + if (ui_do_button(&config.gfx_texture_quality, "Quality Textures", config.gfx_texture_quality, &button, ui_draw_checkbox, 0)) + { + config.gfx_texture_quality ^= 1; + need_restart = true; + } + + ui_hsplit_t(&main_view, 20.0f, &button, &main_view); + if (ui_do_button(&config.gfx_texture_compression, "Texture Compression", config.gfx_texture_compression, &button, ui_draw_checkbox, 0)) + { + config.gfx_texture_compression ^= 1; + need_restart = true; + } + + ui_hsplit_t(&main_view, 20.0f, &button, &main_view); + if (ui_do_button(&config.gfx_high_detail, "High Detail", config.gfx_high_detail, &button, ui_draw_checkbox, 0)) + config.gfx_high_detail ^= 1; + + // + + RECT text; + ui_hsplit_t(&main_view, 20.0f, 0, &main_view); + ui_hsplit_t(&main_view, 20.0f, &text, &main_view); + //ui_vsplit_l(&text, 15.0f, 0, &text); + ui_do_label(&text, "UI Color", 14.0f, -1); + + const char *labels[] = {"Hue", "Sat.", "Lht.", "Alpha"}; + int *color_slider[4] = {&config.ui_color_hue, &config.ui_color_sat, &config.ui_color_lht, &config.ui_color_alpha}; + for(int s = 0; s < 4; s++) + { + RECT text; + ui_hsplit_t(&main_view, 19.0f, &button, &main_view); + ui_vmargin(&button, 15.0f, &button); + ui_vsplit_l(&button, 30.0f, &text, &button); + ui_vsplit_r(&button, 5.0f, &button, 0); + ui_hsplit_t(&button, 4.0f, 0, &button); + + float k = (*color_slider[s]) / 255.0f; + k = ui_do_scrollbar_h(color_slider[s], &button, k); + *color_slider[s] = (int)(k*255.0f); + ui_do_label(&text, labels[s], 15.0f, -1); + } +} + +void MENUS::render_settings_sound(RECT main_view) +{ + RECT button; + ui_vsplit_l(&main_view, 300.0f, &main_view, 0); + + ui_hsplit_t(&main_view, 20.0f, &button, &main_view); + if (ui_do_button(&config.snd_enable, "Use Sounds", config.snd_enable, &button, ui_draw_checkbox, 0)) + { + config.snd_enable ^= 1; + need_restart = true; + } + + if(!config.snd_enable) + return; + + ui_hsplit_t(&main_view, 20.0f, &button, &main_view); + if (ui_do_button(&config.snd_nonactive_mute, "Mute when not active", config.snd_nonactive_mute, &button, ui_draw_checkbox, 0)) + config.snd_nonactive_mute ^= 1; + + // sample rate box + { + char buf[64]; + str_format(buf, sizeof(buf), "%d", config.snd_rate); + ui_hsplit_t(&main_view, 20.0f, &button, &main_view); + ui_do_label(&button, "Sample Rate", 14.0f, -1); + ui_vsplit_l(&button, 110.0f, 0, &button); + ui_vsplit_l(&button, 180.0f, &button, 0); + ui_do_edit_box(&config.snd_rate, &button, buf, sizeof(buf), 14.0f); + int before = config.snd_rate; + config.snd_rate = atoi(buf); + + if(config.snd_rate != before) + need_restart = true; + + if(config.snd_rate < 1) + config.snd_rate = 1; + } + + // volume slider + { + RECT button, label; + ui_hsplit_t(&main_view, 5.0f, &button, &main_view); + ui_hsplit_t(&main_view, 20.0f, &button, &main_view); + ui_vsplit_l(&button, 110.0f, &label, &button); + ui_hmargin(&button, 2.0f, &button); + ui_do_label(&label, "Sound Volume", 14.0f, -1); + config.snd_volume = (int)(ui_do_scrollbar_h(&config.snd_volume, &button, config.snd_volume/100.0f)*100.0f); + ui_hsplit_t(&main_view, 20.0f, 0, &main_view); + } +} + + + /* +static void menu2_render_settings_network(RECT main_view) +{ + RECT button; + ui_vsplit_l(&main_view, 300.0f, &main_view, 0); + + { + ui_hsplit_t(&main_view, 20.0f, &button, &main_view); + ui_do_label(&button, "Rcon Password", 14.0, -1); + ui_vsplit_l(&button, 110.0f, 0, &button); + ui_vsplit_l(&button, 180.0f, &button, 0); + ui_do_edit_box(&config.rcon_password, &button, config.rcon_password, sizeof(config.rcon_password), true); + } +}*/ + +void MENUS::render_settings(RECT main_view) +{ + static int settings_page = 0; + + // render background + RECT temp, tabbar; + ui_vsplit_r(&main_view, 120.0f, &main_view, &tabbar); + ui_draw_rect(&main_view, color_tabbar_active, CORNER_B|CORNER_TL, 10.0f); + ui_hsplit_t(&tabbar, 50.0f, &temp, &tabbar); + ui_draw_rect(&temp, color_tabbar_active, CORNER_R, 10.0f); + + ui_hsplit_t(&main_view, 10.0f, 0, &main_view); + + RECT button; + + const char *tabs[] = {"Player", "Controls", "Graphics", "Sound"}; + int num_tabs = (int)(sizeof(tabs)/sizeof(*tabs)); + + for(int i = 0; i < num_tabs; i++) + { + ui_hsplit_t(&tabbar, 10, &button, &tabbar); + ui_hsplit_t(&tabbar, 26, &button, &tabbar); + if(ui_do_button(tabs[i], tabs[i], settings_page == i, &button, ui_draw_settings_tab_button, 0)) + settings_page = i; + } + + ui_margin(&main_view, 10.0f, &main_view); + + if(settings_page == 0) + render_settings_player(main_view); + else if(settings_page == 1) + render_settings_controls(main_view); + else if(settings_page == 2) + render_settings_graphics(main_view); + else if(settings_page == 3) + render_settings_sound(main_view); + + if(need_restart) + { + RECT restart_warning; + ui_hsplit_b(&main_view, 40, &main_view, &restart_warning); + ui_do_label(&restart_warning, "You must restart the game for all settings to take effect.", 15.0f, -1, 220); + } +} diff --git a/src/game/client/components/motd.cpp b/src/game/client/components/motd.cpp new file mode 100644 index 00000000..bd04d089 --- /dev/null +++ b/src/game/client/components/motd.cpp @@ -0,0 +1,75 @@ +#include <engine/e_client_interface.h> +#include <engine/e_config.h> +#include <game/generated/g_protocol.hpp> +#include <game/generated/gc_data.hpp> + +#include <game/client/gameclient.hpp> +//#include <game/client/gc_anim.hpp> +#include <game/client/gc_client.hpp> + +#include "motd.hpp" + +void MOTD::on_reset() +{ + server_motd_time = 0; +} + +void MOTD::on_render() +{ + float width = 400*3.0f*gfx_screenaspect(); + float height = 400*3.0f; + + // TODO: repair me + if(/* !do_scoreboard && */ time_get() < server_motd_time) + { + gfx_mapscreen(0, 0, width, height); + + float h = 800.0f; + float w = 650.0f; + float x = width/2 - w/2; + float y = 150.0f; + + gfx_blend_normal(); + gfx_texture_set(-1); + gfx_quads_begin(); + gfx_setcolor(0,0,0,0.5f); + draw_round_rect(x, y, w, h, 40.0f); + gfx_quads_end(); + + gfx_text(0, x+40.0f, y+40.0f, 32.0f, server_motd, (int)(w-80.0f)); + } +} + +void MOTD::on_message(int msgtype, void *rawmsg) +{ + if(msgtype == NETMSGTYPE_SV_MOTD) + { + NETMSG_SV_MOTD *msg = (NETMSG_SV_MOTD *)rawmsg; + + // process escaping + str_copy(server_motd, msg->message, sizeof(server_motd)); + for(int i = 0; server_motd[i]; i++) + { + if(server_motd[i] == '\\') + { + if(server_motd[i+1] == 'n') + { + server_motd[i] = ' '; + server_motd[i+1] = '\n'; + i++; + } + } + } + + if(server_motd[0] && config.cl_motd_time) + server_motd_time = time_get()+time_freq()*config.cl_motd_time; + else + server_motd_time = 0; + } +} + +bool MOTD::on_input(INPUT_EVENT e) +{ + return false; +} + diff --git a/src/game/client/components/motd.hpp b/src/game/client/components/motd.hpp new file mode 100644 index 00000000..e26dff9b --- /dev/null +++ b/src/game/client/components/motd.hpp @@ -0,0 +1,15 @@ +#include <game/client/component.hpp> + +class MOTD : public COMPONENT +{ +public: + // motd + int64 server_motd_time; + char server_motd[900]; // FUGLY + + virtual void on_reset(); + virtual void on_render(); + virtual void on_message(int msgtype, void *rawmsg); + virtual bool on_input(INPUT_EVENT e); +}; + diff --git a/src/game/client/components/particles.cpp b/src/game/client/components/particles.cpp new file mode 100644 index 00000000..0c449263 --- /dev/null +++ b/src/game/client/components/particles.cpp @@ -0,0 +1,155 @@ +#include <base/math.hpp> +#include <game/generated/gc_data.hpp> +#include <game/client/gc_render.hpp> +#include "particles.hpp" + +PARTICLES::PARTICLES() +{ + on_reset(); + render_trail.parts = this; + render_explosions.parts = this; + render_general.parts = this; +} + + +void PARTICLES::on_reset() +{ + // reset particles + for(int i = 0; i < MAX_PARTICLES; i++) + { + particles[i].prev_part = i-1; + particles[i].next_part = i+1; + } + + particles[0].prev_part = 0; + particles[MAX_PARTICLES-1].next_part = -1; + first_free = 0; + + for(int i = 0; i < NUM_GROUPS; i++) + first_part[i] = -1; +} + +void PARTICLES::add(int group, PARTICLE *part) +{ + if (first_free == -1) + return; + + // remove from the free list + int id = first_free; + first_free = particles[id].next_part; + particles[first_free].prev_part = -1; + + // copy data + particles[id] = *part; + + // insert to the group list + particles[id].prev_part = -1; + particles[id].next_part = first_part[group]; + if(first_part[group] != -1) + particles[first_part[group]].prev_part = id; + first_part[group] = id; + + // set some parameters + particles[id].life = 0; +} + +void PARTICLES::update(float time_passed) +{ + static float friction_fraction = 0; + friction_fraction += time_passed; + + if(friction_fraction > 2.0f) // safty messure + friction_fraction = 0; + + int friction_count = 0; + while(friction_fraction > 0.05f) + { + friction_count++; + friction_fraction -= 0.05f; + } + + for(int g = 0; g < NUM_GROUPS; g++) + { + int i = first_part[g]; + while(i != -1) + { + int next = particles[i].next_part; + //particles[i].vel += flow_get(particles[i].pos)*time_passed * particles[i].flow_affected; + particles[i].vel.y += particles[i].gravity*time_passed; + + for(int f = 0; f < friction_count; f++) // apply friction + particles[i].vel *= particles[i].friction; + + // move the point + vec2 vel = particles[i].vel*time_passed; + + // TODO: repair me + //move_point(&particles[i].pos, &vel, 0.1f+0.9f*frandom(), NULL); + 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].life_span) + { + // remove it from the group list + if(particles[i].prev_part != -1) + particles[particles[i].prev_part].next_part = particles[i].next_part; + else + first_part[g] = particles[i].next_part; + + if(particles[i].next_part != -1) + particles[particles[i].next_part].prev_part = particles[i].prev_part; + + // insert to the free list + if(first_free != -1) + particles[first_free].prev_part = i; + particles[i].prev_part = -1; + particles[i].next_part = first_free; + first_free = i; + } + + i = next; + } + } +} + +void PARTICLES::on_render() +{ + static int64 lasttime = 0; + int64 t = time_get(); + update((float)((t-lasttime)/(double)time_freq())); + lasttime = t; +} + +void PARTICLES::render_group(int group) +{ + gfx_blend_normal(); + //gfx_blend_additive(); + gfx_texture_set(data->images[IMAGE_PARTICLES].id); + gfx_quads_begin(); + + int i = first_part[group]; + while(i != -1) + { + select_sprite(particles[i].spr); + float a = particles[i].life / particles[i].life_span; + vec2 p = particles[i].pos; + float size = mix(particles[i].start_size, particles[i].end_size, a); + + gfx_quads_setrotation(particles[i].rot); + + gfx_setcolor( + particles[i].color.r, + particles[i].color.g, + particles[i].color.b, + particles[i].color.a); // pow(a, 0.75f) * + + gfx_quads_draw(p.x, p.y, size, size); + + i = particles[i].next_part; + } + gfx_quads_end(); + gfx_blend_normal(); +} diff --git a/src/game/client/components/particles.hpp b/src/game/client/components/particles.hpp new file mode 100644 index 00000000..6c466d94 --- /dev/null +++ b/src/game/client/components/particles.hpp @@ -0,0 +1,91 @@ +#include <base/vmath.hpp> +#include <game/client/component.hpp> + +// particles +struct PARTICLE +{ + void set_default() + { + vel = vec2(0,0); + life_span = 0; + start_size = 32; + end_size = 32; + rot = 0; + rotspeed = 0; + gravity = 0; + friction = 0; + flow_affected = 1.0f; + color = vec4(1,1,1,1); + } + + vec2 pos; + vec2 vel; + + int spr; + + float flow_affected; + + float life_span; + + float start_size; + float end_size; + + float rot; + float rotspeed; + + float gravity; + float friction; + + vec4 color; + + // set by the particle system + float life; + int prev_part; + int next_part; +}; + +class PARTICLES : public COMPONENT +{ + friend class GAMECLIENT; +public: + enum + { + GROUP_PROJECTILE_TRAIL=0, + GROUP_EXPLOSIONS, + GROUP_GENERAL, + NUM_GROUPS + }; + + PARTICLES(); + + void add(int group, PARTICLE *part); + + virtual void on_reset(); + virtual void on_render(); + +private: + + enum + { + MAX_PARTICLES=1024*8, + }; + + PARTICLE particles[MAX_PARTICLES]; + int first_free; + int first_part[NUM_GROUPS]; + + void render_group(int group); + void update(float time_passed); + + template<int TGROUP> + class RENDER_GROUP : public COMPONENT + { + public: + PARTICLES *parts; + virtual void on_render() { parts->render_group(TGROUP); } + }; + + RENDER_GROUP<GROUP_PROJECTILE_TRAIL> render_trail; + RENDER_GROUP<GROUP_EXPLOSIONS> render_explosions; + RENDER_GROUP<GROUP_GENERAL> render_general; +}; diff --git a/src/game/client/components/players.cpp b/src/game/client/components/players.cpp new file mode 100644 index 00000000..3178b82f --- /dev/null +++ b/src/game/client/components/players.cpp @@ -0,0 +1,463 @@ + +extern "C" { + #include <engine/e_config.h> +} + +#include <engine/e_client_interface.h> +#include <game/generated/g_protocol.hpp> +#include <game/generated/gc_data.hpp> + +#include <game/gamecore.hpp> // get_angle +#include <game/client/animstate.hpp> +#include <game/client/gameclient.hpp> +#include <game/client/gc_client.hpp> +#include <game/client/gc_ui.hpp> +#include <game/client/gc_render.hpp> + +#include <game/client/components/flow.hpp> +#include <game/client/components/skins.hpp> +#include <game/client/components/effects.hpp> + +#include "players.hpp" + +void PLAYERS::render_hand(TEE_RENDER_INFO *info, vec2 center_pos, vec2 dir, float angle_offset, vec2 post_rot_offset) +{ + // for drawing hand + //const skin *s = skin_get(skin_id); + + float basesize = 10.0f; + //dir = normalize(hook_pos-pos); + + vec2 hand_pos = center_pos + dir; + float angle = get_angle(dir); + if (dir.x < 0) + angle -= angle_offset; + else + angle += angle_offset; + + vec2 dirx = dir; + vec2 diry(-dir.y,dir.x); + + if (dir.x < 0) + diry = -diry; + + hand_pos += dirx * post_rot_offset.x; + hand_pos += diry * post_rot_offset.y; + + //gfx_texture_set(data->images[IMAGE_CHAR_DEFAULT].id); + gfx_texture_set(info->texture); + gfx_quads_begin(); + gfx_setcolor(info->color_body.r, info->color_body.g, info->color_body.b, info->color_body.a); + + // two passes + for (int i = 0; i < 2; i++) + { + bool outline = i == 0; + + select_sprite(outline?SPRITE_TEE_HAND_OUTLINE:SPRITE_TEE_HAND, 0, 0, 0); + gfx_quads_setrotation(angle); + gfx_quads_draw(hand_pos.x, hand_pos.y, 2*basesize, 2*basesize); + } + + gfx_quads_setrotation(0); + gfx_quads_end(); +} + +void PLAYERS::render_player( + const NETOBJ_CHARACTER *prev_char, + const NETOBJ_CHARACTER *player_char, + const NETOBJ_PLAYER_INFO *prev_info, + const NETOBJ_PLAYER_INFO *player_info + ) +{ + NETOBJ_CHARACTER prev; + NETOBJ_CHARACTER player; + prev = *prev_char; + player = *player_char; + + NETOBJ_PLAYER_INFO info = *player_info; + TEE_RENDER_INFO render_info = gameclient.clients[info.cid].render_info; + + // check for teamplay modes + bool is_teamplay = false; + if(gameclient.snap.gameobj) + is_teamplay = gameclient.snap.gameobj->flags&GAMEFLAG_TEAMS != 0; + + // check for ninja + if (player.weapon == WEAPON_NINJA) + { + // change the skin for the player to the ninja + int skin = gameclient.skins->find("x_ninja"); + if(skin != -1) + { + if(is_teamplay) + render_info.texture = gameclient.skins->get(skin)->color_texture; + else + { + render_info.texture = gameclient.skins->get(skin)->org_texture; + render_info.color_body = vec4(1,1,1,1); + render_info.color_feet = vec4(1,1,1,1); + } + } + } + + // set size + render_info.size = 64.0f; + + float intratick = client_intratick(); + float ticktime = client_ticktime(); + + if(player.health < 0) // dont render dead players + return; + + //float angle = mix((float)prev.angle, (float)player.angle, intratick)/256.0f; + + // TODO: fix this good! + float mixspeed = 0.05f; + if(player.attacktick != prev.attacktick) + mixspeed = 0.1f; + + float angle = mix(gameclient.clients[info.cid].angle, player.angle/256.0f, mixspeed); + gameclient.clients[info.cid].angle = angle; + vec2 direction = get_direction((int)(angle*256.0f)); + + if(info.local && config.cl_predict) + { + if(!gameclient.snap.local_character || (gameclient.snap.local_character->health < 0) || (gameclient.snap.gameobj && gameclient.snap.gameobj->game_over)) + { + } + else + { + // apply predicted results + predicted_char.write(&player); + predicted_prev_char.write(&prev); + intratick = client_predintratick(); + } + } + + vec2 position = mix(vec2(prev.x, prev.y), vec2(player.x, player.y), intratick); + vec2 vel = mix(vec2(prev.vx/256.0f, prev.vy/256.0f), vec2(player.vx/256.0f, player.vy/256.0f), intratick); + + gameclient.flow->add(position, vel*100.0f, 10.0f); + + render_info.got_airjump = player.jumped&2?0:1; + + if(prev.health < 0) // Don't flicker from previous position + position = vec2(player.x, player.y); + + bool stationary = player.vx < 1 && player.vx > -1; + bool inair = col_check_point(player.x, player.y+16) == 0; + bool want_other_dir = (player.wanted_direction == -1 && vel.x > 0) || (player.wanted_direction == 1 && vel.x < 0); + + // evaluate animation + float walk_time = fmod(position.x, 100.0f)/100.0f; + ANIMSTATE state; + state.set(&data->animations[ANIM_BASE], 0); + + if(inair) + state.add(&data->animations[ANIM_INAIR], 0, 1.0f); // TODO: some sort of time here + else if(stationary) + state.add(&data->animations[ANIM_IDLE], 0, 1.0f); // TODO: some sort of time here + else if(!want_other_dir) + state.add(&data->animations[ANIM_WALK], walk_time, 1.0f); + + if (player.weapon == WEAPON_HAMMER) + { + float a = clamp((client_tick()-player.attacktick+ticktime)/10.0f, 0.0f, 1.0f); + state.add(&data->animations[ANIM_HAMMER_SWING], a, 1.0f); + } + if (player.weapon == WEAPON_NINJA) + { + float a = clamp((client_tick()-player.attacktick+ticktime)/40.0f, 0.0f, 1.0f); + state.add(&data->animations[ANIM_NINJA_SWING], a, 1.0f); + } + + // do skidding + if(!inair && want_other_dir && length(vel*50) > 500.0f) + { + static int64 skid_sound_time = 0; + if(time_get()-skid_sound_time > time_freq()/10) + { + snd_play_random(CHN_WORLD, SOUND_PLAYER_SKID, 0.25f, position); + skid_sound_time = time_get(); + } + + gameclient.effects->skidtrail( + position+vec2(-player.wanted_direction*6,12), + vec2(-player.wanted_direction*100*length(vel),-50) + ); + } + + // draw hook + if (prev.hook_state>0 && player.hook_state>0) + { + gfx_texture_set(data->images[IMAGE_GAME].id); + gfx_quads_begin(); + //gfx_quads_begin(); + + vec2 pos = position; + vec2 hook_pos; + + if(player_char->hooked_player != -1) + { + if(gameclient.snap.local_info && player_char->hooked_player == gameclient.snap.local_info->cid) + { + hook_pos = mix(vec2(predicted_prev_char.pos.x, predicted_prev_char.pos.y), + vec2(predicted_char.pos.x, predicted_char.pos.y), client_predintratick()); + } + else + hook_pos = mix(vec2(prev_char->hook_x, prev_char->hook_y), vec2(player_char->hook_x, player_char->hook_y), client_intratick()); + } + else + hook_pos = mix(vec2(prev.hook_x, prev.hook_y), vec2(player.hook_x, player.hook_y), intratick); + + float d = distance(pos, hook_pos); + vec2 dir = normalize(pos-hook_pos); + + gfx_quads_setrotation(get_angle(dir)+pi); + + // render head + select_sprite(SPRITE_HOOK_HEAD); + gfx_quads_draw(hook_pos.x, hook_pos.y, 24,16); + + // render chain + select_sprite(SPRITE_HOOK_CHAIN); + int i = 0; + for(float f = 24; f < d && i < 1024; f += 24, i++) + { + vec2 p = hook_pos + dir*f; + gfx_quads_draw(p.x, p.y,24,16); + } + + gfx_quads_setrotation(0); + gfx_quads_end(); + + render_hand(&render_info, position, normalize(hook_pos-pos), -pi/2, vec2(20, 0)); + } + + // draw gun + { + gfx_texture_set(data->images[IMAGE_GAME].id); + gfx_quads_begin(); + gfx_quads_setrotation(state.attach.angle*pi*2+angle); + + // normal weapons + int iw = clamp(player.weapon, 0, NUM_WEAPONS-1); + select_sprite(data->weapons.id[iw].sprite_body, direction.x < 0 ? SPRITE_FLAG_FLIP_Y : 0); + + vec2 dir = direction; + float recoil = 0.0f; + vec2 p; + if (player.weapon == WEAPON_HAMMER) + { + // Static position for hammer + p = position + vec2(state.attach.x, state.attach.y); + p.y += data->weapons.id[iw].offsety; + // if attack is under way, bash stuffs + if(direction.x < 0) + { + gfx_quads_setrotation(-pi/2-state.attach.angle*pi*2); + p.x -= data->weapons.id[iw].offsetx; + } + else + { + gfx_quads_setrotation(-pi/2+state.attach.angle*pi*2); + } + draw_sprite(p.x, p.y, data->weapons.id[iw].visual_size); + } + else if (player.weapon == WEAPON_NINJA) + { + p = position; + p.y += data->weapons.id[iw].offsety; + + if(direction.x < 0) + { + gfx_quads_setrotation(-pi/2-state.attach.angle*pi*2); + p.x -= data->weapons.id[iw].offsetx; + gameclient.effects->powerupshine(p+vec2(32,0), vec2(32,12)); + } + else + { + gfx_quads_setrotation(-pi/2+state.attach.angle*pi*2); + gameclient.effects->powerupshine(p-vec2(32,0), vec2(32,12)); + } + draw_sprite(p.x, p.y, data->weapons.id[iw].visual_size); + + // HADOKEN + if ((client_tick()-player.attacktick) <= (SERVER_TICK_SPEED / 6) && data->weapons.id[iw].num_sprite_muzzles) + { + int itex = rand() % data->weapons.id[iw].num_sprite_muzzles; + float alpha = 1.0f; + if (alpha > 0.0f && data->weapons.id[iw].sprite_muzzles[itex]) + { + vec2 dir = vec2(player_char->x,player_char->y) - vec2(prev_char->x, prev_char->y); + dir = normalize(dir); + float hadokenangle = get_angle(dir); + gfx_quads_setrotation(hadokenangle); + //float offsety = -data->weapons[iw].muzzleoffsety; + select_sprite(data->weapons.id[iw].sprite_muzzles[itex], 0); + vec2 diry(-dir.y,dir.x); + p = position; + float offsetx = data->weapons.id[iw].muzzleoffsetx; + p -= dir * offsetx; + draw_sprite(p.x, p.y, 160.0f); + } + } + } + else + { + // TODO: should be an animation + recoil = 0; + float a = (client_tick()-player.attacktick+intratick)/5.0f; + if(a < 1) + recoil = sinf(a*pi); + p = position + dir * data->weapons.id[iw].offsetx - dir*recoil*10.0f; + p.y += data->weapons.id[iw].offsety; + draw_sprite(p.x, p.y, data->weapons.id[iw].visual_size); + } + + if (player.weapon == WEAPON_GUN || player.weapon == WEAPON_SHOTGUN) + { + // check if we're firing stuff + if(data->weapons.id[iw].num_sprite_muzzles)//prev.attackticks) + { + float alpha = 0.0f; + int phase1tick = (client_tick() - player.attacktick); + if (phase1tick < (data->weapons.id[iw].muzzleduration + 3)) + { + float t = ((((float)phase1tick) + intratick)/(float)data->weapons.id[iw].muzzleduration); + alpha = LERP(2.0, 0.0f, min(1.0f,max(0.0f,t))); + } + + int itex = rand() % data->weapons.id[iw].num_sprite_muzzles; + if (alpha > 0.0f && data->weapons.id[iw].sprite_muzzles[itex]) + { + float offsety = -data->weapons.id[iw].muzzleoffsety; + select_sprite(data->weapons.id[iw].sprite_muzzles[itex], direction.x < 0 ? SPRITE_FLAG_FLIP_Y : 0); + if(direction.x < 0) + offsety = -offsety; + + vec2 diry(-dir.y,dir.x); + vec2 muzzlepos = p + dir * data->weapons.id[iw].muzzleoffsetx + diry * offsety; + + draw_sprite(muzzlepos.x, muzzlepos.y, data->weapons.id[iw].visual_size); + } + } + } + gfx_quads_end(); + + switch (player.weapon) + { + case WEAPON_GUN: render_hand(&render_info, p, direction, -3*pi/4, vec2(-15, 4)); break; + case WEAPON_SHOTGUN: render_hand(&render_info, p, direction, -pi/2, vec2(-5, 4)); break; + case WEAPON_GRENADE: render_hand(&render_info, p, direction, -pi/2, vec2(-4, 7)); break; + } + + } + + // render the "shadow" tee + if(info.local && config.debug) + { + vec2 ghost_position = mix(vec2(prev_char->x, prev_char->y), vec2(player_char->x, player_char->y), client_intratick()); + TEE_RENDER_INFO ghost = render_info; + ghost.color_body.a = 0.5f; + ghost.color_feet.a = 0.5f; + render_tee(&state, &ghost, player.emote, direction, ghost_position); // render ghost + } + + render_info.size = 64.0f; // force some settings + render_info.color_body.a = 1.0f; + render_info.color_feet.a = 1.0f; + render_tee(&state, &render_info, player.emote, direction, position); + + if(player.player_state == PLAYERSTATE_CHATTING) + { + gfx_texture_set(data->images[IMAGE_EMOTICONS].id); + gfx_quads_begin(); + select_sprite(SPRITE_DOTDOT); + gfx_quads_draw(position.x + 24, position.y - 40, 64,64); + gfx_quads_end(); + } + + if (gameclient.clients[info.cid].emoticon_start != -1 && gameclient.clients[info.cid].emoticon_start + 2 * client_tickspeed() > client_tick()) + { + gfx_texture_set(data->images[IMAGE_EMOTICONS].id); + gfx_quads_begin(); + + int since_start = client_tick() - gameclient.clients[info.cid].emoticon_start; + int from_end = gameclient.clients[info.cid].emoticon_start + 2 * client_tickspeed() - client_tick(); + + float a = 1; + + if (from_end < client_tickspeed() / 5) + a = from_end / (client_tickspeed() / 5.0); + + float h = 1; + if (since_start < client_tickspeed() / 10) + h = since_start / (client_tickspeed() / 10.0); + + float wiggle = 0; + if (since_start < client_tickspeed() / 5) + wiggle = since_start / (client_tickspeed() / 5.0); + + float wiggle_angle = sin(5*wiggle); + + gfx_quads_setrotation(pi/6*wiggle_angle); + + gfx_setcolor(1.0f,1.0f,1.0f,a); + // client_datas::emoticon is an offset from the first emoticon + select_sprite(SPRITE_OOP + gameclient.clients[info.cid].emoticon); + gfx_quads_draw(position.x, position.y - 23 - 32*h, 64, 64*h); + gfx_quads_end(); + } + + // render name plate + if(!info.local && config.cl_nameplates) + { + //gfx_text_color + float a = 1; + if(config.cl_nameplates_always == 0) + a = clamp(1-powf(distance(gameclient.local_target_pos, position)/200.0f,16.0f), 0.0f, 1.0f); + + const char *name = gameclient.clients[info.cid].name; + float tw = gfx_text_width(0, 28.0f, name, -1); + gfx_text_color(1,1,1,a); + gfx_text(0, position.x-tw/2.0f, position.y-60, 28.0f, name, -1); + + if(config.debug) // render client id when in debug aswell + { + char buf[128]; + str_format(buf, sizeof(buf),"%d", info.cid); + gfx_text(0, position.x, position.y-90, 28.0f, buf, -1); + } + + gfx_text_color(1,1,1,1); + } +} + +void PLAYERS::on_render() +{ + int num = snap_num_items(SNAP_CURRENT); + for(int i = 0; i < num; i++) + { + SNAP_ITEM item; + const void *data = snap_get_item(SNAP_CURRENT, i, &item); + + if(item.type == NETOBJTYPE_CHARACTER) + { + const void *prev = snap_find_item(SNAP_PREV, item.type, item.id); + const void *prev_info = snap_find_item(SNAP_PREV, NETOBJTYPE_PLAYER_INFO, item.id); + const void *info = snap_find_item(SNAP_CURRENT, NETOBJTYPE_PLAYER_INFO, item.id); + + if(prev && prev_info && info) + { + render_player( + (const NETOBJ_CHARACTER *)prev, + (const NETOBJ_CHARACTER *)data, + (const NETOBJ_PLAYER_INFO *)prev_info, + (const NETOBJ_PLAYER_INFO *)info + ); + } + } + } +} diff --git a/src/game/client/components/players.hpp b/src/game/client/components/players.hpp new file mode 100644 index 00000000..bdce91de --- /dev/null +++ b/src/game/client/components/players.hpp @@ -0,0 +1,16 @@ +#include <game/client/component.hpp> + +class PLAYERS : public COMPONENT +{ + void render_hand(class TEE_RENDER_INFO *info, vec2 center_pos, vec2 dir, float angle_offset, vec2 post_rot_offset); + void render_player( + const class NETOBJ_CHARACTER *prev_char, + const class NETOBJ_CHARACTER *player_char, + const class NETOBJ_PLAYER_INFO *prev_info, + const class NETOBJ_PLAYER_INFO *player_info + ); + +public: + virtual void on_render(); +}; + diff --git a/src/game/client/components/scoreboard.cpp b/src/game/client/components/scoreboard.cpp new file mode 100644 index 00000000..075f249e --- /dev/null +++ b/src/game/client/components/scoreboard.cpp @@ -0,0 +1,254 @@ +#include <string.h> + +#include <engine/e_client_interface.h> +#include <game/generated/g_protocol.hpp> +#include <game/generated/gc_data.hpp> +#include <game/client/gameclient.hpp> +#include <game/client/animstate.hpp> +#include <game/client/gc_render.hpp> +#include "scoreboard.hpp" + +void SCOREBOARD::render_goals(float x, float y, float w) +{ + float h = 50.0f; + + gfx_blend_normal(); + gfx_texture_set(-1); + gfx_quads_begin(); + gfx_setcolor(0,0,0,0.5f); + draw_round_rect(x-10.f, y-10.f, w, h, 10.0f); + gfx_quads_end(); + + // render goals + //y = ystart+h-54; + if(gameclient.snap.gameobj && gameclient.snap.gameobj->time_limit) + { + char buf[64]; + str_format(buf, sizeof(buf), "Time Limit: %d min", gameclient.snap.gameobj->time_limit); + gfx_text(0, x+w/2, y, 24.0f, buf, -1); + } + if(gameclient.snap.gameobj && gameclient.snap.gameobj->score_limit) + { + char buf[64]; + str_format(buf, sizeof(buf), "Score Limit: %d", gameclient.snap.gameobj->score_limit); + gfx_text(0, x+40, y, 24.0f, buf, -1); + } +} + +void SCOREBOARD::render_spectators(float x, float y, float w) +{ + char buffer[1024*4]; + int count = 0; + float h = 120.0f; + + str_copy(buffer, "Spectators: ", sizeof(buffer)); + + gfx_blend_normal(); + gfx_texture_set(-1); + gfx_quads_begin(); + gfx_setcolor(0,0,0,0.5f); + draw_round_rect(x-10.f, y-10.f, w, h, 10.0f); + gfx_quads_end(); + + for(int i = 0; i < snap_num_items(SNAP_CURRENT); i++) + { + SNAP_ITEM item; + const void *data = snap_get_item(SNAP_CURRENT, i, &item); + + if(item.type == NETOBJTYPE_PLAYER_INFO) + { + const NETOBJ_PLAYER_INFO *info = (const NETOBJ_PLAYER_INFO *)data; + if(info->team == -1) + { + if(count) + strcat(buffer, ", "); + strcat(buffer, gameclient.clients[info->cid].name); + count++; + } + } + } + + gfx_text(0, x+10, y, 32, buffer, (int)w-20); +} + +void SCOREBOARD::render_scoreboard(float x, float y, float w, int team, const char *title) +{ + //float ystart = y; + float h = 750.0f; + + gfx_blend_normal(); + gfx_texture_set(-1); + gfx_quads_begin(); + gfx_setcolor(0,0,0,0.5f); + draw_round_rect(x-10.f, y-10.f, w, h, 40.0f); + gfx_quads_end(); + + // render title + if(!title) + { + if(gameclient.snap.gameobj->game_over) + title = "Game Over"; + else + title = "Score Board"; + } + + float tw = gfx_text_width(0, 48, title, -1); + + if(team == -1) + { + gfx_text(0, x+w/2-tw/2, y, 48, title, -1); + } + else + { + gfx_text(0, x+10, y, 48, title, -1); + + if(gameclient.snap.gameobj) + { + char buf[128]; + int score = team ? gameclient.snap.gameobj->teamscore_blue : gameclient.snap.gameobj->teamscore_red; + str_format(buf, sizeof(buf), "%d", score); + tw = gfx_text_width(0, 48, buf, -1); + gfx_text(0, x+w-tw-30, y, 48, buf, -1); + } + } + + y += 54.0f; + + // find players + const NETOBJ_PLAYER_INFO *players[MAX_CLIENTS] = {0}; + int num_players = 0; + for(int i = 0; i < snap_num_items(SNAP_CURRENT); i++) + { + SNAP_ITEM item; + const void *data = snap_get_item(SNAP_CURRENT, i, &item); + + if(item.type == NETOBJTYPE_PLAYER_INFO) + { + players[num_players] = (const NETOBJ_PLAYER_INFO *)data; + num_players++; + } + } + + // sort players + for(int k = 0; k < num_players; k++) // ffs, bubblesort + { + for(int i = 0; i < num_players-k-1; i++) + { + if(players[i]->score < players[i+1]->score) + { + const NETOBJ_PLAYER_INFO *tmp = players[i]; + players[i] = players[i+1]; + players[i+1] = tmp; + } + } + } + + // render headlines + gfx_text(0, x+10, y, 24.0f, "Score", -1); + gfx_text(0, x+125, y, 24.0f, "Name", -1); + gfx_text(0, x+w-70, y, 24.0f, "Ping", -1); + y += 29.0f; + + // render player scores + for(int i = 0; i < num_players; i++) + { + const NETOBJ_PLAYER_INFO *info = players[i]; + + // make sure that we render the correct team + if(team == -1 || info->team != team) + continue; + + char buf[128]; + float font_size = 35.0f; + if(info->local) + { + // background so it's easy to find the local player + gfx_texture_set(-1); + gfx_quads_begin(); + gfx_setcolor(1,1,1,0.25f); + draw_round_rect(x, y, w-20, 48, 20.0f); + gfx_quads_end(); + } + + str_format(buf, sizeof(buf), "%4d", info->score); + gfx_text(0, x+60-gfx_text_width(0, font_size,buf,-1), y, font_size, buf, -1); + + gfx_text(0, x+128, y, font_size, gameclient.clients[info->cid].name, -1); + + str_format(buf, sizeof(buf), "%4d", info->latency); + float tw = gfx_text_width(0, font_size, buf, -1); + gfx_text(0, x+w-tw-35, y, font_size, buf, -1); + + // render avatar + if((gameclient.snap.flags[0] && gameclient.snap.flags[0]->carried_by == info->cid) || + (gameclient.snap.flags[1] && gameclient.snap.flags[1]->carried_by == info->cid)) + { + gfx_blend_normal(); + gfx_texture_set(data->images[IMAGE_GAME].id); + gfx_quads_begin(); + + if(info->team == 0) select_sprite(SPRITE_FLAG_BLUE, SPRITE_FLAG_FLIP_X); + else select_sprite(SPRITE_FLAG_RED, SPRITE_FLAG_FLIP_X); + + float size = 64.0f; + gfx_quads_drawTL(x+55, y-15, size/2, size); + gfx_quads_end(); + } + + render_tee(ANIMSTATE::get_idle(), &gameclient.clients[info->cid].render_info, EMOTE_NORMAL, vec2(1,0), vec2(x+90, y+28)); + + + y += 50.0f; + } +} + +void SCOREBOARD::on_render() +{ + + // TODO: repair me + /* + bool do_scoreboard = false; + + // if we are dead + if(!spectate && (!gameclient.snap.local_character || gameclient.snap.local_character->health < 0)) + do_scoreboard = true; + + // if we the game is over + if(gameclient.snap.gameobj && gameclient.snap.gameobj->game_over) + do_scoreboard = true;*/ + + + float width = 400*3.0f*gfx_screenaspect(); + float height = 400*3.0f; + + gfx_mapscreen(0, 0, width, height); + + float w = 650.0f; + + if(gameclient.snap.gameobj && !(gameclient.snap.gameobj->flags&GAMEFLAG_TEAMS)) + { + render_scoreboard(width/2-w/2, 150.0f, w, 0, 0); + //render_scoreboard(gameobj, 0, 0, -1, 0); + } + else + { + + if(gameclient.snap.gameobj && gameclient.snap.gameobj->game_over) + { + const char *text = "DRAW!"; + if(gameclient.snap.gameobj->teamscore_red > gameclient.snap.gameobj->teamscore_blue) + text = "Red Team Wins!"; + else if(gameclient.snap.gameobj->teamscore_blue > gameclient.snap.gameobj->teamscore_red) + text = "Blue Team Wins!"; + + float w = gfx_text_width(0, 92.0f, text, -1); + gfx_text(0, width/2-w/2, 45, 92.0f, text, -1); + } + + render_scoreboard(width/2-w-20, 150.0f, w, 0, "Red Team"); + render_scoreboard(width/2 + 20, 150.0f, w, 1, "Blue Team"); + } + + render_goals(width/2-w/2, 150+750+25, w); + render_spectators(width/2-w/2, 150+750+25+50+25, w); +} diff --git a/src/game/client/components/scoreboard.hpp b/src/game/client/components/scoreboard.hpp new file mode 100644 index 00000000..b1913cec --- /dev/null +++ b/src/game/client/components/scoreboard.hpp @@ -0,0 +1,11 @@ +#include <game/client/component.hpp> + +class SCOREBOARD : public COMPONENT +{ + void render_goals(float x, float y, float w); + void render_spectators(float x, float y, float w); + void render_scoreboard(float x, float y, float w, int team, const char *title); +public: + virtual void on_render(); +}; + diff --git a/src/game/client/components/skins.cpp b/src/game/client/components/skins.cpp new file mode 100644 index 00000000..eae45c4d --- /dev/null +++ b/src/game/client/components/skins.cpp @@ -0,0 +1,188 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +#include <string.h> +#include <stdio.h> +#include <math.h> + +#include <base/system.h> +#include <base/math.hpp> + +#include <engine/e_client_interface.h> +#include "skins.hpp" + +SKINS::SKINS() +{ + num_skins = 0; +} + +void SKINS::skinscan(const char *name, int is_dir, void *user) +{ + SKINS *self = (SKINS *)user; + int l = strlen(name); + if(l < 4 || is_dir || self->num_skins == MAX_SKINS) + return; + if(strcmp(name+l-4, ".png") != 0) + return; + + char buf[512]; + str_format(buf, sizeof(buf), "data/skins/%s", name); + IMAGE_INFO info; + if(!gfx_load_png(&info, buf)) + { + dbg_msg("game", "failed to load skin from %s", name); + return; + } + + self->skins[self->num_skins].org_texture = gfx_load_texture_raw(info.width, info.height, info.format, info.data, info.format, 0); + + int body_size = 96; // body size + unsigned char *d = (unsigned char *)info.data; + int pitch = info.width*4; + + // dig out blood color + { + int colors[3] = {0}; + for(int y = 0; y < body_size; y++) + for(int x = 0; x < body_size; x++) + { + if(d[y*pitch+x*4+3] > 128) + { + colors[0] += d[y*pitch+x*4+0]; + colors[1] += d[y*pitch+x*4+1]; + colors[2] += d[y*pitch+x*4+2]; + } + } + + self->skins[self->num_skins].blood_color = normalize(vec3(colors[0], colors[1], colors[2])); + } + + // create colorless version + int step = info.format == IMG_RGBA ? 4 : 3; + + // make the texture gray scale + for(int i = 0; i < info.width*info.height; i++) + { + int v = (d[i*step]+d[i*step+1]+d[i*step+2])/3; + d[i*step] = v; + d[i*step+1] = v; + d[i*step+2] = v; + } + + + if(1) + { + int freq[256] = {0}; + int org_weight = 0; + int new_weight = 192; + + // find most common frequence + for(int y = 0; y < body_size; y++) + for(int x = 0; x < body_size; x++) + { + if(d[y*pitch+x*4+3] > 128) + freq[d[y*pitch+x*4]]++; + } + + for(int i = 1; i < 256; i++) + { + if(freq[org_weight] < freq[i]) + org_weight = i; + } + + // reorder + int inv_org_weight = 255-org_weight; + int inv_new_weight = 255-new_weight; + for(int y = 0; y < body_size; y++) + for(int x = 0; x < body_size; x++) + { + int v = d[y*pitch+x*4]; + if(v <= org_weight) + v = (int)(((v/(float)org_weight) * new_weight)); + else + v = (int)(((v-org_weight)/(float)inv_org_weight)*inv_new_weight + new_weight); + d[y*pitch+x*4] = v; + d[y*pitch+x*4+1] = v; + d[y*pitch+x*4+2] = v; + } + } + + self->skins[self->num_skins].color_texture = gfx_load_texture_raw(info.width, info.height, info.format, info.data, info.format, 0); + mem_free(info.data); + + // set skin data + strncpy(self->skins[self->num_skins].name, name, min((int)sizeof(self->skins[self->num_skins].name),l-4)); + dbg_msg("game", "load skin %s", self->skins[self->num_skins].name); + self->num_skins++; +} + + +void SKINS::init() +{ + // load skins + num_skins = 0; + fs_listdir("data/skins", skinscan, this); +} + +int SKINS::num() +{ + return num_skins; +} + +const SKINS::SKIN *SKINS::get(int index) +{ + return &skins[index%num_skins]; +} + +int SKINS::find(const char *name) +{ + for(int i = 0; i < num_skins; i++) + { + if(strcmp(skins[i].name, name) == 0) + return i; + } + return -1; +} + +// these converter functions were nicked from some random internet pages +static float hue_to_rgb(float v1, float v2, float h) +{ + if(h < 0) h += 1; + if(h > 1) h -= 1; + if((6 * h) < 1) return v1 + ( v2 - v1 ) * 6 * h; + if((2 * h) < 1) return v2; + if((3 * h) < 2) return v1 + ( v2 - v1 ) * ((2.0f/3.0f) - h) * 6; + return v1; +} + +static vec3 hsl_to_rgb(vec3 in) +{ + float v1, v2; + vec3 out; + + if(in.s == 0) + { + out.r = in.l; + out.g = in.l; + out.b = in.l; + } + else + { + if(in.l < 0.5f) + v2 = in.l * (1 + in.s); + else + v2 = (in.l+in.s) - (in.s*in.l); + + v1 = 2 * in.l - v2; + + out.r = hue_to_rgb(v1, v2, in.h + (1.0f/3.0f)); + out.g = hue_to_rgb(v1, v2, in.h); + out.b = hue_to_rgb(v1, v2, in.h - (1.0f/3.0f)); + } + + return out; +} + +vec4 SKINS::get_color(int v) +{ + vec3 r = hsl_to_rgb(vec3((v>>16)/255.0f, ((v>>8)&0xff)/255.0f, 0.5f+(v&0xff)/255.0f*0.5f)); + return vec4(r.r, r.g, r.b, 1.0f); +} diff --git a/src/game/client/components/skins.hpp b/src/game/client/components/skins.hpp new file mode 100644 index 00000000..078fd71d --- /dev/null +++ b/src/game/client/components/skins.hpp @@ -0,0 +1,36 @@ +#include <base/vmath.hpp> +#include <game/client/component.hpp> + +class SKINS : public COMPONENT +{ +public: + // do this better and nicer + typedef struct + { + int org_texture; + int color_texture; + char name[31]; + char term[1]; + vec3 blood_color; + } SKIN; + + SKINS(); + + void init(); + + vec4 get_color(int v); + int num(); + const SKIN *get(int index); + int find(const char *name); + +private: + enum + { + MAX_SKINS=256, + }; + + SKIN skins[MAX_SKINS]; + int num_skins; + + static void skinscan(const char *name, int is_dir, void *user); +}; |