From a2566b3ebd93e0bbc55a920a7be08054a9377f11 Mon Sep 17 00:00:00 2001 From: Magnus Auvinen Date: Sat, 15 Dec 2007 10:24:49 +0000 Subject: cleaned up code structure a bit --- src/game/client/cl_render.h | 24 - src/game/client/cl_skin.cpp | 167 -- src/game/client/cl_skin.h | 20 - src/game/client/data/createdir.txt | 1 - src/game/client/game_client.cpp | 3103 --------------------------------- src/game/client/gc_client.cpp | 3103 +++++++++++++++++++++++++++++++++ src/game/client/gc_mapres_image.cpp | 125 ++ src/game/client/gc_mapres_image.h | 19 + src/game/client/gc_mapres_tilemap.cpp | 102 ++ src/game/client/gc_mapres_tilemap.h | 26 + src/game/client/gc_menu.cpp | 1893 ++++++++++++++++++++ src/game/client/gc_menu.h | 16 + src/game/client/gc_render.h | 24 + src/game/client/gc_skin.cpp | 167 ++ src/game/client/gc_skin.h | 20 + src/game/client/mapres_image.cpp | 125 -- src/game/client/mapres_image.h | 19 - src/game/client/mapres_tilemap.cpp | 102 -- src/game/client/mapres_tilemap.h | 26 - src/game/client/menu.cpp | 1865 -------------------- src/game/client/menu.h | 16 - src/game/client/menu2.cpp | 1893 -------------------- src/game/g_game.cpp | 411 +++++ src/game/g_game.h | 171 ++ src/game/g_mapres.h | 14 + src/game/g_mapres_col.cpp | 69 + src/game/g_mapres_col.h | 13 + src/game/g_math.h | 30 + src/game/g_protocol.h | 215 +++ src/game/g_variables.h | 64 + src/game/g_version.h | 4 + src/game/g_vmath.h | 182 ++ src/game/game.cpp | 411 ----- src/game/game.h | 171 -- src/game/game_protocol.h | 215 --- src/game/game_variables.h | 64 - src/game/generated/createdir.txt | 0 src/game/mapres.h | 14 - src/game/mapres_col.cpp | 69 - src/game/mapres_col.h | 13 - src/game/math.h | 30 - src/game/server/data/createdir.txt | 1 - src/game/server/game_server.cpp | 1860 -------------------- src/game/server/game_server.h | 2 - src/game/server/gs_common.cpp | 264 +++ src/game/server/gs_common.h | 327 ++++ src/game/server/gs_game_ctf.cpp | 178 ++ src/game/server/gs_game_ctf.h | 33 + src/game/server/gs_game_dm.cpp | 42 + src/game/server/gs_game_dm.h | 7 + src/game/server/gs_game_tdm.cpp | 32 + src/game/server/gs_game_tdm.h | 10 + src/game/server/gs_server.cpp | 1859 ++++++++++++++++++++ src/game/server/srv_common.cpp | 264 --- src/game/server/srv_common.h | 327 ---- src/game/server/srv_ctf.cpp | 178 -- src/game/server/srv_ctf.h | 33 - src/game/server/srv_dm.cpp | 42 - src/game/server/srv_dm.h | 7 - src/game/server/srv_tdm.cpp | 32 - src/game/server/srv_tdm.h | 10 - src/game/version.h | 4 - src/game/vmath.h | 182 -- 63 files changed, 9420 insertions(+), 11290 deletions(-) delete mode 100644 src/game/client/cl_render.h delete mode 100644 src/game/client/cl_skin.cpp delete mode 100644 src/game/client/cl_skin.h delete mode 100644 src/game/client/data/createdir.txt delete mode 100644 src/game/client/game_client.cpp create mode 100644 src/game/client/gc_client.cpp create mode 100644 src/game/client/gc_mapres_image.cpp create mode 100644 src/game/client/gc_mapres_image.h create mode 100644 src/game/client/gc_mapres_tilemap.cpp create mode 100644 src/game/client/gc_mapres_tilemap.h create mode 100644 src/game/client/gc_menu.cpp create mode 100644 src/game/client/gc_menu.h create mode 100644 src/game/client/gc_render.h create mode 100644 src/game/client/gc_skin.cpp create mode 100644 src/game/client/gc_skin.h delete mode 100644 src/game/client/mapres_image.cpp delete mode 100644 src/game/client/mapres_image.h delete mode 100644 src/game/client/mapres_tilemap.cpp delete mode 100644 src/game/client/mapres_tilemap.h delete mode 100644 src/game/client/menu.cpp delete mode 100644 src/game/client/menu.h delete mode 100644 src/game/client/menu2.cpp create mode 100644 src/game/g_game.cpp create mode 100644 src/game/g_game.h create mode 100644 src/game/g_mapres.h create mode 100644 src/game/g_mapres_col.cpp create mode 100644 src/game/g_mapres_col.h create mode 100644 src/game/g_math.h create mode 100644 src/game/g_protocol.h create mode 100644 src/game/g_variables.h create mode 100644 src/game/g_version.h create mode 100644 src/game/g_vmath.h delete mode 100644 src/game/game.cpp delete mode 100644 src/game/game.h delete mode 100644 src/game/game_protocol.h delete mode 100644 src/game/game_variables.h create mode 100644 src/game/generated/createdir.txt delete mode 100644 src/game/mapres.h delete mode 100644 src/game/mapres_col.cpp delete mode 100644 src/game/mapres_col.h delete mode 100644 src/game/math.h delete mode 100644 src/game/server/data/createdir.txt delete mode 100644 src/game/server/game_server.cpp delete mode 100644 src/game/server/game_server.h create mode 100644 src/game/server/gs_common.cpp create mode 100644 src/game/server/gs_common.h create mode 100644 src/game/server/gs_game_ctf.cpp create mode 100644 src/game/server/gs_game_ctf.h create mode 100644 src/game/server/gs_game_dm.cpp create mode 100644 src/game/server/gs_game_dm.h create mode 100644 src/game/server/gs_game_tdm.cpp create mode 100644 src/game/server/gs_game_tdm.h create mode 100644 src/game/server/gs_server.cpp delete mode 100644 src/game/server/srv_common.cpp delete mode 100644 src/game/server/srv_common.h delete mode 100644 src/game/server/srv_ctf.cpp delete mode 100644 src/game/server/srv_ctf.h delete mode 100644 src/game/server/srv_dm.cpp delete mode 100644 src/game/server/srv_dm.h delete mode 100644 src/game/server/srv_tdm.cpp delete mode 100644 src/game/server/srv_tdm.h delete mode 100644 src/game/version.h delete mode 100644 src/game/vmath.h (limited to 'src/game') diff --git a/src/game/client/cl_render.h b/src/game/client/cl_render.h deleted file mode 100644 index fc85d49a..00000000 --- a/src/game/client/cl_render.h +++ /dev/null @@ -1,24 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -struct animstate -{ - keyframe body; - keyframe back_foot; - keyframe front_foot; - keyframe attach; -}; - -void anim_seq_eval(sequence *seq, float time, keyframe *frame); -void anim_eval(animation *anim, float time, animstate *state); -void anim_add_keyframe(keyframe *seq, keyframe *added, float amount); -void anim_add(animstate *state, animstate *added, float amount); -void anim_eval_add(animstate *state, animation *anim, float time, float amount); - -struct tee_render_info -{ - int texture; - vec4 color_body; - vec4 color_feet; - float size; -}; - -void render_tee(animstate *anim, tee_render_info *info, int emote, vec2 dir, vec2 pos); diff --git a/src/game/client/cl_skin.cpp b/src/game/client/cl_skin.cpp deleted file mode 100644 index da8fe535..00000000 --- a/src/game/client/cl_skin.cpp +++ /dev/null @@ -1,167 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#include -#include -#include -#include -#include "cl_skin.h" -#include "../math.h" - -enum -{ - MAX_SKINS=256, -}; - -static skin skins[MAX_SKINS] = {{0}}; -static int num_skins = 0; - -static void skinscan(const char *name, int is_dir, void *user) -{ - int l = strlen(name); - if(l < 4 || is_dir || num_skins == MAX_SKINS) - return; - if(strcmp(name+l-4, ".png") != 0) - return; - - char buf[512]; - sprintf(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; - } - - skins[num_skins].org_texture = gfx_load_texture_raw(info.width, info.height, info.format, info.data); - - // create colorless version - unsigned char *d = (unsigned char *)info.data; - int step = info.format == IMG_RGBA ? 4 : 3; - - 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 bs = 96; // body size - int pitch = info.width*4; - int freq[256] = {0}; - - for(int y = 0; y < bs; y++) - for(int x = 0; x < bs; x++) - { - if(d[y*pitch+x*4+3] > 128) - freq[d[y*pitch+x*4]]++; - } - - int org_weight = 0; - int new_weight = 192; - for(int i = 1; i < 256; i++) - { - if(freq[org_weight] < freq[i]) - org_weight = i; - } - - int inv_org_weight = 255-org_weight; - int inv_new_weight = 255-new_weight; - for(int y = 0; y < bs; y++) - for(int x = 0; x < bs; 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; - } - } - - skins[num_skins].color_texture = gfx_load_texture_raw(info.width, info.height, info.format, info.data); - mem_free(info.data); - - // set skin data - strncpy(skins[num_skins].name, name, min((int)sizeof(skins[num_skins].name),l-4)); - dbg_msg("game", "load skin %s", skins[num_skins].name); - num_skins++; -} - - -void skin_init() -{ - // load skins - fs_listdir("data/skins", skinscan, 0); -} - -int skin_num() -{ - return num_skins; -} - -const skin *skin_get(int index) -{ - return &skins[index%num_skins]; -} - -int skin_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; -} - -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 skin_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/cl_skin.h b/src/game/client/cl_skin.h deleted file mode 100644 index bafc76b9..00000000 --- a/src/game/client/cl_skin.h +++ /dev/null @@ -1,20 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#include "../vmath.h" - -// do this better and nicer -typedef struct -{ - int org_texture; - int color_texture; - char name[31]; - char term[1]; -} skin; - -vec4 skin_get_color(int v); -void skin_init(); -int skin_num(); -const skin *skin_get(int index); -int skin_find(const char *name); - - -vec3 hsl_to_rgb(vec3 in); diff --git a/src/game/client/data/createdir.txt b/src/game/client/data/createdir.txt deleted file mode 100644 index 258b42d5..00000000 --- a/src/game/client/data/createdir.txt +++ /dev/null @@ -1 +0,0 @@ -this file is here to make sure that this directory gets created diff --git a/src/game/client/game_client.cpp b/src/game/client/game_client.cpp deleted file mode 100644 index 00deea5f..00000000 --- a/src/game/client/game_client.cpp +++ /dev/null @@ -1,3103 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#include -#include -#include -#include - -extern "C" { - #include - #include -}; - -#include "../game.h" -#include "../version.h" -#include "../mapres.h" -#include "mapres_image.h" -#include "mapres_tilemap.h" -#include "data.h" -#include "menu.h" -#include "cl_skin.h" -#include "cl_render.h" - -// sound channels -enum -{ - CHN_GUI=0, - CHN_MUSIC, - CHN_WORLD, - CHN_GLOBAL, -}; - -data_container *data = 0x0; - -int gametype = GAMETYPE_DM; - -extern void modmenu_render(); - -enum -{ - CHATMODE_NONE=0, - CHATMODE_ALL, - CHATMODE_TEAM, - CHATMODE_CONSOLE, - CHATMODE_REMOTECONSOLE, -}; - -typedef struct -{ - float x, y, w, h; -} RECT; -RECT *ui2_screen(); - -static int chat_mode = CHATMODE_NONE; -bool menu_active = false; -bool menu_game_active = false; -static bool emoticon_selector_active = false; - -static vec2 mouse_pos; -static vec2 local_character_pos; -static vec2 local_target_pos; -static const obj_player_character *local_character = 0; -static const obj_player_character *local_prev_character = 0; -const obj_player_info *local_info = 0; -static const obj_flag *flags[2] = {0,0}; -static const obj_game *gameobj = 0; - -static int picked_up_weapon = 0; - -static struct client_data -{ - char name[64]; - char skin_name[64]; - int skin_id; - int skin_color; - int team; - int emoticon; - int emoticon_start; - player_core predicted; - - tee_render_info skin_info; - -} client_datas[MAX_CLIENTS]; - -class client_effects -{ -public: - float zoom; - float currentzoom; - float stage; - int lastzoomin; - int lastincrease; - - client_effects() - { - currentzoom = zoom = 3.0f; - stage = 0.0f; - } - - float getorgzoom() { return zoom; } - - float getzoom(int tick, float intratick, const obj_player_character *player) - { - float currentstage = ((float)player->weaponstage) * 0.1f; - if (currentstage < stage) - { - if ((tick - lastincrease) > (client_tickspeed() / 2)) - stage = currentstage; - } - else - { - lastincrease = tick; - stage = currentstage; - } - - float targetzoom = 3.0f + stage; - currentzoom = LERP(currentzoom, targetzoom, 0.1); - return currentzoom; - } -}; - -client_effects cl_effects; - -inline float frandom() { return rand()/(float)(RAND_MAX); } - -void snd_play_random(int chn, int setid, float vol, vec2 pos) -{ - soundset *set = &data->sounds[setid]; - - if(!set->num_sounds) - return; - - if(set->num_sounds == 1) - { - snd_play_at(chn, set->sounds[0].id, 0, pos.x, pos.y); - return; - } - - // play a random one - int id; - do { - id = rand() % set->num_sounds; - } while(id == set->last); - snd_play_at(chn, set->sounds[id].id, 0, pos.x, pos.y); - set->last = id; -} - -// sound volume tweak -static const float stereo_separation = 0.001f; -static const float stereo_separation_deadzone = 200.0f; -static const float volume_distance_falloff = 200.0f; -static const float volume_distance_deadzone = 320.0f; -static const float volume_gun = 0.5f; -static const float volume_tee = 0.5f; -static const float volume_hit = 0.5f; -static const float volume_music = 0.8f; - -void sound_vol_pan(const vec2& p, float *vol, float *pan) -{ - vec2 player_to_ev = p - local_character_pos; - *pan = 0.0f; - *vol = 1.0f; - - if(fabs(player_to_ev.x) > stereo_separation_deadzone) - { - *pan = stereo_separation * (player_to_ev.x - sign(player_to_ev.x)*stereo_separation_deadzone); - if(*pan < -1.0f) *pan = -1.0f; - if(*pan > 1.0f) *pan = 1.0f; - } - - float len = length(player_to_ev); - if(len > volume_distance_deadzone) - { - *vol = volume_distance_falloff / (len - volume_distance_deadzone); - - if(*vol < 0.0f) *vol = 0.0f; - if(*vol > 1.0f) *vol = 1.0f; - } -} - -enum -{ - SPRITE_FLAG_FLIP_Y=1, - SPRITE_FLAG_FLIP_X=2, -}; - -static float sprite_w_scale; -static float sprite_h_scale; - -static void select_sprite(sprite *spr, int flags=0, int sx=0, int sy=0) -{ - int x = spr->x+sx; - int y = spr->y+sy; - int w = spr->w; - int h = spr->h; - int cx = spr->set->gridx; - int cy = spr->set->gridy; - - float f = sqrtf(h*h + w*w); - sprite_w_scale = w/f; - sprite_h_scale = h/f; - - float x1 = x/(float)cx; - float x2 = (x+w)/(float)cx; - float y1 = y/(float)cy; - float y2 = (y+h)/(float)cy; - float temp = 0; - - if(flags&SPRITE_FLAG_FLIP_Y) - { - temp = y1; - y1 = y2; - y2 = temp; - } - - if(flags&SPRITE_FLAG_FLIP_X) - { - temp = x1; - x1 = x2; - x2 = temp; - } - - gfx_quads_setsubset(x1, y1, x2, y2); -} - -void select_sprite(int id, int flags=0, int sx=0, int sy=0) -{ - if(id < 0 || id > data->num_sprites) - return; - select_sprite(&data->sprites[id], flags, sx, sy); -} - -void draw_sprite(float x, float y, float size) -{ - gfx_quads_draw(x, y, size*sprite_w_scale, size*sprite_h_scale); -} - -class damage_indicators -{ -public: - int64 lastupdate; - struct item - { - vec2 pos; - vec2 dir; - float life; - float startangle; - }; - - enum - { - MAX_ITEMS=64, - }; - - damage_indicators() - { - lastupdate = 0; - num_items = 0; - } - - item items[MAX_ITEMS]; - int num_items; - - item *create_i() - { - if (num_items < MAX_ITEMS) - { - item *p = &items[num_items]; - num_items++; - return p; - } - return 0; - } - - void destroy_i(item *i) - { - num_items--; - *i = items[num_items]; - } - - void create(vec2 pos, 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 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(); - } - -}; - -static damage_indicators damageind; - -class particle_system -{ -public: - struct particle - { - vec2 pos; - vec2 vel; - float life; - float max_life; - float size; - - float rot; - float rotspeed; - - float gravity; - float friction; - int iparticle; - - vec4 color; - }; - - enum - { - MAX_PARTICLES=1024, - }; - - particle particles[MAX_PARTICLES]; - int num_particles; - - particle_system() - { - num_particles = 0; - } - - void new_particle(vec2 pos, vec2 vel, float life, float size, float gravity, float friction, vec4 color=vec4(1,1,1,1)) - { - if (num_particles >= MAX_PARTICLES) - return; - - particles[num_particles].iparticle = rand() % data->num_particles; - particles[num_particles].pos = pos; - particles[num_particles].vel = vel; - particles[num_particles].life = life - (data->particles[particles[num_particles].iparticle].lifemod/100.0f) * life; - particles[num_particles].size = size; - particles[num_particles].max_life = life; - particles[num_particles].gravity = gravity; - particles[num_particles].friction = friction; - particles[num_particles].rot = frandom()*pi*2; - particles[num_particles].rotspeed = frandom() * 10.0f; - particles[num_particles].color = color; - num_particles++; - } - - void update(float time_passed) - { - for(int i = 0; i < num_particles; i++) - { - particles[i].vel.y += particles[i].gravity*time_passed; - particles[i].vel *= particles[i].friction; - vec2 vel = particles[i].vel*time_passed; - move_point(&particles[i].pos, &vel, 0.1f+0.9f*frandom(), 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].max_life) - { - num_particles--; - particles[i] = particles[num_particles]; - i--; - } - } - } - - void render() - { - gfx_blend_additive(); - gfx_texture_set(data->images[IMAGE_GAME].id); - gfx_quads_begin(); - - for(int i = 0; i < num_particles; i++) - { - int type = particles[i].iparticle; - select_sprite(data->particles[type].spr); - float a = 1 - particles[i].life / particles[i].max_life; - vec2 p = particles[i].pos; - - gfx_quads_setrotation(particles[i].rot); - - gfx_setcolor( - data->particles[type].color_r * particles[i].color.r, - data->particles[type].color_g * particles[i].color.g, - data->particles[type].color_b * particles[i].color.b, - pow(a, 0.75f) * particles[i].color.a); - - gfx_quads_draw(p.x, p.y,particles[i].size,particles[i].size); - } - gfx_quads_end(); - gfx_blend_normal(); - } -}; - -static particle_system temp_system; - -class projectile_particles -{ -public: - enum - { - LISTSIZE = 1000, - }; - // meh, just use size % - int lastadd[LISTSIZE]; - projectile_particles() - { - reset(); - } - - void reset() - { - for (int i = 0; i < LISTSIZE; i++) - lastadd[i] = -1000; - } - - void addparticle(int projectiletype, int projectileid, vec2 pos, vec2 vel) - { - int particlespersecond = data->projectileinfo[projectiletype].particlespersecond; - int lastaddtick = lastadd[projectileid % LISTSIZE]; - - if(!particlespersecond) - return; - - if ((client_tick() - lastaddtick) > (client_tickspeed() / particlespersecond)) - { - lastadd[projectileid % LISTSIZE] = client_tick(); - float life = data->projectileinfo[projectiletype].particlelife; - float size = data->projectileinfo[projectiletype].particlesize; - vec2 v = vel * 0.2f + normalize(vec2(frandom()-0.5f, -frandom()))*(32.0f+frandom()*32.0f); - - // add the particle (from projectiletype later on, but meh...) - temp_system.new_particle(pos, v, life, size, 0, 0.95f); - } - } -}; -static projectile_particles proj_particles; - -static char chat_input[512]; -static unsigned chat_input_len; -static const int chat_max_lines = 10; - -struct chatline -{ - int tick; - int client_id; - int team; - char text[512+64]; -}; - -chatline chat_lines[chat_max_lines]; -static int chat_current_line = 0; - -void chat_reset() -{ - for(int i = 0; i < chat_max_lines; i++) - chat_lines[i].tick = -1000000; - chat_current_line = 0; -} - -void chat_add_line(int client_id, int team, const char *line) -{ - chat_current_line = (chat_current_line+1)%chat_max_lines; - chat_lines[chat_current_line].tick = client_tick(); - chat_lines[chat_current_line].client_id = client_id; - chat_lines[chat_current_line].team = team; - - if(client_id == -1) // server message - sprintf(chat_lines[chat_current_line].text, "*** %s", line); - else - { - sprintf(chat_lines[chat_current_line].text, "%s: %s", client_datas[client_id].name, line); - } -} - -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]; -static int killmsg_current = 0; - -extern unsigned char internal_data[]; - -void create_air_jump_effect(vec2 pos) -{ - const int count = 12; - for(int i = 0; i <= count; i++) - { - float a = i/(float)count; - vec2 v = vec2((a-0.5f)*512.0f, 0); - temp_system.new_particle(pos+vec2(0,28), v, 0.4f, 16.0f, 0, 0.985f, vec4(0.25f,0.4f,1,1)); - } -} - - -extern void draw_round_rect(float x, float y, float w, float h, float r); -extern int render_popup(const char *caption, const char *text, const char *button_text); - -static void render_loading(float percent) -{ - gfx_clear(0.65f,0.78f,0.9f); - RECT screen = *ui2_screen(); - gfx_mapscreen(screen.x, screen.y, screen.w, screen.h); - - 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_pretty_text_width(48.0f, caption, -1); - ui_do_label(x+w/2-tw/2, y+20, caption, 48.0f); - - gfx_texture_set(-1); - gfx_quads_begin(); - gfx_setcolor(1,1,1,1.0f); - draw_round_rect(x+40, y+h-75, (w-80)*percent, 25, 5.0f); - gfx_quads_end(); - - gfx_swap(); -} - -extern "C" void modc_init() -{ - // setup sound channels - snd_set_channel(CHN_GUI, 1.0f, 0.0f); - snd_set_channel(CHN_MUSIC, 1.0f, 0.0f); - snd_set_channel(CHN_WORLD, 0.9f, 1.0f); - snd_set_channel(CHN_GLOBAL, 1.0f, 0.0f); - - // load the data container - data = load_data_from_memory(internal_data); - - // TODO: should be removed - snd_set_listener_pos(0.0f, 0.0f); - - float total = data->num_sounds+data->num_images; - float current = 0; - - // load sounds - for(int s = 0; s < data->num_sounds; s++) - { - render_loading(current/total); - for(int i = 0; i < data->sounds[s].num_sounds; i++) - { - int id; - //if (strcmp(data->sounds[s].sounds[i].filename + strlen(data->sounds[s].sounds[i].filename) - 3, ".wv") == 0) - id = snd_load_wv(data->sounds[s].sounds[i].filename); - //else - // id = snd_load_wav(data->sounds[s].sounds[i].filename); - - data->sounds[s].sounds[i].id = id; - } - - current++; - } - - // load textures - for(int i = 0; i < data->num_images; i++) - { - render_loading(current/total); - data->images[i].id = gfx_load_texture(data->images[i].filename); - current++; - } - - skin_init(); -} - -extern "C" void modc_entergame() -{ -} - -extern "C" void modc_shutdown() -{ - // shutdown the menu -} - -static void process_events(int s) -{ - int num = snap_num_items(s); - for(int index = 0; index < num; index++) - { - SNAP_ITEM item; - const void *data = snap_get_item(s, index, &item); - - if(item.type == EVENT_DAMAGEINDICATION) - { - ev_damageind *ev = (ev_damageind *)data; - damageind.create(vec2(ev->x, ev->y), get_direction(ev->angle)); - } - else if(item.type == EVENT_AIR_JUMP) - { - ev_common *ev = (ev_common *)data; - create_air_jump_effect(vec2(ev->x, ev->y)); - } - else if(item.type == EVENT_EXPLOSION) - { - ev_explosion *ev = (ev_explosion *)data; - vec2 p(ev->x, ev->y); - - // center explosion - temp_system.new_particle(p, vec2(0,0), 0.3f, 96.0f, 0, 0.95f); - temp_system.new_particle(p, vec2(0,0), 0.3f, 64.0f, 0, 0.95f); - temp_system.new_particle(p, vec2(0,0), 0.3f, 32.0f, 0, 0.95f); - temp_system.new_particle(p, vec2(0,0), 0.3f, 16.0f, 0, 0.95f); - - for(int i = 0; i < 16; i++) - { - vec2 v = normalize(vec2(frandom()-0.5f, frandom()-0.5f))*(128.0f+frandom()*128.0f); - temp_system.new_particle(p, v, 0.2f+0.25f*frandom(), 16.0f, 0, 0.985f); - } - - for(int i = 0; i < 16; i++) - { - vec2 v = normalize(vec2(frandom()-0.5f, frandom()-0.5f))*(256.0f+frandom()*512.0f); - temp_system.new_particle(p, v, 0.2f+0.25f*frandom(), 16.0f, 128.0f, 0.985f); - } - - for(int i = 0; i < 64; i++) - { - vec2 v = normalize(vec2(frandom()-0.5f, frandom()-0.5f))*(frandom()*256.0f); - temp_system.new_particle(p, v, 0.2f+0.25f*frandom(), 24.0f, 128.0f, 0.985f); - } - } - else if(item.type == EVENT_SMOKE) - { - ev_explosion *ev = (ev_explosion *)data; - vec2 p(ev->x, ev->y); - - // center explosion - vec2 v = normalize(vec2(frandom()-0.5f, -frandom()))*(32.0f+frandom()*32.0f); - temp_system.new_particle(p, v, 1.2f, 64.0f, 0, 0.95f); - v = normalize(vec2(frandom()-0.5f, -frandom()))*(128.0f+frandom()*128.0f); - temp_system.new_particle(p, v, 1.2f, 32.0f, 0, 0.95f); - v = normalize(vec2(frandom()-0.5f, -frandom()))*(128.0f+frandom()*128.0f); - temp_system.new_particle(p, v, 1.2f, 16.0f, 0, 0.95f); - - for(int i = 0; i < 8; i++) - { - vec2 v = normalize(vec2(frandom()-0.5f, frandom()-0.5f))*(64.0f+frandom()*64.0f); - temp_system.new_particle(p, v, 0.5f+0.5f*frandom(), 16.0f, 0, 0.985f); - } - - for(int i = 0; i < 8; i++) - { - vec2 v = normalize(vec2(frandom()-0.5f, frandom()-0.5f))*(128.0f+frandom()*256.0f); - temp_system.new_particle(p, v, 0.5f+0.5f*frandom(), 16.0f, 128.0f, 0.985f); - } - } - else if(item.type == EVENT_SPAWN) - { - ev_explosion *ev = (ev_explosion *)data; - vec2 p(ev->x, ev->y); - - // center explosion - vec2 v = normalize(vec2(frandom()-0.5f, -frandom()))*(32.0f+frandom()*32.0f); - temp_system.new_particle(p, v, 1.2f, 64.0f, 0, 0.95f); - v = normalize(vec2(frandom()-0.5f, -frandom()))*(128.0f+frandom()*128.0f); - temp_system.new_particle(p, v, 1.2f, 32.0f, 0, 0.95f); - v = normalize(vec2(frandom()-0.5f, -frandom()))*(128.0f+frandom()*128.0f); - temp_system.new_particle(p, v, 1.2f, 16.0f, 0, 0.95f); - - for(int i = 0; i < 8; i++) - { - vec2 v = normalize(vec2(frandom()-0.5f, frandom()-0.5f))*(64.0f+frandom()*64.0f); - temp_system.new_particle(p, v, 0.5f+0.5f*frandom(), 16.0f, 0, 0.985f); - } - - for(int i = 0; i < 8; i++) - { - vec2 v = normalize(vec2(frandom()-0.5f, frandom()-0.5f))*(128.0f+frandom()*256.0f); - temp_system.new_particle(p, v, 0.5f+0.5f*frandom(), 16.0f, 128.0f, 0.985f); - } - } - else if(item.type == EVENT_DEATH) - { - ev_explosion *ev = (ev_explosion *)data; - vec2 p(ev->x, ev->y); - vec4 c(0.5f, 0.1f, 0.1f, 1.0f); - - // center explosion - vec2 v = normalize(vec2(frandom()-0.5f, -frandom()))*(32.0f+frandom()*32.0f); - temp_system.new_particle(p, v, 1.2f, 64.0f, 0, 0.95f, c); - v = normalize(vec2(frandom()-0.5f, -frandom()))*(128.0f+frandom()*128.0f); - temp_system.new_particle(p, v, 1.2f, 32.0f, 0, 0.95f, c); - v = normalize(vec2(frandom()-0.5f, -frandom()))*(128.0f+frandom()*128.0f); - temp_system.new_particle(p, v, 1.2f, 16.0f, 0, 0.95f, c); - - for(int i = 0; i < 8; i++) - { - vec2 v = normalize(vec2(frandom()-0.5f, frandom()-0.5f))*(64.0f+frandom()*64.0f); - temp_system.new_particle(p, v, 0.5f+0.5f*frandom(), 16.0f, 0, 0.985f, c); - } - - for(int i = 0; i < 8; i++) - { - vec2 v = normalize(vec2(frandom()-0.5f, frandom()-0.5f))*(128.0f+frandom()*256.0f); - temp_system.new_particle(p, v, 0.5f+0.5f*frandom(), 16.0f, 128.0f, 0.985f, c); - } - } - else if(item.type == EVENT_SOUND_WORLD) - { - ev_sound *ev = (ev_sound *)data; - if(ev->sound >= 0 && ev->sound < NUM_SOUNDS) - snd_play_random(CHN_WORLD, ev->sound, 1.0f, vec2(ev->x, ev->y)); - } - } -} - -static player_core predicted_prev_player; -static player_core predicted_player; -static int predicted_tick = 0; -static int last_new_predicted_tick = -1; - -extern "C" void modc_predict() -{ - player_core before_prev_player = predicted_prev_player; - player_core before_player = predicted_player; - - - // repredict player - world_core world; - int local_cid = -1; - - // search for players - for(int i = 0; i < snap_num_items(SNAP_CURRENT); i++) - { - SNAP_ITEM item; - const void *data = snap_get_item(SNAP_CURRENT, i, &item); - int client_id = item.id; - - if(item.type == OBJTYPE_PLAYER_CHARACTER) - { - const obj_player_character *character = (const obj_player_character *)data; - client_datas[client_id].predicted.world = &world; - world.players[client_id] = &client_datas[client_id].predicted; - - client_datas[client_id].predicted.read(character); - } - else if(item.type == OBJTYPE_PLAYER_INFO) - { - const obj_player_info *info = (const obj_player_info *)data; - if(info->local) - local_cid = client_id; - } - } - - // predict - for(int tick = client_tick()+1; tick <= client_predtick(); tick++) - { - // fetch the local - if(tick == client_predtick() && world.players[local_cid]) - predicted_prev_player = *world.players[local_cid]; - - // first calculate where everyone should move - for(int c = 0; c < MAX_CLIENTS; c++) - { - if(!world.players[c]) - continue; - - mem_zero(&world.players[c]->input, sizeof(world.players[c]->input)); - if(local_cid == c) - { - // apply player input - int *input = client_get_input(tick); - if(input) - world.players[c]->input = *((player_input*)input); - } - - world.players[c]->tick(); - } - - // move all players and quantize their data - for(int c = 0; c < MAX_CLIENTS; c++) - { - if(!world.players[c]) - continue; - - world.players[c]->move(); - world.players[c]->quantize(); - } - - if(tick > last_new_predicted_tick) - { - last_new_predicted_tick = tick; - - if(local_cid != -1 && world.players[local_cid]) - { - vec2 pos = world.players[local_cid]->pos; - int events = world.players[local_cid]->triggered_events; - if(events&COREEVENT_GROUND_JUMP) snd_play_random(CHN_WORLD, SOUND_PLAYER_JUMP, 1.0f, pos); - if(events&COREEVENT_AIR_JUMP) - { - create_air_jump_effect(pos); - snd_play_random(CHN_WORLD, SOUND_PLAYER_JUMP, 1.0f, pos); - } - //if(events&COREEVENT_HOOK_LAUNCH) snd_play_random(CHN_WORLD, SOUND_HOOK_LOOP, 1.0f, pos); - if(events&COREEVENT_HOOK_ATTACH_PLAYER) snd_play_random(CHN_WORLD, SOUND_HOOK_ATTACH_PLAYER, 1.0f, pos); - if(events&COREEVENT_HOOK_ATTACH_GROUND) snd_play_random(CHN_WORLD, SOUND_HOOK_ATTACH_GROUND, 1.0f, pos); - //if(events&COREEVENT_HOOK_RETRACT) snd_play_random(CHN_WORLD, SOUND_PLAYER_JUMP, 1.0f, pos); - } - - - /* - dbg_msg("predict", "%d %d %d", tick, - (int)world.players[c]->pos.x, (int)world.players[c]->pos.y, - (int)world.players[c]->vel.x, (int)world.players[c]->vel.y);*/ - } - - if(tick == client_predtick() && world.players[local_cid]) - predicted_player = *world.players[local_cid]; - } - - if(config.debug && predicted_tick == client_predtick()) - { - if(predicted_player.pos.x != before_player.pos.x || - predicted_player.pos.y != before_player.pos.y) - { - dbg_msg("client", "prediction error, (%d %d) (%d %d)", - (int)before_player.pos.x, (int)before_player.pos.y, - (int)predicted_player.pos.x, (int)predicted_player.pos.y); - } - - if(predicted_prev_player.pos.x != before_prev_player.pos.x || - predicted_prev_player.pos.y != before_prev_player.pos.y) - { - dbg_msg("client", "prediction error, prev (%d %d) (%d %d)", - (int)before_prev_player.pos.x, (int)before_prev_player.pos.y, - (int)predicted_prev_player.pos.x, (int)predicted_prev_player.pos.y); - } - } - - predicted_tick = client_predtick(); -} - -static void clear_object_pointers() -{ - // clear out the invalid pointers - local_character = 0; - local_prev_character = 0; - local_info = 0; - flags[0] = 0; - flags[1] = 0; - gameobj = 0; -} - -extern "C" void modc_newsnapshot() -{ - process_events(SNAP_CURRENT); - - if(config.dbg_stress) - { - if((client_tick()%250) == 0) - { - msg_pack_start(MSG_SAY, MSGFLAG_VITAL); - msg_pack_int(-1); - msg_pack_string("galenskap!!!!", 512); - msg_pack_end(); - client_send_msg(); - } - } - - clear_object_pointers(); - - // setup world view - { - // 1. fetch local player - // 2. set him to the center - int num = snap_num_items(SNAP_CURRENT); - for(int i = 0; i < num; i++) - { - SNAP_ITEM item; - const void *data = snap_get_item(SNAP_CURRENT, i, &item); - - if(item.type == OBJTYPE_PLAYER_INFO) - { - const obj_player_info *info = (const obj_player_info *)data; - if(info->local) - { - local_info = info; - const void *data = snap_find_item(SNAP_CURRENT, OBJTYPE_PLAYER_CHARACTER, item.id); - if(data) - { - local_character = (const obj_player_character *)data; - local_character_pos = vec2(local_character->x, local_character->y); - - const void *p = snap_find_item(SNAP_PREV, OBJTYPE_PLAYER_CHARACTER, item.id); - if(p) - local_prev_character = (obj_player_character *)p; - } - } - } - else if(item.type == OBJTYPE_GAME) - gameobj = (obj_game *)data; - else if(item.type == OBJTYPE_FLAG) - { - flags[item.id%2] = (const obj_flag *)data; - } - } - } -} - -void send_info(bool start) -{ - if(start) - msg_pack_start(MSG_STARTINFO, MSGFLAG_VITAL); - else - msg_pack_start(MSG_CHANGEINFO, MSGFLAG_VITAL); - msg_pack_string(config.player_name, 64); - msg_pack_string(config.player_skin, 64); - msg_pack_int(config.player_use_custom_color); - msg_pack_int(config.player_color_body); - msg_pack_int(config.player_color_feet); - msg_pack_end(); - client_send_msg(); -} - -void send_emoticon(int emoticon) -{ - msg_pack_start(MSG_EMOTICON, MSGFLAG_VITAL); - msg_pack_int(emoticon); - msg_pack_end(); - client_send_msg(); -} - -static void render_projectile(const obj_projectile *prev, const obj_projectile *current, int itemid) -{ - gfx_texture_set(data->images[IMAGE_GAME].id); - gfx_quads_begin(); - - // get positions - float gravity = -400; - if(current->type != WEAPON_ROCKET) - gravity = -100; - - float ct = (client_tick()-current->start_tick)/(float)SERVER_TICK_SPEED + client_intratick()*1/(float)SERVER_TICK_SPEED; - vec2 startpos(current->x, current->y); - vec2 startvel(current->vx, current->vy); - vec2 pos = calc_pos(startpos, startvel, gravity, ct); - vec2 prevpos = calc_pos(startpos, startvel, gravity, ct-0.001f); - - select_sprite(data->weapons[current->type%data->num_weapons].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 - proj_particles.addparticle(current->type, itemid, pos, vel); - - if(length(vel) > 0.00001f) - gfx_quads_setrotation(get_angle(vel)); - else - gfx_quads_setrotation(0); - - // TODO: do this, but nice - //temp_system.new_particle(pos, vec2(0,0), 0.3f, 14.0f, 0, 0.95f); - - gfx_quads_draw(pos.x, pos.y, 32, 32); - gfx_quads_setrotation(0); - gfx_quads_end(); -} - -static void render_powerup(const obj_powerup *prev, const obj_powerup *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[current->subtype%data->num_weapons].sprite_body); - size = data->weapons[current->subtype%data->num_weapons].visual_size; - } - else - { - const int c[] = { - SPRITE_POWERUP_HEALTH, - SPRITE_POWERUP_ARMOR, - SPRITE_POWERUP_WEAPON, - SPRITE_POWERUP_NINJA, - SPRITE_POWERUP_TIMEFIELD - }; - select_sprite(c[current->type]); - - if(c[current->type] == SPRITE_POWERUP_NINJA) - { - proj_particles.addparticle(0, 0, - pos+vec2((frandom()-0.5f)*80.0f, (frandom()-0.5f)*20.0f), - vec2((frandom()-0.5f)*10.0f, (frandom()-0.5f)*10.0f)); - 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(); -} - -static void render_flag(const obj_flag *prev, const obj_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()); - - if(local_info && current->carried_by == local_info->clientid) - pos = local_character_pos; - - gfx_setcolor(current->team ? 0 : 1,0,current->team ? 1 : 0,1); - //draw_sprite(pos.x, pos.y, size); - gfx_quads_draw(pos.x, pos.y-size*0.75f, size, size*2); - gfx_quads_end(); -} - -void anim_seq_eval(sequence *seq, float time, keyframe *frame) -{ - if(seq->num_frames == 0) - { - frame->time = 0; - frame->x = 0; - frame->y = 0; - frame->angle = 0; - } - else if(seq->num_frames == 1) - { - *frame = seq->frames[0]; - } - else - { - //time = max(0.0f, min(1.0f, time / duration)); // TODO: use clamp - keyframe *frame1 = 0; - keyframe *frame2 = 0; - float blend = 0.0f; - - // TODO: make this smarter.. binary search - for (int i = 1; i < seq->num_frames; i++) - { - if (seq->frames[i-1].time <= time && seq->frames[i].time >= time) - { - frame1 = &seq->frames[i-1]; - frame2 = &seq->frames[i]; - blend = (time - frame1->time) / (frame2->time - frame1->time); - break; - } - } - - if (frame1 && frame2) - { - frame->time = time; - frame->x = mix(frame1->x, frame2->x, blend); - frame->y = mix(frame1->y, frame2->y, blend); - frame->angle = mix(frame1->angle, frame2->angle, blend); - } - } -} - -void anim_eval(animation *anim, float time, animstate *state) -{ - anim_seq_eval(&anim->body, time, &state->body); - anim_seq_eval(&anim->back_foot, time, &state->back_foot); - anim_seq_eval(&anim->front_foot, time, &state->front_foot); - anim_seq_eval(&anim->attach, time, &state->attach); -} - -void anim_add_keyframe(keyframe *seq, keyframe *added, float amount) -{ - seq->x += added->x*amount; - seq->y += added->y*amount; - seq->angle += added->angle*amount; -} - -void anim_add(animstate *state, animstate *added, float amount) -{ - anim_add_keyframe(&state->body, &added->body, amount); - anim_add_keyframe(&state->back_foot, &added->back_foot, amount); - anim_add_keyframe(&state->front_foot, &added->front_foot, amount); - anim_add_keyframe(&state->attach, &added->attach, amount); -} - -void anim_eval_add(animstate *state, animation *anim, float time, float amount) -{ - animstate add; - anim_eval(anim, time, &add); - anim_add(state, &add, amount); -} - -static void render_hand(int skin_id, 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(s->color_texture); - gfx_quads_begin(); - - // 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 render_tee(animstate *anim, tee_render_info *info, int emote, vec2 dir, vec2 pos) -{ - vec2 direction = dir; - vec2 position = pos; - - //gfx_texture_set(data->images[IMAGE_CHAR_DEFAULT].id); - gfx_texture_set(info->texture); - gfx_quads_begin(); - //gfx_quads_draw(pos.x, pos.y-128, 128, 128); - - // first pass we draw the outline - // second pass we draw the filling - for(int p = 0; p < 2; p++) - { - int outline = p==0 ? 1 : 0; - - for(int f = 0; f < 2; f++) - { - float animscale = info->size * 1.0f/64.0f; - float basesize = info->size; - if(f == 1) - { - gfx_quads_setrotation(anim->body.angle*pi*2); - - // draw body - gfx_setcolor(info->color_body.r, info->color_body.g, info->color_body.b, info->color_body.a); - vec2 body_pos = position + vec2(anim->body.x, anim->body.y)*animscale; - select_sprite(outline?SPRITE_TEE_BODY_OUTLINE:SPRITE_TEE_BODY, 0, 0, 0); - gfx_quads_draw(body_pos.x, body_pos.y, basesize, basesize); - - // draw eyes - if(p == 1) - { - switch (emote) - { - case EMOTE_PAIN: - select_sprite(SPRITE_TEE_EYE_PAIN, 0, 0, 0); - break; - case EMOTE_HAPPY: - select_sprite(SPRITE_TEE_EYE_HAPPY, 0, 0, 0); - break; - case EMOTE_SURPRISE: - select_sprite(SPRITE_TEE_EYE_SURPRISE, 0, 0, 0); - break; - case EMOTE_ANGRY: - select_sprite(SPRITE_TEE_EYE_ANGRY, 0, 0, 0); - break; - default: - select_sprite(SPRITE_TEE_EYE_NORMAL, 0, 0, 0); - break; - } - - float eyescale = basesize*0.40f; - float h = emote == EMOTE_BLINK ? basesize*0.15f : eyescale; - float eyeseparation = (0.075f - 0.010f*fabs(direction.x))*basesize; - vec2 offset = vec2(direction.x*0.125f, -0.05f+direction.y*0.10f)*basesize; - gfx_quads_draw(body_pos.x-eyeseparation+offset.x, body_pos.y+offset.y, eyescale, h); - gfx_quads_draw(body_pos.x+eyeseparation+offset.x, body_pos.y+offset.y, -eyescale, h); - } - } - - // draw feet - gfx_setcolor(info->color_feet.r, info->color_feet.g, info->color_feet.b, info->color_feet.a); - select_sprite(outline?SPRITE_TEE_FOOT_OUTLINE:SPRITE_TEE_FOOT, 0, 0, 0); - - keyframe *foot = f ? &anim->front_foot : &anim->back_foot; - - float w = basesize; - float h = basesize/2; - - gfx_quads_setrotation(foot->angle*pi*2); - gfx_quads_draw(position.x+foot->x*animscale, position.y+foot->y*animscale, w, h); - } - } - - gfx_quads_end(); -} - -void 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 draw_round_rect_ext(float x, float y, float w, float h, float r, int corners) -{ - int num = 8; - for(int i = 0; i < num; i+=2) - { - float a1 = i/(float)num * pi/2; - float a2 = (i+1)/(float)num * pi/2; - float a3 = (i+2)/(float)num * pi/2; - float ca1 = cosf(a1); - float ca2 = cosf(a2); - float ca3 = cosf(a3); - float sa1 = sinf(a1); - float sa2 = sinf(a2); - float sa3 = sinf(a3); - - if(corners&1) // TL - gfx_quads_draw_freeform( - x+r, y+r, - x+(1-ca1)*r, y+(1-sa1)*r, - x+(1-ca3)*r, y+(1-sa3)*r, - x+(1-ca2)*r, y+(1-sa2)*r); - - if(corners&2) // TR - gfx_quads_draw_freeform( - x+w-r, y+r, - x+w-r+ca1*r, y+(1-sa1)*r, - x+w-r+ca3*r, y+(1-sa3)*r, - x+w-r+ca2*r, y+(1-sa2)*r); - - if(corners&4) // BL - gfx_quads_draw_freeform( - x+r, y+h-r, - x+(1-ca1)*r, y+h-r+sa1*r, - x+(1-ca3)*r, y+h-r+sa3*r, - x+(1-ca2)*r, y+h-r+sa2*r); - - if(corners&8) // BR - gfx_quads_draw_freeform( - x+w-r, y+h-r, - x+w-r+ca1*r, y+h-r+sa1*r, - x+w-r+ca3*r, y+h-r+sa3*r, - x+w-r+ca2*r, y+h-r+sa2*r); - } - - gfx_quads_drawTL(x+r, y+r, w-r*2, h-r*2); // center - gfx_quads_drawTL(x+r, y, w-r*2, r); // top - gfx_quads_drawTL(x+r, y+h-r, w-r*2, r); // bottom - gfx_quads_drawTL(x, y+r, r, h-r*2); // left - gfx_quads_drawTL(x+w-r, y+r, r, h-r*2); // right - - if(!(corners&1)) gfx_quads_drawTL(x, y, r, r); // TL - if(!(corners&2)) gfx_quads_drawTL(x+w, y, -r, r); // TR - if(!(corners&4)) gfx_quads_drawTL(x, y+h, r, -r); // BL - if(!(corners&8)) gfx_quads_drawTL(x+w, y+h, -r, -r); // BR -} - -void draw_round_rect(float x, float y, float w, float h, float r) -{ - draw_round_rect_ext(x,y,w,h,r,0xf); -} - - -static void render_player( - const obj_player_character *prev_char, - const obj_player_character *player_char, - const obj_player_info *prev_info, - const obj_player_info *player_info - ) -{ - obj_player_character prev; - obj_player_character player; - prev = *prev_char; - player = *player_char; - - obj_player_info info = *player_info; - - float intratick = client_intratick(); - - if(player.health < 0) // dont render dead players - return; - - if(info.local && config.cl_predict) - { - if(!local_character || (local_character->health < 0) || (gameobj && gameobj->game_over)) - { - } - else - { - // apply predicted results - predicted_player.write(&player); - predicted_prev_player.write(&prev); - intratick = client_intrapredtick(); - } - } - - // TODO: proper skin selection - int skin_id = client_datas[info.clientid].skin_id; //charids[info.clientid]; - //if(gametype != GAMETYPE_DM) - //skin_id = info.team*9; // 0 or 9 - - vec2 direction = get_direction(player.angle); - float angle = player.angle/256.0f; - vec2 position = mix(vec2(prev.x, prev.y), vec2(player.x, player.y), intratick); - - 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; - - // evaluate animation - float walk_time = fmod(position.x, 100.0f)/100.0f; - animstate state; - anim_eval(&data->animations[ANIM_BASE], 0, &state); - - if(inair) - anim_eval_add(&state, &data->animations[ANIM_INAIR], 0, 1.0f); // TODO: some sort of time here - else if(stationary) - anim_eval_add(&state, &data->animations[ANIM_IDLE], 0, 1.0f); // TODO: some sort of time here - else - anim_eval_add(&state, &data->animations[ANIM_WALK], walk_time, 1.0f); - - if (player.weapon == WEAPON_HAMMER) - { - float a = clamp((client_tick()-player.attacktick+intratick)/10.0f, 0.0f, 1.0f); - anim_eval_add(&state, &data->animations[ANIM_HAMMER_SWING], a, 1.0f); - } - if (player.weapon == WEAPON_NINJA) - { - float a = clamp((client_tick()-player.attacktick+intratick)/40.0f, 0.0f, 1.0f); - anim_eval_add(&state, &data->animations[ANIM_NINJA_SWING], a, 1.0f); - } - - // 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 = 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); - for(float f = 24; f < d; f += 24) - { - vec2 p = hook_pos + dir*f; - gfx_quads_draw(p.x, p.y,24,16); - } - - gfx_quads_setrotation(0); - gfx_quads_end(); - - render_hand(skin_id, 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[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; - p.y += data->weapons[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[iw].offsetx; - } - else - { - gfx_quads_setrotation(-pi/2+state.attach.angle*pi*2); - } - draw_sprite(p.x, p.y, data->weapons[iw].visual_size); - } - else if (player.weapon == WEAPON_NINJA) - { - p = position; - p.y += data->weapons[iw].offsety; - - if(direction.x < 0) - { - gfx_quads_setrotation(-pi/2-state.attach.angle*pi*2); - p.x -= data->weapons[iw].offsetx; - } - else - { - gfx_quads_setrotation(-pi/2+state.attach.angle*pi*2); - } - draw_sprite(p.x, p.y, data->weapons[iw].visual_size); - - // HADOKEN - if ((client_tick()-player.attacktick) <= (SERVER_TICK_SPEED / 6) && data->weapons[iw].nummuzzlesprites) - { - int itex = rand() % data->weapons[iw].nummuzzlesprites; - float alpha = 1.0f; - if (alpha > 0.0f && data->weapons[iw].sprite_muzzle[itex].psprite) - { - 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[iw].sprite_muzzle[itex].psprite, 0); - vec2 diry(-dir.y,dir.x); - p = position; - float offsetx = data->weapons[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[iw].offsetx - dir*recoil*10.0f; - p.y += data->weapons[iw].offsety; - draw_sprite(p.x, p.y, data->weapons[iw].visual_size); - } - - if (player.weapon == WEAPON_GUN || player.weapon == WEAPON_SHOTGUN) - { - // check if we're firing stuff - if (true)//prev.attackticks) - { - float alpha = 0.0f; - int phase1tick = (client_tick() - player.attacktick); - if (phase1tick < (data->weapons[iw].muzzleduration + 3)) - { - float t = ((((float)phase1tick) + intratick)/(float)data->weapons[iw].muzzleduration); - alpha = LERP(2.0, 0.0f, min(1.0f,max(0.0f,t))); - } - - int itex = rand() % data->weapons[iw].nummuzzlesprites; - if (alpha > 0.0f && data->weapons[iw].sprite_muzzle[itex].psprite) - { - float offsety = -data->weapons[iw].muzzleoffsety; - select_sprite(data->weapons[iw].sprite_muzzle[itex].psprite, 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[iw].muzzleoffsetx + diry * offsety; - - draw_sprite(muzzlepos.x, muzzlepos.y, data->weapons[iw].visual_size); - /*gfx_setcolor(1.0f,1.0f,1.0f,alpha); - vec2 diry(-dir.y,dir.x); - p += dir * muzzleparams[player.weapon].offsetx + diry * offsety; - gfx_quads_draw(p.x,p.y,muzzleparams[player.weapon].sizex, muzzleparams[player.weapon].sizey);*/ - } - } - } - gfx_quads_end(); - - switch (player.weapon) - { - case WEAPON_GUN: render_hand(skin_id, p, direction, -3*pi/4, vec2(-15, 4)); break; - case WEAPON_SHOTGUN: render_hand(skin_id, p, direction, -pi/2, vec2(-5, 4)); break; - case WEAPON_ROCKET: render_hand(skin_id, 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 = client_datas[info.clientid].skin_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 the tee - render_tee(&state, &client_datas[info.clientid].skin_info, player.emote, direction, position); - - if(player.state == STATE_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 (client_datas[info.clientid].emoticon_start != -1 && client_datas[info.clientid].emoticon_start + 2 * client_tickspeed() > client_tick()) - { - gfx_texture_set(data->images[IMAGE_EMOTICONS].id); - gfx_quads_begin(); - - int since_start = client_tick() - client_datas[info.clientid].emoticon_start; - int from_end = client_datas[info.clientid].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 + client_datas[info.clientid].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_pretty_text_color - float a = 1; - if(config.cl_nameplates_always == 0) - a = clamp(1-powf(distance(local_target_pos, position)/200.0f,16.0f), 0.0f, 1.0f); - - const char *name = client_datas[info.clientid].name; - float tw = gfx_pretty_text_width(28.0f, name, -1); - gfx_pretty_text_color(1,1,1,a); - gfx_pretty_text(position.x-tw/2.0f, position.y-60, 28.0f, name, -1); - gfx_pretty_text_color(1,1,1,1); - } -} - -void render_moon(float x, float y) -{ - gfx_texture_set(data->images[IMAGE_MOON].id); - gfx_quads_begin(); - gfx_quads_draw(x, y, 512, 512); - gfx_quads_end(); -} - -void render_stars() -{ - struct star - { - vec2 p; - float tingle; - float rot; - float size; - }; - - static const int NUM_STARS = 100; - static star stars[NUM_STARS]; - static bool init = true; - if(init) - { - for(int i = 0; i < NUM_STARS; i++) - { - stars[i].p.x = (frandom()-0.5f)*800*3; - stars[i].p.y = (frandom()-0.5f)*600*3; - stars[i].tingle = frandom(); - stars[i].rot = frandom()*pi; - stars[i].size = 10.0f+frandom()*10.0f; - } - - init = false; - } - - int64 now = time_get(); - int64 freq = time_freq()*5; - float t = (now%freq)/(float)freq; - gfx_texture_set(data->images[IMAGE_STARS].id); - gfx_quads_begin(); - gfx_quads_setsubset(0,0,0.5f,1); - for(int i = 0; i < NUM_STARS; i++) - { - float a = fmod(t+stars[i].tingle, 1.0f); - gfx_quads_setrotation(stars[i].rot); - gfx_setcolor(1,1,1,0.25f+sinf(a*pi)*0.75f); - gfx_quads_draw(stars[i].p.x, stars[i].p.y, stars[i].size, stars[i].size); - } - - gfx_quads_end(); -} - -void render_snow() -{ - vec2 tl, br; - gfx_getscreen(&tl.x, &tl.y, &br.x, &br.y); - tl.x += 1000; // this is here to fix positions below 0,0 - tl.y += 1000; - br.x += 1000; - br.y += 1000; - - struct flake - { - vec2 p; - float tingle; - float rot; - float size; - float yspeed; - float xspeed; - }; - - static const int NUM_FLAKES = 100; - static flake flakes[NUM_FLAKES]; - static bool init = true; - - float w = br.x-tl.x; - float h = br.y-tl.y; - - if(init) - { - for(int i = 0; i < NUM_FLAKES; i++) - { - flakes[i].p.x = frandom()*w; - flakes[i].p.y = frandom()*h; - flakes[i].tingle = frandom(); - flakes[i].rot = frandom()*pi; - flakes[i].size = 50.0f+frandom()*10.0f; - flakes[i].yspeed = 50+frandom()*20.0f; - flakes[i].xspeed = flakes[i].yspeed * (0.4+frandom()*0.4f); - } - - init = false; - } - - int basex = (int)(tl.x/w); - float splitx = tl.x-basex*w; - int basey = (int)(tl.y/h); - float splity = tl.y-basey*h; - - float f = client_frametime(); - gfx_texture_set(data->images[IMAGE_SNOW].id); - gfx_quads_begin(); - for(int i = 0; i < NUM_FLAKES; i++) - { - flakes[i].p.x -= f*flakes[i].xspeed; - flakes[i].p.y += f*flakes[i].yspeed; - if(flakes[i].p.x < 0) - flakes[i].p.x += w; - if(flakes[i].p.y > h) - flakes[i].p.y -= h; - - float x = flakes[i].p.x + basex*w; - float y = flakes[i].p.y + basey*h; - - if(flakes[i].p.x < splitx) - x += w; - if(flakes[i].p.y < splity) - y += h; - - x -= 1000; - y -= 1000; - - gfx_quads_setrotation(flakes[i].rot); - gfx_quads_draw(x, y, flakes[i].size, flakes[i].size); - } - - gfx_quads_end(); -} - -void render_sun(float x, float y) -{ - vec2 pos(x, y); - - gfx_texture_set(-1); - gfx_blend_additive(); - gfx_quads_begin(); - const int rays = 10; - gfx_setcolor(1.0f,1.0f,1.0f,0.025f); - for(int r = 0; r < rays; r++) - { - float a = r/(float)rays + client_localtime()*0.025f; - float size = (1.0f/(float)rays)*0.25f; - vec2 dir0(sinf((a-size)*pi*2.0f), cosf((a-size)*pi*2.0f)); - vec2 dir1(sinf((a+size)*pi*2.0f), cosf((a+size)*pi*2.0f)); - - gfx_setcolorvertex(0, 1.0f,1.0f,1.0f,0.025f); - gfx_setcolorvertex(1, 1.0f,1.0f,1.0f,0.025f); - gfx_setcolorvertex(2, 1.0f,1.0f,1.0f,0.0f); - gfx_setcolorvertex(3, 1.0f,1.0f,1.0f,0.0f); - const float range = 1000.0f; - gfx_quads_draw_freeform( - pos.x+dir0.x, pos.y+dir0.y, - pos.x+dir1.x, pos.y+dir1.y, - pos.x+dir0.x*range, pos.y+dir0.y*range, - pos.x+dir1.x*range, pos.y+dir1.y*range); - } - gfx_quads_end(); - gfx_blend_normal(); - - gfx_texture_set(data->images[IMAGE_SUN].id); - gfx_quads_begin(); - gfx_quads_draw(pos.x, pos.y, 256, 256); - gfx_quads_end(); -} - -static vec2 emoticon_selector_mouse; - -void emoticon_selector_reset() -{ - emoticon_selector_mouse = vec2(0, 0); -} - -int emoticon_selector_render() -{ - int x, y; - inp_mouse_relative(&x, &y); - - emoticon_selector_mouse.x += x; - emoticon_selector_mouse.y += y; - - if (length(emoticon_selector_mouse) > 140) - emoticon_selector_mouse = normalize(emoticon_selector_mouse) * 140; - - float selected_angle = get_angle(emoticon_selector_mouse) + 2*pi/24; - if (selected_angle < 0) - selected_angle += 2*pi; - - bool return_now = false; - int selected_emoticon = -1; - - if (length(emoticon_selector_mouse) > 100) - selected_emoticon = (int)(selected_angle / (2*pi) * 12.0f); - - if(!inp_key_pressed(config.key_emoticon)) - { - return_now = true; - emoticon_selector_active = false; - } - - RECT screen = *ui2_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_emoticon == 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(emoticon_selector_mouse.x+screen.w/2,emoticon_selector_mouse.y+screen.h/2,24,24); - gfx_quads_end(); - - return return_now ? selected_emoticon : -1; -} - -void 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(gameobj && gameobj->time_limit) - { - char buf[64]; - sprintf(buf, "Time Limit: %d min", gameobj->time_limit); - gfx_pretty_text(x+w/2, y, 32, buf, -1); - } - if(gameobj && gameobj->score_limit) - { - char buf[64]; - sprintf(buf, "Score Limit: %d", gameobj->score_limit); - gfx_pretty_text(x+40, y, 32, buf, -1); - } - -} - -void render_scoreboard(float x, float y, float w, int team, const char *title) -{ - //float w = 550.0f; - //float x = width/2-w/2; - //; - //float y = ystart; - //float w = 550.0f; - - animstate idlestate; - anim_eval(&data->animations[ANIM_BASE], 0, &idlestate); - anim_eval_add(&idlestate, &data->animations[ANIM_IDLE], 0, 1.0f); - - //float ystart = y; - float h = 600.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(gameobj->game_over) - title = "Game Over"; - else - title = "Score Board"; - } - - float tw = gfx_pretty_text_width( 64, title, -1); - - if(team == -1) - { - gfx_pretty_text(x+w/2-tw/2, y, 64, title, -1); - } - else - { - gfx_pretty_text(x+10, y, 64, title, -1); - - if(gameobj) - { - char buf[128]; - sprintf(buf, "%d", gameobj->teamscore[team&1]); - tw = gfx_pretty_text_width(64, buf, -1); - gfx_pretty_text(x+w-tw-40, y, 64, buf, -1); - } - } - - y += 64.0f; - - /* - if(team) - { - char buf[128]; - sprintf(buf, "%4d", gameobj->teamscore[team&1]); - gfx_pretty_text(x+w/2-tw/2, y, 32, buf, -1); - }*/ - - - // find players - const obj_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 == OBJTYPE_PLAYER_INFO) - { - players[num_players] = (const obj_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 obj_player_info *tmp = players[i]; - players[i] = players[i+1]; - players[i+1] = tmp; - } - } - } - - // render headlines - gfx_pretty_text(x+10, y, 32, "Score", -1); - gfx_pretty_text(x+125, y, 32, "Name", -1); - gfx_pretty_text(x+w-70, y, 32, "Ping", -1); - y += 38.0f; - - // render player scores - for(int i = 0; i < num_players; i++) - { - const obj_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 = 46.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(); - } - - sprintf(buf, "%4d", info->score); - gfx_pretty_text(x+60-gfx_pretty_text_width(font_size,buf,-1), y, font_size, buf, -1); - gfx_pretty_text(x+128, y, font_size, client_datas[info->clientid].name, -1); - - sprintf(buf, "%4d", info->latency); - float tw = gfx_pretty_text_width(font_size, buf, -1); - gfx_pretty_text(x+w-tw-35, y, font_size, buf, -1); - - // render avatar - render_tee(&idlestate, &client_datas[info->clientid].skin_info, EMOTE_NORMAL, vec2(1,0), vec2(x+90, y+28)); - y += 50.0f; - } -} - -void mapscreen_to_world(float center_x, float center_y, float zoom) -{ - RECT screen = *ui2_screen(); - - const float default_zoom = 1.5f; - float width = screen.w*default_zoom*zoom; - float height = screen.h*default_zoom*zoom; - gfx_mapscreen(center_x-width/2, center_y-height/2, center_x+width/2, center_y+height/2); -} - -// renders the complete game world -void render_world(float center_x, float center_y, float zoom) -{ - mapscreen_to_world(center_x, center_y, zoom); - //gfx_mapscreen(center_x-width/2, center_y-height/2, center_x+width/2, center_y+height/2); - - // render background environment - int theme_id = 0; - mapres_theme *t = (mapres_theme *)map_find_item(MAPRES_TEMP_THEME, 0); - if(t) - theme_id = t->id; - - if(config.gfx_high_detail) - { - if(theme_id == 1) - { - // Winter night - gfx_mapscreen(0,0,1,1); - gfx_texture_set(-1); - gfx_quads_begin(); - vec4 top(0x11/(float)0xff, 0x1a/(float)0xff, 0x21/(float)0xff, 1.0f); - vec4 bottom(0x2a/(float)0xff, 0x40/(float)0xff, 0x52/(float)0xff, 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, 1, 1); - gfx_quads_end(); - - mapscreen_to_world(center_x*0.1f, center_y*0.1f, zoom); - render_stars(); - - mapscreen_to_world(center_x, center_y, zoom); - - render_moon(center_x*0.8f, center_y*0.8f); - - mapscreen_to_world(center_x, center_y, zoom); - } - else - { - // Summer day - render_sun(20+center_x*0.6f, 20+center_y*0.6f); - - // draw clouds - static vec2 cloud_pos[6] = {vec2(-500,0),vec2(-500,200),vec2(-500,400)}; - static float cloud_speed[6] = {30, 20, 10}; - static int cloud_sprites[6] = {SPRITE_CLOUD1, SPRITE_CLOUD2, SPRITE_CLOUD3}; - - gfx_texture_set(data->images[IMAGE_CLOUDS].id); - gfx_quads_begin(); - for(int i = 0; i < 3; i++) - { - float parallax_amount = 0.55f; - select_sprite(cloud_sprites[i]); - draw_sprite((cloud_pos[i].x+fmod(client_localtime()*cloud_speed[i]+i*100.0f, 3000.0f))+center_x*parallax_amount, - cloud_pos[i].y+center_y*parallax_amount, 300); - } - gfx_quads_end(); - - // draw backdrop - gfx_texture_set(data->images[IMAGE_BACKDROP].id); - gfx_quads_begin(); - float parallax_amount = 0.25f; - for(int x = -1; x < 3; x++) - gfx_quads_drawTL(1024*x+center_x*parallax_amount, (center_y)*parallax_amount+150+512, 1024, 512); - gfx_quads_end(); - } - } - - // render background tilemaps - tilemap_render(32.0f, 0); - - // render items - { - 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 == OBJTYPE_PROJECTILE) - { - const void *prev = snap_find_item(SNAP_PREV, item.type, item.id); - if(prev) - render_projectile((const obj_projectile *)prev, (const obj_projectile *)data, item.id); - } - else if(item.type == OBJTYPE_POWERUP) - { - const void *prev = snap_find_item(SNAP_PREV, item.type, item.id); - if(prev) - render_powerup((const obj_powerup *)prev, (const obj_powerup *)data); - } - else if(item.type == OBJTYPE_FLAG) - { - const void *prev = snap_find_item(SNAP_PREV, item.type, item.id); - if (prev) - render_flag((const obj_flag *)prev, (const obj_flag *)data); - } - } - } - - // render players above all - { - 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 == OBJTYPE_PLAYER_CHARACTER) - { - const void *prev = snap_find_item(SNAP_PREV, item.type, item.id); - const void *prev_info = snap_find_item(SNAP_PREV, OBJTYPE_PLAYER_INFO, item.id); - const void *info = snap_find_item(SNAP_CURRENT, OBJTYPE_PLAYER_INFO, item.id); - if(prev && prev_info && info) - { - client_datas[((const obj_player_info *)info)->clientid].team = ((const obj_player_info *)info)->team; - render_player( - (const obj_player_character *)prev, - (const obj_player_character *)data, - (const obj_player_info *)prev_info, - (const obj_player_info *)info - ); - } - } - } - } - - // render particles - temp_system.update(client_frametime()); - temp_system.render(); - - // render foreground tilemaps - tilemap_render(32.0f, 1); - - // render front environment effects - if(config.gfx_high_detail) - { - if(theme_id == 1) - { - //mapscreen_to_world(center_x, center_y, zoom); - render_snow(); - } - } - - // render damage indications - damageind.render(); -} - -static void do_input(int *v, int key) -{ - *v += inp_key_presses(key) + inp_key_releases(key); - if((*v&1) != inp_key_state(key)) - (*v)++; - *v &= INPUT_STATE_MASK; -} - -void render_game() -{ - float width = 400*3.0f; - float height = 300*3.0f; - - bool spectate = false; - - if(config.cl_predict) - { - if(!local_character || (local_character->health < 0) || (gameobj && gameobj->game_over)) - { - // don't use predicted - } - else - local_character_pos = mix(predicted_prev_player.pos, predicted_player.pos, client_intrapredtick()); - } - else if(local_character && local_prev_character) - { - local_character_pos = mix( - vec2(local_prev_character->x, local_prev_character->y), - vec2(local_character->x, local_character->y), client_intratick()); - } - - if(local_info && local_info->team == -1) - spectate = true; - - animstate idlestate; - anim_eval(&data->animations[ANIM_BASE], 0, &idlestate); - anim_eval_add(&idlestate, &data->animations[ANIM_IDLE], 0, 1.0f); - - if (inp_key_down(KEY_ESC)) - { - if (chat_mode) - chat_mode = CHATMODE_NONE; - else - { - menu_active = !menu_active; - if(menu_active) - menu_game_active = true; - } - } - - // make sure to send our info again if the menu closes - static bool menu_was_active = false; - if(menu_active) - menu_was_active = true; - else if(menu_was_active) - { - send_info(false); - menu_was_active = false; - } - - // handle chat input - if (!menu_active) - { - if(chat_mode != CHATMODE_NONE) - { - if(inp_key_down(KEY_ENTER)) - { - // send message - if(chat_input_len) - { - if(chat_mode == CHATMODE_CONSOLE) - config_set(chat_input); - else if(chat_mode == CHATMODE_REMOTECONSOLE) - client_rcon(chat_input); - else - { - // send chat message - msg_pack_start(MSG_SAY, MSGFLAG_VITAL); - if(chat_mode == CHATMODE_ALL) - msg_pack_int(0); - else - msg_pack_int(1); - msg_pack_string(chat_input, 512); - msg_pack_end(); - client_send_msg(); - } - } - - chat_mode = CHATMODE_NONE; - } - - int c = inp_last_char(); - int k = inp_last_key(); - - if (!(c >= 0 && c < 32)) - { - if (chat_input_len < sizeof(chat_input) - 1) - { - chat_input[chat_input_len] = c; - chat_input[chat_input_len+1] = 0; - chat_input_len++; - } - } - - if(k == KEY_BACKSPACE) - { - if(chat_input_len > 0) - { - chat_input[chat_input_len-1] = 0; - chat_input_len--; - } - } - - } - else - { - if(chat_mode == CHATMODE_NONE) - { - if(inp_key_down(config.key_chat)) - chat_mode = CHATMODE_ALL; - - if(inp_key_down(config.key_teamchat)) - chat_mode = CHATMODE_TEAM; - - if(inp_key_down(config.key_console)) - chat_mode = CHATMODE_CONSOLE; - - if(inp_key_down(config.key_remoteconsole)) - chat_mode = CHATMODE_REMOTECONSOLE; - - if(chat_mode != CHATMODE_NONE) - { - mem_zero(chat_input, sizeof(chat_input)); - chat_input_len = 0; - } - } - } - } - - if (!menu_active) - inp_clear(); - - // fetch new input - if(!menu_active && !emoticon_selector_active) - { - int x, y; - inp_mouse_relative(&x, &y); - mouse_pos += vec2(x, y); - if(!spectate) - { - float l = length(mouse_pos); - if(l > 600.0f) - mouse_pos = normalize(mouse_pos)*600.0f; - } - } - - // set listner pos - if(spectate) - { - local_target_pos = mouse_pos; - snd_set_listener_pos(mouse_pos.x, mouse_pos.y); - } - else - { - local_target_pos = local_character_pos + mouse_pos; - snd_set_listener_pos(local_character_pos.x, local_character_pos.y); - } - - // snap input - { - static player_input input = {0}; - - input.target_x = (int)mouse_pos.x; - input.target_y = (int)mouse_pos.y; - - if(chat_mode != CHATMODE_NONE) - input.state = STATE_CHATTING; - else if(menu_active) - input.state = STATE_IN_MENU; - else - { - input.state = STATE_PLAYING; - input.left = inp_key_state(config.key_move_left); - input.right = inp_key_state(config.key_move_right); - input.hook = inp_key_state(config.key_hook); - input.jump = inp_key_state(config.key_jump); - - if(!emoticon_selector_active) - do_input(&input.fire, config.key_fire); - - // weapon selection - do_input(&input.next_weapon, config.key_next_weapon); - do_input(&input.prev_weapon, config.key_prev_weapon); - - if(inp_key_presses(config.key_next_weapon) || inp_key_presses(config.key_prev_weapon)) - input.wanted_weapon = 0; - else if (config.cl_autoswitch_weapons && picked_up_weapon) - { - input.wanted_weapon = picked_up_weapon; - } - else - { - if(inp_key_presses(config.key_weapon1)) input.wanted_weapon = 1; - if(inp_key_presses(config.key_weapon2)) input.wanted_weapon = 2; - if(inp_key_presses(config.key_weapon3)) input.wanted_weapon = 3; - if(inp_key_presses(config.key_weapon4)) input.wanted_weapon = 4; - if(inp_key_presses(config.key_weapon5)) input.wanted_weapon = 5; - if(inp_key_presses(config.key_weapon6)) input.wanted_weapon = 6; - } - - picked_up_weapon = 0; - } - - // stress testing - if(config.dbg_stress) - { - float t = client_localtime(); - mem_zero(&input, sizeof(input)); - - input.left = ((int)t/2)&1; - input.right = ((int)t/2+1)&1; - input.jump = ((int)t); - input.fire = ((int)(t*10)); - input.hook = ((int)(t*2))&1; - input.wanted_weapon = ((int)t)%NUM_WEAPONS; - input.target_x = (int)(sinf(t*3)*100.0f); - input.target_y = (int)(cosf(t*3)*100.0f); - } - - snap_input(&input, sizeof(input)); - } - - // center at char but can be moved when mouse is far away - float offx = 0, offy = 0; - if (config.cl_dynamic_camera) - { - int deadzone = 300; - if(mouse_pos.x > deadzone) offx = mouse_pos.x-deadzone; - if(mouse_pos.x <-deadzone) offx = mouse_pos.x+deadzone; - if(mouse_pos.y > deadzone) offy = mouse_pos.y-deadzone; - if(mouse_pos.y <-deadzone) offy = mouse_pos.y+deadzone; - offx = offx*2/3; - offy = offy*2/3; - } - - // render the world - gfx_clear(0.65f,0.78f,0.9f); - if(spectate) - render_world(mouse_pos.x, mouse_pos.y, 1.0f); - else - { - render_world(local_character_pos.x+offx, local_character_pos.y+offy, 1.0f); - - // draw screen box - if(0) - { - gfx_texture_set(-1); - gfx_blend_normal(); - gfx_lines_begin(); - float cx = local_character_pos.x+offx; - float cy = local_character_pos.y+offy; - float w = 400*3/2; - float h = 300*3/2; - gfx_lines_draw(cx-w,cy-h,cx+w,cy-h); - gfx_lines_draw(cx+w,cy-h,cx+w,cy+h); - gfx_lines_draw(cx+w,cy+h,cx-w,cy+h); - gfx_lines_draw(cx-w,cy+h,cx-w,cy-h); - gfx_lines_end(); - } - } - - - // pseudo format - // ZOOM ZOOM - float zoom = 3.0; - if(local_character) - cl_effects.getzoom(client_tick(), client_intratick(), local_character); - - // DEBUG TESTING - if(zoom > 3.01f) - { - gfx_clear_mask(0); - - gfx_texture_set(-1); - gfx_blend_normal(); - - gfx_mask_op(MASK_NONE, 1); - - gfx_quads_begin(); - gfx_setcolor(0.65f,0.78f,0.9f,1.0f); - - float fov; - if (zoom > 3.01f) - fov = pi * (zoom - 3.0f) / 6.0f; - else - fov = pi / 6.0f; - - float fade = 0.7f; - - - float a = get_angle(normalize(vec2(mouse_pos.x, mouse_pos.y))); - vec2 d = get_dir(a); - vec2 d0 = get_dir(a-fov/2.0f); - vec2 d1 = get_dir(a+fov/2.0f); - - vec2 cd0 = get_dir(a-(fov*fade)/2.0f); // center direction - vec2 cd1 = get_dir(a+(fov*fade)/2.0f); - - vec2 p0n = local_character_pos + d0*32.0f; - vec2 p1n = local_character_pos + d1*32.0f; - vec2 p0f = local_character_pos + d0*1000.0f; - vec2 p1f = local_character_pos + d1*1000.0f; - - vec2 cn = local_character_pos + d*32.0f; - vec2 cf = local_character_pos + d*1000.0f; - - vec2 cp0n = local_character_pos + cd0*32.0f; - vec2 cp0f = local_character_pos + cd0*1000.0f; - vec2 cp1n = local_character_pos + cd1*32.0f; - vec2 cp1f = local_character_pos + cd1*1000.0f; - - gfx_quads_draw_freeform( - p0n.x,p0n.y, - p1n.x,p1n.y, - p0f.x,p0f.y, - p1f.x,p1f.y); - gfx_quads_end(); - - gfx_mask_op(MASK_SET, 0); - - render_world(local_character_pos.x+offx, local_character_pos.y+offy, 2.0f); - - gfx_mask_op(MASK_NONE, 0); - - mapscreen_to_world(local_character_pos.x+offx, local_character_pos.y+offy, 1.0f); - - gfx_texture_set(-1); - gfx_blend_normal(); - gfx_quads_begin(); - gfx_setcolor(0.5f,0.9f,0.5f,0.25f); - float r=0.5f, g=1.0f, b=0.5f; - float r2=r*0.25f, g2=g*0.25f, b2=b*0.25f; - - gfx_setcolor(r,g,b,0.2f); - gfx_quads_draw_freeform( - cn.x,cn.y, - cn.x,cn.y, - cp0f.x,cp0f.y, - cp1f.x,cp1f.y); - - gfx_setcolorvertex(0, r, g, b, 0.2f); - gfx_setcolorvertex(1, r2, g2, b2, 0.9f); - gfx_setcolorvertex(2, r, g, b, 0.2f); - gfx_setcolorvertex(3, r2, g2, b2, 0.9f); - gfx_quads_draw_freeform( - cn.x,cn.y, - p0n.x,p0n.y, - cp0f.x,cp0f.y, - p0f.x,p0f.y); - - gfx_quads_draw_freeform( - cn.x,cn.y, - p1n.x,p1n.y, - cp1f.x,cp1f.y, - p1f.x,p1f.y); - - gfx_quads_end(); - } - - if(local_character && !spectate && !(gameobj && gameobj->game_over)) - { - gfx_texture_set(data->images[IMAGE_GAME].id); - gfx_quads_begin(); - - // render cursor - if (!menu_active && !emoticon_selector_active) - { - select_sprite(data->weapons[local_character->weapon%data->num_weapons].sprite_cursor); - float cursorsize = 64; - draw_sprite(local_target_pos.x, local_target_pos.y, cursorsize); - } - - float x = 5; - float y = 5; - - // render ammo count - // render gui stuff - gfx_quads_end(); - gfx_quads_begin(); - gfx_mapscreen(0,0,400,300); - // if weaponstage is active, put a "glow" around the stage ammo - select_sprite(SPRITE_TEE_BODY); - for (int i = 0; i < local_character->weaponstage; i++) - gfx_quads_drawTL(x+local_character->ammocount * 12 -i*12, y+22, 11, 11); - select_sprite(data->weapons[local_character->weapon%data->num_weapons].sprite_proj); - for (int i = 0; i < local_character->ammocount; 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 < 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 < 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(); - } - - // render kill messages - { - 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 = 48.0f; - float killername_w = gfx_pretty_text_width(font_size, client_datas[killmsgs[r].killer].name, -1); - float victimname_w = gfx_pretty_text_width(font_size, client_datas[killmsgs[r].victim].name, -1); - - float x = startx; - - // render victim name - x -= victimname_w; - gfx_pretty_text(x, y, font_size, client_datas[killmsgs[r].victim].name, -1); - - // render victim tee - x -= 24.0f; - - if(gameobj && 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(client_datas[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(&idlestate, &client_datas[killmsgs[r].victim].skin_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[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(gameobj && 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(client_datas[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(&idlestate, &client_datas[killmsgs[r].killer].skin_info, EMOTE_ANGRY, vec2(1,0), vec2(x, y+28)); - x -= 32.0f; - - // render killer name - x -= killername_w; - gfx_pretty_text(x, y, font_size, client_datas[killmsgs[r].killer].name, -1); - } - - y += 44; - } - } - - // render chat - { - gfx_mapscreen(0,0,400,300); - float x = 10.0f; - float y = 300.0f-50.0f; - float starty = -1; - if(chat_mode != CHATMODE_NONE) - { - // render chat input - char buf[sizeof(chat_input)+16]; - if(chat_mode == CHATMODE_ALL) - sprintf(buf, "All: %s_", chat_input); - else if(chat_mode == CHATMODE_TEAM) - sprintf(buf, "Team: %s_", chat_input); - else if(chat_mode == CHATMODE_CONSOLE) - sprintf(buf, "Console: %s_", chat_input); - else if(chat_mode == CHATMODE_REMOTECONSOLE) - sprintf(buf, "Rcon: %s_", chat_input); - else - sprintf(buf, "Chat: %s_", chat_input); - gfx_pretty_text(x, y, 10.0f, buf, 380); - starty = y; - } - - y -= 10; - - int i; - for(i = 0; i < chat_max_lines; i++) - { - int r = ((chat_current_line-i)+chat_max_lines)%chat_max_lines; - if(client_tick() > chat_lines[r].tick+50*15) - break; - - int lines = int(gfx_pretty_text_width(10, chat_lines[r].text, -1)) / 380 + 1; - - gfx_pretty_text_color(1,1,1,1); - if(chat_lines[r].client_id == -1) - gfx_pretty_text_color(1,1,0.5f,1); // system - else if(chat_lines[r].team) - gfx_pretty_text_color(0.5f,1,0.5f,1); // team message - - gfx_pretty_text(x, y - 8 * (lines - 1), 10, chat_lines[r].text, 380); - y -= 8 * lines; - } - - gfx_pretty_text_color(1,1,1,1); - } - - // render goals - if(gameobj) - { - gametype = gameobj->gametype; - gfx_mapscreen(0,0,400,300); - if(!gameobj->sudden_death) - { - char buf[32]; - int time = 0; - if(gameobj->time_limit) - { - time = gameobj->time_limit*60 - ((client_tick()-gameobj->round_start_tick)/client_tickspeed()); - - if(gameobj->game_over) - time = 0; - } - else - time = (client_tick()-gameobj->round_start_tick)/client_tickspeed(); - - sprintf(buf, "%d:%02d", time /60, time %60); - float w = gfx_pretty_text_width(16, buf, -1); - gfx_pretty_text(200-w/2, 2, 16, buf, -1); - } - - if(gameobj->sudden_death) - { - const char *text = "Sudden Death"; - float w = gfx_pretty_text_width(16, text, -1); - gfx_pretty_text(200-w/2, 2, 16, text, -1); - } - - // render small score hud - if(!(gameobj && gameobj->game_over) && (gametype == GAMETYPE_TDM || gametype == GAMETYPE_CTF)) - { - 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(400-40, 300-40-15+t*20, 50, 18, 5.0f); - gfx_quads_end(); - - char buf[32]; - sprintf(buf, "%d", gameobj->teamscore[t]); - float w = gfx_pretty_text_width(14, buf, -1); - - if(gametype == GAMETYPE_CTF) - { - gfx_pretty_text(400-20-w/2+5, 300-40-15+t*20+2, 14, buf, -1); - if(flags[t]) - { - if(flags[t]->carried_by == -2 || (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(400-40+5, 300-40-15+t*20+1, size/2, size); - gfx_quads_end(); - } - else if(flags[t]->carried_by >= 0) - { - int id = flags[t]->carried_by%MAX_CLIENTS; - const char *name = client_datas[id].name; - float w = gfx_pretty_text_width(10, name, -1); - gfx_pretty_text(400-40-5-w, 300-40-15+t*20+2, 10, name, -1); - tee_render_info info = client_datas[id].skin_info; - info.size = 18.0f; - - render_tee(&idlestate, &info, EMOTE_NORMAL, vec2(1,0), - vec2(400-40+10, 300-40-15+9+t*20+1)); - } - } - } - else - gfx_pretty_text(400-20-w/2, 300-40-15+t*20+2, 14, buf, -1); - } - } - - // render warmup timer - if(gameobj->warmup) - { - char buf[256]; - float w = gfx_pretty_text_width(24, "Warmup", -1); - gfx_pretty_text(200+-w/2, 50, 24, "Warmup", -1); - - int seconds = gameobj->warmup/SERVER_TICK_SPEED; - if(seconds < 5) - sprintf(buf, "%d.%d", seconds, (gameobj->warmup*10/SERVER_TICK_SPEED)%10); - else - sprintf(buf, "%d", seconds); - w = gfx_pretty_text_width(24, buf, -1); - gfx_pretty_text(200+-w/2, 75, 24, buf, -1); - } - } - - if (menu_active) - { - modmenu_render(); - return; - } - - if(chat_mode == CHATMODE_NONE && !menu_active && !spectate) - { - if(!emoticon_selector_active && inp_key_pressed(config.key_emoticon)) - { - emoticon_selector_active = true; - emoticon_selector_reset(); - } - } - else - emoticon_selector_active = false; - - if(emoticon_selector_active) - { - int emoticon = emoticon_selector_render(); - if (emoticon != -1) - { - send_emoticon(emoticon); - emoticon_selector_active = false; - } - } - - if(client_connection_problems()) - { - gfx_mapscreen(0, 0, 400, 300); - const char *text = "Connection Problems..."; - float w = gfx_pretty_text_width(24, text, -1); - gfx_pretty_text(200-w/2, 50, 24, text, -1); - } - - // render score board - if(inp_key_pressed(KEY_TAB) || // user requested - (!spectate && (!local_character || local_character->health < 0)) || // not spectating and is dead - (gameobj && gameobj->game_over) // game over - ) - { - gfx_mapscreen(0, 0, width, height); - - float w = 550.0f; - - if (gameobj && gameobj->gametype == GAMETYPE_DM) - { - render_scoreboard(width/2-w/2, 150.0f, w, -1, 0); - //render_scoreboard(gameobj, 0, 0, -1, 0); - } - else - { - - if(gameobj && gameobj->game_over) - { - const char *text = "DRAW!"; - if(gameobj->teamscore[0] > gameobj->teamscore[1]) - text = "Red Team Wins!"; - else if(gameobj->teamscore[1] > gameobj->teamscore[0]) - text = "Blue Team Wins!"; - - float w = gfx_pretty_text_width(92.0f, text, -1); - gfx_pretty_text(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+600+25, w); - - } -} - -extern "C" void modc_render() -{ - // this should be moved around abit - if(client_state() == CLIENTSTATE_ONLINE) - { - render_game(); - - // handle team switching - // TODO: FUGLY!!! - if(config.cl_team != -10) - { - msg_pack_start(MSG_SETTEAM, MSGFLAG_VITAL); - msg_pack_int(config.cl_team); - msg_pack_end(); - client_send_msg(); - } - } - else // if (client_state() != CLIENTSTATE_CONNECTING && client_state() != CLIENTSTATE_LOADING) - { - modmenu_render(); - } - - // - config.cl_team = -10; -} - - -void menu_do_disconnected(); -void menu_do_connecting(); -void menu_do_connected(); - -extern "C" void modc_statechange(int state, int old) -{ - clear_object_pointers(); - - if(state == CLIENTSTATE_OFFLINE) - { - menu_do_disconnected(); - menu_game_active = false; - } - else if(state == CLIENTSTATE_CONNECTING) - menu_do_connecting(); - else if (state == CLIENTSTATE_ONLINE) - { - menu_active = false; - menu_game_active = true; - menu_do_connected(); - } -} - -extern "C" void modc_message(int msg) -{ - if(msg == MSG_CHAT) - { - int cid = msg_unpack_int(); - int team = msg_unpack_int(); - const char *message = msg_unpack_string(); - dbg_msg("message", "chat cid=%d team=%d msg='%s'", cid, team, message); - chat_add_line(cid, team, message); - - if(cid >= 0) - 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); - } - else if(msg == MSG_SETINFO) - { - int cid = msg_unpack_int(); - const char *name = msg_unpack_string(); - const char *skinname = msg_unpack_string(); - - strncpy(client_datas[cid].name, name, 64); - strncpy(client_datas[cid].skin_name, skinname, 64); - - int use_custom_color = msg_unpack_int(); - client_datas[cid].skin_info.color_body = skin_get_color(msg_unpack_int()); - client_datas[cid].skin_info.color_feet = skin_get_color(msg_unpack_int()); - client_datas[cid].skin_info.size = 64; - - // find new skin - client_datas[cid].skin_id = skin_find(client_datas[cid].skin_name); - if(client_datas[cid].skin_id < 0) - client_datas[cid].skin_id = 0; - - if(use_custom_color) - client_datas[cid].skin_info.texture = skin_get(client_datas[cid].skin_id)->color_texture; - else - { - client_datas[cid].skin_info.texture = skin_get(client_datas[cid].skin_id)->org_texture; - client_datas[cid].skin_info.color_body = vec4(1,1,1,1); - client_datas[cid].skin_info.color_feet = vec4(1,1,1,1); - } - } - else if(msg == MSG_WEAPON_PICKUP) - { - int weapon = msg_unpack_int(); - - picked_up_weapon = weapon+1; - } - else if(msg == MSG_READY_TO_ENTER) - { - client_entergame(); - } - else if(msg == MSG_KILLMSG) - { - killmsg_current = (killmsg_current+1)%killmsg_max; - killmsgs[killmsg_current].killer = msg_unpack_int(); - killmsgs[killmsg_current].victim = msg_unpack_int(); - killmsgs[killmsg_current].weapon = msg_unpack_int(); - killmsgs[killmsg_current].mode_special = msg_unpack_int(); - killmsgs[killmsg_current].tick = client_tick(); - } - else if (msg == MSG_EMOTICON) - { - int cid = msg_unpack_int(); - int emoticon = msg_unpack_int(); - client_datas[cid].emoticon = emoticon; - client_datas[cid].emoticon_start = client_tick(); - } - else if(msg == MSG_SOUND_GLOBAL) - { - int soundid = msg_unpack_int(); - snd_play_random(CHN_GLOBAL, soundid, 1.0f, vec2(0,0)); - } -} - -extern "C" void modc_connected() -{ - // init some stuff - col_init(32); - img_init(); - tilemap_init(); - chat_reset(); - - proj_particles.reset(); - - clear_object_pointers(); - last_new_predicted_tick = -1; - - for(int i = 0; i < MAX_CLIENTS; i++) - { - client_datas[i].name[0] = 0; - client_datas[i].team = 0; - client_datas[i].emoticon = 0; - client_datas[i].emoticon_start = -1; - } - - for(int i = 0; i < killmsg_max; i++) - killmsgs[i].tick = -100000; - - send_info(true); -} - -extern "C" const char *modc_net_version() { return TEEWARS_NETVERSION; } diff --git a/src/game/client/gc_client.cpp b/src/game/client/gc_client.cpp new file mode 100644 index 00000000..98275188 --- /dev/null +++ b/src/game/client/gc_client.cpp @@ -0,0 +1,3103 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +#include +#include +#include +#include + +extern "C" { + #include + #include +}; + +#include "../g_game.h" +#include "../g_version.h" +#include "../g_mapres.h" +#include "gc_mapres_image.h" +#include "gc_mapres_tilemap.h" +#include "../generated/gc_data.h" +#include "gc_menu.h" +#include "gc_skin.h" +#include "gc_render.h" + +// sound channels +enum +{ + CHN_GUI=0, + CHN_MUSIC, + CHN_WORLD, + CHN_GLOBAL, +}; + +data_container *data = 0x0; + +int gametype = GAMETYPE_DM; + +extern void modmenu_render(); + +enum +{ + CHATMODE_NONE=0, + CHATMODE_ALL, + CHATMODE_TEAM, + CHATMODE_CONSOLE, + CHATMODE_REMOTECONSOLE, +}; + +typedef struct +{ + float x, y, w, h; +} RECT; +RECT *ui2_screen(); + +static int chat_mode = CHATMODE_NONE; +bool menu_active = false; +bool menu_game_active = false; +static bool emoticon_selector_active = false; + +static vec2 mouse_pos; +static vec2 local_character_pos; +static vec2 local_target_pos; +static const obj_player_character *local_character = 0; +static const obj_player_character *local_prev_character = 0; +const obj_player_info *local_info = 0; +static const obj_flag *flags[2] = {0,0}; +static const obj_game *gameobj = 0; + +static int picked_up_weapon = 0; + +static struct client_data +{ + char name[64]; + char skin_name[64]; + int skin_id; + int skin_color; + int team; + int emoticon; + int emoticon_start; + player_core predicted; + + tee_render_info skin_info; + +} client_datas[MAX_CLIENTS]; + +class client_effects +{ +public: + float zoom; + float currentzoom; + float stage; + int lastzoomin; + int lastincrease; + + client_effects() + { + currentzoom = zoom = 3.0f; + stage = 0.0f; + } + + float getorgzoom() { return zoom; } + + float getzoom(int tick, float intratick, const obj_player_character *player) + { + float currentstage = ((float)player->weaponstage) * 0.1f; + if (currentstage < stage) + { + if ((tick - lastincrease) > (client_tickspeed() / 2)) + stage = currentstage; + } + else + { + lastincrease = tick; + stage = currentstage; + } + + float targetzoom = 3.0f + stage; + currentzoom = LERP(currentzoom, targetzoom, 0.1); + return currentzoom; + } +}; + +client_effects cl_effects; + +inline float frandom() { return rand()/(float)(RAND_MAX); } + +void snd_play_random(int chn, int setid, float vol, vec2 pos) +{ + soundset *set = &data->sounds[setid]; + + if(!set->num_sounds) + return; + + if(set->num_sounds == 1) + { + snd_play_at(chn, set->sounds[0].id, 0, pos.x, pos.y); + return; + } + + // play a random one + int id; + do { + id = rand() % set->num_sounds; + } while(id == set->last); + snd_play_at(chn, set->sounds[id].id, 0, pos.x, pos.y); + set->last = id; +} + +// sound volume tweak +static const float stereo_separation = 0.001f; +static const float stereo_separation_deadzone = 200.0f; +static const float volume_distance_falloff = 200.0f; +static const float volume_distance_deadzone = 320.0f; +static const float volume_gun = 0.5f; +static const float volume_tee = 0.5f; +static const float volume_hit = 0.5f; +static const float volume_music = 0.8f; + +void sound_vol_pan(const vec2& p, float *vol, float *pan) +{ + vec2 player_to_ev = p - local_character_pos; + *pan = 0.0f; + *vol = 1.0f; + + if(fabs(player_to_ev.x) > stereo_separation_deadzone) + { + *pan = stereo_separation * (player_to_ev.x - sign(player_to_ev.x)*stereo_separation_deadzone); + if(*pan < -1.0f) *pan = -1.0f; + if(*pan > 1.0f) *pan = 1.0f; + } + + float len = length(player_to_ev); + if(len > volume_distance_deadzone) + { + *vol = volume_distance_falloff / (len - volume_distance_deadzone); + + if(*vol < 0.0f) *vol = 0.0f; + if(*vol > 1.0f) *vol = 1.0f; + } +} + +enum +{ + SPRITE_FLAG_FLIP_Y=1, + SPRITE_FLAG_FLIP_X=2, +}; + +static float sprite_w_scale; +static float sprite_h_scale; + +static void select_sprite(sprite *spr, int flags=0, int sx=0, int sy=0) +{ + int x = spr->x+sx; + int y = spr->y+sy; + int w = spr->w; + int h = spr->h; + int cx = spr->set->gridx; + int cy = spr->set->gridy; + + float f = sqrtf(h*h + w*w); + sprite_w_scale = w/f; + sprite_h_scale = h/f; + + float x1 = x/(float)cx; + float x2 = (x+w)/(float)cx; + float y1 = y/(float)cy; + float y2 = (y+h)/(float)cy; + float temp = 0; + + if(flags&SPRITE_FLAG_FLIP_Y) + { + temp = y1; + y1 = y2; + y2 = temp; + } + + if(flags&SPRITE_FLAG_FLIP_X) + { + temp = x1; + x1 = x2; + x2 = temp; + } + + gfx_quads_setsubset(x1, y1, x2, y2); +} + +void select_sprite(int id, int flags=0, int sx=0, int sy=0) +{ + if(id < 0 || id > data->num_sprites) + return; + select_sprite(&data->sprites[id], flags, sx, sy); +} + +void draw_sprite(float x, float y, float size) +{ + gfx_quads_draw(x, y, size*sprite_w_scale, size*sprite_h_scale); +} + +class damage_indicators +{ +public: + int64 lastupdate; + struct item + { + vec2 pos; + vec2 dir; + float life; + float startangle; + }; + + enum + { + MAX_ITEMS=64, + }; + + damage_indicators() + { + lastupdate = 0; + num_items = 0; + } + + item items[MAX_ITEMS]; + int num_items; + + item *create_i() + { + if (num_items < MAX_ITEMS) + { + item *p = &items[num_items]; + num_items++; + return p; + } + return 0; + } + + void destroy_i(item *i) + { + num_items--; + *i = items[num_items]; + } + + void create(vec2 pos, 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 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(); + } + +}; + +static damage_indicators damageind; + +class particle_system +{ +public: + struct particle + { + vec2 pos; + vec2 vel; + float life; + float max_life; + float size; + + float rot; + float rotspeed; + + float gravity; + float friction; + int iparticle; + + vec4 color; + }; + + enum + { + MAX_PARTICLES=1024, + }; + + particle particles[MAX_PARTICLES]; + int num_particles; + + particle_system() + { + num_particles = 0; + } + + void new_particle(vec2 pos, vec2 vel, float life, float size, float gravity, float friction, vec4 color=vec4(1,1,1,1)) + { + if (num_particles >= MAX_PARTICLES) + return; + + particles[num_particles].iparticle = rand() % data->num_particles; + particles[num_particles].pos = pos; + particles[num_particles].vel = vel; + particles[num_particles].life = life - (data->particles[particles[num_particles].iparticle].lifemod/100.0f) * life; + particles[num_particles].size = size; + particles[num_particles].max_life = life; + particles[num_particles].gravity = gravity; + particles[num_particles].friction = friction; + particles[num_particles].rot = frandom()*pi*2; + particles[num_particles].rotspeed = frandom() * 10.0f; + particles[num_particles].color = color; + num_particles++; + } + + void update(float time_passed) + { + for(int i = 0; i < num_particles; i++) + { + particles[i].vel.y += particles[i].gravity*time_passed; + particles[i].vel *= particles[i].friction; + vec2 vel = particles[i].vel*time_passed; + move_point(&particles[i].pos, &vel, 0.1f+0.9f*frandom(), 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].max_life) + { + num_particles--; + particles[i] = particles[num_particles]; + i--; + } + } + } + + void render() + { + gfx_blend_additive(); + gfx_texture_set(data->images[IMAGE_GAME].id); + gfx_quads_begin(); + + for(int i = 0; i < num_particles; i++) + { + int type = particles[i].iparticle; + select_sprite(data->particles[type].spr); + float a = 1 - particles[i].life / particles[i].max_life; + vec2 p = particles[i].pos; + + gfx_quads_setrotation(particles[i].rot); + + gfx_setcolor( + data->particles[type].color_r * particles[i].color.r, + data->particles[type].color_g * particles[i].color.g, + data->particles[type].color_b * particles[i].color.b, + pow(a, 0.75f) * particles[i].color.a); + + gfx_quads_draw(p.x, p.y,particles[i].size,particles[i].size); + } + gfx_quads_end(); + gfx_blend_normal(); + } +}; + +static particle_system temp_system; + +class projectile_particles +{ +public: + enum + { + LISTSIZE = 1000, + }; + // meh, just use size % + int lastadd[LISTSIZE]; + projectile_particles() + { + reset(); + } + + void reset() + { + for (int i = 0; i < LISTSIZE; i++) + lastadd[i] = -1000; + } + + void addparticle(int projectiletype, int projectileid, vec2 pos, vec2 vel) + { + int particlespersecond = data->projectileinfo[projectiletype].particlespersecond; + int lastaddtick = lastadd[projectileid % LISTSIZE]; + + if(!particlespersecond) + return; + + if ((client_tick() - lastaddtick) > (client_tickspeed() / particlespersecond)) + { + lastadd[projectileid % LISTSIZE] = client_tick(); + float life = data->projectileinfo[projectiletype].particlelife; + float size = data->projectileinfo[projectiletype].particlesize; + vec2 v = vel * 0.2f + normalize(vec2(frandom()-0.5f, -frandom()))*(32.0f+frandom()*32.0f); + + // add the particle (from projectiletype later on, but meh...) + temp_system.new_particle(pos, v, life, size, 0, 0.95f); + } + } +}; +static projectile_particles proj_particles; + +static char chat_input[512]; +static unsigned chat_input_len; +static const int chat_max_lines = 10; + +struct chatline +{ + int tick; + int client_id; + int team; + char text[512+64]; +}; + +chatline chat_lines[chat_max_lines]; +static int chat_current_line = 0; + +void chat_reset() +{ + for(int i = 0; i < chat_max_lines; i++) + chat_lines[i].tick = -1000000; + chat_current_line = 0; +} + +void chat_add_line(int client_id, int team, const char *line) +{ + chat_current_line = (chat_current_line+1)%chat_max_lines; + chat_lines[chat_current_line].tick = client_tick(); + chat_lines[chat_current_line].client_id = client_id; + chat_lines[chat_current_line].team = team; + + if(client_id == -1) // server message + sprintf(chat_lines[chat_current_line].text, "*** %s", line); + else + { + sprintf(chat_lines[chat_current_line].text, "%s: %s", client_datas[client_id].name, line); + } +} + +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]; +static int killmsg_current = 0; + +extern unsigned char internal_data[]; + +void create_air_jump_effect(vec2 pos) +{ + const int count = 12; + for(int i = 0; i <= count; i++) + { + float a = i/(float)count; + vec2 v = vec2((a-0.5f)*512.0f, 0); + temp_system.new_particle(pos+vec2(0,28), v, 0.4f, 16.0f, 0, 0.985f, vec4(0.25f,0.4f,1,1)); + } +} + + +extern void draw_round_rect(float x, float y, float w, float h, float r); +extern int render_popup(const char *caption, const char *text, const char *button_text); + +static void render_loading(float percent) +{ + gfx_clear(0.65f,0.78f,0.9f); + RECT screen = *ui2_screen(); + gfx_mapscreen(screen.x, screen.y, screen.w, screen.h); + + 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_pretty_text_width(48.0f, caption, -1); + ui_do_label(x+w/2-tw/2, y+20, caption, 48.0f); + + gfx_texture_set(-1); + gfx_quads_begin(); + gfx_setcolor(1,1,1,1.0f); + draw_round_rect(x+40, y+h-75, (w-80)*percent, 25, 5.0f); + gfx_quads_end(); + + gfx_swap(); +} + +extern "C" void modc_init() +{ + // setup sound channels + snd_set_channel(CHN_GUI, 1.0f, 0.0f); + snd_set_channel(CHN_MUSIC, 1.0f, 0.0f); + snd_set_channel(CHN_WORLD, 0.9f, 1.0f); + snd_set_channel(CHN_GLOBAL, 1.0f, 0.0f); + + // load the data container + data = load_data_from_memory(internal_data); + + // TODO: should be removed + snd_set_listener_pos(0.0f, 0.0f); + + float total = data->num_sounds+data->num_images; + float current = 0; + + // load sounds + for(int s = 0; s < data->num_sounds; s++) + { + render_loading(current/total); + for(int i = 0; i < data->sounds[s].num_sounds; i++) + { + int id; + //if (strcmp(data->sounds[s].sounds[i].filename + strlen(data->sounds[s].sounds[i].filename) - 3, ".wv") == 0) + id = snd_load_wv(data->sounds[s].sounds[i].filename); + //else + // id = snd_load_wav(data->sounds[s].sounds[i].filename); + + data->sounds[s].sounds[i].id = id; + } + + current++; + } + + // load textures + for(int i = 0; i < data->num_images; i++) + { + render_loading(current/total); + data->images[i].id = gfx_load_texture(data->images[i].filename); + current++; + } + + skin_init(); +} + +extern "C" void modc_entergame() +{ +} + +extern "C" void modc_shutdown() +{ + // shutdown the menu +} + +static void process_events(int s) +{ + int num = snap_num_items(s); + for(int index = 0; index < num; index++) + { + SNAP_ITEM item; + const void *data = snap_get_item(s, index, &item); + + if(item.type == EVENT_DAMAGEINDICATION) + { + ev_damageind *ev = (ev_damageind *)data; + damageind.create(vec2(ev->x, ev->y), get_direction(ev->angle)); + } + else if(item.type == EVENT_AIR_JUMP) + { + ev_common *ev = (ev_common *)data; + create_air_jump_effect(vec2(ev->x, ev->y)); + } + else if(item.type == EVENT_EXPLOSION) + { + ev_explosion *ev = (ev_explosion *)data; + vec2 p(ev->x, ev->y); + + // center explosion + temp_system.new_particle(p, vec2(0,0), 0.3f, 96.0f, 0, 0.95f); + temp_system.new_particle(p, vec2(0,0), 0.3f, 64.0f, 0, 0.95f); + temp_system.new_particle(p, vec2(0,0), 0.3f, 32.0f, 0, 0.95f); + temp_system.new_particle(p, vec2(0,0), 0.3f, 16.0f, 0, 0.95f); + + for(int i = 0; i < 16; i++) + { + vec2 v = normalize(vec2(frandom()-0.5f, frandom()-0.5f))*(128.0f+frandom()*128.0f); + temp_system.new_particle(p, v, 0.2f+0.25f*frandom(), 16.0f, 0, 0.985f); + } + + for(int i = 0; i < 16; i++) + { + vec2 v = normalize(vec2(frandom()-0.5f, frandom()-0.5f))*(256.0f+frandom()*512.0f); + temp_system.new_particle(p, v, 0.2f+0.25f*frandom(), 16.0f, 128.0f, 0.985f); + } + + for(int i = 0; i < 64; i++) + { + vec2 v = normalize(vec2(frandom()-0.5f, frandom()-0.5f))*(frandom()*256.0f); + temp_system.new_particle(p, v, 0.2f+0.25f*frandom(), 24.0f, 128.0f, 0.985f); + } + } + else if(item.type == EVENT_SMOKE) + { + ev_explosion *ev = (ev_explosion *)data; + vec2 p(ev->x, ev->y); + + // center explosion + vec2 v = normalize(vec2(frandom()-0.5f, -frandom()))*(32.0f+frandom()*32.0f); + temp_system.new_particle(p, v, 1.2f, 64.0f, 0, 0.95f); + v = normalize(vec2(frandom()-0.5f, -frandom()))*(128.0f+frandom()*128.0f); + temp_system.new_particle(p, v, 1.2f, 32.0f, 0, 0.95f); + v = normalize(vec2(frandom()-0.5f, -frandom()))*(128.0f+frandom()*128.0f); + temp_system.new_particle(p, v, 1.2f, 16.0f, 0, 0.95f); + + for(int i = 0; i < 8; i++) + { + vec2 v = normalize(vec2(frandom()-0.5f, frandom()-0.5f))*(64.0f+frandom()*64.0f); + temp_system.new_particle(p, v, 0.5f+0.5f*frandom(), 16.0f, 0, 0.985f); + } + + for(int i = 0; i < 8; i++) + { + vec2 v = normalize(vec2(frandom()-0.5f, frandom()-0.5f))*(128.0f+frandom()*256.0f); + temp_system.new_particle(p, v, 0.5f+0.5f*frandom(), 16.0f, 128.0f, 0.985f); + } + } + else if(item.type == EVENT_SPAWN) + { + ev_explosion *ev = (ev_explosion *)data; + vec2 p(ev->x, ev->y); + + // center explosion + vec2 v = normalize(vec2(frandom()-0.5f, -frandom()))*(32.0f+frandom()*32.0f); + temp_system.new_particle(p, v, 1.2f, 64.0f, 0, 0.95f); + v = normalize(vec2(frandom()-0.5f, -frandom()))*(128.0f+frandom()*128.0f); + temp_system.new_particle(p, v, 1.2f, 32.0f, 0, 0.95f); + v = normalize(vec2(frandom()-0.5f, -frandom()))*(128.0f+frandom()*128.0f); + temp_system.new_particle(p, v, 1.2f, 16.0f, 0, 0.95f); + + for(int i = 0; i < 8; i++) + { + vec2 v = normalize(vec2(frandom()-0.5f, frandom()-0.5f))*(64.0f+frandom()*64.0f); + temp_system.new_particle(p, v, 0.5f+0.5f*frandom(), 16.0f, 0, 0.985f); + } + + for(int i = 0; i < 8; i++) + { + vec2 v = normalize(vec2(frandom()-0.5f, frandom()-0.5f))*(128.0f+frandom()*256.0f); + temp_system.new_particle(p, v, 0.5f+0.5f*frandom(), 16.0f, 128.0f, 0.985f); + } + } + else if(item.type == EVENT_DEATH) + { + ev_explosion *ev = (ev_explosion *)data; + vec2 p(ev->x, ev->y); + vec4 c(0.5f, 0.1f, 0.1f, 1.0f); + + // center explosion + vec2 v = normalize(vec2(frandom()-0.5f, -frandom()))*(32.0f+frandom()*32.0f); + temp_system.new_particle(p, v, 1.2f, 64.0f, 0, 0.95f, c); + v = normalize(vec2(frandom()-0.5f, -frandom()))*(128.0f+frandom()*128.0f); + temp_system.new_particle(p, v, 1.2f, 32.0f, 0, 0.95f, c); + v = normalize(vec2(frandom()-0.5f, -frandom()))*(128.0f+frandom()*128.0f); + temp_system.new_particle(p, v, 1.2f, 16.0f, 0, 0.95f, c); + + for(int i = 0; i < 8; i++) + { + vec2 v = normalize(vec2(frandom()-0.5f, frandom()-0.5f))*(64.0f+frandom()*64.0f); + temp_system.new_particle(p, v, 0.5f+0.5f*frandom(), 16.0f, 0, 0.985f, c); + } + + for(int i = 0; i < 8; i++) + { + vec2 v = normalize(vec2(frandom()-0.5f, frandom()-0.5f))*(128.0f+frandom()*256.0f); + temp_system.new_particle(p, v, 0.5f+0.5f*frandom(), 16.0f, 128.0f, 0.985f, c); + } + } + else if(item.type == EVENT_SOUND_WORLD) + { + ev_sound *ev = (ev_sound *)data; + if(ev->sound >= 0 && ev->sound < NUM_SOUNDS) + snd_play_random(CHN_WORLD, ev->sound, 1.0f, vec2(ev->x, ev->y)); + } + } +} + +static player_core predicted_prev_player; +static player_core predicted_player; +static int predicted_tick = 0; +static int last_new_predicted_tick = -1; + +extern "C" void modc_predict() +{ + player_core before_prev_player = predicted_prev_player; + player_core before_player = predicted_player; + + + // repredict player + world_core world; + int local_cid = -1; + + // search for players + for(int i = 0; i < snap_num_items(SNAP_CURRENT); i++) + { + SNAP_ITEM item; + const void *data = snap_get_item(SNAP_CURRENT, i, &item); + int client_id = item.id; + + if(item.type == OBJTYPE_PLAYER_CHARACTER) + { + const obj_player_character *character = (const obj_player_character *)data; + client_datas[client_id].predicted.world = &world; + world.players[client_id] = &client_datas[client_id].predicted; + + client_datas[client_id].predicted.read(character); + } + else if(item.type == OBJTYPE_PLAYER_INFO) + { + const obj_player_info *info = (const obj_player_info *)data; + if(info->local) + local_cid = client_id; + } + } + + // predict + for(int tick = client_tick()+1; tick <= client_predtick(); tick++) + { + // fetch the local + if(tick == client_predtick() && world.players[local_cid]) + predicted_prev_player = *world.players[local_cid]; + + // first calculate where everyone should move + for(int c = 0; c < MAX_CLIENTS; c++) + { + if(!world.players[c]) + continue; + + mem_zero(&world.players[c]->input, sizeof(world.players[c]->input)); + if(local_cid == c) + { + // apply player input + int *input = client_get_input(tick); + if(input) + world.players[c]->input = *((player_input*)input); + } + + world.players[c]->tick(); + } + + // move all players and quantize their data + for(int c = 0; c < MAX_CLIENTS; c++) + { + if(!world.players[c]) + continue; + + world.players[c]->move(); + world.players[c]->quantize(); + } + + if(tick > last_new_predicted_tick) + { + last_new_predicted_tick = tick; + + if(local_cid != -1 && world.players[local_cid]) + { + vec2 pos = world.players[local_cid]->pos; + int events = world.players[local_cid]->triggered_events; + if(events&COREEVENT_GROUND_JUMP) snd_play_random(CHN_WORLD, SOUND_PLAYER_JUMP, 1.0f, pos); + if(events&COREEVENT_AIR_JUMP) + { + create_air_jump_effect(pos); + snd_play_random(CHN_WORLD, SOUND_PLAYER_JUMP, 1.0f, pos); + } + //if(events&COREEVENT_HOOK_LAUNCH) snd_play_random(CHN_WORLD, SOUND_HOOK_LOOP, 1.0f, pos); + if(events&COREEVENT_HOOK_ATTACH_PLAYER) snd_play_random(CHN_WORLD, SOUND_HOOK_ATTACH_PLAYER, 1.0f, pos); + if(events&COREEVENT_HOOK_ATTACH_GROUND) snd_play_random(CHN_WORLD, SOUND_HOOK_ATTACH_GROUND, 1.0f, pos); + //if(events&COREEVENT_HOOK_RETRACT) snd_play_random(CHN_WORLD, SOUND_PLAYER_JUMP, 1.0f, pos); + } + + + /* + dbg_msg("predict", "%d %d %d", tick, + (int)world.players[c]->pos.x, (int)world.players[c]->pos.y, + (int)world.players[c]->vel.x, (int)world.players[c]->vel.y);*/ + } + + if(tick == client_predtick() && world.players[local_cid]) + predicted_player = *world.players[local_cid]; + } + + if(config.debug && predicted_tick == client_predtick()) + { + if(predicted_player.pos.x != before_player.pos.x || + predicted_player.pos.y != before_player.pos.y) + { + dbg_msg("client", "prediction error, (%d %d) (%d %d)", + (int)before_player.pos.x, (int)before_player.pos.y, + (int)predicted_player.pos.x, (int)predicted_player.pos.y); + } + + if(predicted_prev_player.pos.x != before_prev_player.pos.x || + predicted_prev_player.pos.y != before_prev_player.pos.y) + { + dbg_msg("client", "prediction error, prev (%d %d) (%d %d)", + (int)before_prev_player.pos.x, (int)before_prev_player.pos.y, + (int)predicted_prev_player.pos.x, (int)predicted_prev_player.pos.y); + } + } + + predicted_tick = client_predtick(); +} + +static void clear_object_pointers() +{ + // clear out the invalid pointers + local_character = 0; + local_prev_character = 0; + local_info = 0; + flags[0] = 0; + flags[1] = 0; + gameobj = 0; +} + +extern "C" void modc_newsnapshot() +{ + process_events(SNAP_CURRENT); + + if(config.dbg_stress) + { + if((client_tick()%250) == 0) + { + msg_pack_start(MSG_SAY, MSGFLAG_VITAL); + msg_pack_int(-1); + msg_pack_string("galenskap!!!!", 512); + msg_pack_end(); + client_send_msg(); + } + } + + clear_object_pointers(); + + // setup world view + { + // 1. fetch local player + // 2. set him to the center + int num = snap_num_items(SNAP_CURRENT); + for(int i = 0; i < num; i++) + { + SNAP_ITEM item; + const void *data = snap_get_item(SNAP_CURRENT, i, &item); + + if(item.type == OBJTYPE_PLAYER_INFO) + { + const obj_player_info *info = (const obj_player_info *)data; + if(info->local) + { + local_info = info; + const void *data = snap_find_item(SNAP_CURRENT, OBJTYPE_PLAYER_CHARACTER, item.id); + if(data) + { + local_character = (const obj_player_character *)data; + local_character_pos = vec2(local_character->x, local_character->y); + + const void *p = snap_find_item(SNAP_PREV, OBJTYPE_PLAYER_CHARACTER, item.id); + if(p) + local_prev_character = (obj_player_character *)p; + } + } + } + else if(item.type == OBJTYPE_GAME) + gameobj = (obj_game *)data; + else if(item.type == OBJTYPE_FLAG) + { + flags[item.id%2] = (const obj_flag *)data; + } + } + } +} + +void send_info(bool start) +{ + if(start) + msg_pack_start(MSG_STARTINFO, MSGFLAG_VITAL); + else + msg_pack_start(MSG_CHANGEINFO, MSGFLAG_VITAL); + msg_pack_string(config.player_name, 64); + msg_pack_string(config.player_skin, 64); + msg_pack_int(config.player_use_custom_color); + msg_pack_int(config.player_color_body); + msg_pack_int(config.player_color_feet); + msg_pack_end(); + client_send_msg(); +} + +void send_emoticon(int emoticon) +{ + msg_pack_start(MSG_EMOTICON, MSGFLAG_VITAL); + msg_pack_int(emoticon); + msg_pack_end(); + client_send_msg(); +} + +static void render_projectile(const obj_projectile *prev, const obj_projectile *current, int itemid) +{ + gfx_texture_set(data->images[IMAGE_GAME].id); + gfx_quads_begin(); + + // get positions + float gravity = -400; + if(current->type != WEAPON_ROCKET) + gravity = -100; + + float ct = (client_tick()-current->start_tick)/(float)SERVER_TICK_SPEED + client_intratick()*1/(float)SERVER_TICK_SPEED; + vec2 startpos(current->x, current->y); + vec2 startvel(current->vx, current->vy); + vec2 pos = calc_pos(startpos, startvel, gravity, ct); + vec2 prevpos = calc_pos(startpos, startvel, gravity, ct-0.001f); + + select_sprite(data->weapons[current->type%data->num_weapons].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 + proj_particles.addparticle(current->type, itemid, pos, vel); + + if(length(vel) > 0.00001f) + gfx_quads_setrotation(get_angle(vel)); + else + gfx_quads_setrotation(0); + + // TODO: do this, but nice + //temp_system.new_particle(pos, vec2(0,0), 0.3f, 14.0f, 0, 0.95f); + + gfx_quads_draw(pos.x, pos.y, 32, 32); + gfx_quads_setrotation(0); + gfx_quads_end(); +} + +static void render_powerup(const obj_powerup *prev, const obj_powerup *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[current->subtype%data->num_weapons].sprite_body); + size = data->weapons[current->subtype%data->num_weapons].visual_size; + } + else + { + const int c[] = { + SPRITE_POWERUP_HEALTH, + SPRITE_POWERUP_ARMOR, + SPRITE_POWERUP_WEAPON, + SPRITE_POWERUP_NINJA, + SPRITE_POWERUP_TIMEFIELD + }; + select_sprite(c[current->type]); + + if(c[current->type] == SPRITE_POWERUP_NINJA) + { + proj_particles.addparticle(0, 0, + pos+vec2((frandom()-0.5f)*80.0f, (frandom()-0.5f)*20.0f), + vec2((frandom()-0.5f)*10.0f, (frandom()-0.5f)*10.0f)); + 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(); +} + +static void render_flag(const obj_flag *prev, const obj_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()); + + if(local_info && current->carried_by == local_info->clientid) + pos = local_character_pos; + + gfx_setcolor(current->team ? 0 : 1,0,current->team ? 1 : 0,1); + //draw_sprite(pos.x, pos.y, size); + gfx_quads_draw(pos.x, pos.y-size*0.75f, size, size*2); + gfx_quads_end(); +} + +void anim_seq_eval(sequence *seq, float time, keyframe *frame) +{ + if(seq->num_frames == 0) + { + frame->time = 0; + frame->x = 0; + frame->y = 0; + frame->angle = 0; + } + else if(seq->num_frames == 1) + { + *frame = seq->frames[0]; + } + else + { + //time = max(0.0f, min(1.0f, time / duration)); // TODO: use clamp + keyframe *frame1 = 0; + keyframe *frame2 = 0; + float blend = 0.0f; + + // TODO: make this smarter.. binary search + for (int i = 1; i < seq->num_frames; i++) + { + if (seq->frames[i-1].time <= time && seq->frames[i].time >= time) + { + frame1 = &seq->frames[i-1]; + frame2 = &seq->frames[i]; + blend = (time - frame1->time) / (frame2->time - frame1->time); + break; + } + } + + if (frame1 && frame2) + { + frame->time = time; + frame->x = mix(frame1->x, frame2->x, blend); + frame->y = mix(frame1->y, frame2->y, blend); + frame->angle = mix(frame1->angle, frame2->angle, blend); + } + } +} + +void anim_eval(animation *anim, float time, animstate *state) +{ + anim_seq_eval(&anim->body, time, &state->body); + anim_seq_eval(&anim->back_foot, time, &state->back_foot); + anim_seq_eval(&anim->front_foot, time, &state->front_foot); + anim_seq_eval(&anim->attach, time, &state->attach); +} + +void anim_add_keyframe(keyframe *seq, keyframe *added, float amount) +{ + seq->x += added->x*amount; + seq->y += added->y*amount; + seq->angle += added->angle*amount; +} + +void anim_add(animstate *state, animstate *added, float amount) +{ + anim_add_keyframe(&state->body, &added->body, amount); + anim_add_keyframe(&state->back_foot, &added->back_foot, amount); + anim_add_keyframe(&state->front_foot, &added->front_foot, amount); + anim_add_keyframe(&state->attach, &added->attach, amount); +} + +void anim_eval_add(animstate *state, animation *anim, float time, float amount) +{ + animstate add; + anim_eval(anim, time, &add); + anim_add(state, &add, amount); +} + +static void render_hand(int skin_id, 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(s->color_texture); + gfx_quads_begin(); + + // 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 render_tee(animstate *anim, tee_render_info *info, int emote, vec2 dir, vec2 pos) +{ + vec2 direction = dir; + vec2 position = pos; + + //gfx_texture_set(data->images[IMAGE_CHAR_DEFAULT].id); + gfx_texture_set(info->texture); + gfx_quads_begin(); + //gfx_quads_draw(pos.x, pos.y-128, 128, 128); + + // first pass we draw the outline + // second pass we draw the filling + for(int p = 0; p < 2; p++) + { + int outline = p==0 ? 1 : 0; + + for(int f = 0; f < 2; f++) + { + float animscale = info->size * 1.0f/64.0f; + float basesize = info->size; + if(f == 1) + { + gfx_quads_setrotation(anim->body.angle*pi*2); + + // draw body + gfx_setcolor(info->color_body.r, info->color_body.g, info->color_body.b, info->color_body.a); + vec2 body_pos = position + vec2(anim->body.x, anim->body.y)*animscale; + select_sprite(outline?SPRITE_TEE_BODY_OUTLINE:SPRITE_TEE_BODY, 0, 0, 0); + gfx_quads_draw(body_pos.x, body_pos.y, basesize, basesize); + + // draw eyes + if(p == 1) + { + switch (emote) + { + case EMOTE_PAIN: + select_sprite(SPRITE_TEE_EYE_PAIN, 0, 0, 0); + break; + case EMOTE_HAPPY: + select_sprite(SPRITE_TEE_EYE_HAPPY, 0, 0, 0); + break; + case EMOTE_SURPRISE: + select_sprite(SPRITE_TEE_EYE_SURPRISE, 0, 0, 0); + break; + case EMOTE_ANGRY: + select_sprite(SPRITE_TEE_EYE_ANGRY, 0, 0, 0); + break; + default: + select_sprite(SPRITE_TEE_EYE_NORMAL, 0, 0, 0); + break; + } + + float eyescale = basesize*0.40f; + float h = emote == EMOTE_BLINK ? basesize*0.15f : eyescale; + float eyeseparation = (0.075f - 0.010f*fabs(direction.x))*basesize; + vec2 offset = vec2(direction.x*0.125f, -0.05f+direction.y*0.10f)*basesize; + gfx_quads_draw(body_pos.x-eyeseparation+offset.x, body_pos.y+offset.y, eyescale, h); + gfx_quads_draw(body_pos.x+eyeseparation+offset.x, body_pos.y+offset.y, -eyescale, h); + } + } + + // draw feet + gfx_setcolor(info->color_feet.r, info->color_feet.g, info->color_feet.b, info->color_feet.a); + select_sprite(outline?SPRITE_TEE_FOOT_OUTLINE:SPRITE_TEE_FOOT, 0, 0, 0); + + keyframe *foot = f ? &anim->front_foot : &anim->back_foot; + + float w = basesize; + float h = basesize/2; + + gfx_quads_setrotation(foot->angle*pi*2); + gfx_quads_draw(position.x+foot->x*animscale, position.y+foot->y*animscale, w, h); + } + } + + gfx_quads_end(); +} + +void 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 draw_round_rect_ext(float x, float y, float w, float h, float r, int corners) +{ + int num = 8; + for(int i = 0; i < num; i+=2) + { + float a1 = i/(float)num * pi/2; + float a2 = (i+1)/(float)num * pi/2; + float a3 = (i+2)/(float)num * pi/2; + float ca1 = cosf(a1); + float ca2 = cosf(a2); + float ca3 = cosf(a3); + float sa1 = sinf(a1); + float sa2 = sinf(a2); + float sa3 = sinf(a3); + + if(corners&1) // TL + gfx_quads_draw_freeform( + x+r, y+r, + x+(1-ca1)*r, y+(1-sa1)*r, + x+(1-ca3)*r, y+(1-sa3)*r, + x+(1-ca2)*r, y+(1-sa2)*r); + + if(corners&2) // TR + gfx_quads_draw_freeform( + x+w-r, y+r, + x+w-r+ca1*r, y+(1-sa1)*r, + x+w-r+ca3*r, y+(1-sa3)*r, + x+w-r+ca2*r, y+(1-sa2)*r); + + if(corners&4) // BL + gfx_quads_draw_freeform( + x+r, y+h-r, + x+(1-ca1)*r, y+h-r+sa1*r, + x+(1-ca3)*r, y+h-r+sa3*r, + x+(1-ca2)*r, y+h-r+sa2*r); + + if(corners&8) // BR + gfx_quads_draw_freeform( + x+w-r, y+h-r, + x+w-r+ca1*r, y+h-r+sa1*r, + x+w-r+ca3*r, y+h-r+sa3*r, + x+w-r+ca2*r, y+h-r+sa2*r); + } + + gfx_quads_drawTL(x+r, y+r, w-r*2, h-r*2); // center + gfx_quads_drawTL(x+r, y, w-r*2, r); // top + gfx_quads_drawTL(x+r, y+h-r, w-r*2, r); // bottom + gfx_quads_drawTL(x, y+r, r, h-r*2); // left + gfx_quads_drawTL(x+w-r, y+r, r, h-r*2); // right + + if(!(corners&1)) gfx_quads_drawTL(x, y, r, r); // TL + if(!(corners&2)) gfx_quads_drawTL(x+w, y, -r, r); // TR + if(!(corners&4)) gfx_quads_drawTL(x, y+h, r, -r); // BL + if(!(corners&8)) gfx_quads_drawTL(x+w, y+h, -r, -r); // BR +} + +void draw_round_rect(float x, float y, float w, float h, float r) +{ + draw_round_rect_ext(x,y,w,h,r,0xf); +} + + +static void render_player( + const obj_player_character *prev_char, + const obj_player_character *player_char, + const obj_player_info *prev_info, + const obj_player_info *player_info + ) +{ + obj_player_character prev; + obj_player_character player; + prev = *prev_char; + player = *player_char; + + obj_player_info info = *player_info; + + float intratick = client_intratick(); + + if(player.health < 0) // dont render dead players + return; + + if(info.local && config.cl_predict) + { + if(!local_character || (local_character->health < 0) || (gameobj && gameobj->game_over)) + { + } + else + { + // apply predicted results + predicted_player.write(&player); + predicted_prev_player.write(&prev); + intratick = client_intrapredtick(); + } + } + + // TODO: proper skin selection + int skin_id = client_datas[info.clientid].skin_id; //charids[info.clientid]; + //if(gametype != GAMETYPE_DM) + //skin_id = info.team*9; // 0 or 9 + + vec2 direction = get_direction(player.angle); + float angle = player.angle/256.0f; + vec2 position = mix(vec2(prev.x, prev.y), vec2(player.x, player.y), intratick); + + 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; + + // evaluate animation + float walk_time = fmod(position.x, 100.0f)/100.0f; + animstate state; + anim_eval(&data->animations[ANIM_BASE], 0, &state); + + if(inair) + anim_eval_add(&state, &data->animations[ANIM_INAIR], 0, 1.0f); // TODO: some sort of time here + else if(stationary) + anim_eval_add(&state, &data->animations[ANIM_IDLE], 0, 1.0f); // TODO: some sort of time here + else + anim_eval_add(&state, &data->animations[ANIM_WALK], walk_time, 1.0f); + + if (player.weapon == WEAPON_HAMMER) + { + float a = clamp((client_tick()-player.attacktick+intratick)/10.0f, 0.0f, 1.0f); + anim_eval_add(&state, &data->animations[ANIM_HAMMER_SWING], a, 1.0f); + } + if (player.weapon == WEAPON_NINJA) + { + float a = clamp((client_tick()-player.attacktick+intratick)/40.0f, 0.0f, 1.0f); + anim_eval_add(&state, &data->animations[ANIM_NINJA_SWING], a, 1.0f); + } + + // 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 = 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); + for(float f = 24; f < d; f += 24) + { + vec2 p = hook_pos + dir*f; + gfx_quads_draw(p.x, p.y,24,16); + } + + gfx_quads_setrotation(0); + gfx_quads_end(); + + render_hand(skin_id, 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[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; + p.y += data->weapons[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[iw].offsetx; + } + else + { + gfx_quads_setrotation(-pi/2+state.attach.angle*pi*2); + } + draw_sprite(p.x, p.y, data->weapons[iw].visual_size); + } + else if (player.weapon == WEAPON_NINJA) + { + p = position; + p.y += data->weapons[iw].offsety; + + if(direction.x < 0) + { + gfx_quads_setrotation(-pi/2-state.attach.angle*pi*2); + p.x -= data->weapons[iw].offsetx; + } + else + { + gfx_quads_setrotation(-pi/2+state.attach.angle*pi*2); + } + draw_sprite(p.x, p.y, data->weapons[iw].visual_size); + + // HADOKEN + if ((client_tick()-player.attacktick) <= (SERVER_TICK_SPEED / 6) && data->weapons[iw].nummuzzlesprites) + { + int itex = rand() % data->weapons[iw].nummuzzlesprites; + float alpha = 1.0f; + if (alpha > 0.0f && data->weapons[iw].sprite_muzzle[itex].psprite) + { + 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[iw].sprite_muzzle[itex].psprite, 0); + vec2 diry(-dir.y,dir.x); + p = position; + float offsetx = data->weapons[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[iw].offsetx - dir*recoil*10.0f; + p.y += data->weapons[iw].offsety; + draw_sprite(p.x, p.y, data->weapons[iw].visual_size); + } + + if (player.weapon == WEAPON_GUN || player.weapon == WEAPON_SHOTGUN) + { + // check if we're firing stuff + if (true)//prev.attackticks) + { + float alpha = 0.0f; + int phase1tick = (client_tick() - player.attacktick); + if (phase1tick < (data->weapons[iw].muzzleduration + 3)) + { + float t = ((((float)phase1tick) + intratick)/(float)data->weapons[iw].muzzleduration); + alpha = LERP(2.0, 0.0f, min(1.0f,max(0.0f,t))); + } + + int itex = rand() % data->weapons[iw].nummuzzlesprites; + if (alpha > 0.0f && data->weapons[iw].sprite_muzzle[itex].psprite) + { + float offsety = -data->weapons[iw].muzzleoffsety; + select_sprite(data->weapons[iw].sprite_muzzle[itex].psprite, 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[iw].muzzleoffsetx + diry * offsety; + + draw_sprite(muzzlepos.x, muzzlepos.y, data->weapons[iw].visual_size); + /*gfx_setcolor(1.0f,1.0f,1.0f,alpha); + vec2 diry(-dir.y,dir.x); + p += dir * muzzleparams[player.weapon].offsetx + diry * offsety; + gfx_quads_draw(p.x,p.y,muzzleparams[player.weapon].sizex, muzzleparams[player.weapon].sizey);*/ + } + } + } + gfx_quads_end(); + + switch (player.weapon) + { + case WEAPON_GUN: render_hand(skin_id, p, direction, -3*pi/4, vec2(-15, 4)); break; + case WEAPON_SHOTGUN: render_hand(skin_id, p, direction, -pi/2, vec2(-5, 4)); break; + case WEAPON_ROCKET: render_hand(skin_id, 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 = client_datas[info.clientid].skin_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 the tee + render_tee(&state, &client_datas[info.clientid].skin_info, player.emote, direction, position); + + if(player.state == STATE_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 (client_datas[info.clientid].emoticon_start != -1 && client_datas[info.clientid].emoticon_start + 2 * client_tickspeed() > client_tick()) + { + gfx_texture_set(data->images[IMAGE_EMOTICONS].id); + gfx_quads_begin(); + + int since_start = client_tick() - client_datas[info.clientid].emoticon_start; + int from_end = client_datas[info.clientid].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 + client_datas[info.clientid].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_pretty_text_color + float a = 1; + if(config.cl_nameplates_always == 0) + a = clamp(1-powf(distance(local_target_pos, position)/200.0f,16.0f), 0.0f, 1.0f); + + const char *name = client_datas[info.clientid].name; + float tw = gfx_pretty_text_width(28.0f, name, -1); + gfx_pretty_text_color(1,1,1,a); + gfx_pretty_text(position.x-tw/2.0f, position.y-60, 28.0f, name, -1); + gfx_pretty_text_color(1,1,1,1); + } +} + +void render_moon(float x, float y) +{ + gfx_texture_set(data->images[IMAGE_MOON].id); + gfx_quads_begin(); + gfx_quads_draw(x, y, 512, 512); + gfx_quads_end(); +} + +void render_stars() +{ + struct star + { + vec2 p; + float tingle; + float rot; + float size; + }; + + static const int NUM_STARS = 100; + static star stars[NUM_STARS]; + static bool init = true; + if(init) + { + for(int i = 0; i < NUM_STARS; i++) + { + stars[i].p.x = (frandom()-0.5f)*800*3; + stars[i].p.y = (frandom()-0.5f)*600*3; + stars[i].tingle = frandom(); + stars[i].rot = frandom()*pi; + stars[i].size = 10.0f+frandom()*10.0f; + } + + init = false; + } + + int64 now = time_get(); + int64 freq = time_freq()*5; + float t = (now%freq)/(float)freq; + gfx_texture_set(data->images[IMAGE_STARS].id); + gfx_quads_begin(); + gfx_quads_setsubset(0,0,0.5f,1); + for(int i = 0; i < NUM_STARS; i++) + { + float a = fmod(t+stars[i].tingle, 1.0f); + gfx_quads_setrotation(stars[i].rot); + gfx_setcolor(1,1,1,0.25f+sinf(a*pi)*0.75f); + gfx_quads_draw(stars[i].p.x, stars[i].p.y, stars[i].size, stars[i].size); + } + + gfx_quads_end(); +} + +void render_snow() +{ + vec2 tl, br; + gfx_getscreen(&tl.x, &tl.y, &br.x, &br.y); + tl.x += 1000; // this is here to fix positions below 0,0 + tl.y += 1000; + br.x += 1000; + br.y += 1000; + + struct flake + { + vec2 p; + float tingle; + float rot; + float size; + float yspeed; + float xspeed; + }; + + static const int NUM_FLAKES = 100; + static flake flakes[NUM_FLAKES]; + static bool init = true; + + float w = br.x-tl.x; + float h = br.y-tl.y; + + if(init) + { + for(int i = 0; i < NUM_FLAKES; i++) + { + flakes[i].p.x = frandom()*w; + flakes[i].p.y = frandom()*h; + flakes[i].tingle = frandom(); + flakes[i].rot = frandom()*pi; + flakes[i].size = 50.0f+frandom()*10.0f; + flakes[i].yspeed = 50+frandom()*20.0f; + flakes[i].xspeed = flakes[i].yspeed * (0.4+frandom()*0.4f); + } + + init = false; + } + + int basex = (int)(tl.x/w); + float splitx = tl.x-basex*w; + int basey = (int)(tl.y/h); + float splity = tl.y-basey*h; + + float f = client_frametime(); + gfx_texture_set(data->images[IMAGE_SNOW].id); + gfx_quads_begin(); + for(int i = 0; i < NUM_FLAKES; i++) + { + flakes[i].p.x -= f*flakes[i].xspeed; + flakes[i].p.y += f*flakes[i].yspeed; + if(flakes[i].p.x < 0) + flakes[i].p.x += w; + if(flakes[i].p.y > h) + flakes[i].p.y -= h; + + float x = flakes[i].p.x + basex*w; + float y = flakes[i].p.y + basey*h; + + if(flakes[i].p.x < splitx) + x += w; + if(flakes[i].p.y < splity) + y += h; + + x -= 1000; + y -= 1000; + + gfx_quads_setrotation(flakes[i].rot); + gfx_quads_draw(x, y, flakes[i].size, flakes[i].size); + } + + gfx_quads_end(); +} + +void render_sun(float x, float y) +{ + vec2 pos(x, y); + + gfx_texture_set(-1); + gfx_blend_additive(); + gfx_quads_begin(); + const int rays = 10; + gfx_setcolor(1.0f,1.0f,1.0f,0.025f); + for(int r = 0; r < rays; r++) + { + float a = r/(float)rays + client_localtime()*0.025f; + float size = (1.0f/(float)rays)*0.25f; + vec2 dir0(sinf((a-size)*pi*2.0f), cosf((a-size)*pi*2.0f)); + vec2 dir1(sinf((a+size)*pi*2.0f), cosf((a+size)*pi*2.0f)); + + gfx_setcolorvertex(0, 1.0f,1.0f,1.0f,0.025f); + gfx_setcolorvertex(1, 1.0f,1.0f,1.0f,0.025f); + gfx_setcolorvertex(2, 1.0f,1.0f,1.0f,0.0f); + gfx_setcolorvertex(3, 1.0f,1.0f,1.0f,0.0f); + const float range = 1000.0f; + gfx_quads_draw_freeform( + pos.x+dir0.x, pos.y+dir0.y, + pos.x+dir1.x, pos.y+dir1.y, + pos.x+dir0.x*range, pos.y+dir0.y*range, + pos.x+dir1.x*range, pos.y+dir1.y*range); + } + gfx_quads_end(); + gfx_blend_normal(); + + gfx_texture_set(data->images[IMAGE_SUN].id); + gfx_quads_begin(); + gfx_quads_draw(pos.x, pos.y, 256, 256); + gfx_quads_end(); +} + +static vec2 emoticon_selector_mouse; + +void emoticon_selector_reset() +{ + emoticon_selector_mouse = vec2(0, 0); +} + +int emoticon_selector_render() +{ + int x, y; + inp_mouse_relative(&x, &y); + + emoticon_selector_mouse.x += x; + emoticon_selector_mouse.y += y; + + if (length(emoticon_selector_mouse) > 140) + emoticon_selector_mouse = normalize(emoticon_selector_mouse) * 140; + + float selected_angle = get_angle(emoticon_selector_mouse) + 2*pi/24; + if (selected_angle < 0) + selected_angle += 2*pi; + + bool return_now = false; + int selected_emoticon = -1; + + if (length(emoticon_selector_mouse) > 100) + selected_emoticon = (int)(selected_angle / (2*pi) * 12.0f); + + if(!inp_key_pressed(config.key_emoticon)) + { + return_now = true; + emoticon_selector_active = false; + } + + RECT screen = *ui2_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_emoticon == 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(emoticon_selector_mouse.x+screen.w/2,emoticon_selector_mouse.y+screen.h/2,24,24); + gfx_quads_end(); + + return return_now ? selected_emoticon : -1; +} + +void 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(gameobj && gameobj->time_limit) + { + char buf[64]; + sprintf(buf, "Time Limit: %d min", gameobj->time_limit); + gfx_pretty_text(x+w/2, y, 32, buf, -1); + } + if(gameobj && gameobj->score_limit) + { + char buf[64]; + sprintf(buf, "Score Limit: %d", gameobj->score_limit); + gfx_pretty_text(x+40, y, 32, buf, -1); + } + +} + +void render_scoreboard(float x, float y, float w, int team, const char *title) +{ + //float w = 550.0f; + //float x = width/2-w/2; + //; + //float y = ystart; + //float w = 550.0f; + + animstate idlestate; + anim_eval(&data->animations[ANIM_BASE], 0, &idlestate); + anim_eval_add(&idlestate, &data->animations[ANIM_IDLE], 0, 1.0f); + + //float ystart = y; + float h = 600.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(gameobj->game_over) + title = "Game Over"; + else + title = "Score Board"; + } + + float tw = gfx_pretty_text_width( 64, title, -1); + + if(team == -1) + { + gfx_pretty_text(x+w/2-tw/2, y, 64, title, -1); + } + else + { + gfx_pretty_text(x+10, y, 64, title, -1); + + if(gameobj) + { + char buf[128]; + sprintf(buf, "%d", gameobj->teamscore[team&1]); + tw = gfx_pretty_text_width(64, buf, -1); + gfx_pretty_text(x+w-tw-40, y, 64, buf, -1); + } + } + + y += 64.0f; + + /* + if(team) + { + char buf[128]; + sprintf(buf, "%4d", gameobj->teamscore[team&1]); + gfx_pretty_text(x+w/2-tw/2, y, 32, buf, -1); + }*/ + + + // find players + const obj_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 == OBJTYPE_PLAYER_INFO) + { + players[num_players] = (const obj_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 obj_player_info *tmp = players[i]; + players[i] = players[i+1]; + players[i+1] = tmp; + } + } + } + + // render headlines + gfx_pretty_text(x+10, y, 32, "Score", -1); + gfx_pretty_text(x+125, y, 32, "Name", -1); + gfx_pretty_text(x+w-70, y, 32, "Ping", -1); + y += 38.0f; + + // render player scores + for(int i = 0; i < num_players; i++) + { + const obj_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 = 46.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(); + } + + sprintf(buf, "%4d", info->score); + gfx_pretty_text(x+60-gfx_pretty_text_width(font_size,buf,-1), y, font_size, buf, -1); + gfx_pretty_text(x+128, y, font_size, client_datas[info->clientid].name, -1); + + sprintf(buf, "%4d", info->latency); + float tw = gfx_pretty_text_width(font_size, buf, -1); + gfx_pretty_text(x+w-tw-35, y, font_size, buf, -1); + + // render avatar + render_tee(&idlestate, &client_datas[info->clientid].skin_info, EMOTE_NORMAL, vec2(1,0), vec2(x+90, y+28)); + y += 50.0f; + } +} + +void mapscreen_to_world(float center_x, float center_y, float zoom) +{ + RECT screen = *ui2_screen(); + + const float default_zoom = 1.5f; + float width = screen.w*default_zoom*zoom; + float height = screen.h*default_zoom*zoom; + gfx_mapscreen(center_x-width/2, center_y-height/2, center_x+width/2, center_y+height/2); +} + +// renders the complete game world +void render_world(float center_x, float center_y, float zoom) +{ + mapscreen_to_world(center_x, center_y, zoom); + //gfx_mapscreen(center_x-width/2, center_y-height/2, center_x+width/2, center_y+height/2); + + // render background environment + int theme_id = 0; + mapres_theme *t = (mapres_theme *)map_find_item(MAPRES_TEMP_THEME, 0); + if(t) + theme_id = t->id; + + if(config.gfx_high_detail) + { + if(theme_id == 1) + { + // Winter night + gfx_mapscreen(0,0,1,1); + gfx_texture_set(-1); + gfx_quads_begin(); + vec4 top(0x11/(float)0xff, 0x1a/(float)0xff, 0x21/(float)0xff, 1.0f); + vec4 bottom(0x2a/(float)0xff, 0x40/(float)0xff, 0x52/(float)0xff, 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, 1, 1); + gfx_quads_end(); + + mapscreen_to_world(center_x*0.1f, center_y*0.1f, zoom); + render_stars(); + + mapscreen_to_world(center_x, center_y, zoom); + + render_moon(center_x*0.8f, center_y*0.8f); + + mapscreen_to_world(center_x, center_y, zoom); + } + else + { + // Summer day + render_sun(20+center_x*0.6f, 20+center_y*0.6f); + + // draw clouds + static vec2 cloud_pos[6] = {vec2(-500,0),vec2(-500,200),vec2(-500,400)}; + static float cloud_speed[6] = {30, 20, 10}; + static int cloud_sprites[6] = {SPRITE_CLOUD1, SPRITE_CLOUD2, SPRITE_CLOUD3}; + + gfx_texture_set(data->images[IMAGE_CLOUDS].id); + gfx_quads_begin(); + for(int i = 0; i < 3; i++) + { + float parallax_amount = 0.55f; + select_sprite(cloud_sprites[i]); + draw_sprite((cloud_pos[i].x+fmod(client_localtime()*cloud_speed[i]+i*100.0f, 3000.0f))+center_x*parallax_amount, + cloud_pos[i].y+center_y*parallax_amount, 300); + } + gfx_quads_end(); + + // draw backdrop + gfx_texture_set(data->images[IMAGE_BACKDROP].id); + gfx_quads_begin(); + float parallax_amount = 0.25f; + for(int x = -1; x < 3; x++) + gfx_quads_drawTL(1024*x+center_x*parallax_amount, (center_y)*parallax_amount+150+512, 1024, 512); + gfx_quads_end(); + } + } + + // render background tilemaps + tilemap_render(32.0f, 0); + + // render items + { + 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 == OBJTYPE_PROJECTILE) + { + const void *prev = snap_find_item(SNAP_PREV, item.type, item.id); + if(prev) + render_projectile((const obj_projectile *)prev, (const obj_projectile *)data, item.id); + } + else if(item.type == OBJTYPE_POWERUP) + { + const void *prev = snap_find_item(SNAP_PREV, item.type, item.id); + if(prev) + render_powerup((const obj_powerup *)prev, (const obj_powerup *)data); + } + else if(item.type == OBJTYPE_FLAG) + { + const void *prev = snap_find_item(SNAP_PREV, item.type, item.id); + if (prev) + render_flag((const obj_flag *)prev, (const obj_flag *)data); + } + } + } + + // render players above all + { + 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 == OBJTYPE_PLAYER_CHARACTER) + { + const void *prev = snap_find_item(SNAP_PREV, item.type, item.id); + const void *prev_info = snap_find_item(SNAP_PREV, OBJTYPE_PLAYER_INFO, item.id); + const void *info = snap_find_item(SNAP_CURRENT, OBJTYPE_PLAYER_INFO, item.id); + if(prev && prev_info && info) + { + client_datas[((const obj_player_info *)info)->clientid].team = ((const obj_player_info *)info)->team; + render_player( + (const obj_player_character *)prev, + (const obj_player_character *)data, + (const obj_player_info *)prev_info, + (const obj_player_info *)info + ); + } + } + } + } + + // render particles + temp_system.update(client_frametime()); + temp_system.render(); + + // render foreground tilemaps + tilemap_render(32.0f, 1); + + // render front environment effects + if(config.gfx_high_detail) + { + if(theme_id == 1) + { + //mapscreen_to_world(center_x, center_y, zoom); + render_snow(); + } + } + + // render damage indications + damageind.render(); +} + +static void do_input(int *v, int key) +{ + *v += inp_key_presses(key) + inp_key_releases(key); + if((*v&1) != inp_key_state(key)) + (*v)++; + *v &= INPUT_STATE_MASK; +} + +void render_game() +{ + float width = 400*3.0f; + float height = 300*3.0f; + + bool spectate = false; + + if(config.cl_predict) + { + if(!local_character || (local_character->health < 0) || (gameobj && gameobj->game_over)) + { + // don't use predicted + } + else + local_character_pos = mix(predicted_prev_player.pos, predicted_player.pos, client_intrapredtick()); + } + else if(local_character && local_prev_character) + { + local_character_pos = mix( + vec2(local_prev_character->x, local_prev_character->y), + vec2(local_character->x, local_character->y), client_intratick()); + } + + if(local_info && local_info->team == -1) + spectate = true; + + animstate idlestate; + anim_eval(&data->animations[ANIM_BASE], 0, &idlestate); + anim_eval_add(&idlestate, &data->animations[ANIM_IDLE], 0, 1.0f); + + if (inp_key_down(KEY_ESC)) + { + if (chat_mode) + chat_mode = CHATMODE_NONE; + else + { + menu_active = !menu_active; + if(menu_active) + menu_game_active = true; + } + } + + // make sure to send our info again if the menu closes + static bool menu_was_active = false; + if(menu_active) + menu_was_active = true; + else if(menu_was_active) + { + send_info(false); + menu_was_active = false; + } + + // handle chat input + if (!menu_active) + { + if(chat_mode != CHATMODE_NONE) + { + if(inp_key_down(KEY_ENTER)) + { + // send message + if(chat_input_len) + { + if(chat_mode == CHATMODE_CONSOLE) + config_set(chat_input); + else if(chat_mode == CHATMODE_REMOTECONSOLE) + client_rcon(chat_input); + else + { + // send chat message + msg_pack_start(MSG_SAY, MSGFLAG_VITAL); + if(chat_mode == CHATMODE_ALL) + msg_pack_int(0); + else + msg_pack_int(1); + msg_pack_string(chat_input, 512); + msg_pack_end(); + client_send_msg(); + } + } + + chat_mode = CHATMODE_NONE; + } + + int c = inp_last_char(); + int k = inp_last_key(); + + if (!(c >= 0 && c < 32)) + { + if (chat_input_len < sizeof(chat_input) - 1) + { + chat_input[chat_input_len] = c; + chat_input[chat_input_len+1] = 0; + chat_input_len++; + } + } + + if(k == KEY_BACKSPACE) + { + if(chat_input_len > 0) + { + chat_input[chat_input_len-1] = 0; + chat_input_len--; + } + } + + } + else + { + if(chat_mode == CHATMODE_NONE) + { + if(inp_key_down(config.key_chat)) + chat_mode = CHATMODE_ALL; + + if(inp_key_down(config.key_teamchat)) + chat_mode = CHATMODE_TEAM; + + if(inp_key_down(config.key_console)) + chat_mode = CHATMODE_CONSOLE; + + if(inp_key_down(config.key_remoteconsole)) + chat_mode = CHATMODE_REMOTECONSOLE; + + if(chat_mode != CHATMODE_NONE) + { + mem_zero(chat_input, sizeof(chat_input)); + chat_input_len = 0; + } + } + } + } + + if (!menu_active) + inp_clear(); + + // fetch new input + if(!menu_active && !emoticon_selector_active) + { + int x, y; + inp_mouse_relative(&x, &y); + mouse_pos += vec2(x, y); + if(!spectate) + { + float l = length(mouse_pos); + if(l > 600.0f) + mouse_pos = normalize(mouse_pos)*600.0f; + } + } + + // set listner pos + if(spectate) + { + local_target_pos = mouse_pos; + snd_set_listener_pos(mouse_pos.x, mouse_pos.y); + } + else + { + local_target_pos = local_character_pos + mouse_pos; + snd_set_listener_pos(local_character_pos.x, local_character_pos.y); + } + + // snap input + { + static player_input input = {0}; + + input.target_x = (int)mouse_pos.x; + input.target_y = (int)mouse_pos.y; + + if(chat_mode != CHATMODE_NONE) + input.state = STATE_CHATTING; + else if(menu_active) + input.state = STATE_IN_MENU; + else + { + input.state = STATE_PLAYING; + input.left = inp_key_state(config.key_move_left); + input.right = inp_key_state(config.key_move_right); + input.hook = inp_key_state(config.key_hook); + input.jump = inp_key_state(config.key_jump); + + if(!emoticon_selector_active) + do_input(&input.fire, config.key_fire); + + // weapon selection + do_input(&input.next_weapon, config.key_next_weapon); + do_input(&input.prev_weapon, config.key_prev_weapon); + + if(inp_key_presses(config.key_next_weapon) || inp_key_presses(config.key_prev_weapon)) + input.wanted_weapon = 0; + else if (config.cl_autoswitch_weapons && picked_up_weapon) + { + input.wanted_weapon = picked_up_weapon; + } + else + { + if(inp_key_presses(config.key_weapon1)) input.wanted_weapon = 1; + if(inp_key_presses(config.key_weapon2)) input.wanted_weapon = 2; + if(inp_key_presses(config.key_weapon3)) input.wanted_weapon = 3; + if(inp_key_presses(config.key_weapon4)) input.wanted_weapon = 4; + if(inp_key_presses(config.key_weapon5)) input.wanted_weapon = 5; + if(inp_key_presses(config.key_weapon6)) input.wanted_weapon = 6; + } + + picked_up_weapon = 0; + } + + // stress testing + if(config.dbg_stress) + { + float t = client_localtime(); + mem_zero(&input, sizeof(input)); + + input.left = ((int)t/2)&1; + input.right = ((int)t/2+1)&1; + input.jump = ((int)t); + input.fire = ((int)(t*10)); + input.hook = ((int)(t*2))&1; + input.wanted_weapon = ((int)t)%NUM_WEAPONS; + input.target_x = (int)(sinf(t*3)*100.0f); + input.target_y = (int)(cosf(t*3)*100.0f); + } + + snap_input(&input, sizeof(input)); + } + + // center at char but can be moved when mouse is far away + float offx = 0, offy = 0; + if (config.cl_dynamic_camera) + { + int deadzone = 300; + if(mouse_pos.x > deadzone) offx = mouse_pos.x-deadzone; + if(mouse_pos.x <-deadzone) offx = mouse_pos.x+deadzone; + if(mouse_pos.y > deadzone) offy = mouse_pos.y-deadzone; + if(mouse_pos.y <-deadzone) offy = mouse_pos.y+deadzone; + offx = offx*2/3; + offy = offy*2/3; + } + + // render the world + gfx_clear(0.65f,0.78f,0.9f); + if(spectate) + render_world(mouse_pos.x, mouse_pos.y, 1.0f); + else + { + render_world(local_character_pos.x+offx, local_character_pos.y+offy, 1.0f); + + // draw screen box + if(0) + { + gfx_texture_set(-1); + gfx_blend_normal(); + gfx_lines_begin(); + float cx = local_character_pos.x+offx; + float cy = local_character_pos.y+offy; + float w = 400*3/2; + float h = 300*3/2; + gfx_lines_draw(cx-w,cy-h,cx+w,cy-h); + gfx_lines_draw(cx+w,cy-h,cx+w,cy+h); + gfx_lines_draw(cx+w,cy+h,cx-w,cy+h); + gfx_lines_draw(cx-w,cy+h,cx-w,cy-h); + gfx_lines_end(); + } + } + + + // pseudo format + // ZOOM ZOOM + float zoom = 3.0; + if(local_character) + cl_effects.getzoom(client_tick(), client_intratick(), local_character); + + // DEBUG TESTING + if(zoom > 3.01f) + { + gfx_clear_mask(0); + + gfx_texture_set(-1); + gfx_blend_normal(); + + gfx_mask_op(MASK_NONE, 1); + + gfx_quads_begin(); + gfx_setcolor(0.65f,0.78f,0.9f,1.0f); + + float fov; + if (zoom > 3.01f) + fov = pi * (zoom - 3.0f) / 6.0f; + else + fov = pi / 6.0f; + + float fade = 0.7f; + + + float a = get_angle(normalize(vec2(mouse_pos.x, mouse_pos.y))); + vec2 d = get_dir(a); + vec2 d0 = get_dir(a-fov/2.0f); + vec2 d1 = get_dir(a+fov/2.0f); + + vec2 cd0 = get_dir(a-(fov*fade)/2.0f); // center direction + vec2 cd1 = get_dir(a+(fov*fade)/2.0f); + + vec2 p0n = local_character_pos + d0*32.0f; + vec2 p1n = local_character_pos + d1*32.0f; + vec2 p0f = local_character_pos + d0*1000.0f; + vec2 p1f = local_character_pos + d1*1000.0f; + + vec2 cn = local_character_pos + d*32.0f; + vec2 cf = local_character_pos + d*1000.0f; + + vec2 cp0n = local_character_pos + cd0*32.0f; + vec2 cp0f = local_character_pos + cd0*1000.0f; + vec2 cp1n = local_character_pos + cd1*32.0f; + vec2 cp1f = local_character_pos + cd1*1000.0f; + + gfx_quads_draw_freeform( + p0n.x,p0n.y, + p1n.x,p1n.y, + p0f.x,p0f.y, + p1f.x,p1f.y); + gfx_quads_end(); + + gfx_mask_op(MASK_SET, 0); + + render_world(local_character_pos.x+offx, local_character_pos.y+offy, 2.0f); + + gfx_mask_op(MASK_NONE, 0); + + mapscreen_to_world(local_character_pos.x+offx, local_character_pos.y+offy, 1.0f); + + gfx_texture_set(-1); + gfx_blend_normal(); + gfx_quads_begin(); + gfx_setcolor(0.5f,0.9f,0.5f,0.25f); + float r=0.5f, g=1.0f, b=0.5f; + float r2=r*0.25f, g2=g*0.25f, b2=b*0.25f; + + gfx_setcolor(r,g,b,0.2f); + gfx_quads_draw_freeform( + cn.x,cn.y, + cn.x,cn.y, + cp0f.x,cp0f.y, + cp1f.x,cp1f.y); + + gfx_setcolorvertex(0, r, g, b, 0.2f); + gfx_setcolorvertex(1, r2, g2, b2, 0.9f); + gfx_setcolorvertex(2, r, g, b, 0.2f); + gfx_setcolorvertex(3, r2, g2, b2, 0.9f); + gfx_quads_draw_freeform( + cn.x,cn.y, + p0n.x,p0n.y, + cp0f.x,cp0f.y, + p0f.x,p0f.y); + + gfx_quads_draw_freeform( + cn.x,cn.y, + p1n.x,p1n.y, + cp1f.x,cp1f.y, + p1f.x,p1f.y); + + gfx_quads_end(); + } + + if(local_character && !spectate && !(gameobj && gameobj->game_over)) + { + gfx_texture_set(data->images[IMAGE_GAME].id); + gfx_quads_begin(); + + // render cursor + if (!menu_active && !emoticon_selector_active) + { + select_sprite(data->weapons[local_character->weapon%data->num_weapons].sprite_cursor); + float cursorsize = 64; + draw_sprite(local_target_pos.x, local_target_pos.y, cursorsize); + } + + float x = 5; + float y = 5; + + // render ammo count + // render gui stuff + gfx_quads_end(); + gfx_quads_begin(); + gfx_mapscreen(0,0,400,300); + // if weaponstage is active, put a "glow" around the stage ammo + select_sprite(SPRITE_TEE_BODY); + for (int i = 0; i < local_character->weaponstage; i++) + gfx_quads_drawTL(x+local_character->ammocount * 12 -i*12, y+22, 11, 11); + select_sprite(data->weapons[local_character->weapon%data->num_weapons].sprite_proj); + for (int i = 0; i < local_character->ammocount; 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 < 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 < 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(); + } + + // render kill messages + { + 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 = 48.0f; + float killername_w = gfx_pretty_text_width(font_size, client_datas[killmsgs[r].killer].name, -1); + float victimname_w = gfx_pretty_text_width(font_size, client_datas[killmsgs[r].victim].name, -1); + + float x = startx; + + // render victim name + x -= victimname_w; + gfx_pretty_text(x, y, font_size, client_datas[killmsgs[r].victim].name, -1); + + // render victim tee + x -= 24.0f; + + if(gameobj && 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(client_datas[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(&idlestate, &client_datas[killmsgs[r].victim].skin_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[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(gameobj && 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(client_datas[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(&idlestate, &client_datas[killmsgs[r].killer].skin_info, EMOTE_ANGRY, vec2(1,0), vec2(x, y+28)); + x -= 32.0f; + + // render killer name + x -= killername_w; + gfx_pretty_text(x, y, font_size, client_datas[killmsgs[r].killer].name, -1); + } + + y += 44; + } + } + + // render chat + { + gfx_mapscreen(0,0,400,300); + float x = 10.0f; + float y = 300.0f-50.0f; + float starty = -1; + if(chat_mode != CHATMODE_NONE) + { + // render chat input + char buf[sizeof(chat_input)+16]; + if(chat_mode == CHATMODE_ALL) + sprintf(buf, "All: %s_", chat_input); + else if(chat_mode == CHATMODE_TEAM) + sprintf(buf, "Team: %s_", chat_input); + else if(chat_mode == CHATMODE_CONSOLE) + sprintf(buf, "Console: %s_", chat_input); + else if(chat_mode == CHATMODE_REMOTECONSOLE) + sprintf(buf, "Rcon: %s_", chat_input); + else + sprintf(buf, "Chat: %s_", chat_input); + gfx_pretty_text(x, y, 10.0f, buf, 380); + starty = y; + } + + y -= 10; + + int i; + for(i = 0; i < chat_max_lines; i++) + { + int r = ((chat_current_line-i)+chat_max_lines)%chat_max_lines; + if(client_tick() > chat_lines[r].tick+50*15) + break; + + int lines = int(gfx_pretty_text_width(10, chat_lines[r].text, -1)) / 380 + 1; + + gfx_pretty_text_color(1,1,1,1); + if(chat_lines[r].client_id == -1) + gfx_pretty_text_color(1,1,0.5f,1); // system + else if(chat_lines[r].team) + gfx_pretty_text_color(0.5f,1,0.5f,1); // team message + + gfx_pretty_text(x, y - 8 * (lines - 1), 10, chat_lines[r].text, 380); + y -= 8 * lines; + } + + gfx_pretty_text_color(1,1,1,1); + } + + // render goals + if(gameobj) + { + gametype = gameobj->gametype; + gfx_mapscreen(0,0,400,300); + if(!gameobj->sudden_death) + { + char buf[32]; + int time = 0; + if(gameobj->time_limit) + { + time = gameobj->time_limit*60 - ((client_tick()-gameobj->round_start_tick)/client_tickspeed()); + + if(gameobj->game_over) + time = 0; + } + else + time = (client_tick()-gameobj->round_start_tick)/client_tickspeed(); + + sprintf(buf, "%d:%02d", time /60, time %60); + float w = gfx_pretty_text_width(16, buf, -1); + gfx_pretty_text(200-w/2, 2, 16, buf, -1); + } + + if(gameobj->sudden_death) + { + const char *text = "Sudden Death"; + float w = gfx_pretty_text_width(16, text, -1); + gfx_pretty_text(200-w/2, 2, 16, text, -1); + } + + // render small score hud + if(!(gameobj && gameobj->game_over) && (gametype == GAMETYPE_TDM || gametype == GAMETYPE_CTF)) + { + 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(400-40, 300-40-15+t*20, 50, 18, 5.0f); + gfx_quads_end(); + + char buf[32]; + sprintf(buf, "%d", gameobj->teamscore[t]); + float w = gfx_pretty_text_width(14, buf, -1); + + if(gametype == GAMETYPE_CTF) + { + gfx_pretty_text(400-20-w/2+5, 300-40-15+t*20+2, 14, buf, -1); + if(flags[t]) + { + if(flags[t]->carried_by == -2 || (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(400-40+5, 300-40-15+t*20+1, size/2, size); + gfx_quads_end(); + } + else if(flags[t]->carried_by >= 0) + { + int id = flags[t]->carried_by%MAX_CLIENTS; + const char *name = client_datas[id].name; + float w = gfx_pretty_text_width(10, name, -1); + gfx_pretty_text(400-40-5-w, 300-40-15+t*20+2, 10, name, -1); + tee_render_info info = client_datas[id].skin_info; + info.size = 18.0f; + + render_tee(&idlestate, &info, EMOTE_NORMAL, vec2(1,0), + vec2(400-40+10, 300-40-15+9+t*20+1)); + } + } + } + else + gfx_pretty_text(400-20-w/2, 300-40-15+t*20+2, 14, buf, -1); + } + } + + // render warmup timer + if(gameobj->warmup) + { + char buf[256]; + float w = gfx_pretty_text_width(24, "Warmup", -1); + gfx_pretty_text(200+-w/2, 50, 24, "Warmup", -1); + + int seconds = gameobj->warmup/SERVER_TICK_SPEED; + if(seconds < 5) + sprintf(buf, "%d.%d", seconds, (gameobj->warmup*10/SERVER_TICK_SPEED)%10); + else + sprintf(buf, "%d", seconds); + w = gfx_pretty_text_width(24, buf, -1); + gfx_pretty_text(200+-w/2, 75, 24, buf, -1); + } + } + + if (menu_active) + { + modmenu_render(); + return; + } + + if(chat_mode == CHATMODE_NONE && !menu_active && !spectate) + { + if(!emoticon_selector_active && inp_key_pressed(config.key_emoticon)) + { + emoticon_selector_active = true; + emoticon_selector_reset(); + } + } + else + emoticon_selector_active = false; + + if(emoticon_selector_active) + { + int emoticon = emoticon_selector_render(); + if (emoticon != -1) + { + send_emoticon(emoticon); + emoticon_selector_active = false; + } + } + + if(client_connection_problems()) + { + gfx_mapscreen(0, 0, 400, 300); + const char *text = "Connection Problems..."; + float w = gfx_pretty_text_width(24, text, -1); + gfx_pretty_text(200-w/2, 50, 24, text, -1); + } + + // render score board + if(inp_key_pressed(KEY_TAB) || // user requested + (!spectate && (!local_character || local_character->health < 0)) || // not spectating and is dead + (gameobj && gameobj->game_over) // game over + ) + { + gfx_mapscreen(0, 0, width, height); + + float w = 550.0f; + + if (gameobj && gameobj->gametype == GAMETYPE_DM) + { + render_scoreboard(width/2-w/2, 150.0f, w, -1, 0); + //render_scoreboard(gameobj, 0, 0, -1, 0); + } + else + { + + if(gameobj && gameobj->game_over) + { + const char *text = "DRAW!"; + if(gameobj->teamscore[0] > gameobj->teamscore[1]) + text = "Red Team Wins!"; + else if(gameobj->teamscore[1] > gameobj->teamscore[0]) + text = "Blue Team Wins!"; + + float w = gfx_pretty_text_width(92.0f, text, -1); + gfx_pretty_text(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+600+25, w); + + } +} + +extern "C" void modc_render() +{ + // this should be moved around abit + if(client_state() == CLIENTSTATE_ONLINE) + { + render_game(); + + // handle team switching + // TODO: FUGLY!!! + if(config.cl_team != -10) + { + msg_pack_start(MSG_SETTEAM, MSGFLAG_VITAL); + msg_pack_int(config.cl_team); + msg_pack_end(); + client_send_msg(); + } + } + else // if (client_state() != CLIENTSTATE_CONNECTING && client_state() != CLIENTSTATE_LOADING) + { + modmenu_render(); + } + + // + config.cl_team = -10; +} + + +void menu_do_disconnected(); +void menu_do_connecting(); +void menu_do_connected(); + +extern "C" void modc_statechange(int state, int old) +{ + clear_object_pointers(); + + if(state == CLIENTSTATE_OFFLINE) + { + menu_do_disconnected(); + menu_game_active = false; + } + else if(state == CLIENTSTATE_CONNECTING) + menu_do_connecting(); + else if (state == CLIENTSTATE_ONLINE) + { + menu_active = false; + menu_game_active = true; + menu_do_connected(); + } +} + +extern "C" void modc_message(int msg) +{ + if(msg == MSG_CHAT) + { + int cid = msg_unpack_int(); + int team = msg_unpack_int(); + const char *message = msg_unpack_string(); + dbg_msg("message", "chat cid=%d team=%d msg='%s'", cid, team, message); + chat_add_line(cid, team, message); + + if(cid >= 0) + 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); + } + else if(msg == MSG_SETINFO) + { + int cid = msg_unpack_int(); + const char *name = msg_unpack_string(); + const char *skinname = msg_unpack_string(); + + strncpy(client_datas[cid].name, name, 64); + strncpy(client_datas[cid].skin_name, skinname, 64); + + int use_custom_color = msg_unpack_int(); + client_datas[cid].skin_info.color_body = skin_get_color(msg_unpack_int()); + client_datas[cid].skin_info.color_feet = skin_get_color(msg_unpack_int()); + client_datas[cid].skin_info.size = 64; + + // find new skin + client_datas[cid].skin_id = skin_find(client_datas[cid].skin_name); + if(client_datas[cid].skin_id < 0) + client_datas[cid].skin_id = 0; + + if(use_custom_color) + client_datas[cid].skin_info.texture = skin_get(client_datas[cid].skin_id)->color_texture; + else + { + client_datas[cid].skin_info.texture = skin_get(client_datas[cid].skin_id)->org_texture; + client_datas[cid].skin_info.color_body = vec4(1,1,1,1); + client_datas[cid].skin_info.color_feet = vec4(1,1,1,1); + } + } + else if(msg == MSG_WEAPON_PICKUP) + { + int weapon = msg_unpack_int(); + + picked_up_weapon = weapon+1; + } + else if(msg == MSG_READY_TO_ENTER) + { + client_entergame(); + } + else if(msg == MSG_KILLMSG) + { + killmsg_current = (killmsg_current+1)%killmsg_max; + killmsgs[killmsg_current].killer = msg_unpack_int(); + killmsgs[killmsg_current].victim = msg_unpack_int(); + killmsgs[killmsg_current].weapon = msg_unpack_int(); + killmsgs[killmsg_current].mode_special = msg_unpack_int(); + killmsgs[killmsg_current].tick = client_tick(); + } + else if (msg == MSG_EMOTICON) + { + int cid = msg_unpack_int(); + int emoticon = msg_unpack_int(); + client_datas[cid].emoticon = emoticon; + client_datas[cid].emoticon_start = client_tick(); + } + else if(msg == MSG_SOUND_GLOBAL) + { + int soundid = msg_unpack_int(); + snd_play_random(CHN_GLOBAL, soundid, 1.0f, vec2(0,0)); + } +} + +extern "C" void modc_connected() +{ + // init some stuff + col_init(32); + img_init(); + tilemap_init(); + chat_reset(); + + proj_particles.reset(); + + clear_object_pointers(); + last_new_predicted_tick = -1; + + for(int i = 0; i < MAX_CLIENTS; i++) + { + client_datas[i].name[0] = 0; + client_datas[i].team = 0; + client_datas[i].emoticon = 0; + client_datas[i].emoticon_start = -1; + } + + for(int i = 0; i < killmsg_max; i++) + killmsgs[i].tick = -100000; + + send_info(true); +} + +extern "C" const char *modc_net_version() { return TEEWARS_NETVERSION; } diff --git a/src/game/client/gc_mapres_image.cpp b/src/game/client/gc_mapres_image.cpp new file mode 100644 index 00000000..cc9a8143 --- /dev/null +++ b/src/game/client/gc_mapres_image.cpp @@ -0,0 +1,125 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +#include +#include +#include "gc_mapres_image.h" +#include "../g_mapres.h" + +static int map_textures[64] = {0}; +static int count = 0; +/* +static void calc_mipmaps(void *data_in, unsigned width, unsigned height, void *data_out) +{ + unsigned char *src = (unsigned char*)data_in; + unsigned char *dst = (unsigned char*)data_out; + unsigned mip_w = width; + unsigned mip_h = height; + unsigned prev_w; + unsigned prev_h; + + // Highest level - no mod + for(unsigned x = 0; x < mip_w; x++) + { + for(unsigned y = 0; y < mip_h; y++) + { + unsigned i = (y * mip_w + x)<<2; + for(unsigned j = 0; j < 4; j++) + dst[i+j] = src[i+j]; + } + } + + src = dst; + dst += mip_w * mip_h * 4; + prev_w = mip_w; + prev_h = mip_h; + mip_w = mip_w>>1; + mip_h = mip_h>>1; + + while(mip_w > 0 && mip_h > 0) + { + for(unsigned x = 0; x < mip_w; x++) + { + for(unsigned y = 0; y < mip_h; y++) + { + unsigned i = (y * mip_w + x)<<2; + + unsigned r = 0; + unsigned g = 0; + unsigned b = 0; + unsigned a = 0; + + + r += src[(((y<<1) * prev_w + (x<<1))<<2)]; + g += src[(((y<<1) * prev_w + (x<<1))<<2)+1]; + b += src[(((y<<1) * prev_w + (x<<1))<<2)+2]; + a += src[(((y<<1) * prev_w + (x<<1))<<2)+3]; + + r += src[(((y<<1) * prev_w + ((x+1)<<1))<<2)]; + g += src[(((y<<1) * prev_w + ((x+1)<<1))<<2)+1]; + b += src[(((y<<1) * prev_w + ((x+1)<<1))<<2)+2]; + a += src[(((y<<1) * prev_w + ((x+1)<<1))<<2)+3]; + + r += src[((((y+1)<<1) * prev_w + (x<<1))<<2)]; + g += src[((((y+1)<<1) * prev_w + (x<<1))<<2)+1]; + b += src[((((y+1)<<1) * prev_w + (x<<1))<<2)+2]; + a += src[((((y+1)<<1) * prev_w + (x<<1))<<2)+3]; + + r += src[((((y+1)<<1) * prev_w + ((x+1)<<1))<<2)]; + g += src[((((y+1)<<1) * prev_w + ((x+1)<<1))<<2)+1]; + b += src[((((y+1)<<1) * prev_w + ((x+1)<<1))<<2)+2]; + a += src[((((y+1)<<1) * prev_w + ((x+1)<<1))<<2)+3]; + + dst[i] = r>>2; + dst[i+1] = g>>2; + dst[i+2] = b>>2; + dst[i+3] = a>>2; + } + } + + src = dst; + dst = dst + mip_w*mip_h*4; + prev_w = mip_w; + prev_h = mip_h; + mip_w = mip_w>>1; + mip_h = mip_h>>1; + } +} +*/ +extern int DEBUGTEST_MAPIMAGE; + +int img_init() +{ + int start, count; + map_get_type(MAPRES_IMAGE, &start, &count); + dbg_msg("mapres_image", "start=%d count=%d", start, count); + for(int i = 0; i < 64; i++) + { + if(map_textures[i]) + { + gfx_unload_texture(map_textures[i]); + map_textures[i] = 0; + } + } + + //void *data_res = (void*)mem_alloc(1024*1024*4*2, 16); + for(int i = 0; i < count; i++) + { + mapres_image *img = (mapres_image *)map_get_item(start+i, 0, 0); + void *data = map_get_data(img->image_data); + //calc_mipmaps(data, img->width, img->height, data_res); + map_textures[i] = gfx_load_texture_raw(img->width, img->height, IMG_RGBA, data); + map_unload_data(img->image_data); + } + + //mem_free(data_res); + return count; +} + +int img_num() +{ + return count; +} + +int img_get(int index) +{ + return map_textures[index]; +} diff --git a/src/game/client/gc_mapres_image.h b/src/game/client/gc_mapres_image.h new file mode 100644 index 00000000..f841ca53 --- /dev/null +++ b/src/game/client/gc_mapres_image.h @@ -0,0 +1,19 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ + +// loads images from the map to textures +int img_init(); + +// returns the number of images in the map +int img_num(); + +// fetches the texture id for the image +int img_get(int index); + + +class mapres_image +{ +public: + int width; + int height; + int image_data; +}; diff --git a/src/game/client/gc_mapres_tilemap.cpp b/src/game/client/gc_mapres_tilemap.cpp new file mode 100644 index 00000000..6e687495 --- /dev/null +++ b/src/game/client/gc_mapres_tilemap.cpp @@ -0,0 +1,102 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +#include +#include +#include "gc_mapres_tilemap.h" +#include "gc_mapres_image.h" +#include "../g_mapres.h" + +int tilemap_init() +{ + return 0; +} + +void tilemap_render(float scale, int fg) +{ + if(!map_is_loaded()) + return; + + float screen_x0, screen_y0, screen_x1, screen_y1; + gfx_getscreen(&screen_x0, &screen_y0, &screen_x1, &screen_y1); + + // fetch indecies + int start, num; + map_get_type(MAPRES_TILEMAP, &start, &num); + + // render tilemaps + int passed_main = 0; + for(int t = 0; t < num; t++) + { + mapres_tilemap *tmap = (mapres_tilemap *)map_get_item(start+t,0,0); + unsigned char *data = (unsigned char *)map_get_data(tmap->data); + + if(tmap->main) + passed_main = 1; + + if((fg && passed_main) || (!fg && !passed_main)) + { + if(!config.gfx_high_detail && !tmap->main) + continue; + gfx_texture_set(img_get(tmap->image)); + + gfx_quads_begin(); + + int starty = (int)(screen_y0/scale)-1; + int startx = (int)(screen_x0/scale)-1; + int endy = (int)(screen_y1/scale)+1; + int endx = (int)(screen_x1/scale)+1; + + float frac = (1.25f/1024.0f);//2.0f; //2.0f; + float texsize = 1024.0f; + float nudge = 0.5f/texsize; + for(int y = starty; y < endy; y++) + for(int x = startx; x < endx; x++) + { + int mx = x; + int my = y; + if(mx<0) mx = 0; + if(mx>=tmap->width) mx = tmap->width-1; + if(my<0) my = 0; + if(my>=tmap->height) my = tmap->height-1; + + int c = mx + my*tmap->width; + + unsigned char d = data[c*2]; + unsigned char f = data[c*2+1]; + if(d) + { + int tx = d%16; + int ty = d/16; + int px0 = tx*(1024/16); + int py0 = ty*(1024/16); + int px1 = (tx+1)*(1024/16)-1; + int py1 = (ty+1)*(1024/16)-1; + + float u0 = nudge + px0/texsize+frac; + float v0 = nudge + py0/texsize+frac; + float u1 = nudge + px1/texsize-frac; + float v1 = nudge + py1/texsize-frac; + + if(f&TILEFLAG_VFLIP) + { + float tmp = u0; + u0 = u1; + u1 = tmp; + } + + if(f&TILEFLAG_HFLIP) + { + float tmp = v0; + v0 = v1; + v1 = tmp; + } + + gfx_quads_setsubset(u0,v0,u1,v1); + + gfx_quads_drawTL(x*scale, y*scale, scale, scale); + } + } + + gfx_quads_end(); + } + } +} diff --git a/src/game/client/gc_mapres_tilemap.h b/src/game/client/gc_mapres_tilemap.h new file mode 100644 index 00000000..a13495ed --- /dev/null +++ b/src/game/client/gc_mapres_tilemap.h @@ -0,0 +1,26 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ + +// dependencies: image + +// +int tilemap_init(); + +// renders the tilemaps +void tilemap_render(float scale, int fg); + +enum +{ + TILEFLAG_VFLIP=1, + TILEFLAG_HFLIP=2, +}; + +struct mapres_tilemap +{ + int image; + int width; + int height; + int x, y; + int scale; + int data; + int main; +}; diff --git a/src/game/client/gc_menu.cpp b/src/game/client/gc_menu.cpp new file mode 100644 index 00000000..de9047df --- /dev/null +++ b/src/game/client/gc_menu.cpp @@ -0,0 +1,1893 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +#include +#include +#include +#include + +#include +#include + +extern "C" { + #include + #include + #include + #include +} + +#include "../g_mapres.h" +#include "../g_version.h" +#include "../g_protocol.h" + +#include "gc_mapres_image.h" +#include "gc_mapres_tilemap.h" + +#include "../generated/gc_data.h" +#include "gc_render.h" +#include "gc_skin.h" +#include + +extern data_container *data; + +// abit uglyness +extern const obj_player_info *local_info; +extern bool menu_active; +extern bool menu_game_active; + +enum +{ + POPUP_NONE=0, + POPUP_CONNECTING, + POPUP_DISCONNECTED, + POPUP_PASSWORD, + POPUP_QUIT, +}; + +static int popup = POPUP_NONE; + +//static vec4 gui_color(0.9f,0.78f,0.65f, 0.5f); +//static vec4 gui_color(0.78f,0.9f,0.65f, 0.5f); + +static vec4 gui_color(0.65f,0.78f,0.9f, 0.5f); + +static vec4 color_tabbar_inactive_outgame(0,0,0,0.25f); +static vec4 color_tabbar_active_outgame(0,0,0,0.5f); + +static float color_ingame_scale_i = 0.5f; +static float color_ingame_scale_a = 0.2f; +static vec4 color_tabbar_inactive_ingame(gui_color.r*color_ingame_scale_i, gui_color.g*color_ingame_scale_i, gui_color.b*color_ingame_scale_i,0.75f); +static vec4 color_tabbar_active_ingame(gui_color.r*color_ingame_scale_a, gui_color.g*color_ingame_scale_a, gui_color.b*color_ingame_scale_a,0.85f); +//static vec4 color_tabbar_inactive_ingame(0.2f,0.2f,0.2f,0.5f); +//static vec4 color_tabbar_active_ingame(0.2f,0.2f,0.2f,0.75f); + +static vec4 color_tabbar_inactive = color_tabbar_inactive_outgame; +static vec4 color_tabbar_active = color_tabbar_active_outgame; + +enum +{ + CORNER_TL=1, + CORNER_TR=2, + CORNER_BL=4, + CORNER_BR=8, + + CORNER_T=CORNER_TL|CORNER_TR, + CORNER_B=CORNER_BL|CORNER_BR, + CORNER_R=CORNER_TR|CORNER_BR, + CORNER_L=CORNER_TL|CORNER_BL, + + CORNER_ALL=CORNER_T|CORNER_B, + + PAGE_NEWS=0, + PAGE_INTERNET, + PAGE_LAN, + PAGE_FAVORITES, + PAGE_SETTINGS, + //PAGE_GAME, // not a real page + PAGE_SYSTEM, +}; + +typedef struct +{ + float x, y, w, h; +} RECT; + +static RECT screen = { 0.0f, 0.0f, 848.0f, 480.0f }; + +extern void select_sprite(int id, int flags=0, int sx=0, int sy=0); + +RECT *ui2_screen() +{ + float aspect = gfx_screenaspect(); + float w, h; + + h = 600; + w = aspect*h; + + screen.w = w; + screen.h = h; + + return &screen; +} + +void ui2_set_scale(float s) +{ + config.ui_scale = (int)(s*100.0f); +} + +float ui2_scale() +{ + return config.ui_scale/100.0f; +} + +void ui2_hsplit_t(const RECT *original, float cut, RECT *top, RECT *bottom) +{ + RECT r = *original; + cut *= ui2_scale(); + + if (top) + { + top->x = r.x; + top->y = r.y; + top->w = r.w; + top->h = cut; + } + + if (bottom) + { + bottom->x = r.x; + bottom->y = r.y + cut; + bottom->w = r.w; + bottom->h = r.h - cut; + } +} + +void ui2_hsplit_b(const RECT *original, float cut, RECT *top, RECT *bottom) +{ + RECT r = *original; + cut *= ui2_scale(); + + if (top) + { + top->x = r.x; + top->y = r.y; + top->w = r.w; + top->h = r.h - cut; + } + + if (bottom) + { + bottom->x = r.x; + bottom->y = r.y + r.h - cut; + bottom->w = r.w; + bottom->h = cut; + } +} + +void ui2_vsplit_l(const RECT *original, float cut, RECT *left, RECT *right) +{ + RECT r = *original; + cut *= ui2_scale(); + + if (left) + { + left->x = r.x; + left->y = r.y; + left->w = cut; + left->h = r.h; + } + + if (right) + { + right->x = r.x + cut; + right->y = r.y; + right->w = r.w - cut; + right->h = r.h; + } +} + +void ui2_vsplit_r(const RECT *original, float cut, RECT *left, RECT *right) +{ + RECT r = *original; + cut *= ui2_scale(); + + if (left) + { + left->x = r.x; + left->y = r.y; + left->w = r.w - cut; + left->h = r.h; + } + + if (right) + { + right->x = r.x + r.w - cut; + right->y = r.y; + right->w = cut; + right->h = r.h; + } +} + +void ui2_margin(const RECT *original, float cut, RECT *other_rect) +{ + RECT r = *original; + cut *= ui2_scale(); + + other_rect->x = r.x + cut; + other_rect->y = r.y + cut; + other_rect->w = r.w - 2*cut; + other_rect->h = r.h - 2*cut; +} + +void ui2_vmargin(const RECT *original, float cut, RECT *other_rect) +{ + RECT r = *original; + cut *= ui2_scale(); + + other_rect->x = r.x + cut; + other_rect->y = r.y; + other_rect->w = r.w - 2*cut; + other_rect->h = r.h; +} + +void ui2_hmargin(const RECT *original, float cut, RECT *other_rect) +{ + RECT r = *original; + cut *= ui2_scale(); + + other_rect->x = r.x; + other_rect->y = r.y + cut; + other_rect->w = r.w; + other_rect->h = r.h - 2*cut; +} + +typedef void (*ui2_draw_button_func)(const void *id, const char *text, int checked, const RECT *r, void *extra); + +int ui2_do_button(const void *id, const char *text, int checked, const RECT *r, ui2_draw_button_func draw_func, void *extra) +{ + /* logic */ + int ret = 0; + int inside = ui_mouse_inside(r->x,r->y,r->w,r->h); + + if(ui_active_item() == id) + { + if(!ui_mouse_button(0)) + { + if(inside) + ret = 1; + ui_set_active_item(0); + } + } + else if(ui_hot_item() == id) + { + if(ui_mouse_button(0)) + ui_set_active_item(id); + } + + if(inside) + ui_set_hot_item(id); + + if(draw_func) + draw_func(id, text, checked, r, extra); + return ret; +} + + +void ui2_do_label(const RECT *r, const char *text, float size, int align) +{ + gfx_blend_normal(); + size *= ui2_scale(); + if(align == 0) + { + float tw = gfx_pretty_text_width(size, text, -1); + gfx_pretty_text(r->x + r->w/2-tw/2, r->y, size, text, -1); + } + else if(align < 0) + gfx_pretty_text(r->x, r->y, size, text, -1); + else if(align > 0) + { + float tw = gfx_pretty_text_width(size, text, -1); + gfx_pretty_text(r->x + r->w-tw, r->y, size, text, -1); + } +} + + +extern void draw_round_rect_ext(float x, float y, float w, float h, float r, int corners); +extern void draw_round_rect(float x, float y, float w, float h, float r); + +static void ui2_draw_rect(const RECT *r, vec4 color, int corners, float rounding) +{ + gfx_texture_set(-1); + gfx_quads_begin(); + gfx_setcolor(color.r, color.g, color.b, color.a); + draw_round_rect_ext(r->x,r->y,r->w,r->h,rounding*ui2_scale(), corners); + gfx_quads_end(); +} + +static void ui2_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 <= 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(); +} + +static void ui2_draw_menu_button(const void *id, const char *text, int checked, const RECT *r, void *extra) +{ + ui2_draw_rect(r, vec4(1,1,1,0.5f), CORNER_ALL, 5.0f); + ui2_do_label(r, text, 24, 0); +} + + +static void ui2_draw_keyselect_button(const void *id, const char *text, int checked, const RECT *r, void *extra) +{ + ui2_draw_rect(r, vec4(1,1,1,0.5f), CORNER_ALL, 5.0f); + ui2_do_label(r, text, 18, 0); +} + +static void ui2_draw_menu_tab_button(const void *id, const char *text, int checked, const RECT *r, void *extra) +{ + if(checked) + ui2_draw_rect(r, color_tabbar_active, CORNER_T, 10.0f); + else + ui2_draw_rect(r, color_tabbar_inactive, CORNER_T, 10.0f); + ui2_do_label(r, text, 26, 0); +} + + +static void ui2_draw_settings_tab_button(const void *id, const char *text, int checked, const RECT *r, void *extra) +{ + if(checked) + ui2_draw_rect(r, color_tabbar_active, CORNER_R, 10.0f); + else + ui2_draw_rect(r, color_tabbar_inactive, CORNER_R, 10.0f); + ui2_do_label(r, text, 24, 0); +} + +static void ui2_draw_grid_header(const void *id, const char *text, int checked, const RECT *r, void *extra) +{ + if(checked) + ui2_draw_rect(r, vec4(1,1,1,0.5f), CORNER_T, 5.0f); + //else + // ui2_draw_rect(r, vec4(1,1,1,0.1f), CORNER_T, 5.0f); + RECT t; + ui2_vsplit_l(r, 5.0f, 0, &t); + ui2_do_label(&t, text, 18, -1); +} +/* +static void ui2_draw_grid_cell_l(const void *id, const char *text, int checked, const RECT *r, void *extra) +{ + ui2_do_label(r, text, 18, -1); +} + +static void ui2_draw_grid_cell_r(const void *id, const char *text, int checked, const RECT *r, void *extra) +{ + ui2_do_label(r, text, 18, 1); +}*/ + +static void ui2_draw_list_row(const void *id, const char *text, int checked, const RECT *r, void *extra) +{ + if(checked) + { + RECT sr = *r; + ui2_margin(&sr, 1.5f, &sr); + ui2_draw_rect(&sr, vec4(1,1,1,0.5f), CORNER_ALL, 4.0f); + } + ui2_do_label(r, text, 18, -1); +} + +static void ui2_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; + ui2_vsplit_l(&t, 5.0f, 0, &t); + + ui2_margin(&c, 2.0f, &c); + ui2_draw_rect(&c, vec4(1,1,1,0.25f), CORNER_ALL, 3.0f); + ui2_do_label(&c, boxtext, 16, 0); + ui2_do_label(&t, text, 18, -1); +} + +static void ui2_draw_checkbox(const void *id, const char *text, int checked, const RECT *r, void *extra) +{ + ui2_draw_checkbox_common(id, text, checked?"X":"", r); +} + +static void ui2_draw_checkbox_number(const void *id, const char *text, int checked, const RECT *r, void *extra) +{ + char buf[16]; + sprintf(buf, "%d", checked); + ui2_draw_checkbox_common(id, text, buf, r); +} + +int ui2_do_edit_box(void *id, const RECT *rect, char *str, int str_size, bool hidden=false) +{ + int inside = ui_mouse_inside(rect->x,rect->y,rect->w,rect->h); + int r = 0; + static int at_index = 0; + + if(ui_last_active_item() == id) + { + int c = inp_last_char(); + int k = inp_last_key(); + 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_pretty_text_width(18.0f, str, i) + 10 > mx_rel) + { + at_index = i - 1; + break; + } + + if (i == len) + at_index = len; + } + } + + 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 (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; + } + + int box_type; + if (ui_active_item() == id || ui_hot_item() == id || ui_last_active_item() == id) + box_type = GUI_BOX_SCREEN_INFO; + else + box_type = GUI_BOX_SCREEN_TEXTBOX; + + 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; + ui2_draw_rect(&textbox, vec4(1,1,1,0.5f), CORNER_ALL, 5.0f); + ui2_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; + } + + ui2_do_label(&textbox, display_str, 18, -1); + + if (ui_last_active_item() == id && !just_got_active) + { + float w = gfx_pretty_text_width(18.0f, display_str, at_index); + textbox.x += w*ui2_scale(); + ui2_do_label(&textbox, "_", 18, -1); + } + + return r; +} + +float ui2_do_scrollbar_v(const void *id, const RECT *rect, float current) +{ + RECT handle; + static float offset_y; + ui2_hsplit_t(rect, 33, &handle, 0); + + handle.y += (rect->h-handle.h)*current; + + /* logic */ + float ret = current; + int inside = ui_mouse_inside(handle.x,handle.y,handle.w,handle.h); + + 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; + ui2_vmargin(rect, 5.0f, &rail); + ui2_draw_rect(&rail, vec4(1,1,1,0.25f), 0, 0.0f); + + RECT slider = handle; + slider.w = rail.x-slider.x; + ui2_draw_rect(&slider, vec4(1,1,1,0.25f), CORNER_L, 2.5f); + slider.x = rail.x+rail.w; + ui2_draw_rect(&slider, vec4(1,1,1,0.25f), CORNER_R, 2.5f); + + slider = handle; + ui2_margin(&slider, 5.0f, &slider); + ui2_draw_rect(&slider, vec4(1,1,1,0.25f), CORNER_ALL, 2.5f); + + return ret; +} + + + +float ui2_do_scrollbar_h(const void *id, const RECT *rect, float current) +{ + RECT handle; + static float offset_x; + ui2_vsplit_l(rect, 33, &handle, 0); + + handle.x += (rect->w-handle.w)*current; + + /* logic */ + float ret = current; + int inside = ui_mouse_inside(handle.x,handle.y,handle.w,handle.h); + + 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; + ui2_hmargin(rect, 5.0f, &rail); + ui2_draw_rect(&rail, vec4(1,1,1,0.25f), 0, 0.0f); + + RECT slider = handle; + slider.h = rail.y-slider.y; + ui2_draw_rect(&slider, vec4(1,1,1,0.25f), CORNER_T, 2.5f); + slider.y = rail.y+rail.h; + ui2_draw_rect(&slider, vec4(1,1,1,0.25f), CORNER_B, 2.5f); + + slider = handle; + ui2_margin(&slider, 5.0f, &slider); + ui2_draw_rect(&slider, vec4(1,1,1,0.25f), CORNER_ALL, 2.5f); + + return ret; +} + +int ui2_do_key_reader(void *id, const RECT *rect, int key) +{ + // process + static bool mouse_released = true; + int inside = ui_mouse_inside(rect->x, rect->y, rect->w, rect->h); + int new_key = key; + + if(!ui_mouse_button(0)) + mouse_released = true; + + if(ui_active_item() == id) + { + int k = inp_last_key(); + if (k) + { + new_key = k; + ui_set_active_item(0); + mouse_released = false; + } + } + 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) + ui2_draw_keyselect_button(id, "???", 0, rect, 0); + else + ui2_draw_keyselect_button(id, inp_key_name(key), 0, rect, 0); + return new_key; +} + + +static int menu2_render_menubar(RECT r) +{ + RECT box = r; + RECT button; + + int active_page = config.ui_page; + int new_page = -1; + if(menu_game_active) + active_page = -1; + + if(client_state() == CLIENTSTATE_OFFLINE) + { + if(0) // this is not done yet + { + ui2_vsplit_l(&box, 90.0f, &button, &box); + static int news_button=0; + if (ui2_do_button(&news_button, "News", active_page==PAGE_NEWS, &button, ui2_draw_menu_tab_button, 0)) + new_page = PAGE_NEWS; + ui2_vsplit_l(&box, 30.0f, 0, &box); + } + } + else + { + ui2_vsplit_l(&box, 90.0f, &button, &box); + static int game_button=0; + if (ui2_do_button(&game_button, "Game", menu_game_active, &button, ui2_draw_menu_tab_button, 0)) + menu_game_active = true; + + ui2_vsplit_l(&box, 30.0f, 0, &box); + } + + ui2_vsplit_l(&box, 110.0f, &button, &box); + static int internet_button=0; + if (ui2_do_button(&internet_button, "Internet", active_page==PAGE_INTERNET, &button, ui2_draw_menu_tab_button, 0)) + { + client_serverbrowse_refresh(0); + new_page = PAGE_INTERNET; + } + + ui2_vsplit_l(&box, 4.0f, 0, &box); + ui2_vsplit_l(&box, 90.0f, &button, &box); + static int lan_button=0; + if (ui2_do_button(&lan_button, "LAN", active_page==PAGE_LAN, &button, ui2_draw_menu_tab_button, 0)) + { + client_serverbrowse_refresh(1); + new_page = PAGE_LAN; + } + + if(0) // this one is not done yet + { + ui2_vsplit_l(&box, 4.0f, 0, &box); + ui2_vsplit_l(&box, 120.0f, &button, &box); + static int favorites_button=0; + if (ui2_do_button(&favorites_button, "Favorites", active_page==PAGE_FAVORITES, &button, ui2_draw_menu_tab_button, 0)) + new_page = PAGE_FAVORITES; + } + + /* + ui2_vsplit_r(&box, 110.0f, &box, &button); + static int system_button=0; + if (ui2_do_button(&system_button, "System", config.ui_page==PAGE_SYSTEM, &button, ui2_draw_menu_tab_button, 0)) + config.ui_page = PAGE_SYSTEM; + + ui2_vsplit_r(&box, 30.0f, &box, 0); + */ + + ui2_vsplit_r(&box, 110.0f, &box, &button); + static int quit_button=0; + if (ui2_do_button(&quit_button, "Quit", 0, &button, ui2_draw_menu_tab_button, 0)) + popup = POPUP_QUIT; + + ui2_vsplit_r(&box, 10.0f, &box, &button); + ui2_vsplit_r(&box, 110.0f, &box, &button); + static int settings_button=0; + if (ui2_do_button(&settings_button, "Settings", active_page==PAGE_SETTINGS, &button, ui2_draw_menu_tab_button, 0)) + new_page = PAGE_SETTINGS; + + if(new_page != -1) + { + config.ui_page = new_page; + menu_game_active = false; + } + + return 0; +} + +static void menu2_render_background() +{ + //gfx_clear(0.65f,0.78f,0.9f); + gfx_clear(gui_color.r, gui_color.g, gui_color.b); + //gfx_clear(0.78f,0.9f,0.65f); + + 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(); +} + +static void menu2_render_serverbrowser(RECT main_view) +{ + ui2_draw_rect(&main_view, color_tabbar_active, CORNER_ALL, 10.0f); + + RECT view; + ui2_margin(&main_view, 10.0f, &view); + + RECT headers; + RECT filters; + RECT status; + RECT toolbox; + + //ui2_hsplit_t(&view, 20.0f, &status, &view); + ui2_hsplit_t(&view, 20.0f, &headers, &view); + ui2_hsplit_b(&view, 90.0f, &view, &filters); + ui2_hsplit_b(&view, 5.0f, &view, 0); + ui2_hsplit_b(&view, 20.0f, &view, &status); + + ui2_vsplit_r(&filters, 300.0f, &filters, &toolbox); + ui2_vsplit_r(&filters, 150.0f, &filters, 0); + + // split of the scrollbar + ui2_draw_rect(&headers, vec4(1,1,1,0.25f), CORNER_T, 5.0f); + ui2_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, + }; + + 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_PROGRESS, BROWSESORT_PROGRESSION, "%", 1, 20.0f, FIXED, {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) + { + ui2_vsplit_l(&headers, cols[i].width, &cols[i].rect, &headers); + + if(i+1 < num_cols) + { + //cols[i].flags |= SPACER; + ui2_vsplit_l(&headers, 2, &cols[i].spacer, &headers); + } + } + } + + for(int i = num_cols-1; i >= 0; i--) + { + if(cols[i].direction == 1) + { + ui2_vsplit_r(&headers, cols[i].width, &headers, &cols[i].rect); + ui2_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(ui2_do_button(cols[i].caption, cols[i].caption, config.b_sort == cols[i].sort, &cols[i].rect, ui2_draw_grid_header, 0)) + { + if(cols[i].sort != -1) + config.b_sort = cols[i].sort; + } + } + + ui2_draw_rect(&view, vec4(0,0,0,0.15f), 0, 0); + + RECT scroll; + ui2_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; + ui2_hmargin(&scroll, 5.0f, &scroll); + scrollvalue = ui2_do_scrollbar_v(&scrollbar, &scroll, scrollvalue); + + int start = (int)((num_servers-num)*scrollvalue); + if(start < 0) + start = 0; + + //int r = -1; + int new_selected = selected_index; + + for (int i = start, k = 0; i < num_servers && k < num; i++, k++) + { + int item_index = i; + SERVER_INFO *item = client_serverbrowse_sorted_get(item_index); + RECT row; + + int l = selected_index==item_index; + + if(l) + { + // selected server, draw the players on it + RECT whole; + int h = (item->num_players+2)/3; + + ui2_hsplit_t(&view, 25.0f+h*15.0f, &whole, &view); + + RECT r = whole; + ui2_margin(&r, 1.5f, &r); + ui2_draw_rect(&r, vec4(1,1,1,0.5f), CORNER_ALL, 4.0f); + + ui2_hsplit_t(&whole, 20.0f, &row, &whole); + ui2_vsplit_l(&whole, 50.0f, 0, &whole); + + for(int p = 0; p < item->num_players; p+=3) + { + RECT player_row; + RECT player_rect; + RECT player_score; + RECT player_name; + ui2_hsplit_t(&whole, 15.0f, &player_row, &whole); + + for(int a = 0; a < 3; a++) + { + if(p+a >= item->num_players) + break; + + ui2_vsplit_l(&player_row, 170.0f, &player_rect, &player_row); + ui2_vsplit_l(&player_rect, 30.0f, &player_score, &player_name); + ui2_vsplit_l(&player_name, 10.0f, 0, &player_name); + char buf[32]; + sprintf(buf, "%d", item->player_scores[p+a]); + ui2_do_label(&player_score, buf, 16.0f, 1); + ui2_do_label(&player_name, item->player_names[p+a], 16.0f, -1); + } + } + + k += h*3/4; + } + else + ui2_hsplit_t(&view, 20.0f, &row, &view); + + if(ui2_do_button(item, "", l, &row, 0, 0)) + { + new_selected = item_index; + dbg_msg("dbg", "addr = %s", item->address); + strncpy(config.ui_server_address, item->address, sizeof(config.ui_server_address)); + if(inp_mouse_doubleclick()) + client_connect(config.ui_server_address); + } + + 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 = ui2_do_button(item, "L", l, &button, ui2_draw_browse_icon, 0); + + if(id == COL_FLAGS) + { + if(item->flags&1) + ui2_draw_browse_icon(0x100, &button); + } + else if(id == COL_NAME) + ui2_do_label(&button, item->name, 20.0f, -1); + else if(id == COL_MAP) + ui2_do_label(&button, item->map, 20.0f, -1); + else if(id == COL_PLAYERS) + { + sprintf(temp, "%i/%i", item->num_players, item->max_players); + ui2_do_label(&button, temp, 20.0f, 1); + } + else if(id == COL_PING) + { + sprintf(temp, "%i", item->latency); + ui2_do_label(&button, temp, 20.0f, 1); + } + else if(id == COL_PROGRESS) + { + ui2_draw_browse_icon(item->progression, &button); + } + else if(id == COL_GAMETYPE) + { + const char *type = "???"; + if(item->game_type == GAMETYPE_DM) type = "DM"; + else if(item->game_type == GAMETYPE_TDM) type = "TDM"; + else if(item->game_type == GAMETYPE_CTF) type = "CTF"; + ui2_do_label(&button, type, 20.0f, 0); + } + /* + if(s) + { + new_selected = item_index; + dbg_msg("dbg", "addr = %s", item->address); + strncpy(config.ui_server_address, item->address, sizeof(config.ui_server_address)); + }*/ + } + } + + selected_index = new_selected; + + + // render quick search + RECT button; + ui2_hsplit_t(&filters, 20.0f, &button, &filters); + ui2_do_label(&button, "Quick search: ", 18, -1); + ui2_vsplit_l(&button, 95.0f, 0, &button); + ui2_do_edit_box(&config.b_filter_string, &button, config.b_filter_string, sizeof(config.b_filter_string)); + + // render filters + ui2_hsplit_t(&filters, 20.0f, &button, &filters); + if (ui2_do_button(&config.b_filter_empty, "Has people playing", config.b_filter_empty, &button, ui2_draw_checkbox, 0)) + config.b_filter_empty ^= 1; + + ui2_hsplit_t(&filters, 20.0f, &button, &filters); + if (ui2_do_button(&config.b_filter_full, "Server not full", config.b_filter_full, &button, ui2_draw_checkbox, 0)) + config.b_filter_full ^= 1; + + ui2_hsplit_t(&filters, 20.0f, &button, &filters); + if (ui2_do_button(&config.b_filter_pw, "Is not password protected", config.b_filter_pw, &button, ui2_draw_checkbox, 0)) + config.b_filter_pw ^= 1; + + + // render status + ui2_draw_rect(&status, vec4(1,1,1,0.25f), CORNER_B, 5.0f); + ui2_vmargin(&status, 50.0f, &status); + char buf[128]; + sprintf(buf, "%d of %d servers", client_serverbrowse_sorted_num(), client_serverbrowse_num()); + ui2_do_label(&status, buf, 18.0f, -1); + + // render toolbox + { + RECT buttons, button; + ui2_hsplit_b(&toolbox, 25.0f, &toolbox, &buttons); + + ui2_vsplit_r(&buttons, 100.0f, &buttons, &button); + ui2_vmargin(&button, 2.0f, &button); + static int join_button = 0; + if(ui2_do_button(&join_button, "Connect", 0, &button, ui2_draw_menu_button, 0)) + client_connect(config.ui_server_address); + + ui2_vsplit_r(&buttons, 20.0f, &buttons, &button); + ui2_vsplit_r(&buttons, 100.0f, &buttons, &button); + ui2_vmargin(&button, 2.0f, &button); + static int refresh_button = 0; + if(ui2_do_button(&refresh_button, "Refresh", 0, &button, ui2_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); + } + + ui2_hsplit_t(&toolbox, 20.0f, &button, &toolbox); + ui2_do_label(&button, "Host address:", 18, -1); + ui2_vsplit_l(&button, 100.0f, 0, &button); + ui2_do_edit_box(&config.ui_server_address, &button, config.ui_server_address, sizeof(config.ui_server_address)); + } +} + +static void menu2_render_settings_player(RECT main_view) +{ + RECT button; + RECT skinselection; + ui2_vsplit_l(&main_view, 300.0f, &main_view, &skinselection); + + + ui2_hsplit_t(&main_view, 20.0f, &button, &main_view); + + // render settings + { + ui2_hsplit_t(&main_view, 20.0f, &button, &main_view); + ui2_do_label(&button, "Name:", 18.0, -1); + ui2_vsplit_l(&button, 80.0f, 0, &button); + ui2_vsplit_l(&button, 180.0f, &button, 0); + ui2_do_edit_box(config.player_name, &button, config.player_name, sizeof(config.player_name)); + + ui2_hsplit_t(&main_view, 20.0f, &button, &main_view); + if (ui2_do_button(&config.cl_dynamic_camera, "Dynamic camera", config.cl_dynamic_camera, &button, ui2_draw_checkbox, 0)) + config.cl_dynamic_camera ^= 1; + + ui2_hsplit_t(&main_view, 20.0f, &button, &main_view); + if (ui2_do_button(&config.cl_autoswitch_weapons, "Switch weapon on pickup", config.cl_autoswitch_weapons, &button, ui2_draw_checkbox, 0)) + config.cl_autoswitch_weapons ^= 1; + + ui2_hsplit_t(&main_view, 20.0f, &button, &main_view); + if (ui2_do_button(&config.cl_nameplates, "Show name plates", config.cl_nameplates, &button, ui2_draw_checkbox, 0)) + config.cl_nameplates ^= 1; + + if(config.cl_nameplates) + { + ui2_hsplit_t(&main_view, 20.0f, &button, &main_view); + ui2_vsplit_l(&button, 15.0f, 0, &button); + if (ui2_do_button(&config.cl_nameplates_always, "Always show name plates", config.cl_nameplates_always, &button, ui2_draw_checkbox, 0)) + config.cl_nameplates_always ^= 1; + } + + ui2_hsplit_t(&main_view, 20.0f, &button, &main_view); + + ui2_hsplit_t(&main_view, 20.0f, &button, &main_view); + if (ui2_do_button(&config.player_color_body, "Custom colors", config.player_use_custom_color, &button, ui2_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; + ui2_hsplit_t(&main_view, 20.0f, &text, &main_view); + ui2_vsplit_l(&text, 15.0f, 0, &text); + ui2_do_label(&text, parts[i], 18, -1); + + int prevcolor = *colors[i]; + int color = 0; + for(int s = 0; s < 3; s++) + { + RECT text; + ui2_hsplit_t(&main_view, 19.0f, &button, &main_view); + ui2_vsplit_l(&button, 30.0f, 0, &button); + ui2_vsplit_l(&button, 30.0f, &text, &button); + ui2_vsplit_r(&button, 5.0f, &button, 0); + ui2_hsplit_t(&button, 4.0f, 0, &button); + + float k = ((prevcolor>>((2-s)*8))&0xff) / 255.0f; + k = ui2_do_scrollbar_h(&color_slider[i][s], &button, k); + color <<= 8; + color += clamp((int)(k*255), 0, 255); + ui2_do_label(&text, labels[s], 20, -1); + + } + + *colors[i] = color; + ui2_hsplit_t(&main_view, 5.0f, 0, &main_view); + } + } + } + + // draw header + RECT header, footer; + ui2_hsplit_t(&skinselection, 20, &header, &skinselection); + ui2_draw_rect(&header, vec4(1,1,1,0.25f), CORNER_T, 5.0f); + ui2_do_label(&header, "Skins", 18.0f, 0); + + // draw footers + ui2_hsplit_b(&skinselection, 20, &skinselection, &footer); + ui2_draw_rect(&footer, vec4(1,1,1,0.25f), CORNER_B, 5.0f); + ui2_vsplit_l(&footer, 10.0f, 0, &footer); + + // modes + ui2_draw_rect(&skinselection, vec4(0,0,0,0.15f), 0, 0); + + RECT scroll; + ui2_vsplit_r(&skinselection, 15, &skinselection, &scroll); + + RECT list = skinselection; + ui2_hsplit_t(&list, 50, &button, &list); + + int num = (int)(skinselection.h/button.h); + static float scrollvalue = 0; + static int scrollbar = 0; + ui2_hmargin(&scroll, 5.0f, &scroll); + scrollvalue = ui2_do_scrollbar_v(&scrollbar, &scroll, scrollvalue); + + int start = (int)((skin_num()-num)*scrollvalue); + if(start < 0) + start = 0; + + animstate state; + anim_eval(&data->animations[ANIM_BASE], 0, &state); + anim_eval_add(&state, &data->animations[ANIM_IDLE], 0, 1.0f); + //anim_eval_add(&state, &data->animations[ANIM_WALK], fmod(client_localtime(), 1.0f), 1.0f); + + for(int i = start; i < start+num && i < skin_num(); i++) + { + const skin *s = skin_get(i); + char buf[128]; + sprintf(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 = skin_get_color(config.player_color_body); + info.color_feet = skin_get_color(config.player_color_feet); + info.texture = s->color_texture; + } + + info.size = ui2_scale()*50.0f; + + RECT icon; + RECT text; + ui2_vsplit_l(&button, 50.0f, &icon, &text); + + if(ui2_do_button(s, "", selected, &button, ui2_draw_list_row, 0)) + config_set_player_skin(&config, s->name); + + ui2_hsplit_t(&text, 12.0f, 0, &text); // some margin from the top + ui2_do_label(&text, buf, 24, 0); + + ui2_hsplit_t(&icon, 5.0f, 0, &icon); // some margin from the top + render_tee(&state, &info, 0, vec2(1, 0), vec2(icon.x+icon.w/2, icon.y+icon.h/2)); + + ui2_hsplit_t(&list, 50, &button, &list); + } +} + +typedef void (*assign_func_callback)(CONFIGURATION *config, int value); + +static void menu2_render_settings_controls(RECT main_view) +{ + ui2_vsplit_l(&main_view, 300.0f, &main_view, 0); + + { + RECT button, label; + ui2_hsplit_t(&main_view, 20.0f, &button, &main_view); + ui2_vsplit_l(&button, 110.0f, &label, &button); + ui2_do_label(&label, "Mouse sens.", 18.0f, -1); + ui2_hmargin(&button, 2.0f, &button); + config.inp_mousesens = (int)(ui2_do_scrollbar_h(&config.inp_mousesens, &button, config.inp_mousesens/500.0f)*500.0f); + //*key.key = ui2_do_key_reader(key.key, &button, *key.key); + ui2_hsplit_t(&main_view, 20.0f, 0, &main_view); + } + + typedef struct + { + char name[32]; + int *key; + } KEYINFO; + + const KEYINFO keys[] = + { + { "Move Left:", &config.key_move_left }, + { "Move Right:", &config.key_move_right }, + { "Jump:", &config.key_jump }, + { "Fire:", &config.key_fire }, + { "Hook:", &config.key_hook }, + { "Hammer:", &config.key_weapon1 }, + { "Pistol:", &config.key_weapon2 }, + { "Shotgun:", &config.key_weapon3 }, + { "Grenade:", &config.key_weapon4 }, + { "Next Weapon:", &config.key_next_weapon }, + { "Prev. Weapon:", &config.key_prev_weapon }, + { "Emoticon:", &config.key_emoticon }, + { "Chat:", &config.key_chat }, + { "Team Chat:", &config.key_teamchat }, + { "Console:", &config.key_console }, + { "Remote Console:", &config.key_remoteconsole }, + { "Screenshot:", &config.key_screenshot }, + }; + + const int key_count = sizeof(keys) / sizeof(KEYINFO); + + for (int i = 0; i < key_count; i++) + { + KEYINFO key = keys[i]; + RECT button, label; + ui2_hsplit_t(&main_view, 20.0f, &button, &main_view); + ui2_vsplit_l(&button, 110.0f, &label, &button); + + ui2_do_label(&label, key.name, 18.0f, -1); + *key.key = ui2_do_key_reader(key.key, &button, *key.key); + ui2_hsplit_t(&main_view, 5.0f, 0, &main_view); + } +} + +static void menu2_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; + ui2_vsplit_l(&main_view, 300.0f, &main_view, &modelist); + + // draw allmodes switch + RECT header, footer; + ui2_hsplit_t(&modelist, 20, &button, &modelist); + if (ui2_do_button(&config.gfx_display_all_modes, "Show only supported", config.gfx_display_all_modes^1, &button, ui2_draw_checkbox, 0)) + { + config.gfx_display_all_modes ^= 1; + num_modes = gfx_get_video_modes(modes, MAX_RESOLUTIONS); + } + + // draw header + ui2_hsplit_t(&modelist, 20, &header, &modelist); + ui2_draw_rect(&header, vec4(1,1,1,0.25f), CORNER_T, 5.0f); + ui2_do_label(&header, "Display Modes", 18.0f, 0); + + // draw footers + ui2_hsplit_b(&modelist, 20, &modelist, &footer); + sprintf(buf, "Current: %dx%d %d bit", config.gfx_screen_width, config.gfx_screen_height, config.gfx_color_depth); + ui2_draw_rect(&footer, vec4(1,1,1,0.25f), CORNER_B, 5.0f); + ui2_vsplit_l(&footer, 10.0f, 0, &footer); + ui2_do_label(&footer, buf, 18.0f, -1); + + // modes + ui2_draw_rect(&modelist, vec4(0,0,0,0.15f), 0, 0); + + RECT scroll; + ui2_vsplit_r(&modelist, 15, &modelist, &scroll); + + RECT list = modelist; + ui2_hsplit_t(&list, 20, &button, &list); + + int num = (int)(modelist.h/button.h); + static float scrollvalue = 0; + static int scrollbar = 0; + ui2_hmargin(&scroll, 5.0f, &scroll); + scrollvalue = ui2_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; + } + + sprintf(buf, " %dx%d %d bit", modes[i].width, modes[i].height, depth); + if(ui2_do_button(&modes[i], buf, selected, &button, ui2_draw_list_row, 0)) + { + config.gfx_color_depth = depth; + config.gfx_screen_width = modes[i].width; + config.gfx_screen_height = modes[i].height; + } + + ui2_hsplit_t(&list, 20, &button, &list); + } + + + // switches + ui2_hsplit_t(&main_view, 20.0f, &button, &main_view); + if (ui2_do_button(&config.gfx_fullscreen, "Fullscreen", config.gfx_fullscreen, &button, ui2_draw_checkbox, 0)) + config.gfx_fullscreen ^= 1; + + ui2_hsplit_t(&main_view, 20.0f, &button, &main_view); + if (ui2_do_button(&config.gfx_vsync, "V-Sync", config.gfx_vsync, &button, ui2_draw_checkbox, 0)) + config.gfx_vsync ^= 1; + + ui2_hsplit_t(&main_view, 20.0f, &button, &main_view); + if (ui2_do_button(&config.gfx_fsaa_samples, "FSAA samples", config.gfx_fsaa_samples, &button, ui2_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; + } + + ui2_hsplit_t(&main_view, 40.0f, &button, &main_view); + ui2_hsplit_t(&main_view, 20.0f, &button, &main_view); + if (ui2_do_button(&config.gfx_texture_quality, "Quality Textures", config.gfx_texture_quality, &button, ui2_draw_checkbox, 0)) + config.gfx_texture_quality ^= 1; + + ui2_hsplit_t(&main_view, 20.0f, &button, &main_view); + if (ui2_do_button(&config.gfx_texture_compression, "Texture Compression", config.gfx_texture_compression, &button, ui2_draw_checkbox, 0)) + config.gfx_texture_compression ^= 1; + + ui2_hsplit_t(&main_view, 20.0f, &button, &main_view); + if (ui2_do_button(&config.gfx_high_detail, "High Detail", config.gfx_high_detail, &button, ui2_draw_checkbox, 0)) + config.gfx_high_detail ^= 1; + + // + + RECT text; + ui2_hsplit_t(&main_view, 20.0f, 0, &main_view); + ui2_hsplit_t(&main_view, 20.0f, &text, &main_view); + //ui2_vsplit_l(&text, 15.0f, 0, &text); + ui2_do_label(&text, "UI Color", 18, -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; + ui2_hsplit_t(&main_view, 19.0f, &button, &main_view); + ui2_vmargin(&button, 15.0f, &button); + ui2_vsplit_l(&button, 30.0f, &text, &button); + ui2_vsplit_r(&button, 5.0f, &button, 0); + ui2_hsplit_t(&button, 4.0f, 0, &button); + + float k = (*color_slider[s]) / 255.0f; + k = ui2_do_scrollbar_h(color_slider[s], &button, k); + *color_slider[s] = (int)(k*255.0f); + ui2_do_label(&text, labels[s], 20, -1); + } +} + +static void menu2_render_settings_sound(RECT main_view) +{ + RECT button; + ui2_vsplit_l(&main_view, 300.0f, &main_view, 0); + + ui2_hsplit_t(&main_view, 20.0f, &button, &main_view); + if (ui2_do_button(&config.snd_enable, "Use Sounds", config.snd_enable, &button, ui2_draw_checkbox, 0)) + config.snd_enable ^= 1; + + if(!config.snd_enable) + return; + + // sample rate box + { + char buf[64]; + sprintf(buf, "%d", config.snd_rate); + ui2_hsplit_t(&main_view, 20.0f, &button, &main_view); + ui2_do_label(&button, "Sample Rate", 18.0, -1); + ui2_vsplit_l(&button, 110.0f, 0, &button); + ui2_vsplit_l(&button, 180.0f, &button, 0); + ui2_do_edit_box(&config.snd_rate, &button, buf, sizeof(buf)); + config.snd_rate = atoi(buf); + + if(config.snd_rate < 1) + config.snd_rate = 1; + } + + // volume slider + { + RECT button, label; + ui2_hsplit_t(&main_view, 5.0f, &button, &main_view); + ui2_hsplit_t(&main_view, 20.0f, &button, &main_view); + ui2_vsplit_l(&button, 110.0f, &label, &button); + ui2_hmargin(&button, 2.0f, &button); + ui2_do_label(&label, "Sound Volume", 18.0f, -1); + config.snd_volume = (int)(ui2_do_scrollbar_h(&config.snd_volume, &button, config.snd_volume/100.0f)*100.0f); + ui2_hsplit_t(&main_view, 20.0f, 0, &main_view); + } +} + + +static void menu2_render_settings_network(RECT main_view) +{ + RECT button; + ui2_vsplit_l(&main_view, 300.0f, &main_view, 0); + + { + ui2_hsplit_t(&main_view, 20.0f, &button, &main_view); + ui2_do_label(&button, "Rcon Password", 18.0, -1); + ui2_vsplit_l(&button, 110.0f, 0, &button); + ui2_vsplit_l(&button, 180.0f, &button, 0); + ui2_do_edit_box(&config.rcon_password, &button, config.rcon_password, sizeof(config.rcon_password), true); + } +} + +static void menu2_render_settings(RECT main_view) +{ + static int settings_page = 0; + + // render background + RECT temp, tabbar; + ui2_vsplit_r(&main_view, 120.0f, &main_view, &tabbar); + ui2_draw_rect(&main_view, color_tabbar_active, CORNER_B|CORNER_TL, 10.0f); + ui2_hsplit_t(&tabbar, 50.0f, &temp, &tabbar); + ui2_draw_rect(&temp, color_tabbar_active, CORNER_R, 10.0f); + + ui2_hsplit_t(&main_view, 10.0f, 0, &main_view); + + RECT button; + + const char *tabs[] = {"Player", "Controls", "Network", "Graphics", "Sound"}; + int num_tabs = (int)(sizeof(tabs)/sizeof(*tabs)); + + for(int i = 0; i < num_tabs; i++) + { + ui2_hsplit_t(&tabbar, 10, &button, &tabbar); + ui2_hsplit_t(&tabbar, 26, &button, &tabbar); + if(ui2_do_button(tabs[i], tabs[i], settings_page == i, &button, ui2_draw_settings_tab_button, 0)) + settings_page = i; + } + + ui2_margin(&main_view, 10.0f, &main_view); + + if(settings_page == 0) + menu2_render_settings_player(main_view); + else if(settings_page == 1) + menu2_render_settings_controls(main_view); + else if(settings_page == 2) + menu2_render_settings_network(main_view); + else if(settings_page == 3) + menu2_render_settings_graphics(main_view); + else if(settings_page == 4) + menu2_render_settings_sound(main_view); +} + +static void menu2_render_news(RECT main_view) +{ + ui2_draw_rect(&main_view, color_tabbar_active, CORNER_ALL, 10.0f); +} + + +static void menu2_render_game(RECT main_view) +{ + RECT button; + ui2_hsplit_t(&main_view, 45.0f, &main_view, 0); + ui2_draw_rect(&main_view, color_tabbar_active, CORNER_ALL, 10.0f); + + ui2_hsplit_t(&main_view, 10.0f, 0, &main_view); + ui2_hsplit_t(&main_view, 25.0f, &main_view, 0); + ui2_vmargin(&main_view, 10.0f, &main_view); + + ui2_vsplit_r(&main_view, 120.0f, &main_view, &button); + static int disconnect_button = 0; + if(ui2_do_button(&disconnect_button, "Disconnect", 0, &button, ui2_draw_menu_button, 0)) + client_disconnect(); + + if(local_info) + { + if(local_info->team != -1) + { + ui2_vsplit_l(&main_view, 10.0f, &button, &main_view); + ui2_vsplit_l(&main_view, 120.0f, &button, &main_view); + static int spectate_button = 0; + if(ui2_do_button(&spectate_button, "Spectate", 0, &button, ui2_draw_menu_button, 0)) + { + config.cl_team = -1; + menu_active = false; + } + } + + if(local_info->team != 0) + { + ui2_vsplit_l(&main_view, 10.0f, &button, &main_view); + ui2_vsplit_l(&main_view, 120.0f, &button, &main_view); + static int spectate_button = 0; + if(ui2_do_button(&spectate_button, "Join Red", 0, &button, ui2_draw_menu_button, 0)) + { + config.cl_team = 0; + menu_active = false; + } + } + + if(local_info->team != 1) + { + ui2_vsplit_l(&main_view, 10.0f, &button, &main_view); + ui2_vsplit_l(&main_view, 120.0f, &button, &main_view); + static int spectate_button = 0; + if(ui2_do_button(&spectate_button, "Join Blue", 0, &button, ui2_draw_menu_button, 0)) + { + config.cl_team = 1; + menu_active = false; + } + } + } +} + +void menu_do_disconnected() +{ + 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; + } +} + +void menu_do_connecting() +{ + popup = POPUP_CONNECTING; +} + +void menu_do_connected() +{ + popup = POPUP_NONE; +} + +int menu2_render() +{ + if(0) + { + gfx_mapscreen(0,0,10*4/3.0f,10); + gfx_clear(gui_color.r, gui_color.g, gui_color.b); + + animstate state; + anim_eval(&data->animations[ANIM_BASE], 0, &state); + anim_eval_add(&state, &data->animations[ANIM_IDLE], 0, 1.0f); + //anim_eval_add(&state, &data->animations[ANIM_WALK], fmod(client_localtime(), 1.0f), 1.0f); + + for(int i = 0; i < skin_num(); i++) + { + float x = (i/8)*3; + float y = (i%8); + for(int c = 0; c < 2; c++) + { + //int colors[2] = {54090, 10998628}; + //int colors[2] = {65432, 9895832}; // NEW + int colors[2] = {65387, 10223467}; // NEW + + tee_render_info info; + info.texture = skin_get(i)->color_texture; + info.color_feet = info.color_body = skin_get_color(colors[c]); + //info.color_feet = info.color_body = vec4(1,1,1,1); + info.size = 1.0f; //ui2_scale()*16.0f; + //render_tee(&state, &info, 0, vec2(sinf(client_localtime()*3), cosf(client_localtime()*3)), vec2(1+x+c,1+y)); + render_tee(&state, &info, 0, vec2(1,0), vec2(1+x+c,1+y)); + } + } + + return 0; + } + + RECT screen = *ui2_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 + { + menu2_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 + ui2_margin(&screen, 10.0f, &screen); + + if(popup == POPUP_NONE) + { + // do tab bar + ui2_hsplit_t(&screen, 26.0f, &tab_bar, &main_view); + ui2_vmargin(&tab_bar, 20.0f, &tab_bar); + menu2_render_menubar(tab_bar); + + // render current page + if(menu_game_active) + menu2_render_game(main_view); + else if(config.ui_page == PAGE_NEWS) + menu2_render_news(main_view); + else if(config.ui_page == PAGE_INTERNET) + menu2_render_serverbrowser(main_view); + else if(config.ui_page == PAGE_LAN) + menu2_render_serverbrowser(main_view); + else if(config.ui_page == PAGE_FAVORITES) + menu2_render_serverbrowser(main_view); + else if(config.ui_page == PAGE_SETTINGS) + menu2_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); + const char *title = ""; + const char *extra_text = ""; + const char *button_text = ""; + + if(popup == POPUP_CONNECTING) + { + title = "Connecting to"; + extra_text = config.ui_server_address; // TODO: query the client about the address + button_text = "Abort"; + } + else if(popup == POPUP_DISCONNECTED) + { + title = "Disconnected"; + extra_text = client_error_string(); + button_text = "Ok"; + } + 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?"; + } + + + RECT box, part; + box = screen; + ui2_vmargin(&box, 150.0f, &box); + ui2_hmargin(&box, 150.0f, &box); + + // render the box + ui2_draw_rect(&box, vec4(0,0,0,0.5f), CORNER_ALL, 15.0f); + + ui2_hsplit_t(&box, 20.f, &part, &box); + ui2_hsplit_t(&box, 24.f, &part, &box); + ui2_do_label(&part, title, 24.f, 0); + ui2_hsplit_t(&box, 20.f, &part, &box); + ui2_hsplit_t(&box, 24.f, &part, &box); + ui2_do_label(&part, extra_text, 20.f, 0); + + if(popup == POPUP_QUIT) + { + RECT tryagain, abort; + ui2_hsplit_b(&box, 20.f, &box, &part); + ui2_hsplit_b(&box, 24.f, &box, &part); + ui2_vmargin(&part, 120.0f, &part); + + ui2_vsplit_l(&part, 100.0f, &abort, &part); + ui2_vsplit_r(&part, 100.0f, 0, &tryagain); + + static int button_abort = 0; + if(ui2_do_button(&button_abort, "No", 0, &abort, ui2_draw_menu_button, 0) || inp_key_down(KEY_ESC)) + popup = POPUP_NONE; + + static int button_tryagain = 0; + if(ui2_do_button(&button_tryagain, "Yes", 0, &tryagain, ui2_draw_menu_button, 0) || inp_key_down(KEY_ENTER)) + client_quit(); + } + else if(popup == POPUP_PASSWORD) + { + RECT label, textbox, tryagain, abort; + + ui2_hsplit_b(&box, 20.f, &box, &part); + ui2_hsplit_b(&box, 24.f, &box, &part); + ui2_vmargin(&part, 120.0f, &part); + + ui2_vsplit_l(&part, 100.0f, &abort, &part); + ui2_vsplit_r(&part, 100.0f, 0, &tryagain); + + static int button_abort = 0; + if(ui2_do_button(&button_abort, "Abort", 0, &abort, ui2_draw_menu_button, 0) || inp_key_down(KEY_ESC)) + popup = POPUP_NONE; + + static int button_tryagain = 0; + if(ui2_do_button(&button_tryagain, "Try again", 0, &tryagain, ui2_draw_menu_button, 0) || inp_key_down(KEY_ENTER)) + { + client_connect(config.ui_server_address); + } + + ui2_hsplit_b(&box, 60.f, &box, &part); + ui2_hsplit_b(&box, 24.f, &box, &part); + + ui2_vsplit_l(&part, 60.0f, 0, &label); + ui2_vsplit_l(&label, 100.0f, 0, &textbox); + ui2_vsplit_l(&textbox, 20.0f, 0, &textbox); + ui2_vsplit_r(&textbox, 60.0f, &textbox, 0); + ui2_do_label(&label, "Password:", 20, -1); + ui2_do_edit_box(&config.password, &textbox, config.password, sizeof(config.password), true); + } + else + { + ui2_hsplit_b(&box, 20.f, &box, &part); + ui2_hsplit_b(&box, 24.f, &box, &part); + ui2_vmargin(&part, 120.0f, &part); + + static int button = 0; + if(ui2_do_button(&button, button_text, 0, &part, ui2_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 modmenu_render() +{ + static int mouse_x = 0; + static int mouse_y = 0; + + // 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); + + color_ingame_scale_i = 0.5f; + 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); + + + // handle mouse movement + float mx, my; + { + int rx, ry; + inp_mouse_relative(&rx, &ry); + mouse_x += rx; + mouse_y += ry; + if(mouse_x < 0) mouse_x = 0; + if(mouse_y < 0) mouse_y = 0; + if(mouse_x > gfx_screenwidth()) mouse_x = gfx_screenwidth(); + if(mouse_y > gfx_screenheight()) mouse_y = gfx_screenheight(); + + // update the ui + RECT *screen = ui2_screen(); + mx = (mouse_x/(float)gfx_screenwidth())*screen->w; + my = (mouse_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); + } + + menu2_render(); + + 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(); + + inp_clear(); +} diff --git a/src/game/client/gc_menu.h b/src/game/client/gc_menu.h new file mode 100644 index 00000000..5c68c53d --- /dev/null +++ b/src/game/client/gc_menu.h @@ -0,0 +1,16 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +#ifndef __MENU_H +#define __MENU_H + +void draw_image_button(void *id, const char *text, int checked, float x, float y, float w, float h, void *extra); +void draw_single_part_button(void *id, const char *text, int checked, float x, float y, float w, float h, void *extra); +void draw_menu_button(void *id, const char *text, int checked, float x, float y, float w, float h, void *extra); +void draw_teewars_button(void *id, const char *text, int checked, float x, float y, float w, float h, void *extra); +int ui_do_key_reader(void *id, float x, float y, float w, float h, int key); +int ui_do_combo_box(void *id, float x, float y, float w, char *lines, int line_count, int selected_index); +int ui_do_edit_box(void *id, float x, float y, float w, float h, char *str, int str_size); +int ui_do_check_box(void *id, float x, float y, float w, float h, int value); +int do_scroll_bar_horiz(void *id, float x, float y, float width, int steps, int last_index); +int do_scroll_bar_vert(void *id, float x, float y, float height, int steps, int last_index); + +#endif diff --git a/src/game/client/gc_render.h b/src/game/client/gc_render.h new file mode 100644 index 00000000..fc85d49a --- /dev/null +++ b/src/game/client/gc_render.h @@ -0,0 +1,24 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +struct animstate +{ + keyframe body; + keyframe back_foot; + keyframe front_foot; + keyframe attach; +}; + +void anim_seq_eval(sequence *seq, float time, keyframe *frame); +void anim_eval(animation *anim, float time, animstate *state); +void anim_add_keyframe(keyframe *seq, keyframe *added, float amount); +void anim_add(animstate *state, animstate *added, float amount); +void anim_eval_add(animstate *state, animation *anim, float time, float amount); + +struct tee_render_info +{ + int texture; + vec4 color_body; + vec4 color_feet; + float size; +}; + +void render_tee(animstate *anim, tee_render_info *info, int emote, vec2 dir, vec2 pos); diff --git a/src/game/client/gc_skin.cpp b/src/game/client/gc_skin.cpp new file mode 100644 index 00000000..cc5699e4 --- /dev/null +++ b/src/game/client/gc_skin.cpp @@ -0,0 +1,167 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +#include +#include +#include +#include +#include "gc_skin.h" +#include "../g_math.h" + +enum +{ + MAX_SKINS=256, +}; + +static skin skins[MAX_SKINS] = {{0}}; +static int num_skins = 0; + +static void skinscan(const char *name, int is_dir, void *user) +{ + int l = strlen(name); + if(l < 4 || is_dir || num_skins == MAX_SKINS) + return; + if(strcmp(name+l-4, ".png") != 0) + return; + + char buf[512]; + sprintf(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; + } + + skins[num_skins].org_texture = gfx_load_texture_raw(info.width, info.height, info.format, info.data); + + // create colorless version + unsigned char *d = (unsigned char *)info.data; + int step = info.format == IMG_RGBA ? 4 : 3; + + 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 bs = 96; // body size + int pitch = info.width*4; + int freq[256] = {0}; + + for(int y = 0; y < bs; y++) + for(int x = 0; x < bs; x++) + { + if(d[y*pitch+x*4+3] > 128) + freq[d[y*pitch+x*4]]++; + } + + int org_weight = 0; + int new_weight = 192; + for(int i = 1; i < 256; i++) + { + if(freq[org_weight] < freq[i]) + org_weight = i; + } + + int inv_org_weight = 255-org_weight; + int inv_new_weight = 255-new_weight; + for(int y = 0; y < bs; y++) + for(int x = 0; x < bs; 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; + } + } + + skins[num_skins].color_texture = gfx_load_texture_raw(info.width, info.height, info.format, info.data); + mem_free(info.data); + + // set skin data + strncpy(skins[num_skins].name, name, min((int)sizeof(skins[num_skins].name),l-4)); + dbg_msg("game", "load skin %s", skins[num_skins].name); + num_skins++; +} + + +void skin_init() +{ + // load skins + fs_listdir("data/skins", skinscan, 0); +} + +int skin_num() +{ + return num_skins; +} + +const skin *skin_get(int index) +{ + return &skins[index%num_skins]; +} + +int skin_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; +} + +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 skin_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/gc_skin.h b/src/game/client/gc_skin.h new file mode 100644 index 00000000..3742d04e --- /dev/null +++ b/src/game/client/gc_skin.h @@ -0,0 +1,20 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +#include "../g_vmath.h" + +// do this better and nicer +typedef struct +{ + int org_texture; + int color_texture; + char name[31]; + char term[1]; +} skin; + +vec4 skin_get_color(int v); +void skin_init(); +int skin_num(); +const skin *skin_get(int index); +int skin_find(const char *name); + + +vec3 hsl_to_rgb(vec3 in); diff --git a/src/game/client/mapres_image.cpp b/src/game/client/mapres_image.cpp deleted file mode 100644 index 1ef1617c..00000000 --- a/src/game/client/mapres_image.cpp +++ /dev/null @@ -1,125 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#include -#include -#include "mapres_image.h" -#include "../mapres.h" - -static int map_textures[64] = {0}; -static int count = 0; -/* -static void calc_mipmaps(void *data_in, unsigned width, unsigned height, void *data_out) -{ - unsigned char *src = (unsigned char*)data_in; - unsigned char *dst = (unsigned char*)data_out; - unsigned mip_w = width; - unsigned mip_h = height; - unsigned prev_w; - unsigned prev_h; - - // Highest level - no mod - for(unsigned x = 0; x < mip_w; x++) - { - for(unsigned y = 0; y < mip_h; y++) - { - unsigned i = (y * mip_w + x)<<2; - for(unsigned j = 0; j < 4; j++) - dst[i+j] = src[i+j]; - } - } - - src = dst; - dst += mip_w * mip_h * 4; - prev_w = mip_w; - prev_h = mip_h; - mip_w = mip_w>>1; - mip_h = mip_h>>1; - - while(mip_w > 0 && mip_h > 0) - { - for(unsigned x = 0; x < mip_w; x++) - { - for(unsigned y = 0; y < mip_h; y++) - { - unsigned i = (y * mip_w + x)<<2; - - unsigned r = 0; - unsigned g = 0; - unsigned b = 0; - unsigned a = 0; - - - r += src[(((y<<1) * prev_w + (x<<1))<<2)]; - g += src[(((y<<1) * prev_w + (x<<1))<<2)+1]; - b += src[(((y<<1) * prev_w + (x<<1))<<2)+2]; - a += src[(((y<<1) * prev_w + (x<<1))<<2)+3]; - - r += src[(((y<<1) * prev_w + ((x+1)<<1))<<2)]; - g += src[(((y<<1) * prev_w + ((x+1)<<1))<<2)+1]; - b += src[(((y<<1) * prev_w + ((x+1)<<1))<<2)+2]; - a += src[(((y<<1) * prev_w + ((x+1)<<1))<<2)+3]; - - r += src[((((y+1)<<1) * prev_w + (x<<1))<<2)]; - g += src[((((y+1)<<1) * prev_w + (x<<1))<<2)+1]; - b += src[((((y+1)<<1) * prev_w + (x<<1))<<2)+2]; - a += src[((((y+1)<<1) * prev_w + (x<<1))<<2)+3]; - - r += src[((((y+1)<<1) * prev_w + ((x+1)<<1))<<2)]; - g += src[((((y+1)<<1) * prev_w + ((x+1)<<1))<<2)+1]; - b += src[((((y+1)<<1) * prev_w + ((x+1)<<1))<<2)+2]; - a += src[((((y+1)<<1) * prev_w + ((x+1)<<1))<<2)+3]; - - dst[i] = r>>2; - dst[i+1] = g>>2; - dst[i+2] = b>>2; - dst[i+3] = a>>2; - } - } - - src = dst; - dst = dst + mip_w*mip_h*4; - prev_w = mip_w; - prev_h = mip_h; - mip_w = mip_w>>1; - mip_h = mip_h>>1; - } -} -*/ -extern int DEBUGTEST_MAPIMAGE; - -int img_init() -{ - int start, count; - map_get_type(MAPRES_IMAGE, &start, &count); - dbg_msg("mapres_image", "start=%d count=%d", start, count); - for(int i = 0; i < 64; i++) - { - if(map_textures[i]) - { - gfx_unload_texture(map_textures[i]); - map_textures[i] = 0; - } - } - - //void *data_res = (void*)mem_alloc(1024*1024*4*2, 16); - for(int i = 0; i < count; i++) - { - mapres_image *img = (mapres_image *)map_get_item(start+i, 0, 0); - void *data = map_get_data(img->image_data); - //calc_mipmaps(data, img->width, img->height, data_res); - map_textures[i] = gfx_load_texture_raw(img->width, img->height, IMG_RGBA, data); - map_unload_data(img->image_data); - } - - //mem_free(data_res); - return count; -} - -int img_num() -{ - return count; -} - -int img_get(int index) -{ - return map_textures[index]; -} diff --git a/src/game/client/mapres_image.h b/src/game/client/mapres_image.h deleted file mode 100644 index f841ca53..00000000 --- a/src/game/client/mapres_image.h +++ /dev/null @@ -1,19 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ - -// loads images from the map to textures -int img_init(); - -// returns the number of images in the map -int img_num(); - -// fetches the texture id for the image -int img_get(int index); - - -class mapres_image -{ -public: - int width; - int height; - int image_data; -}; diff --git a/src/game/client/mapres_tilemap.cpp b/src/game/client/mapres_tilemap.cpp deleted file mode 100644 index 6bee4081..00000000 --- a/src/game/client/mapres_tilemap.cpp +++ /dev/null @@ -1,102 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#include -#include -#include "mapres_tilemap.h" -#include "mapres_image.h" -#include "../mapres.h" - -int tilemap_init() -{ - return 0; -} - -void tilemap_render(float scale, int fg) -{ - if(!map_is_loaded()) - return; - - float screen_x0, screen_y0, screen_x1, screen_y1; - gfx_getscreen(&screen_x0, &screen_y0, &screen_x1, &screen_y1); - - // fetch indecies - int start, num; - map_get_type(MAPRES_TILEMAP, &start, &num); - - // render tilemaps - int passed_main = 0; - for(int t = 0; t < num; t++) - { - mapres_tilemap *tmap = (mapres_tilemap *)map_get_item(start+t,0,0); - unsigned char *data = (unsigned char *)map_get_data(tmap->data); - - if(tmap->main) - passed_main = 1; - - if((fg && passed_main) || (!fg && !passed_main)) - { - if(!config.gfx_high_detail && !tmap->main) - continue; - gfx_texture_set(img_get(tmap->image)); - - gfx_quads_begin(); - - int starty = (int)(screen_y0/scale)-1; - int startx = (int)(screen_x0/scale)-1; - int endy = (int)(screen_y1/scale)+1; - int endx = (int)(screen_x1/scale)+1; - - float frac = (1.25f/1024.0f);//2.0f; //2.0f; - float texsize = 1024.0f; - float nudge = 0.5f/texsize; - for(int y = starty; y < endy; y++) - for(int x = startx; x < endx; x++) - { - int mx = x; - int my = y; - if(mx<0) mx = 0; - if(mx>=tmap->width) mx = tmap->width-1; - if(my<0) my = 0; - if(my>=tmap->height) my = tmap->height-1; - - int c = mx + my*tmap->width; - - unsigned char d = data[c*2]; - unsigned char f = data[c*2+1]; - if(d) - { - int tx = d%16; - int ty = d/16; - int px0 = tx*(1024/16); - int py0 = ty*(1024/16); - int px1 = (tx+1)*(1024/16)-1; - int py1 = (ty+1)*(1024/16)-1; - - float u0 = nudge + px0/texsize+frac; - float v0 = nudge + py0/texsize+frac; - float u1 = nudge + px1/texsize-frac; - float v1 = nudge + py1/texsize-frac; - - if(f&TILEFLAG_VFLIP) - { - float tmp = u0; - u0 = u1; - u1 = tmp; - } - - if(f&TILEFLAG_HFLIP) - { - float tmp = v0; - v0 = v1; - v1 = tmp; - } - - gfx_quads_setsubset(u0,v0,u1,v1); - - gfx_quads_drawTL(x*scale, y*scale, scale, scale); - } - } - - gfx_quads_end(); - } - } -} diff --git a/src/game/client/mapres_tilemap.h b/src/game/client/mapres_tilemap.h deleted file mode 100644 index a13495ed..00000000 --- a/src/game/client/mapres_tilemap.h +++ /dev/null @@ -1,26 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ - -// dependencies: image - -// -int tilemap_init(); - -// renders the tilemaps -void tilemap_render(float scale, int fg); - -enum -{ - TILEFLAG_VFLIP=1, - TILEFLAG_HFLIP=2, -}; - -struct mapres_tilemap -{ - int image; - int width; - int height; - int x, y; - int scale; - int data; - int main; -}; diff --git a/src/game/client/menu.cpp b/src/game/client/menu.cpp deleted file mode 100644 index 10dafc39..00000000 --- a/src/game/client/menu.cpp +++ /dev/null @@ -1,1865 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#if 0 - -#include -#include -#include -#include - -#include - -extern "C" { - #include - #include - #include - #include -} - -#include "../mapres.h" -#include "../version.h" - -#include "mapres_image.h" -#include "mapres_tilemap.h" - -#include "data.h" -#include - -extern data_container *data; - -/******************************************************** - MENU -*********************************************************/ - -enum gui_tileset_enum -{ - tileset_regular, - tileset_hot, - tileset_active, - tileset_inactive -}; - -void draw_area(gui_tileset_enum tileset, int areax, int areay, int areaw, int areah, float x, float y, float w, float h) -{ -/* - const float tex_w = 512.0, tex_h = 512.0; - - switch (tileset) - { - case tileset_regular: - break; - case tileset_hot: - areax += 192; areay += 192; break; - case tileset_active: - areay += 192; break; - case tileset_inactive: - areax += 192; break; - default: - dbg_msg("menu", "invalid tileset given to draw_part"); - } - - float ts_x = areax / tex_w; - float ts_y = areay / tex_h; - float te_x = (areax + areaw) / tex_w; - float te_y = (areay + areah) / tex_h; - - gfx_blend_normal(); - gfx_texture_set(data->images[IMAGE_GUI_WIDGETS].id); - gfx_quads_begin(); - gfx_setcolor(1,1,1,1); - gfx_quads_setsubset( - ts_x, // startx - ts_y, // starty - te_x, // endx - te_y); // endy - gfx_quads_drawTL(x,y,w,h); - gfx_quads_end(); - */ -} - -void draw_part(int part_type, gui_tileset_enum tileset, float x, float y, float w, float h) -{ - gui_box part = data->gui.misc[part_type]; - - draw_area(tileset, part.x, part.y, part.w, part.h, x, y, w, h); - - //draw_part(parts[part], tileset, x, y, w, h); -} - -void draw_part(int part_type, gui_tileset_enum tileset, float x, float y) -{ - gui_box part = data->gui.misc[part_type]; - - draw_part(part_type, tileset, x, y, part.w, part.h); -} - -void draw_box(int box_type, gui_tileset_enum tileset, float x, float y, float w, float h) -{ - gui_compositebox box = data->gui.boxes[box_type]; - - /* A composite box consists of 9 parts. To get the coordinates for all corners, we need A, B, C and D: - * A----+----+----+ - * | tl | tm | tr | - * +----B----+----+ - * | ml | mm | mr | - * +----+----C----+ - * | bl | bm | br | - * +----+----+----D - */ - - int ax = box.rect.x; - int ay = box.rect.y; - - int bx = box.center.x; - int by = box.center.y; - - int cx = box.center.x + box.center.w; - int cy = box.center.y + box.center.h; - - int dx = box.rect.x + box.rect.w; - int dy = box.rect.y + box.rect.h; - - draw_area(tileset, ax, ay, bx-ax, by-ay, x, y, bx-ax, by-ay); - draw_area(tileset, bx, ay, cx-bx, by-ay, x+bx-ax, y, w-(bx-ax)-(dx-cx), by-ay); - draw_area(tileset, cx, ay, dx-cx, by-ay, x+w-(dx-cx), y, dx-cx, by-ay); - - draw_area(tileset, ax, by, bx-ax, min(int(h-(by-ay)-(dy-cy)), cy-by), x, y+(by-ay), bx-ax, h-(by-ay)-(dy-cy)); - draw_area(tileset, bx, by, cx-bx, cy-by, x+bx-ax, y+(by-ay), w-(bx-ax)-(dx-cx), h-(by-ay)-(dy-cy)); - draw_area(tileset, cx, by, dx-cx, min(int(h-(by-ay)-(dy-cy)), cy-by), x+w-(dx-cx), y+(by-ay), dx-cx, h-(by-ay)-(dy-cy)); - - draw_area(tileset, ax, cy, bx-ax, dy-cy, x, y+h-(dy-cy), bx-ax, dy-cy); - draw_area(tileset, bx, cy, cx-bx, dy-cy, x+bx-ax, y+h-(dy-cy), w-(bx-ax)-(dx-cx), dy-cy); - draw_area(tileset, cx, cy, dx-cx, dy-cy, x+w-(dx-cx), y+h-(dy-cy), dx-cx, dy-cy); -} - -extern "C" -{ - -struct pretty_font -{ - float m_CharStartTable[256]; - float m_CharEndTable[256]; - int font_texture; -}; - -extern pretty_font *current_font; - -} - -extern void render_sun(float x, float y); -extern void select_sprite(int id, int flags=0, int sx=0, int sy=0); -extern void draw_sprite(float x, float y, float size); - -void draw_background(float t) -{ - // background color - gfx_clear(0.65f,0.78f,0.9f); - - gfx_blend_normal(); - - render_sun(170, 170); - - gfx_texture_set(data->images[IMAGE_CLOUDS].id); - gfx_quads_begin(); - select_sprite(SPRITE_CLOUD1); - draw_sprite(3500 - fmod(t * 20 + 2000, 4524), 250, 512); - select_sprite(SPRITE_CLOUD2); - draw_sprite(3000 - fmod(t * 50 + 2000, 4024), 150+250, 512); - select_sprite(SPRITE_CLOUD3); - draw_sprite(4000 - fmod(t * 60 + 500, 4512), 300+130, 256); - gfx_quads_end(); - - /* - gfx_texture_set(data->images[IMAGE_MENU_BACKGROUND].id); - gfx_quads_begin(); - gfx_quads_drawTL(0, 430, 1600, 1600/2); - gfx_quads_end(); - */ - -/* - int frame = int(t * 10) % 3; - - //float x_path = -t - - float x_nudge = 3*cos(t*10); - float y_nudge = 8*sin(t*3); - - x_nudge += 150 * cos(t/3); - y_nudge += 30 * sin(t/3); - - float angl = t/3; - angl = fmod(angl, 2*pi); - - bool flip = angl > pi; - - gfx_texture_set(data->images[IMAGE_MENU_BUTTERFLY].id); - gfx_quads_begin(); - gfx_setcolor(1, 1, 1, 1); - gfx_quads_setsubset( - flip ? (frame + 1) * 0.25f : frame * 0.25f, // startx - 0.0f, // starty - flip ? frame * 0.25f : (frame + 1) * 0.25f, // endx - 0.5f); // endy - gfx_quads_drawTL(1250 + x_nudge, 480 + y_nudge, 64, 64); - gfx_quads_end(); - */ -} - -void draw_image_button(const void *id, const char *text, int checked, float x, float y, float w, float h, void *extra) -{ - ui_do_image(*(int *)id, x, y, w, h); -} - -void draw_single_part_button(const void *id, const char *text, int checked, float x, float y, float w, float h, void *extra) -{ - gui_tileset_enum tileset; - - if (ui_active_item() == id && ui_hot_item() == id) - tileset = tileset_active; - else if (ui_hot_item() == id) - tileset = tileset_hot; - else - tileset = tileset_regular; - - - draw_part((int)((char*)extra-(char*)0), tileset, x, y, w, h); -} - -void draw_menu_button(const void *id, const char *text, int checked, float x, float y, float w, float h, void *extra) -{ - int box_type; - if ((int)((char*)extra-(char*)0)) - box_type = GUI_BOX_SCREEN_INFO; - else - box_type = GUI_BOX_SCREEN_LIST; - draw_box(box_type, tileset_regular, x, y, w, h); - - ui_do_label(x + 10, y, text, 28); -} - -void draw_teewars_button(const void *id, const char *text, int checked, float x, float y, float w, float h, void *extra) -{ - const float font_size = h-6.0f;//42.0f; - - float text_width = gfx_pretty_text_width(font_size, text, -1); - gui_tileset_enum tileset; - - if (ui_active_item() == id) - { - int inside = ui_mouse_inside(x, y, w, h); - tileset = inside ? tileset_active : tileset_hot; - } - else if (ui_hot_item() == id) - tileset = tileset_hot; - else - tileset = tileset_regular; - - if ((int)((char*)extra-(char*)0) == 1) - tileset = tileset_inactive; - - draw_box(GUI_BOX_BUTTON, tileset, x, y, w, h); - - ui_do_label(x + w/2 - text_width/2, y + 2, text, font_size); -} - -/* -struct server_info -{ - int version; - int players; - int max_players; - netaddr4 address; - char name[129]; - char map[65]; -};*/ - -struct server_list -{ - int active_count, info_count; - int scroll_index; - int selected_index; -}; - -int ui_do_key_reader(void *id, float x, float y, float w, float h, int key) -{ - // process - static bool mouse_released = true; - int inside = ui_mouse_inside(x, y, w, h); - int new_key = key; - - if(!ui_mouse_button(0)) - mouse_released = true; - - if(ui_active_item() == id) - { - int k = inp_last_key(); - if (k) - { - new_key = k; - ui_set_active_item(0); - mouse_released = false; - } - } - 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 - int box_type; - if (ui_active_item() == id || ui_hot_item() == id) - box_type = GUI_BOX_SCREEN_INFO; - else - box_type = GUI_BOX_SCREEN_TEXTBOX; - draw_box(box_type, tileset_regular, x, y, w, h); - - const char *str = inp_key_name(key); - ui_do_label(x + 10, y, str, 36); - if (ui_active_item() == id) - { - float w = gfx_pretty_text_width(36.0f, str, -1); - ui_do_label(x + 10 + w, y, "_", 36); - } - - return new_key; -} - -int ui_do_combo_box(void *id, float x, float y, float w, const char **lines, int line_count, int selected_index) -{ - float line_height = 36.0f; - float height = line_count * line_height; - - int inside = (ui_active_item() == id) ? ui_mouse_inside(x, y, w, height) : ui_mouse_inside(x, y, w, line_height); - int hover_index = (int)((ui_mouse_y() - y) / line_height); - - if (ui_active_item() == id) - { - if (!ui_mouse_button(0)) - { - ui_set_active_item(0); - - if (inside) - selected_index = hover_index; - } - } - else if(ui_hot_item() == id) - { - if (ui_mouse_button(0)) - { - ui_set_active_item(id); - } - } - - if (inside) - { - ui_set_hot_item(id); - } - - if (ui_active_item() == id) - { - for (int i = 0; i < line_count; i++) - { - int box_type; - if (inside && hover_index == i) - box_type = GUI_BOX_SCREEN_INFO; - else - box_type = GUI_BOX_SCREEN_LIST; - - draw_box(box_type, tileset_regular, x, y + i * line_height, w, line_height); - ui_do_label(x + 10 + 10, y + i * line_height, lines[i], 36); - if (selected_index == i) - ui_do_label(x + 10, y + i * line_height, "-", 36); - } - } - else - { - int box_type; - if (ui_active_item() == id || ui_hot_item() == id) - box_type = GUI_BOX_SCREEN_INFO; - else - box_type = GUI_BOX_SCREEN_TEXTBOX; - draw_box(box_type, tileset_regular, x, y, w, line_height); - ui_do_label(x + 10, y, lines[selected_index], 36); - } - - return selected_index; -} - -int ui_do_edit_box(void *id, float x, float y, float w, float h, char *str, int str_size) -{ - int inside = ui_mouse_inside(x, y, w, h); - int r = 0; - static int at_index = 0; - - if(ui_last_active_item() == id) - { - int c = inp_last_char(); - int k = inp_last_key(); - int len = strlen(str); - - if (inside && ui_mouse_button(0)) - { - int mx_rel = (int)(ui_mouse_x() - x); - - for (int i = 1; i <= len; i++) - { - if (gfx_pretty_text_width(36.0f, str, i) + 10 > mx_rel) - { - at_index = i - 1; - break; - } - - if (i == len) - at_index = len; - } - } - - 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 (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; - } - - int box_type; - if (ui_active_item() == id || ui_hot_item() == id || ui_last_active_item() == id) - box_type = GUI_BOX_SCREEN_INFO; - else - box_type = GUI_BOX_SCREEN_TEXTBOX; - - 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); - - draw_box(box_type, tileset_regular, x, y, w, h); - - ui_do_label(x + 10, y, str, 36); - - if (ui_last_active_item() == id && !just_got_active) - { - float w = gfx_pretty_text_width(36.0f, str, at_index); - ui_do_label(x + 10 + w, y, "_", 36); - } - - return r; -} - -int ui_do_check_box(void *id, float x, float y, float w, float h, int value) -{ - int inside = ui_mouse_inside(x, y, w, h); - int r = value; - - if(ui_active_item() == id) - { - if(!ui_mouse_button(0)) - { - ui_set_active_item(0); - r = r ? 0 : 1; - } - } - else if(ui_hot_item() == id) - { - if(ui_mouse_button(0)) - ui_set_active_item(id); - } - - if(inside) - ui_set_hot_item(id); - - // render - gui_tileset_enum tileset; - int part_type; - if (ui_active_item() == id) - tileset = tileset_active; - else if (ui_hot_item() == id) - tileset = tileset_hot; - else - tileset = tileset_regular; - - part_type = r ? GUI_MISC_RADIO_CHECKED : GUI_MISC_RADIO_UNCHECKED; - - draw_part(part_type, tileset, x, y, w, h); - - return r; -} - -int do_scroll_bar_horiz(void *id, float x, float y, float width, int steps, int last_index) -{ - int r = last_index; - - static int up_button; - static int down_button; - - if (ui_do_button(&up_button, "", 0, x, y + 8, 16, 16, draw_single_part_button, (void *)GUI_MISC_SLIDER_BIG_ARROW_LEFT)) - { - if (r > 0) - --r; - } - if (ui_do_button(&down_button, "", 0, x + width - 16, y + 8, 16, 16, draw_single_part_button, (void *)GUI_MISC_SLIDER_BIG_ARROW_RIGHT)) - { - if (r < steps) - ++r; - } - if (steps > 0) // only if there's actually stuff to scroll through - { - int inside = ui_mouse_inside(x + 16, y, width - 32, 32); - if (inside && (!ui_active_item() || ui_active_item() == id)) - ui_set_hot_item(id); - - if(ui_active_item() == id) - { - if (ui_mouse_button(0)) - { - float pos = ui_mouse_x() - x - 16; - float perc = pos / (width - 32); - - r = (int)((steps + 1) * perc); - if (r < 0) - r = 0; - else if (r > steps) - r = steps; - } - else - ui_set_active_item(0); - } - else if (ui_hot_item() == id && ui_mouse_button(0)) - ui_set_active_item(id); - else if (inside && (!ui_active_item() || ui_active_item() == id)) - ui_set_hot_item(id); - } - - draw_part(GUI_MISC_SLIDER_BIG_HORIZ_BEGIN, tileset_regular, x + 16, y + 8, 16, 16); - draw_part(GUI_MISC_SLIDER_BIG_HORIZ_MID, tileset_regular, x + 32, y + 8, width - 32 - 32, 16); - draw_part(GUI_MISC_SLIDER_BIG_HORIZ_END, tileset_regular, x + width - 32, y + 8, 16, 16); - - draw_part(GUI_MISC_SLIDER_BIG_HANDLE_HORIZ, tileset_regular, x + 16 + r * ((width - 64) / steps), y + 8, 32, 16); - - return r; -} - -int do_scroll_bar_vert(void *id, float x, float y, float height, int steps, int last_index) -{ - int r = last_index; - - static int up_button; - static int down_button; - - if (ui_do_button(&up_button, "", 0, x + 8, y, 16, 16, draw_single_part_button, (void *)GUI_MISC_SLIDER_BIG_ARROW_UP)) - { - if (r > 0) - --r; - } - if (ui_do_button(&down_button, "", 0, x + 8, y + height - 16, 16, 16, draw_single_part_button, (void *)GUI_MISC_SLIDER_BIG_ARROW_DOWN)) - { - if (r < steps) - ++r; - } - if (steps > 0) // only if there's actually stuff to scroll through - { - int inside = ui_mouse_inside(x, y + 16, 16, height - 32); - if (inside && (!ui_active_item() || ui_active_item() == id)) - ui_set_hot_item(id); - - if(ui_active_item() == id) - { - if (ui_mouse_button(0)) - { - float pos = ui_mouse_y() - y - 32; - float perc = pos / (height - 32); - - r = (int)((steps + 1) * perc); - if (r < 0) - r = 0; - else if (r > steps) - r = steps; - } - else - ui_set_active_item(0); - } - else if (ui_hot_item() == id && ui_mouse_button(0)) - ui_set_active_item(id); - else if (inside && (!ui_active_item() || ui_active_item() == id)) - ui_set_hot_item(id); - } - - draw_part(GUI_MISC_SLIDER_BIG_VERT_BEGIN, tileset_regular, x + 8, y + 16, 16, 16); - draw_part(GUI_MISC_SLIDER_BIG_VERT_MID, tileset_regular, x + 8, y + 32, 16, height - 32 - 32); - draw_part(GUI_MISC_SLIDER_BIG_VERT_END, tileset_regular, x + 8, y + height - 32, 16, 16); - - draw_part(GUI_MISC_SLIDER_BIG_HANDLE_VERT, tileset_regular, x + 8, y + 16 + r * ((height - 64) / steps), 16, 32); - - return r; -} - -int ui_do_button_rect(const void *id, const char *text, int checked, const struct rect *r, draw_button_callback draw_func, void *extra) -{ - return ui_do_button((void *)id, text, checked, r->x, r->y, r->w, r->h, draw_func, extra); -} - -void ui_do_label_rect(const struct rect *r, char *str) -{ - float size = r->h; - ui_do_label(r->x + 3, r->y + r->h/2 - size/2, str, size); -} - -void ui_do_edit_box_rect(const void *id, const struct rect *r, char *buffer, int bufferSize) -{ - ui_do_edit_box((void *)id, r->x, r->y, r->w, r->h, buffer, bufferSize); -} - -int ui_do_check_box_rect(const void *id, const struct rect *r, int checked) -{ - return ui_do_check_box((void *)id, r->x, r->y, 32, 32, checked); -} - -int ui_do_key_reader_rect(const void *id, const struct rect *r, int key) -{ - return ui_do_key_reader((void *)id, r->x, r->y, r->w, r->h, key); -} - -int do_scroll_bar_horiz_rect(const void *id, const struct rect *r, int steps, int last_index) -{ - return do_scroll_bar_horiz((void *)id, r->x, r->y, r->w, steps, last_index); - //return do_scroll_bar_horiz((void *)id, r->x, r->y, r->w, r->h, steps, last_index); -} - - -static int do_server_list(float x, float y, int *scroll_index, int *selected_index, int visible_items) -{ - const float spacing = 3.f; - const float item_height = 28; - const float item_width = 728; - const float real_width = item_width + 20; - const float real_height = item_height * visible_items + spacing * (visible_items - 1); - - int num_servers = client_serverbrowse_sorted_num(); - - int r = -1; - - for (int i = 0; i < visible_items; i++) - { - int item_index = i + *scroll_index; - if (item_index >= num_servers) - ; - //ui_do_image(empty_item_texture, x, y + i * item_height + i * spacing, item_width, item_height); - else - { - SERVER_INFO *item = client_serverbrowse_sorted_get(item_index); - - bool clicked = false; - clicked = ui_do_button(item, item->name, 0, x, y + i * item_height + i * spacing, item_width, item_height, - draw_menu_button, (*selected_index == item_index) ? (void *)1 : 0); - - char temp[64]; // plenty of extra room so we don't get sad :o - sprintf(temp, "%i/%i %3d", item->num_players, item->max_players, item->latency); - - ui_do_label(x + 600, y + i * item_height + i * spacing, temp, item_height); - ui_do_label(x + 360, y + i * item_height + i * spacing, item->map, item_height); - - if (clicked) - { - r = item_index; - *selected_index = item_index; - } - } - } - - *scroll_index = do_scroll_bar_vert(scroll_index, x + real_width - 16, y, real_height, - max(num_servers - visible_items, 0), *scroll_index); - - return r; -} - -enum -{ - SCREEN_SERVERS, - SCREEN_NEWS, - SCREEN_HOST, - SCREEN_DISCONNECTED, - SCREEN_CONNECTING, - SCREEN_SETTINGS_GENERAL, - SCREEN_SETTINGS_CONTROLS, - SCREEN_SETTINGS_VIDEO, - SCREEN_SETTINGS_VIDEO_SELECT_MODE, - SCREEN_SETTINGS_VIDEO_CUSTOM, - SCREEN_SETTINGS_SOUND, - SCREEN_KERNING -}; - -static int screen = SCREEN_SERVERS; -static CONFIGURATION config_copy; - -const float column1_x = 250; -const float column2_x = column1_x + 170; -const float column3_x = column2_x + 170; -const float row1_y = 180; -const float row2_y = row1_y + 40; -const float row3_y = row2_y + 40; -const float row4_y = row3_y + 40; -const float row5_y = row4_y + 40; -const float row6_y = row5_y + 40; -const float row7_y = row6_y + 40; - -static char address[128] = "localhost:8303"; - -static float colors[7][3] = -{ - { 0, 0, 1 }, - { 0, 1, 0 }, - { 0, 1, 1 }, - { 1, 0, 0 }, - { 1, 0, 1 }, - { 1, 1, 0 }, - { 1, 1, 1 }, -}; - -static void draw_rect(const struct rect *r) -{ - float *color = colors[rand()%7]; - gfx_setcolor(color[0], color[1], color[2], 1); - - gfx_lines_draw(r->x, r->y, r->x+r->w, r->y); - gfx_lines_draw(r->x+r->w, r->y, r->x+r->w, r->y+r->h); - gfx_lines_draw(r->x+r->w, r->y+r->h, r->x, r->y+r->h); - gfx_lines_draw(r->x, r->y+r->h, r->x, r->y); -} - -void ui_settings_general_render(const struct rect *r) -{ - static struct rect row1; - static struct rect row2; - static struct rect rest; - - static struct rect cells[4]; - - ui_hsplit_t(r, 50, &row1, &rest); - ui_hsplit_t(&rest, 50, &row2, &rest); - - ui_vsplit_l(&row1, 100, &cells[0], &cells[1]); - ui_vsplit_l(&row2, 100, &cells[2], &cells[3]); - - // NAME - ui_do_label_rect(&cells[0], "Name:"); - ui_do_edit_box_rect(config_copy.player_name, &cells[1], config_copy.player_name, sizeof(config_copy.player_name)); - - // Dynamic camera - ui_do_label_rect(&cells[2], "Dynamic Camera:"); - config_set_dynamic_camera(&config_copy, ui_do_check_box_rect(&config_copy.dynamic_camera, &cells[3], config_copy.dynamic_camera)); -} - -typedef void (*assign_func_callback)(CONFIGURATION *config, int value); - -struct key_thing -{ - char name[32]; - int *key; - assign_func_callback assign_func; -}; - -static void ui_settings_controls_render(const struct rect *r) -{ - static int scroll_index = 0; - - const key_thing keys[] = - { - { "Move Left:", &config_copy.key_move_left, config_set_key_move_left }, - { "Move Right:", &config_copy.key_move_right, config_set_key_move_right }, - { "Jump:", &config_copy.key_jump, config_set_key_jump }, - { "Fire:", &config_copy.key_fire, config_set_key_fire }, - { "Hook:", &config_copy.key_hook, config_set_key_hook }, - { "Hammer:", &config_copy.key_weapon1, config_set_key_weapon1 }, - { "Pistol:", &config_copy.key_weapon2, config_set_key_weapon2 }, - { "Shotgun:", &config_copy.key_weapon3, config_set_key_weapon3 }, - { "Grenade:", &config_copy.key_weapon4, config_set_key_weapon4 }, - { "Next Weapon:", &config_copy.key_next_weapon, config_set_key_next_weapon }, - { "Prev. Weapon:", &config_copy.key_prev_weapon, config_set_key_prev_weapon }, - { "Emoticon:", &config_copy.key_emoticon, config_set_key_emoticon }, - { "Screenshot:", &config_copy.key_screenshot, config_set_key_screenshot }, - }; - - const int key_count = sizeof(keys) / sizeof(key_thing); - - struct rect rest = *r; - - for (int i = 0; i < key_count; i++) - { - struct rect row; - struct rect left, right; - - ui_hsplit_t(&rest, 32, &row, &rest); - ui_vsplit_l(&row, 128, &left, &right); - - key_thing key = keys[i + scroll_index]; - - ui_do_label_rect(&left, key.name); - key.assign_func(&config_copy, ui_do_key_reader_rect(key.key, &right, *key.key)); - } -} - -void ui_settings_video_render(const struct rect *r) -{ -} - -void ui_settings_video_render_select_mode(const struct rect *r) -{ -} - -void ui_settings_video_render_custom(const struct rect *r) -{ -} - -void ui_settings_sound_render(const struct rect *r) -{ - struct rect row; - struct rect left, right; - - ui_hsplit_t(r, 32, &row, 0x0); - ui_vsplit_l(&row, 128, &left, &right); - - ui_do_label_rect(&left, "Volume:"); - - config_set_volume(&config_copy, do_scroll_bar_horiz_rect(&config_copy.volume, &right, 256, config_copy.volume)); - snd_set_master_volume(config_copy.volume / 255.0f); -} - -static void tab_menu_button_render(const struct rect *r, char *name, int s) -{ - if (ui_do_button_rect(r, name, 0, r, draw_teewars_button, 0)) - screen = s; -} - -static void tab_menu_render(const struct rect *r) -{ - static struct rect button_news; - static struct rect button_servers; - static struct rect button_host; - static struct rect button_settings; - static struct rect rest; - - ui_vsplit_l(r, 130, &button_news, &rest); - ui_vsplit_l(&rest, 130, &button_servers, &rest); - ui_vsplit_l(&rest, 130, &button_host, &rest); - ui_vsplit_l(&rest, 130, &button_settings, &rest); - - tab_menu_button_render(&button_news, "News", SCREEN_NEWS); - tab_menu_button_render(&button_servers, "Servers", SCREEN_SERVERS); - tab_menu_button_render(&button_host, "Host", SCREEN_HOST); - tab_menu_button_render(&button_settings, "Settings", SCREEN_SETTINGS_GENERAL); -} - -static void settings_tab_menu_render(const struct rect *r) -{ - static struct rect button_general; - static struct rect button_controls; - static struct rect button_video; - static struct rect button_sound; - static struct rect rest; - - ui_hsplit_t(r, 60, &button_general, &rest); - ui_hsplit_t(&rest, 60, &button_controls, &rest); - ui_hsplit_t(&rest, 60, &button_video, &rest); - ui_hsplit_t(&rest, 60, &button_sound, &rest); - - tab_menu_button_render(&button_general, "General", SCREEN_SETTINGS_GENERAL); - tab_menu_button_render(&button_controls, "Controls", SCREEN_SETTINGS_CONTROLS); - tab_menu_button_render(&button_video, "Video", SCREEN_SETTINGS_VIDEO); - tab_menu_button_render(&button_sound, "Sound", SCREEN_SETTINGS_SOUND); -} - -static void ui_settings_render(const struct rect *r) -{ - static struct rect sub_menu_selector; - static struct rect center; - - ui_vsplit_l(r, 100, &sub_menu_selector, ¢er); - - settings_tab_menu_render(&sub_menu_selector); - - switch (screen) - { - case SCREEN_SETTINGS_GENERAL: ui_settings_general_render(¢er); break; - case SCREEN_SETTINGS_CONTROLS: ui_settings_controls_render(¢er); break; - case SCREEN_SETTINGS_VIDEO: ui_settings_video_render(¢er); break; - case SCREEN_SETTINGS_VIDEO_SELECT_MODE: ui_settings_video_render_select_mode(¢er); break; - case SCREEN_SETTINGS_VIDEO_CUSTOM: ui_settings_video_render_custom(¢er); break; - case SCREEN_SETTINGS_SOUND: ui_settings_sound_render(¢er); break; - } -} - -static void middle_render(const struct rect *r) -{ - bool ingame = false; - - switch (screen) - { - case SCREEN_SERVERS: - { - if (ingame) - {} - //return ingame_main_render(); - else - { - static struct rect browser; - static bool inited = false; - - ui_margin(r, 5, &browser); - - { - struct rect button_row; - static struct rect button_refresh, button_connect; - - ui_hsplit_b(&browser, 30, &browser, &button_row); - ui_vsplit_l(&button_row, 100, &button_refresh, &button_row); - ui_vsplit_l(&button_row, 100, &button_connect, &button_row); - - if (ui_do_button_rect(&button_refresh, "Refresh", 0, &button_refresh, draw_teewars_button, 0)) - client_serverbrowse_refresh(0); - - if (ui_do_button_rect(&button_connect, "Connect", 0, &button_connect, draw_teewars_button, 0)) - {} - - } - - if (!inited) - { - client_serverbrowse_refresh(0); - inited = true; - } - - { - int server_count = client_serverbrowse_sorted_num(); - int i; - - struct rect rest = browser; - - struct rect button_row; - - static struct rect button_name, button_players, button_players_max, button_map, button_latency, button_progression; - - ui_hsplit_t(&rest, 32, &button_row, &rest); - ui_margin(&button_row, 1, &button_row); - - ui_vsplit_l(&button_row, 400, &button_name, &button_row); - ui_vsplit_l(&button_row, 40, &button_players, &button_row); - ui_vsplit_l(&button_row, 40, &button_players_max, &button_row); - ui_vsplit_l(&button_row, 80, &button_map, &button_row); - ui_vsplit_l(&button_row, 40, &button_latency, &button_row); - ui_vsplit_l(&button_row, 40, &button_progression, &button_row); - - if (ui_do_button_rect(&button_name, "Name", 0, &button_name, draw_teewars_button, 0)) - config.b_sort = BROWSESORT_NAME; - if (ui_do_button_rect(&button_players, "Players", 0, &button_players, draw_teewars_button, 0)) - config.b_sort = BROWSESORT_NUMPLAYERS; - if (ui_do_button_rect(&button_players_max, "Max Players", 0, &button_players_max, draw_teewars_button, 0)) - config.b_sort = BROWSESORT_NUMPLAYERS; // TODO: real enum here - if (ui_do_button_rect(&button_map, "Map", 0, &button_map, draw_teewars_button, 0)) - config.b_sort = BROWSESORT_MAP; - if (ui_do_button_rect(&button_latency, "Ping", 0, &button_latency, draw_teewars_button, 0)) - config.b_sort = BROWSESORT_PING; - if (ui_do_button_rect(&button_progression, "Progression", 0, &button_progression, draw_teewars_button, 0)) - config.b_sort = BROWSESORT_PING; // TODO: real enum here - - for (i = 0; i < server_count; i++) - { - SERVER_INFO *info = client_serverbrowse_sorted_get(i); - struct rect row; - struct rect col_name, col_players, col_players_max, col_map, col_latency, col_progression; - char temp[16]; - - - ui_hsplit_t(&rest, 32, &row, &rest); - - if (rest.h < 0) - break; - - ui_margin(&row, 1, &row); - - ui_vsplit_l(&row, 400, &col_name, &row); - ui_vsplit_l(&row, 40, &col_players, &row); - ui_vsplit_l(&row, 40, &col_players_max, &row); - ui_vsplit_l(&row, 80, &col_map, &row); - ui_vsplit_l(&row, 40, &col_latency, &row); - ui_vsplit_l(&row, 40, &col_progression, &row); - - ui_do_label_rect(&col_name, info->name); - - sprintf(temp, "%i", info->num_players); - ui_do_label_rect(&col_players, temp); - sprintf(temp, "%i", info->max_players); - ui_do_label_rect(&col_players_max, temp); - ui_do_label_rect(&col_map, info->map); - - sprintf(temp, "%i", info->latency); - ui_do_label_rect(&col_latency, temp); - sprintf(temp, "%i", info->progression); - ui_do_label_rect(&col_progression, temp); - } - } - } - break; - } - case SCREEN_NEWS: - { - break; - } - case SCREEN_HOST: - { - break; - //return disconnected_render(); - } - case SCREEN_CONNECTING: - { - break; - //return connecting_render(); - } - case SCREEN_SETTINGS_GENERAL: - case SCREEN_SETTINGS_CONTROLS: - case SCREEN_SETTINGS_VIDEO: - case SCREEN_SETTINGS_VIDEO_SELECT_MODE: - case SCREEN_SETTINGS_VIDEO_CUSTOM: - case SCREEN_SETTINGS_SOUND: - { - ui_settings_render(r); - break; - } - case SCREEN_KERNING: - { - break; - } - default: dbg_msg("menu", "invalid screen selected..."); break; - } -} - -static int ui_menu_render(const struct rect *r) -{ - static struct rect top; - static struct rect middle; - static struct rect bottom; - - ui_hsplit_t(r, 48, &top, &middle); - ui_hsplit_b(&middle, 32, &middle, &bottom); - tab_menu_render(&top); - middle_render(&middle); - - return -ui_do_button_rect(&bottom, "Quit", 0, &bottom, draw_teewars_button, 0); -} - -static int main_render() -{ - static bool inited = false; - - if (!inited) - { - inited = true; - client_serverbrowse_refresh(0); - } - - static int scoll_index = 0, selected_index = -1; - int last_selected_index = selected_index; - do_server_list(20, 160, &scoll_index, &selected_index, 8); - - ui_do_edit_box(address, 280, 425, 300, 36, address, sizeof(address)); - - if (last_selected_index != selected_index && selected_index != -1) - { - SERVER_INFO *server; - server = client_serverbrowse_get(selected_index); - - strcpy(address, server->address); - } - - static int refresh_button, join_button, quit_button; - static int use_lan = 0; - - - if (ui_do_button(&refresh_button, "Refresh", 0, 20, 460, 170, 48, draw_teewars_button, 0)) - client_serverbrowse_refresh(use_lan); - - ui_do_label(60, 420, "Search LAN ", 36); - int last_lan = use_lan; - use_lan = ui_do_check_box(&use_lan, 20, 424, 32, 32, use_lan); - if (use_lan != last_lan) - client_serverbrowse_refresh(use_lan); - - if (ui_do_button(&join_button, "Join", 0, 620, 420, 128, 48, draw_teewars_button, 0)) - { - client_connect(address); - - return 1; - } - - if (ui_do_button(&quit_button, "Quit", 0, 620, 490, 128, 48, draw_teewars_button, 0)) - return -1; - - static int settings_button; - if (ui_do_button(&settings_button, "Settings", 0, 400, 490, 170, 48, draw_teewars_button, 0)) - { - config_copy = config; - screen = SCREEN_SETTINGS_GENERAL; - } - - // render status text - if(client_serverbrowse_num_requests()) - { - char buf[512]; - sprintf(buf, "Refreshing %d servers...", client_serverbrowse_num_requests()); - ui_do_label(20, 400, buf, 28); - } - else - { - char buf[512]; - sprintf(buf, "%d of %d servers", client_serverbrowse_sorted_num(), client_serverbrowse_num()); - ui_do_label(20, 400, buf, 28); - } - - ui_do_edit_box(&config.b_filter_string, 20+150, 600-80, 200, 36, config.b_filter_string, sizeof(config.b_filter_string)); - config.b_filter_empty = ui_do_check_box(&config.b_filter_empty, 20, 600-80, 32, 32, config.b_filter_empty); - config.b_filter_full = ui_do_check_box(&config.b_filter_full, 20+50, 600-80, 32, 32, config.b_filter_full); - config.b_filter_pw = ui_do_check_box(&config.b_filter_pw, 20+100, 600-80, 32, 32, config.b_filter_pw); - - - return 0; -} - - -static int settings_general_render() -{ - // NAME - ui_do_label(column1_x, row1_y, "Name:", 36); - ui_do_edit_box(config_copy.player_name, column2_x, row1_y, 300, 36, config_copy.player_name, sizeof(config_copy.player_name)); - - // Dynamic camera - ui_do_label(column1_x, row2_y, "Dynamic Camera:", 36); - config_set_dynamic_camera(&config_copy, ui_do_check_box(&config_copy.dynamic_camera, column2_x + 55, row2_y + 4, 32, 32, config_copy.dynamic_camera)); - - return 0; -} - -static int settings_controls_render() -{ - static int scroll_index = 0; - - const key_thing keys[] = - { - { "Move Left:", &config_copy.key_move_left, config_set_key_move_left }, - { "Move Right:", &config_copy.key_move_right, config_set_key_move_right }, - { "Jump:", &config_copy.key_jump, config_set_key_jump }, - { "Fire:", &config_copy.key_fire, config_set_key_fire }, - { "Hook:", &config_copy.key_hook, config_set_key_hook }, - { "Hammer:", &config_copy.key_weapon1, config_set_key_weapon1 }, - { "Pistol:", &config_copy.key_weapon2, config_set_key_weapon2 }, - { "Shotgun:", &config_copy.key_weapon3, config_set_key_weapon3 }, - { "Grenade:", &config_copy.key_weapon4, config_set_key_weapon4 }, - { "Next Weapon:", &config_copy.key_next_weapon, config_set_key_next_weapon }, - { "Prev. Weapon:", &config_copy.key_prev_weapon, config_set_key_prev_weapon }, - { "Emoticon:", &config_copy.key_emoticon, config_set_key_emoticon }, - { "Screenshot:", &config_copy.key_screenshot, config_set_key_screenshot }, - }; - - const int key_count = sizeof(keys) / sizeof(key_thing); - - for (int i = 0; i < 6; i++) - { - key_thing key = keys[i + scroll_index]; - - ui_do_label(column1_x, row1_y + 40 * i, key.name, 36); - key.assign_func(&config_copy, ui_do_key_reader(key.key, column2_x, row1_y + 40 * i, 150, 36, *key.key)); - } - - scroll_index = do_scroll_bar_vert(&scroll_index, 600, row1_y, 40 * 6, key_count - 6, scroll_index); - - ui_do_label(column1_x, row1_y + 40 * 6, "Scroll wheel weapon switch", 22); - config_set_scroll_weapon(&config_copy, ui_do_check_box(&config_copy.scroll_weapon, column2_x + 50, row1_y + 40 * 6, 22, 22, config_copy.scroll_weapon)); - - return 0; -} - -static const int MAX_RESOLUTIONS = 128; -static int settings_video_render_select_mode() -{ - static VIDEO_MODE modes[MAX_RESOLUTIONS]; - static int num_modes = -1; - - if(num_modes == -1) - num_modes = gfx_get_video_modes(modes, MAX_RESOLUTIONS); - - static int scroll_index = 0; - scroll_index = do_scroll_bar_vert(&scroll_index, 500, row1_y, 40 * 7, num_modes - 7, scroll_index); - - for (int i = 0; i < 7; i++) - { - int index = i + scroll_index; - if(index >= num_modes) - break; - - //key_thing key = keys[i + scroll_index]; - int depth = modes[index].red+modes[index].green+modes[index].blue; - if(depth < 16) - depth = 16; - else if(depth > 16) - depth = 24; - - char buf[128]; - int s = 0; - if(modes[index].width == config_copy.gfx_screen_width && - modes[index].height == config_copy.gfx_screen_height && - depth == config_copy.gfx_color_depth) - { - s = 1; - } - - sprintf(buf, "%c %dx%d %d bit %c", s?'>':' ', modes[index].width, modes[index].height, depth, s?'<':' '); - - if(ui_do_button((void*)&modes[index], buf, 0, - column1_x, row1_y + 40 * i, 250, 32.0f, draw_teewars_button, 0)) - { - // select - config_set_gfx_color_depth(&config_copy, depth); - config_set_gfx_screen_width(&config_copy, modes[index].width); - config_set_gfx_screen_height(&config_copy, modes[index].height); - screen = SCREEN_SETTINGS_VIDEO; - } - } - - static int back_button = 0; - if(ui_do_button(&back_button, "Back", 0, column3_x, row7_y, 150, 32, draw_teewars_button, 0)) - screen = SCREEN_SETTINGS_VIDEO; - - return 0; -} - -static int settings_video_render_custom() -{ - ui_do_label(column1_x, row1_y, "Quality Textures:", 36); - config_set_gfx_texture_quality(&config_copy, ui_do_check_box(&config_copy.gfx_texture_quality, column3_x, row1_y + 5, 32, 32, config_copy.gfx_texture_quality)); - - ui_do_label(column1_x, row2_y, "Texture Compression:", 36); - config_set_gfx_texture_compression(&config_copy, ui_do_check_box(&config_copy.gfx_texture_compression, column3_x, row2_y + 5, 32, 32, config_copy.gfx_texture_compression)); - - ui_do_label(column1_x, row3_y, "High Detail:", 36); - config_set_gfx_high_detail(&config_copy, ui_do_check_box(&config_copy.gfx_high_detail, column3_x, row3_y + 5, 32, 32, config_copy.gfx_high_detail)); - - static int back_button = 0; - if(ui_do_button(&back_button, "Back", 0, column3_x, row7_y, 150, 32, draw_teewars_button, 0)) - screen = SCREEN_SETTINGS_VIDEO; - - return 0; -} - -static int settings_video_render() -{ - - ui_do_label(column1_x, row1_y, "Mode:", 36); - - char buf[128]; - sprintf(buf, "%dx%d %d bit", config_copy.gfx_screen_width, config_copy.gfx_screen_height, config_copy.gfx_color_depth); - static int select_button = 0; - if(ui_do_button(&select_button, buf, 0, column2_x, row1_y, 300, 32, draw_teewars_button, 0)) - screen = SCREEN_SETTINGS_VIDEO_SELECT_MODE; - - // we need to draw these bottom up, to make overlapping work correctly - - ui_do_label(column1_x, row2_y, "Fullscreen:", 36); - config_set_gfx_fullscreen(&config_copy, ui_do_check_box(&config_copy.gfx_fullscreen, column2_x, row2_y + 5, 32, 32, config_copy.gfx_fullscreen)); - - ui_do_label(column1_x, row3_y, "V-sync:", 36); - config_set_gfx_vsync(&config_copy, ui_do_check_box(&config_copy.gfx_vsync, column2_x, row3_y + 5, 32, 32, config_copy.gfx_vsync)); - - - int current_level = - (config_copy.gfx_texture_quality<<8)| - (config_copy.gfx_texture_compression<<4)| - (config_copy.gfx_high_detail); - - static const int opt_levels[3] = {0x101,0x001,0x010}; - static const char *opts[] = { - "High", - "Medium", - "Low", - "Custom"}; - - int selected = 0; // custom per default - for(; selected < 3; selected++) - { - if(current_level == opt_levels[selected]) - break; - } - - ui_do_label(column1_x, row4_y, "Quality:", 36); - int new_level = ui_do_combo_box(&config_copy.gfx_texture_quality, column2_x, row4_y, 150, opts, 4, selected); - if(new_level < 3) - { - config_set_gfx_texture_quality(&config_copy, (opt_levels[new_level]>>8)&1); - config_set_gfx_texture_compression(&config_copy, (opt_levels[new_level]>>4)&1); - config_set_gfx_high_detail(&config_copy, opt_levels[new_level]&1); - } - - static int custom_button=0; - if(ui_do_button(&custom_button, "Customize", 0, column3_x, row4_y, 130, 32, draw_teewars_button, 0)) - screen = SCREEN_SETTINGS_VIDEO_CUSTOM; - - ui_do_label(column1_x, row6_y + 50, "(A restart of the game is required for these settings to take effect.)", 20); - - return 0; -} - -static int settings_sound_render() -{ - ui_do_label(column1_x, row1_y, "Volume:", 36); - - config_set_volume(&config_copy, do_scroll_bar_horiz(&config_copy.volume, column2_x, row1_y, 200, 255, config_copy.volume)); - snd_set_master_volume(config_copy.volume / 255.0f); - - return 0; -} - -extern void draw_round_rect(float x, float y, float w, float h, float r); -extern void send_info(bool); - -static int settings_render(bool ingame) -{ - if (ingame) - { - gfx_blend_normal(); - - gfx_texture_set(-1); - gfx_quads_begin(); - gfx_setcolor(0,0,0,0.5f); - draw_round_rect(10, 120, 780, 460, 30.0f); - gfx_quads_end(); - } - - static int general_button, controls_button, video_button, sound_button; - - if (ui_do_button(&general_button, "General", 0, 30, 200, 170, 48, draw_teewars_button, 0)) - screen = SCREEN_SETTINGS_GENERAL; - if (ui_do_button(&controls_button, "Controls", 0, 30, 250, 170, 48, draw_teewars_button, 0)) - screen = SCREEN_SETTINGS_CONTROLS; - if (ui_do_button(&video_button, "Video", 0, 30, 300, 170, 48, draw_teewars_button, 0)) - screen = SCREEN_SETTINGS_VIDEO; - if (ui_do_button(&sound_button, "Sound", 0, 30, 350, 170, 48, draw_teewars_button, 0)) - screen = SCREEN_SETTINGS_SOUND; - - switch (screen) - { - case SCREEN_SETTINGS_GENERAL: settings_general_render(); break; - case SCREEN_SETTINGS_CONTROLS: settings_controls_render(); break; - case SCREEN_SETTINGS_VIDEO: settings_video_render(); break; - case SCREEN_SETTINGS_VIDEO_SELECT_MODE: settings_video_render_select_mode(); break; - case SCREEN_SETTINGS_VIDEO_CUSTOM: settings_video_render_custom(); break; - case SCREEN_SETTINGS_SOUND: settings_sound_render(); break; - } - - // SAVE BUTTON - static int save_button; - if (ui_do_button(&save_button, "Save", 0, 482, 490, 128, 48, draw_teewars_button, 0)) - { - // did we change our name? - bool name_changed = strcmp(config.player_name, config_copy.player_name) != 0; - - config = config_copy; - - if (ingame && name_changed) - send_info(false); - -#ifdef CONF_PLATFORM_MACOSX - config_save("~/.teewars"); -#else - config_save("default.cfg"); -#endif - screen = SCREEN_SERVERS; - } - - // CANCEL BUTTON - static int cancel_button; - if (ui_do_button(&cancel_button, "Cancel", 0, 620, 490, 150, 48, draw_teewars_button, 0)) - { - snd_set_master_volume(config.volume / 255.0f); - screen = SCREEN_SERVERS; - } - - return 0; -} - -extern int gametype; -static int ingame_main_render() -{ - static int menu_resume, menu_quit, menu_settings; - /*if (gametype == GAMETYPE_TDM) - { - char buf[128]; - // Switch team - ui_do_label(100,100,"Switch Team",40); - sprintf(buf,"Team: %s",local_player->team ? "A" : "B"); - if (ui_do_button(&menu_team, buf, 0, 30, 150, 170, 48, draw_teewars_button)) - { - msg_pack_start(MSG_SWITCHTEAM, MSGFLAG_VITAL); - msg_pack_end(); - client_send_msg(); - menu_active = false; - } - }*/ - - const int column1_x = 275; - const int row1_y = 200; - const int row2_y = row1_y + 60; - const int row3_y = row2_y + 60; - const int row4_y = row3_y + 60; - - gfx_blend_normal(); - - gfx_texture_set(-1); - gfx_quads_begin(); - gfx_setcolor(0,0,0,0.5f); - draw_round_rect(170, 120, 460, 360, 30.0f); - gfx_quads_end(); - - ui_do_image(data->images[IMAGE_BANNER].id, 214, 150, 384, 96); - - if (ui_do_button(&menu_resume, "Resume Game", 0, column1_x, row2_y, 250, 48, draw_teewars_button, 0)) - { - return 1; - } - - if (ui_do_button(&menu_quit, "Disconnect", 0, column1_x, row4_y, 250, 48, draw_teewars_button, 0)) - { - client_disconnect(); - return 1; - } - - if (ui_do_button(&menu_settings, "Settings", 0, column1_x, row3_y, 250, 48, draw_teewars_button, 0)) - { - config_copy = config; - screen = SCREEN_SETTINGS_GENERAL; - } - - return 0; -} - -extern "C" double extra_kerning[256*256]; - -static int kerning_render() -{ - static bool loaded = false; - static char text[32] = {0}; - - if (!loaded) - { - // TODO: fix me - /* - file_stream file; - - if (file.open_r("kerning.txt")) - { - line_stream lstream(&file); - int i = 0; - char *line; - - while ((line = lstream.get_line())) - extra_kerning[i++] = atof(line); - - file.close(); - } - - if (file.open_r("tracking.txt")) - { - line_stream lstream(&file); - char *line; - - for (int i = 0; i < 256; i++) - { - line = lstream.get_line(); - current_font->m_CharStartTable[i] = atof(line); - line = lstream.get_line(); - current_font->m_CharEndTable[i] = atof(line); - } - - file.close(); - } - */ - - loaded = true; - } - - ui_do_edit_box(text, 160, 20, 300, 36, text, sizeof(text)); - - ui_do_label(160, 250, text, 70); - - int len = strlen(text); - - for (int i = 0; i < len-1; i++) - { - char s[3] = {0}; - s[0] = text[i]; - s[1] = text[i+1]; - ui_do_label(10, 30 * i + 10, s, 45); - - int index = s[0] + s[1] * 256; - - // less - if (ui_do_button((void *)(100 + i * 2), "", 0, 50, 30 * i + 10 + 20, 16, 16, draw_single_part_button, (void *)GUI_MISC_SLIDER_BIG_ARROW_LEFT)) - { - extra_kerning[index] -= 0.01; - } - - // more - if (ui_do_button((void *)(100 + i * 2 + 1), "", 0, 66, 30 * i + 10 + 20, 16, 16, draw_single_part_button, (void *)GUI_MISC_SLIDER_BIG_ARROW_RIGHT)) - { - extra_kerning[index] += 0.01; - } - - char num[16]; - sprintf(num, "(%f)", extra_kerning[index]); - ui_do_label(84, 30 * i + 30, num, 12); - } - - for (int i = 0; i < len; i++) - { - char s[2] = {0}; - s[0] = text[i]; - - ui_do_label(700, 35 * i + 10, s, 45); - - gfx_blend_normal(); - gfx_texture_set(-1); - gfx_quads_begin(); - gfx_setcolor(0,0,0,0.5); - gfx_quads_drawTL(700,35*i+20,1,30); - gfx_quads_drawTL(700+45*(current_font->m_CharEndTable[(int)s[0]]-current_font->m_CharStartTable[(int)s[0]]),35*i+20,1,30); - gfx_quads_end(); - // less - if (ui_do_button((void *)(200 + i * 2), "", 0, 650, 35 * i + 10 + 15, 16, 16, draw_single_part_button, (void *)GUI_MISC_SLIDER_BIG_ARROW_LEFT)) - { - current_font->m_CharStartTable[(int)s[0]] -= 0.01f; - } - - // more - if (ui_do_button((void *)(200 + i * 2 + 1), "", 0, 666, 35 * i + 10 + 15, 16, 16, draw_single_part_button, (void *)GUI_MISC_SLIDER_BIG_ARROW_RIGHT)) - { - current_font->m_CharStartTable[(int)s[0]] += 0.01f; - } - - char num[16]; - sprintf(num, "(%f)", current_font->m_CharStartTable[(int)s[0]]); - ui_do_label(645, 35 * i + 40, num, 12); - - - - - // less - if (ui_do_button((void *)(300 + i * 2), "", 0, 750, 35 * i + 10 + 15, 16, 16, draw_single_part_button, (void *)GUI_MISC_SLIDER_BIG_ARROW_LEFT)) - { - current_font->m_CharEndTable[(int)s[0]] -= 0.01f; - } - - // more - if (ui_do_button((void *)(300 + i * 2 + 1), "", 0, 766, 35 * i + 10 + 15, 16, 16, draw_single_part_button, (void *)GUI_MISC_SLIDER_BIG_ARROW_RIGHT)) - { - current_font->m_CharEndTable[(int)s[0]] += 0.01f; - } - - sprintf(num, "(%f)", current_font->m_CharEndTable[(int)s[0]]); - ui_do_label(745, 35 * i + 40, num, 12); - - } - - // SAVE BUTTON - static int save_button; - if (ui_do_button(&save_button, "Save", 0, 482, 520, 128, 48, draw_teewars_button, 0)) - { - // TODO: fix or remove me - /* - file_stream file; - - if (file.open_w("kerning.txt")) - { - char t[16]; - - for (int i = 0; i < 256*256; i++) - { - sprintf(t, "%f\n", extra_kerning[i]); - file.write(t, strlen(t)); - } - - file.close(); - } - - if (file.open_w("tracking.txt")) - { - char t[16]; - - for (int i = 0; i < 256; i++) - { - sprintf(t, "%f\n", current_font->m_CharStartTable[i]); - file.write(t, strlen(t)); - sprintf(t, "%f\n", current_font->m_CharEndTable[i]); - file.write(t, strlen(t)); - } - - file.close(); - } - */ - - //screen = 0; - } - - // CANCEL BUTTON - static int cancel_button; - if (ui_do_button(&cancel_button, "Cancel", 0, 620, 520, 150, 48, draw_teewars_button, 0)) - screen = SCREEN_SERVERS; - - return 0; -} - - -int render_popup(const char *caption, const char *text, const char *button_text) -{ - float tw; - - float w = 700; - float h = 300; - float x = 800/2-w/2; - float y = 600/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(); - - tw = gfx_pretty_text_width(48.0f, caption, -1); - ui_do_label(x+w/2-tw/2, y+20, caption, 48.0f); - - tw = gfx_pretty_text_width(32.0f, text, -1); - gfx_pretty_text(x+w/2-tw/2, y+130, 32.0f, text, -1); - - if(button_text) - { - static int back_button = 0; - if(ui_do_button(&back_button, button_text, 0, x+w/2-100, y+220, 200, 48, draw_teewars_button, 0)) - return 1; - if(inp_key_down(KEY_ESC) || inp_key_down(KEY_ENTER)) - return 1; - } - - return 0; -} - -static int disconnected_render() -{ - if(strlen(client_error_string()) == 0) - screen = SCREEN_SERVERS; - else - { - if(render_popup("Disconnected", client_error_string(), "Back")) - screen = SCREEN_SERVERS; - } - return 0; -} - -static int connecting_render() -{ - char buf[256]; - sprintf(buf, "Server: %s", address); - if(render_popup("Connecting", buf, "Abort")) - { - client_disconnect(); - screen = SCREEN_SERVERS; - } - return 0; -} - - -void menu_do_disconnected() -{ - screen = SCREEN_DISCONNECTED; -} - - -void menu_do_connecting() -{ - screen = SCREEN_CONNECTING; -} - -void menu_do_connected() -{ - screen = SCREEN_SERVERS; -} - -static int menu_render(bool ingame) -{ - if (!ingame) - { - // background color - gfx_clear(0.65f,0.78f,0.9f); - //gfx_clear(89/255.f,122/255.f,0.0); - - // GUI coordsys - gfx_mapscreen(0,0,800.0f,600.0f); - - static int64 start = time_get(); - - float t = double(time_get() - start) / double(time_freq()); - gfx_mapscreen(0,0,1600.0f,1200.0f); - draw_background(t); - gfx_mapscreen(0,0,800.0f,600.0f); - - if (screen != SCREEN_KERNING) - { - ui_do_image(data->images[IMAGE_BANNER].id, 200, 20, 512, 128); - ui_do_label(20.0f, 600.0f-40.0f, "Version: " TEEWARS_VERSION, 36); - if(config.debug) - ui_do_label(20.0f, 600.0f-60.0f, "Nethash: " TEEWARS_NETVERSION_HASH, 24); - } - } - else - { - gfx_mapscreen(0, 0, 800, 600); - } - - if(config.dbg_new_gui) - { - struct rect *screen = ui_screen(); - - static float scale = 1.0f; - - if (inp_key_pressed('I')) - scale += 0.01; - if (inp_key_pressed('O')) - scale -= 0.01; - - ui_scale(scale); - int retn = ui_menu_render(screen); - - /*gfx_texture_set(-1); - gfx_lines_begin(); - ui_foreach_rect(draw_rect); - gfx_lines_end();*/ - - return retn; - } - else - { - switch (screen) - { - case SCREEN_SERVERS: return ingame ? ingame_main_render() : main_render(); - case SCREEN_DISCONNECTED: return disconnected_render(); - case SCREEN_CONNECTING: return connecting_render(); - case SCREEN_SETTINGS_GENERAL: - case SCREEN_SETTINGS_CONTROLS: - case SCREEN_SETTINGS_VIDEO: - case SCREEN_SETTINGS_VIDEO_SELECT_MODE: - case SCREEN_SETTINGS_VIDEO_CUSTOM: - case SCREEN_SETTINGS_SOUND: return settings_render(ingame); - case SCREEN_KERNING: return kerning_render(); - default: dbg_msg("menu", "invalid screen selected..."); return 0; - } - } -} - -extern "C" void modmenu_init() // TODO: nastyness -{ - // TODO: should be removed - current_font->font_texture = gfx_load_texture("data/big_font.png"); -} - -void modmenu_shutdown() -{ -} - -extern int menu2_render(); - -extern "C" int modmenu_render(int ingame) // TODO: nastyness -{ - static int mouse_x = 0; - static int mouse_y = 0; - - // handle mouse movement - float mx, my; - { - int rx, ry; - inp_mouse_relative(&rx, &ry); - mouse_x += rx; - mouse_y += ry; - if(mouse_x < 0) mouse_x = 0; - if(mouse_y < 0) mouse_y = 0; - if(mouse_x > gfx_screenwidth()) mouse_x = gfx_screenwidth(); - if(mouse_y > gfx_screenheight()) mouse_y = gfx_screenheight(); - - // update the ui - mx = (mouse_x/(float)gfx_screenwidth())*800.0f; - my = (mouse_y/(float)gfx_screenheight())*600.0f; - - 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); - } - - //int r = menu_render(server_address, str, max_len); - //int r = menu2_render(ingame); - (void)menu_render; - int r = menu2_render(); - - 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(); - - inp_clear(); - - return r; -} - -#endif diff --git a/src/game/client/menu.h b/src/game/client/menu.h deleted file mode 100644 index 5c68c53d..00000000 --- a/src/game/client/menu.h +++ /dev/null @@ -1,16 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#ifndef __MENU_H -#define __MENU_H - -void draw_image_button(void *id, const char *text, int checked, float x, float y, float w, float h, void *extra); -void draw_single_part_button(void *id, const char *text, int checked, float x, float y, float w, float h, void *extra); -void draw_menu_button(void *id, const char *text, int checked, float x, float y, float w, float h, void *extra); -void draw_teewars_button(void *id, const char *text, int checked, float x, float y, float w, float h, void *extra); -int ui_do_key_reader(void *id, float x, float y, float w, float h, int key); -int ui_do_combo_box(void *id, float x, float y, float w, char *lines, int line_count, int selected_index); -int ui_do_edit_box(void *id, float x, float y, float w, float h, char *str, int str_size); -int ui_do_check_box(void *id, float x, float y, float w, float h, int value); -int do_scroll_bar_horiz(void *id, float x, float y, float width, int steps, int last_index); -int do_scroll_bar_vert(void *id, float x, float y, float height, int steps, int last_index); - -#endif diff --git a/src/game/client/menu2.cpp b/src/game/client/menu2.cpp deleted file mode 100644 index 04ed48b0..00000000 --- a/src/game/client/menu2.cpp +++ /dev/null @@ -1,1893 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#include -#include -#include -#include - -#include -#include - -extern "C" { - #include - #include - #include - #include -} - -#include "../mapres.h" -#include "../version.h" -#include "../game_protocol.h" - -#include "mapres_image.h" -#include "mapres_tilemap.h" - -#include "data.h" -#include "cl_render.h" -#include "cl_skin.h" -#include - -extern data_container *data; - -// abit uglyness -extern const obj_player_info *local_info; -extern bool menu_active; -extern bool menu_game_active; - -enum -{ - POPUP_NONE=0, - POPUP_CONNECTING, - POPUP_DISCONNECTED, - POPUP_PASSWORD, - POPUP_QUIT, -}; - -static int popup = POPUP_NONE; - -//static vec4 gui_color(0.9f,0.78f,0.65f, 0.5f); -//static vec4 gui_color(0.78f,0.9f,0.65f, 0.5f); - -static vec4 gui_color(0.65f,0.78f,0.9f, 0.5f); - -static vec4 color_tabbar_inactive_outgame(0,0,0,0.25f); -static vec4 color_tabbar_active_outgame(0,0,0,0.5f); - -static float color_ingame_scale_i = 0.5f; -static float color_ingame_scale_a = 0.2f; -static vec4 color_tabbar_inactive_ingame(gui_color.r*color_ingame_scale_i, gui_color.g*color_ingame_scale_i, gui_color.b*color_ingame_scale_i,0.75f); -static vec4 color_tabbar_active_ingame(gui_color.r*color_ingame_scale_a, gui_color.g*color_ingame_scale_a, gui_color.b*color_ingame_scale_a,0.85f); -//static vec4 color_tabbar_inactive_ingame(0.2f,0.2f,0.2f,0.5f); -//static vec4 color_tabbar_active_ingame(0.2f,0.2f,0.2f,0.75f); - -static vec4 color_tabbar_inactive = color_tabbar_inactive_outgame; -static vec4 color_tabbar_active = color_tabbar_active_outgame; - -enum -{ - CORNER_TL=1, - CORNER_TR=2, - CORNER_BL=4, - CORNER_BR=8, - - CORNER_T=CORNER_TL|CORNER_TR, - CORNER_B=CORNER_BL|CORNER_BR, - CORNER_R=CORNER_TR|CORNER_BR, - CORNER_L=CORNER_TL|CORNER_BL, - - CORNER_ALL=CORNER_T|CORNER_B, - - PAGE_NEWS=0, - PAGE_INTERNET, - PAGE_LAN, - PAGE_FAVORITES, - PAGE_SETTINGS, - //PAGE_GAME, // not a real page - PAGE_SYSTEM, -}; - -typedef struct -{ - float x, y, w, h; -} RECT; - -static RECT screen = { 0.0f, 0.0f, 848.0f, 480.0f }; - -extern void select_sprite(int id, int flags=0, int sx=0, int sy=0); - -RECT *ui2_screen() -{ - float aspect = gfx_screenaspect(); - float w, h; - - h = 600; - w = aspect*h; - - screen.w = w; - screen.h = h; - - return &screen; -} - -void ui2_set_scale(float s) -{ - config.ui_scale = (int)(s*100.0f); -} - -float ui2_scale() -{ - return config.ui_scale/100.0f; -} - -void ui2_hsplit_t(const RECT *original, float cut, RECT *top, RECT *bottom) -{ - RECT r = *original; - cut *= ui2_scale(); - - if (top) - { - top->x = r.x; - top->y = r.y; - top->w = r.w; - top->h = cut; - } - - if (bottom) - { - bottom->x = r.x; - bottom->y = r.y + cut; - bottom->w = r.w; - bottom->h = r.h - cut; - } -} - -void ui2_hsplit_b(const RECT *original, float cut, RECT *top, RECT *bottom) -{ - RECT r = *original; - cut *= ui2_scale(); - - if (top) - { - top->x = r.x; - top->y = r.y; - top->w = r.w; - top->h = r.h - cut; - } - - if (bottom) - { - bottom->x = r.x; - bottom->y = r.y + r.h - cut; - bottom->w = r.w; - bottom->h = cut; - } -} - -void ui2_vsplit_l(const RECT *original, float cut, RECT *left, RECT *right) -{ - RECT r = *original; - cut *= ui2_scale(); - - if (left) - { - left->x = r.x; - left->y = r.y; - left->w = cut; - left->h = r.h; - } - - if (right) - { - right->x = r.x + cut; - right->y = r.y; - right->w = r.w - cut; - right->h = r.h; - } -} - -void ui2_vsplit_r(const RECT *original, float cut, RECT *left, RECT *right) -{ - RECT r = *original; - cut *= ui2_scale(); - - if (left) - { - left->x = r.x; - left->y = r.y; - left->w = r.w - cut; - left->h = r.h; - } - - if (right) - { - right->x = r.x + r.w - cut; - right->y = r.y; - right->w = cut; - right->h = r.h; - } -} - -void ui2_margin(const RECT *original, float cut, RECT *other_rect) -{ - RECT r = *original; - cut *= ui2_scale(); - - other_rect->x = r.x + cut; - other_rect->y = r.y + cut; - other_rect->w = r.w - 2*cut; - other_rect->h = r.h - 2*cut; -} - -void ui2_vmargin(const RECT *original, float cut, RECT *other_rect) -{ - RECT r = *original; - cut *= ui2_scale(); - - other_rect->x = r.x + cut; - other_rect->y = r.y; - other_rect->w = r.w - 2*cut; - other_rect->h = r.h; -} - -void ui2_hmargin(const RECT *original, float cut, RECT *other_rect) -{ - RECT r = *original; - cut *= ui2_scale(); - - other_rect->x = r.x; - other_rect->y = r.y + cut; - other_rect->w = r.w; - other_rect->h = r.h - 2*cut; -} - -typedef void (*ui2_draw_button_func)(const void *id, const char *text, int checked, const RECT *r, void *extra); - -int ui2_do_button(const void *id, const char *text, int checked, const RECT *r, ui2_draw_button_func draw_func, void *extra) -{ - /* logic */ - int ret = 0; - int inside = ui_mouse_inside(r->x,r->y,r->w,r->h); - - if(ui_active_item() == id) - { - if(!ui_mouse_button(0)) - { - if(inside) - ret = 1; - ui_set_active_item(0); - } - } - else if(ui_hot_item() == id) - { - if(ui_mouse_button(0)) - ui_set_active_item(id); - } - - if(inside) - ui_set_hot_item(id); - - if(draw_func) - draw_func(id, text, checked, r, extra); - return ret; -} - - -void ui2_do_label(const RECT *r, const char *text, float size, int align) -{ - gfx_blend_normal(); - size *= ui2_scale(); - if(align == 0) - { - float tw = gfx_pretty_text_width(size, text, -1); - gfx_pretty_text(r->x + r->w/2-tw/2, r->y, size, text, -1); - } - else if(align < 0) - gfx_pretty_text(r->x, r->y, size, text, -1); - else if(align > 0) - { - float tw = gfx_pretty_text_width(size, text, -1); - gfx_pretty_text(r->x + r->w-tw, r->y, size, text, -1); - } -} - - -extern void draw_round_rect_ext(float x, float y, float w, float h, float r, int corners); -extern void draw_round_rect(float x, float y, float w, float h, float r); - -static void ui2_draw_rect(const RECT *r, vec4 color, int corners, float rounding) -{ - gfx_texture_set(-1); - gfx_quads_begin(); - gfx_setcolor(color.r, color.g, color.b, color.a); - draw_round_rect_ext(r->x,r->y,r->w,r->h,rounding*ui2_scale(), corners); - gfx_quads_end(); -} - -static void ui2_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 <= 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(); -} - -static void ui2_draw_menu_button(const void *id, const char *text, int checked, const RECT *r, void *extra) -{ - ui2_draw_rect(r, vec4(1,1,1,0.5f), CORNER_ALL, 5.0f); - ui2_do_label(r, text, 24, 0); -} - - -static void ui2_draw_keyselect_button(const void *id, const char *text, int checked, const RECT *r, void *extra) -{ - ui2_draw_rect(r, vec4(1,1,1,0.5f), CORNER_ALL, 5.0f); - ui2_do_label(r, text, 18, 0); -} - -static void ui2_draw_menu_tab_button(const void *id, const char *text, int checked, const RECT *r, void *extra) -{ - if(checked) - ui2_draw_rect(r, color_tabbar_active, CORNER_T, 10.0f); - else - ui2_draw_rect(r, color_tabbar_inactive, CORNER_T, 10.0f); - ui2_do_label(r, text, 26, 0); -} - - -static void ui2_draw_settings_tab_button(const void *id, const char *text, int checked, const RECT *r, void *extra) -{ - if(checked) - ui2_draw_rect(r, color_tabbar_active, CORNER_R, 10.0f); - else - ui2_draw_rect(r, color_tabbar_inactive, CORNER_R, 10.0f); - ui2_do_label(r, text, 24, 0); -} - -static void ui2_draw_grid_header(const void *id, const char *text, int checked, const RECT *r, void *extra) -{ - if(checked) - ui2_draw_rect(r, vec4(1,1,1,0.5f), CORNER_T, 5.0f); - //else - // ui2_draw_rect(r, vec4(1,1,1,0.1f), CORNER_T, 5.0f); - RECT t; - ui2_vsplit_l(r, 5.0f, 0, &t); - ui2_do_label(&t, text, 18, -1); -} -/* -static void ui2_draw_grid_cell_l(const void *id, const char *text, int checked, const RECT *r, void *extra) -{ - ui2_do_label(r, text, 18, -1); -} - -static void ui2_draw_grid_cell_r(const void *id, const char *text, int checked, const RECT *r, void *extra) -{ - ui2_do_label(r, text, 18, 1); -}*/ - -static void ui2_draw_list_row(const void *id, const char *text, int checked, const RECT *r, void *extra) -{ - if(checked) - { - RECT sr = *r; - ui2_margin(&sr, 1.5f, &sr); - ui2_draw_rect(&sr, vec4(1,1,1,0.5f), CORNER_ALL, 4.0f); - } - ui2_do_label(r, text, 18, -1); -} - -static void ui2_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; - ui2_vsplit_l(&t, 5.0f, 0, &t); - - ui2_margin(&c, 2.0f, &c); - ui2_draw_rect(&c, vec4(1,1,1,0.25f), CORNER_ALL, 3.0f); - ui2_do_label(&c, boxtext, 16, 0); - ui2_do_label(&t, text, 18, -1); -} - -static void ui2_draw_checkbox(const void *id, const char *text, int checked, const RECT *r, void *extra) -{ - ui2_draw_checkbox_common(id, text, checked?"X":"", r); -} - -static void ui2_draw_checkbox_number(const void *id, const char *text, int checked, const RECT *r, void *extra) -{ - char buf[16]; - sprintf(buf, "%d", checked); - ui2_draw_checkbox_common(id, text, buf, r); -} - -int ui2_do_edit_box(void *id, const RECT *rect, char *str, int str_size, bool hidden=false) -{ - int inside = ui_mouse_inside(rect->x,rect->y,rect->w,rect->h); - int r = 0; - static int at_index = 0; - - if(ui_last_active_item() == id) - { - int c = inp_last_char(); - int k = inp_last_key(); - 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_pretty_text_width(18.0f, str, i) + 10 > mx_rel) - { - at_index = i - 1; - break; - } - - if (i == len) - at_index = len; - } - } - - 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 (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; - } - - int box_type; - if (ui_active_item() == id || ui_hot_item() == id || ui_last_active_item() == id) - box_type = GUI_BOX_SCREEN_INFO; - else - box_type = GUI_BOX_SCREEN_TEXTBOX; - - 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; - ui2_draw_rect(&textbox, vec4(1,1,1,0.5f), CORNER_ALL, 5.0f); - ui2_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; - } - - ui2_do_label(&textbox, display_str, 18, -1); - - if (ui_last_active_item() == id && !just_got_active) - { - float w = gfx_pretty_text_width(18.0f, display_str, at_index); - textbox.x += w*ui2_scale(); - ui2_do_label(&textbox, "_", 18, -1); - } - - return r; -} - -float ui2_do_scrollbar_v(const void *id, const RECT *rect, float current) -{ - RECT handle; - static float offset_y; - ui2_hsplit_t(rect, 33, &handle, 0); - - handle.y += (rect->h-handle.h)*current; - - /* logic */ - float ret = current; - int inside = ui_mouse_inside(handle.x,handle.y,handle.w,handle.h); - - 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; - ui2_vmargin(rect, 5.0f, &rail); - ui2_draw_rect(&rail, vec4(1,1,1,0.25f), 0, 0.0f); - - RECT slider = handle; - slider.w = rail.x-slider.x; - ui2_draw_rect(&slider, vec4(1,1,1,0.25f), CORNER_L, 2.5f); - slider.x = rail.x+rail.w; - ui2_draw_rect(&slider, vec4(1,1,1,0.25f), CORNER_R, 2.5f); - - slider = handle; - ui2_margin(&slider, 5.0f, &slider); - ui2_draw_rect(&slider, vec4(1,1,1,0.25f), CORNER_ALL, 2.5f); - - return ret; -} - - - -float ui2_do_scrollbar_h(const void *id, const RECT *rect, float current) -{ - RECT handle; - static float offset_x; - ui2_vsplit_l(rect, 33, &handle, 0); - - handle.x += (rect->w-handle.w)*current; - - /* logic */ - float ret = current; - int inside = ui_mouse_inside(handle.x,handle.y,handle.w,handle.h); - - 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; - ui2_hmargin(rect, 5.0f, &rail); - ui2_draw_rect(&rail, vec4(1,1,1,0.25f), 0, 0.0f); - - RECT slider = handle; - slider.h = rail.y-slider.y; - ui2_draw_rect(&slider, vec4(1,1,1,0.25f), CORNER_T, 2.5f); - slider.y = rail.y+rail.h; - ui2_draw_rect(&slider, vec4(1,1,1,0.25f), CORNER_B, 2.5f); - - slider = handle; - ui2_margin(&slider, 5.0f, &slider); - ui2_draw_rect(&slider, vec4(1,1,1,0.25f), CORNER_ALL, 2.5f); - - return ret; -} - -int ui2_do_key_reader(void *id, const RECT *rect, int key) -{ - // process - static bool mouse_released = true; - int inside = ui_mouse_inside(rect->x, rect->y, rect->w, rect->h); - int new_key = key; - - if(!ui_mouse_button(0)) - mouse_released = true; - - if(ui_active_item() == id) - { - int k = inp_last_key(); - if (k) - { - new_key = k; - ui_set_active_item(0); - mouse_released = false; - } - } - 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) - ui2_draw_keyselect_button(id, "???", 0, rect, 0); - else - ui2_draw_keyselect_button(id, inp_key_name(key), 0, rect, 0); - return new_key; -} - - -static int menu2_render_menubar(RECT r) -{ - RECT box = r; - RECT button; - - int active_page = config.ui_page; - int new_page = -1; - if(menu_game_active) - active_page = -1; - - if(client_state() == CLIENTSTATE_OFFLINE) - { - if(0) // this is not done yet - { - ui2_vsplit_l(&box, 90.0f, &button, &box); - static int news_button=0; - if (ui2_do_button(&news_button, "News", active_page==PAGE_NEWS, &button, ui2_draw_menu_tab_button, 0)) - new_page = PAGE_NEWS; - ui2_vsplit_l(&box, 30.0f, 0, &box); - } - } - else - { - ui2_vsplit_l(&box, 90.0f, &button, &box); - static int game_button=0; - if (ui2_do_button(&game_button, "Game", menu_game_active, &button, ui2_draw_menu_tab_button, 0)) - menu_game_active = true; - - ui2_vsplit_l(&box, 30.0f, 0, &box); - } - - ui2_vsplit_l(&box, 110.0f, &button, &box); - static int internet_button=0; - if (ui2_do_button(&internet_button, "Internet", active_page==PAGE_INTERNET, &button, ui2_draw_menu_tab_button, 0)) - { - client_serverbrowse_refresh(0); - new_page = PAGE_INTERNET; - } - - ui2_vsplit_l(&box, 4.0f, 0, &box); - ui2_vsplit_l(&box, 90.0f, &button, &box); - static int lan_button=0; - if (ui2_do_button(&lan_button, "LAN", active_page==PAGE_LAN, &button, ui2_draw_menu_tab_button, 0)) - { - client_serverbrowse_refresh(1); - new_page = PAGE_LAN; - } - - if(0) // this one is not done yet - { - ui2_vsplit_l(&box, 4.0f, 0, &box); - ui2_vsplit_l(&box, 120.0f, &button, &box); - static int favorites_button=0; - if (ui2_do_button(&favorites_button, "Favorites", active_page==PAGE_FAVORITES, &button, ui2_draw_menu_tab_button, 0)) - new_page = PAGE_FAVORITES; - } - - /* - ui2_vsplit_r(&box, 110.0f, &box, &button); - static int system_button=0; - if (ui2_do_button(&system_button, "System", config.ui_page==PAGE_SYSTEM, &button, ui2_draw_menu_tab_button, 0)) - config.ui_page = PAGE_SYSTEM; - - ui2_vsplit_r(&box, 30.0f, &box, 0); - */ - - ui2_vsplit_r(&box, 110.0f, &box, &button); - static int quit_button=0; - if (ui2_do_button(&quit_button, "Quit", 0, &button, ui2_draw_menu_tab_button, 0)) - popup = POPUP_QUIT; - - ui2_vsplit_r(&box, 10.0f, &box, &button); - ui2_vsplit_r(&box, 110.0f, &box, &button); - static int settings_button=0; - if (ui2_do_button(&settings_button, "Settings", active_page==PAGE_SETTINGS, &button, ui2_draw_menu_tab_button, 0)) - new_page = PAGE_SETTINGS; - - if(new_page != -1) - { - config.ui_page = new_page; - menu_game_active = false; - } - - return 0; -} - -static void menu2_render_background() -{ - //gfx_clear(0.65f,0.78f,0.9f); - gfx_clear(gui_color.r, gui_color.g, gui_color.b); - //gfx_clear(0.78f,0.9f,0.65f); - - 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(); -} - -static void menu2_render_serverbrowser(RECT main_view) -{ - ui2_draw_rect(&main_view, color_tabbar_active, CORNER_ALL, 10.0f); - - RECT view; - ui2_margin(&main_view, 10.0f, &view); - - RECT headers; - RECT filters; - RECT status; - RECT toolbox; - - //ui2_hsplit_t(&view, 20.0f, &status, &view); - ui2_hsplit_t(&view, 20.0f, &headers, &view); - ui2_hsplit_b(&view, 90.0f, &view, &filters); - ui2_hsplit_b(&view, 5.0f, &view, 0); - ui2_hsplit_b(&view, 20.0f, &view, &status); - - ui2_vsplit_r(&filters, 300.0f, &filters, &toolbox); - ui2_vsplit_r(&filters, 150.0f, &filters, 0); - - // split of the scrollbar - ui2_draw_rect(&headers, vec4(1,1,1,0.25f), CORNER_T, 5.0f); - ui2_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, - }; - - 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_PROGRESS, BROWSESORT_PROGRESSION, "%", 1, 20.0f, FIXED, {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) - { - ui2_vsplit_l(&headers, cols[i].width, &cols[i].rect, &headers); - - if(i+1 < num_cols) - { - //cols[i].flags |= SPACER; - ui2_vsplit_l(&headers, 2, &cols[i].spacer, &headers); - } - } - } - - for(int i = num_cols-1; i >= 0; i--) - { - if(cols[i].direction == 1) - { - ui2_vsplit_r(&headers, cols[i].width, &headers, &cols[i].rect); - ui2_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(ui2_do_button(cols[i].caption, cols[i].caption, config.b_sort == cols[i].sort, &cols[i].rect, ui2_draw_grid_header, 0)) - { - if(cols[i].sort != -1) - config.b_sort = cols[i].sort; - } - } - - ui2_draw_rect(&view, vec4(0,0,0,0.15f), 0, 0); - - RECT scroll; - ui2_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; - ui2_hmargin(&scroll, 5.0f, &scroll); - scrollvalue = ui2_do_scrollbar_v(&scrollbar, &scroll, scrollvalue); - - int start = (int)((num_servers-num)*scrollvalue); - if(start < 0) - start = 0; - - //int r = -1; - int new_selected = selected_index; - - for (int i = start, k = 0; i < num_servers && k < num; i++, k++) - { - int item_index = i; - SERVER_INFO *item = client_serverbrowse_sorted_get(item_index); - RECT row; - - int l = selected_index==item_index; - - if(l) - { - // selected server, draw the players on it - RECT whole; - int h = (item->num_players+2)/3; - - ui2_hsplit_t(&view, 25.0f+h*15.0f, &whole, &view); - - RECT r = whole; - ui2_margin(&r, 1.5f, &r); - ui2_draw_rect(&r, vec4(1,1,1,0.5f), CORNER_ALL, 4.0f); - - ui2_hsplit_t(&whole, 20.0f, &row, &whole); - ui2_vsplit_l(&whole, 50.0f, 0, &whole); - - for(int p = 0; p < item->num_players; p+=3) - { - RECT player_row; - RECT player_rect; - RECT player_score; - RECT player_name; - ui2_hsplit_t(&whole, 15.0f, &player_row, &whole); - - for(int a = 0; a < 3; a++) - { - if(p+a >= item->num_players) - break; - - ui2_vsplit_l(&player_row, 170.0f, &player_rect, &player_row); - ui2_vsplit_l(&player_rect, 30.0f, &player_score, &player_name); - ui2_vsplit_l(&player_name, 10.0f, 0, &player_name); - char buf[32]; - sprintf(buf, "%d", item->player_scores[p+a]); - ui2_do_label(&player_score, buf, 16.0f, 1); - ui2_do_label(&player_name, item->player_names[p+a], 16.0f, -1); - } - } - - k += h*3/4; - } - else - ui2_hsplit_t(&view, 20.0f, &row, &view); - - if(ui2_do_button(item, "", l, &row, 0, 0)) - { - new_selected = item_index; - dbg_msg("dbg", "addr = %s", item->address); - strncpy(config.ui_server_address, item->address, sizeof(config.ui_server_address)); - if(inp_mouse_doubleclick()) - client_connect(config.ui_server_address); - } - - 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 = ui2_do_button(item, "L", l, &button, ui2_draw_browse_icon, 0); - - if(id == COL_FLAGS) - { - if(item->flags&1) - ui2_draw_browse_icon(0x100, &button); - } - else if(id == COL_NAME) - ui2_do_label(&button, item->name, 20.0f, -1); - else if(id == COL_MAP) - ui2_do_label(&button, item->map, 20.0f, -1); - else if(id == COL_PLAYERS) - { - sprintf(temp, "%i/%i", item->num_players, item->max_players); - ui2_do_label(&button, temp, 20.0f, 1); - } - else if(id == COL_PING) - { - sprintf(temp, "%i", item->latency); - ui2_do_label(&button, temp, 20.0f, 1); - } - else if(id == COL_PROGRESS) - { - ui2_draw_browse_icon(item->progression, &button); - } - else if(id == COL_GAMETYPE) - { - const char *type = "???"; - if(item->game_type == GAMETYPE_DM) type = "DM"; - else if(item->game_type == GAMETYPE_TDM) type = "TDM"; - else if(item->game_type == GAMETYPE_CTF) type = "CTF"; - ui2_do_label(&button, type, 20.0f, 0); - } - /* - if(s) - { - new_selected = item_index; - dbg_msg("dbg", "addr = %s", item->address); - strncpy(config.ui_server_address, item->address, sizeof(config.ui_server_address)); - }*/ - } - } - - selected_index = new_selected; - - - // render quick search - RECT button; - ui2_hsplit_t(&filters, 20.0f, &button, &filters); - ui2_do_label(&button, "Quick search: ", 18, -1); - ui2_vsplit_l(&button, 95.0f, 0, &button); - ui2_do_edit_box(&config.b_filter_string, &button, config.b_filter_string, sizeof(config.b_filter_string)); - - // render filters - ui2_hsplit_t(&filters, 20.0f, &button, &filters); - if (ui2_do_button(&config.b_filter_empty, "Has people playing", config.b_filter_empty, &button, ui2_draw_checkbox, 0)) - config.b_filter_empty ^= 1; - - ui2_hsplit_t(&filters, 20.0f, &button, &filters); - if (ui2_do_button(&config.b_filter_full, "Server not full", config.b_filter_full, &button, ui2_draw_checkbox, 0)) - config.b_filter_full ^= 1; - - ui2_hsplit_t(&filters, 20.0f, &button, &filters); - if (ui2_do_button(&config.b_filter_pw, "Is not password protected", config.b_filter_pw, &button, ui2_draw_checkbox, 0)) - config.b_filter_pw ^= 1; - - - // render status - ui2_draw_rect(&status, vec4(1,1,1,0.25f), CORNER_B, 5.0f); - ui2_vmargin(&status, 50.0f, &status); - char buf[128]; - sprintf(buf, "%d of %d servers", client_serverbrowse_sorted_num(), client_serverbrowse_num()); - ui2_do_label(&status, buf, 18.0f, -1); - - // render toolbox - { - RECT buttons, button; - ui2_hsplit_b(&toolbox, 25.0f, &toolbox, &buttons); - - ui2_vsplit_r(&buttons, 100.0f, &buttons, &button); - ui2_vmargin(&button, 2.0f, &button); - static int join_button = 0; - if(ui2_do_button(&join_button, "Connect", 0, &button, ui2_draw_menu_button, 0)) - client_connect(config.ui_server_address); - - ui2_vsplit_r(&buttons, 20.0f, &buttons, &button); - ui2_vsplit_r(&buttons, 100.0f, &buttons, &button); - ui2_vmargin(&button, 2.0f, &button); - static int refresh_button = 0; - if(ui2_do_button(&refresh_button, "Refresh", 0, &button, ui2_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); - } - - ui2_hsplit_t(&toolbox, 20.0f, &button, &toolbox); - ui2_do_label(&button, "Host address:", 18, -1); - ui2_vsplit_l(&button, 100.0f, 0, &button); - ui2_do_edit_box(&config.ui_server_address, &button, config.ui_server_address, sizeof(config.ui_server_address)); - } -} - -static void menu2_render_settings_player(RECT main_view) -{ - RECT button; - RECT skinselection; - ui2_vsplit_l(&main_view, 300.0f, &main_view, &skinselection); - - - ui2_hsplit_t(&main_view, 20.0f, &button, &main_view); - - // render settings - { - ui2_hsplit_t(&main_view, 20.0f, &button, &main_view); - ui2_do_label(&button, "Name:", 18.0, -1); - ui2_vsplit_l(&button, 80.0f, 0, &button); - ui2_vsplit_l(&button, 180.0f, &button, 0); - ui2_do_edit_box(config.player_name, &button, config.player_name, sizeof(config.player_name)); - - ui2_hsplit_t(&main_view, 20.0f, &button, &main_view); - if (ui2_do_button(&config.cl_dynamic_camera, "Dynamic camera", config.cl_dynamic_camera, &button, ui2_draw_checkbox, 0)) - config.cl_dynamic_camera ^= 1; - - ui2_hsplit_t(&main_view, 20.0f, &button, &main_view); - if (ui2_do_button(&config.cl_autoswitch_weapons, "Switch weapon on pickup", config.cl_autoswitch_weapons, &button, ui2_draw_checkbox, 0)) - config.cl_autoswitch_weapons ^= 1; - - ui2_hsplit_t(&main_view, 20.0f, &button, &main_view); - if (ui2_do_button(&config.cl_nameplates, "Show name plates", config.cl_nameplates, &button, ui2_draw_checkbox, 0)) - config.cl_nameplates ^= 1; - - if(config.cl_nameplates) - { - ui2_hsplit_t(&main_view, 20.0f, &button, &main_view); - ui2_vsplit_l(&button, 15.0f, 0, &button); - if (ui2_do_button(&config.cl_nameplates_always, "Always show name plates", config.cl_nameplates_always, &button, ui2_draw_checkbox, 0)) - config.cl_nameplates_always ^= 1; - } - - ui2_hsplit_t(&main_view, 20.0f, &button, &main_view); - - ui2_hsplit_t(&main_view, 20.0f, &button, &main_view); - if (ui2_do_button(&config.player_color_body, "Custom colors", config.player_use_custom_color, &button, ui2_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; - ui2_hsplit_t(&main_view, 20.0f, &text, &main_view); - ui2_vsplit_l(&text, 15.0f, 0, &text); - ui2_do_label(&text, parts[i], 18, -1); - - int prevcolor = *colors[i]; - int color = 0; - for(int s = 0; s < 3; s++) - { - RECT text; - ui2_hsplit_t(&main_view, 19.0f, &button, &main_view); - ui2_vsplit_l(&button, 30.0f, 0, &button); - ui2_vsplit_l(&button, 30.0f, &text, &button); - ui2_vsplit_r(&button, 5.0f, &button, 0); - ui2_hsplit_t(&button, 4.0f, 0, &button); - - float k = ((prevcolor>>((2-s)*8))&0xff) / 255.0f; - k = ui2_do_scrollbar_h(&color_slider[i][s], &button, k); - color <<= 8; - color += clamp((int)(k*255), 0, 255); - ui2_do_label(&text, labels[s], 20, -1); - - } - - *colors[i] = color; - ui2_hsplit_t(&main_view, 5.0f, 0, &main_view); - } - } - } - - // draw header - RECT header, footer; - ui2_hsplit_t(&skinselection, 20, &header, &skinselection); - ui2_draw_rect(&header, vec4(1,1,1,0.25f), CORNER_T, 5.0f); - ui2_do_label(&header, "Skins", 18.0f, 0); - - // draw footers - ui2_hsplit_b(&skinselection, 20, &skinselection, &footer); - ui2_draw_rect(&footer, vec4(1,1,1,0.25f), CORNER_B, 5.0f); - ui2_vsplit_l(&footer, 10.0f, 0, &footer); - - // modes - ui2_draw_rect(&skinselection, vec4(0,0,0,0.15f), 0, 0); - - RECT scroll; - ui2_vsplit_r(&skinselection, 15, &skinselection, &scroll); - - RECT list = skinselection; - ui2_hsplit_t(&list, 50, &button, &list); - - int num = (int)(skinselection.h/button.h); - static float scrollvalue = 0; - static int scrollbar = 0; - ui2_hmargin(&scroll, 5.0f, &scroll); - scrollvalue = ui2_do_scrollbar_v(&scrollbar, &scroll, scrollvalue); - - int start = (int)((skin_num()-num)*scrollvalue); - if(start < 0) - start = 0; - - animstate state; - anim_eval(&data->animations[ANIM_BASE], 0, &state); - anim_eval_add(&state, &data->animations[ANIM_IDLE], 0, 1.0f); - //anim_eval_add(&state, &data->animations[ANIM_WALK], fmod(client_localtime(), 1.0f), 1.0f); - - for(int i = start; i < start+num && i < skin_num(); i++) - { - const skin *s = skin_get(i); - char buf[128]; - sprintf(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 = skin_get_color(config.player_color_body); - info.color_feet = skin_get_color(config.player_color_feet); - info.texture = s->color_texture; - } - - info.size = ui2_scale()*50.0f; - - RECT icon; - RECT text; - ui2_vsplit_l(&button, 50.0f, &icon, &text); - - if(ui2_do_button(s, "", selected, &button, ui2_draw_list_row, 0)) - config_set_player_skin(&config, s->name); - - ui2_hsplit_t(&text, 12.0f, 0, &text); // some margin from the top - ui2_do_label(&text, buf, 24, 0); - - ui2_hsplit_t(&icon, 5.0f, 0, &icon); // some margin from the top - render_tee(&state, &info, 0, vec2(1, 0), vec2(icon.x+icon.w/2, icon.y+icon.h/2)); - - ui2_hsplit_t(&list, 50, &button, &list); - } -} - -typedef void (*assign_func_callback)(CONFIGURATION *config, int value); - -static void menu2_render_settings_controls(RECT main_view) -{ - ui2_vsplit_l(&main_view, 300.0f, &main_view, 0); - - { - RECT button, label; - ui2_hsplit_t(&main_view, 20.0f, &button, &main_view); - ui2_vsplit_l(&button, 110.0f, &label, &button); - ui2_do_label(&label, "Mouse sens.", 18.0f, -1); - ui2_hmargin(&button, 2.0f, &button); - config.inp_mousesens = (int)(ui2_do_scrollbar_h(&config.inp_mousesens, &button, config.inp_mousesens/500.0f)*500.0f); - //*key.key = ui2_do_key_reader(key.key, &button, *key.key); - ui2_hsplit_t(&main_view, 20.0f, 0, &main_view); - } - - typedef struct - { - char name[32]; - int *key; - } KEYINFO; - - const KEYINFO keys[] = - { - { "Move Left:", &config.key_move_left }, - { "Move Right:", &config.key_move_right }, - { "Jump:", &config.key_jump }, - { "Fire:", &config.key_fire }, - { "Hook:", &config.key_hook }, - { "Hammer:", &config.key_weapon1 }, - { "Pistol:", &config.key_weapon2 }, - { "Shotgun:", &config.key_weapon3 }, - { "Grenade:", &config.key_weapon4 }, - { "Next Weapon:", &config.key_next_weapon }, - { "Prev. Weapon:", &config.key_prev_weapon }, - { "Emoticon:", &config.key_emoticon }, - { "Chat:", &config.key_chat }, - { "Team Chat:", &config.key_teamchat }, - { "Console:", &config.key_console }, - { "Remote Console:", &config.key_remoteconsole }, - { "Screenshot:", &config.key_screenshot }, - }; - - const int key_count = sizeof(keys) / sizeof(KEYINFO); - - for (int i = 0; i < key_count; i++) - { - KEYINFO key = keys[i]; - RECT button, label; - ui2_hsplit_t(&main_view, 20.0f, &button, &main_view); - ui2_vsplit_l(&button, 110.0f, &label, &button); - - ui2_do_label(&label, key.name, 18.0f, -1); - *key.key = ui2_do_key_reader(key.key, &button, *key.key); - ui2_hsplit_t(&main_view, 5.0f, 0, &main_view); - } -} - -static void menu2_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; - ui2_vsplit_l(&main_view, 300.0f, &main_view, &modelist); - - // draw allmodes switch - RECT header, footer; - ui2_hsplit_t(&modelist, 20, &button, &modelist); - if (ui2_do_button(&config.gfx_display_all_modes, "Show only supported", config.gfx_display_all_modes^1, &button, ui2_draw_checkbox, 0)) - { - config.gfx_display_all_modes ^= 1; - num_modes = gfx_get_video_modes(modes, MAX_RESOLUTIONS); - } - - // draw header - ui2_hsplit_t(&modelist, 20, &header, &modelist); - ui2_draw_rect(&header, vec4(1,1,1,0.25f), CORNER_T, 5.0f); - ui2_do_label(&header, "Display Modes", 18.0f, 0); - - // draw footers - ui2_hsplit_b(&modelist, 20, &modelist, &footer); - sprintf(buf, "Current: %dx%d %d bit", config.gfx_screen_width, config.gfx_screen_height, config.gfx_color_depth); - ui2_draw_rect(&footer, vec4(1,1,1,0.25f), CORNER_B, 5.0f); - ui2_vsplit_l(&footer, 10.0f, 0, &footer); - ui2_do_label(&footer, buf, 18.0f, -1); - - // modes - ui2_draw_rect(&modelist, vec4(0,0,0,0.15f), 0, 0); - - RECT scroll; - ui2_vsplit_r(&modelist, 15, &modelist, &scroll); - - RECT list = modelist; - ui2_hsplit_t(&list, 20, &button, &list); - - int num = (int)(modelist.h/button.h); - static float scrollvalue = 0; - static int scrollbar = 0; - ui2_hmargin(&scroll, 5.0f, &scroll); - scrollvalue = ui2_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; - } - - sprintf(buf, " %dx%d %d bit", modes[i].width, modes[i].height, depth); - if(ui2_do_button(&modes[i], buf, selected, &button, ui2_draw_list_row, 0)) - { - config.gfx_color_depth = depth; - config.gfx_screen_width = modes[i].width; - config.gfx_screen_height = modes[i].height; - } - - ui2_hsplit_t(&list, 20, &button, &list); - } - - - // switches - ui2_hsplit_t(&main_view, 20.0f, &button, &main_view); - if (ui2_do_button(&config.gfx_fullscreen, "Fullscreen", config.gfx_fullscreen, &button, ui2_draw_checkbox, 0)) - config.gfx_fullscreen ^= 1; - - ui2_hsplit_t(&main_view, 20.0f, &button, &main_view); - if (ui2_do_button(&config.gfx_vsync, "V-Sync", config.gfx_vsync, &button, ui2_draw_checkbox, 0)) - config.gfx_vsync ^= 1; - - ui2_hsplit_t(&main_view, 20.0f, &button, &main_view); - if (ui2_do_button(&config.gfx_fsaa_samples, "FSAA samples", config.gfx_fsaa_samples, &button, ui2_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; - } - - ui2_hsplit_t(&main_view, 40.0f, &button, &main_view); - ui2_hsplit_t(&main_view, 20.0f, &button, &main_view); - if (ui2_do_button(&config.gfx_texture_quality, "Quality Textures", config.gfx_texture_quality, &button, ui2_draw_checkbox, 0)) - config.gfx_texture_quality ^= 1; - - ui2_hsplit_t(&main_view, 20.0f, &button, &main_view); - if (ui2_do_button(&config.gfx_texture_compression, "Texture Compression", config.gfx_texture_compression, &button, ui2_draw_checkbox, 0)) - config.gfx_texture_compression ^= 1; - - ui2_hsplit_t(&main_view, 20.0f, &button, &main_view); - if (ui2_do_button(&config.gfx_high_detail, "High Detail", config.gfx_high_detail, &button, ui2_draw_checkbox, 0)) - config.gfx_high_detail ^= 1; - - // - - RECT text; - ui2_hsplit_t(&main_view, 20.0f, 0, &main_view); - ui2_hsplit_t(&main_view, 20.0f, &text, &main_view); - //ui2_vsplit_l(&text, 15.0f, 0, &text); - ui2_do_label(&text, "UI Color", 18, -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; - ui2_hsplit_t(&main_view, 19.0f, &button, &main_view); - ui2_vmargin(&button, 15.0f, &button); - ui2_vsplit_l(&button, 30.0f, &text, &button); - ui2_vsplit_r(&button, 5.0f, &button, 0); - ui2_hsplit_t(&button, 4.0f, 0, &button); - - float k = (*color_slider[s]) / 255.0f; - k = ui2_do_scrollbar_h(color_slider[s], &button, k); - *color_slider[s] = (int)(k*255.0f); - ui2_do_label(&text, labels[s], 20, -1); - } -} - -static void menu2_render_settings_sound(RECT main_view) -{ - RECT button; - ui2_vsplit_l(&main_view, 300.0f, &main_view, 0); - - ui2_hsplit_t(&main_view, 20.0f, &button, &main_view); - if (ui2_do_button(&config.snd_enable, "Use Sounds", config.snd_enable, &button, ui2_draw_checkbox, 0)) - config.snd_enable ^= 1; - - if(!config.snd_enable) - return; - - // sample rate box - { - char buf[64]; - sprintf(buf, "%d", config.snd_rate); - ui2_hsplit_t(&main_view, 20.0f, &button, &main_view); - ui2_do_label(&button, "Sample Rate", 18.0, -1); - ui2_vsplit_l(&button, 110.0f, 0, &button); - ui2_vsplit_l(&button, 180.0f, &button, 0); - ui2_do_edit_box(&config.snd_rate, &button, buf, sizeof(buf)); - config.snd_rate = atoi(buf); - - if(config.snd_rate < 1) - config.snd_rate = 1; - } - - // volume slider - { - RECT button, label; - ui2_hsplit_t(&main_view, 5.0f, &button, &main_view); - ui2_hsplit_t(&main_view, 20.0f, &button, &main_view); - ui2_vsplit_l(&button, 110.0f, &label, &button); - ui2_hmargin(&button, 2.0f, &button); - ui2_do_label(&label, "Sound Volume", 18.0f, -1); - config.snd_volume = (int)(ui2_do_scrollbar_h(&config.snd_volume, &button, config.snd_volume/100.0f)*100.0f); - ui2_hsplit_t(&main_view, 20.0f, 0, &main_view); - } -} - - -static void menu2_render_settings_network(RECT main_view) -{ - RECT button; - ui2_vsplit_l(&main_view, 300.0f, &main_view, 0); - - { - ui2_hsplit_t(&main_view, 20.0f, &button, &main_view); - ui2_do_label(&button, "Rcon Password", 18.0, -1); - ui2_vsplit_l(&button, 110.0f, 0, &button); - ui2_vsplit_l(&button, 180.0f, &button, 0); - ui2_do_edit_box(&config.rcon_password, &button, config.rcon_password, sizeof(config.rcon_password), true); - } -} - -static void menu2_render_settings(RECT main_view) -{ - static int settings_page = 0; - - // render background - RECT temp, tabbar; - ui2_vsplit_r(&main_view, 120.0f, &main_view, &tabbar); - ui2_draw_rect(&main_view, color_tabbar_active, CORNER_B|CORNER_TL, 10.0f); - ui2_hsplit_t(&tabbar, 50.0f, &temp, &tabbar); - ui2_draw_rect(&temp, color_tabbar_active, CORNER_R, 10.0f); - - ui2_hsplit_t(&main_view, 10.0f, 0, &main_view); - - RECT button; - - const char *tabs[] = {"Player", "Controls", "Network", "Graphics", "Sound"}; - int num_tabs = (int)(sizeof(tabs)/sizeof(*tabs)); - - for(int i = 0; i < num_tabs; i++) - { - ui2_hsplit_t(&tabbar, 10, &button, &tabbar); - ui2_hsplit_t(&tabbar, 26, &button, &tabbar); - if(ui2_do_button(tabs[i], tabs[i], settings_page == i, &button, ui2_draw_settings_tab_button, 0)) - settings_page = i; - } - - ui2_margin(&main_view, 10.0f, &main_view); - - if(settings_page == 0) - menu2_render_settings_player(main_view); - else if(settings_page == 1) - menu2_render_settings_controls(main_view); - else if(settings_page == 2) - menu2_render_settings_network(main_view); - else if(settings_page == 3) - menu2_render_settings_graphics(main_view); - else if(settings_page == 4) - menu2_render_settings_sound(main_view); -} - -static void menu2_render_news(RECT main_view) -{ - ui2_draw_rect(&main_view, color_tabbar_active, CORNER_ALL, 10.0f); -} - - -static void menu2_render_game(RECT main_view) -{ - RECT button; - ui2_hsplit_t(&main_view, 45.0f, &main_view, 0); - ui2_draw_rect(&main_view, color_tabbar_active, CORNER_ALL, 10.0f); - - ui2_hsplit_t(&main_view, 10.0f, 0, &main_view); - ui2_hsplit_t(&main_view, 25.0f, &main_view, 0); - ui2_vmargin(&main_view, 10.0f, &main_view); - - ui2_vsplit_r(&main_view, 120.0f, &main_view, &button); - static int disconnect_button = 0; - if(ui2_do_button(&disconnect_button, "Disconnect", 0, &button, ui2_draw_menu_button, 0)) - client_disconnect(); - - if(local_info) - { - if(local_info->team != -1) - { - ui2_vsplit_l(&main_view, 10.0f, &button, &main_view); - ui2_vsplit_l(&main_view, 120.0f, &button, &main_view); - static int spectate_button = 0; - if(ui2_do_button(&spectate_button, "Spectate", 0, &button, ui2_draw_menu_button, 0)) - { - config.cl_team = -1; - menu_active = false; - } - } - - if(local_info->team != 0) - { - ui2_vsplit_l(&main_view, 10.0f, &button, &main_view); - ui2_vsplit_l(&main_view, 120.0f, &button, &main_view); - static int spectate_button = 0; - if(ui2_do_button(&spectate_button, "Join Red", 0, &button, ui2_draw_menu_button, 0)) - { - config.cl_team = 0; - menu_active = false; - } - } - - if(local_info->team != 1) - { - ui2_vsplit_l(&main_view, 10.0f, &button, &main_view); - ui2_vsplit_l(&main_view, 120.0f, &button, &main_view); - static int spectate_button = 0; - if(ui2_do_button(&spectate_button, "Join Blue", 0, &button, ui2_draw_menu_button, 0)) - { - config.cl_team = 1; - menu_active = false; - } - } - } -} - -void menu_do_disconnected() -{ - 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; - } -} - -void menu_do_connecting() -{ - popup = POPUP_CONNECTING; -} - -void menu_do_connected() -{ - popup = POPUP_NONE; -} - -int menu2_render() -{ - if(0) - { - gfx_mapscreen(0,0,10*4/3.0f,10); - gfx_clear(gui_color.r, gui_color.g, gui_color.b); - - animstate state; - anim_eval(&data->animations[ANIM_BASE], 0, &state); - anim_eval_add(&state, &data->animations[ANIM_IDLE], 0, 1.0f); - //anim_eval_add(&state, &data->animations[ANIM_WALK], fmod(client_localtime(), 1.0f), 1.0f); - - for(int i = 0; i < skin_num(); i++) - { - float x = (i/8)*3; - float y = (i%8); - for(int c = 0; c < 2; c++) - { - //int colors[2] = {54090, 10998628}; - //int colors[2] = {65432, 9895832}; // NEW - int colors[2] = {65387, 10223467}; // NEW - - tee_render_info info; - info.texture = skin_get(i)->color_texture; - info.color_feet = info.color_body = skin_get_color(colors[c]); - //info.color_feet = info.color_body = vec4(1,1,1,1); - info.size = 1.0f; //ui2_scale()*16.0f; - //render_tee(&state, &info, 0, vec2(sinf(client_localtime()*3), cosf(client_localtime()*3)), vec2(1+x+c,1+y)); - render_tee(&state, &info, 0, vec2(1,0), vec2(1+x+c,1+y)); - } - } - - return 0; - } - - RECT screen = *ui2_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 - { - menu2_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 - ui2_margin(&screen, 10.0f, &screen); - - if(popup == POPUP_NONE) - { - // do tab bar - ui2_hsplit_t(&screen, 26.0f, &tab_bar, &main_view); - ui2_vmargin(&tab_bar, 20.0f, &tab_bar); - menu2_render_menubar(tab_bar); - - // render current page - if(menu_game_active) - menu2_render_game(main_view); - else if(config.ui_page == PAGE_NEWS) - menu2_render_news(main_view); - else if(config.ui_page == PAGE_INTERNET) - menu2_render_serverbrowser(main_view); - else if(config.ui_page == PAGE_LAN) - menu2_render_serverbrowser(main_view); - else if(config.ui_page == PAGE_FAVORITES) - menu2_render_serverbrowser(main_view); - else if(config.ui_page == PAGE_SETTINGS) - menu2_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); - const char *title = ""; - const char *extra_text = ""; - const char *button_text = ""; - - if(popup == POPUP_CONNECTING) - { - title = "Connecting to"; - extra_text = config.ui_server_address; // TODO: query the client about the address - button_text = "Abort"; - } - else if(popup == POPUP_DISCONNECTED) - { - title = "Disconnected"; - extra_text = client_error_string(); - button_text = "Ok"; - } - 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?"; - } - - - RECT box, part; - box = screen; - ui2_vmargin(&box, 150.0f, &box); - ui2_hmargin(&box, 150.0f, &box); - - // render the box - ui2_draw_rect(&box, vec4(0,0,0,0.5f), CORNER_ALL, 15.0f); - - ui2_hsplit_t(&box, 20.f, &part, &box); - ui2_hsplit_t(&box, 24.f, &part, &box); - ui2_do_label(&part, title, 24.f, 0); - ui2_hsplit_t(&box, 20.f, &part, &box); - ui2_hsplit_t(&box, 24.f, &part, &box); - ui2_do_label(&part, extra_text, 20.f, 0); - - if(popup == POPUP_QUIT) - { - RECT tryagain, abort; - ui2_hsplit_b(&box, 20.f, &box, &part); - ui2_hsplit_b(&box, 24.f, &box, &part); - ui2_vmargin(&part, 120.0f, &part); - - ui2_vsplit_l(&part, 100.0f, &abort, &part); - ui2_vsplit_r(&part, 100.0f, 0, &tryagain); - - static int button_abort = 0; - if(ui2_do_button(&button_abort, "No", 0, &abort, ui2_draw_menu_button, 0) || inp_key_down(KEY_ESC)) - popup = POPUP_NONE; - - static int button_tryagain = 0; - if(ui2_do_button(&button_tryagain, "Yes", 0, &tryagain, ui2_draw_menu_button, 0) || inp_key_down(KEY_ENTER)) - client_quit(); - } - else if(popup == POPUP_PASSWORD) - { - RECT label, textbox, tryagain, abort; - - ui2_hsplit_b(&box, 20.f, &box, &part); - ui2_hsplit_b(&box, 24.f, &box, &part); - ui2_vmargin(&part, 120.0f, &part); - - ui2_vsplit_l(&part, 100.0f, &abort, &part); - ui2_vsplit_r(&part, 100.0f, 0, &tryagain); - - static int button_abort = 0; - if(ui2_do_button(&button_abort, "Abort", 0, &abort, ui2_draw_menu_button, 0) || inp_key_down(KEY_ESC)) - popup = POPUP_NONE; - - static int button_tryagain = 0; - if(ui2_do_button(&button_tryagain, "Try again", 0, &tryagain, ui2_draw_menu_button, 0) || inp_key_down(KEY_ENTER)) - { - client_connect(config.ui_server_address); - } - - ui2_hsplit_b(&box, 60.f, &box, &part); - ui2_hsplit_b(&box, 24.f, &box, &part); - - ui2_vsplit_l(&part, 60.0f, 0, &label); - ui2_vsplit_l(&label, 100.0f, 0, &textbox); - ui2_vsplit_l(&textbox, 20.0f, 0, &textbox); - ui2_vsplit_r(&textbox, 60.0f, &textbox, 0); - ui2_do_label(&label, "Password:", 20, -1); - ui2_do_edit_box(&config.password, &textbox, config.password, sizeof(config.password), true); - } - else - { - ui2_hsplit_b(&box, 20.f, &box, &part); - ui2_hsplit_b(&box, 24.f, &box, &part); - ui2_vmargin(&part, 120.0f, &part); - - static int button = 0; - if(ui2_do_button(&button, button_text, 0, &part, ui2_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 modmenu_render() -{ - static int mouse_x = 0; - static int mouse_y = 0; - - // 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); - - color_ingame_scale_i = 0.5f; - 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); - - - // handle mouse movement - float mx, my; - { - int rx, ry; - inp_mouse_relative(&rx, &ry); - mouse_x += rx; - mouse_y += ry; - if(mouse_x < 0) mouse_x = 0; - if(mouse_y < 0) mouse_y = 0; - if(mouse_x > gfx_screenwidth()) mouse_x = gfx_screenwidth(); - if(mouse_y > gfx_screenheight()) mouse_y = gfx_screenheight(); - - // update the ui - RECT *screen = ui2_screen(); - mx = (mouse_x/(float)gfx_screenwidth())*screen->w; - my = (mouse_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); - } - - menu2_render(); - - 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(); - - inp_clear(); -} diff --git a/src/game/g_game.cpp b/src/game/g_game.cpp new file mode 100644 index 00000000..e5b36ea0 --- /dev/null +++ b/src/game/g_game.cpp @@ -0,0 +1,411 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +#include "g_game.h" + +// TODO: OPT: rewrite this smarter! +void move_point(vec2 *inout_pos, vec2 *inout_vel, float elasticity, int *bounces) +{ + if(bounces) + *bounces = 0; + + vec2 pos = *inout_pos; + vec2 vel = *inout_vel; + if(col_check_point(pos + vel)) + { + int affected = 0; + if(col_check_point(pos.x + vel.x, pos.y)) + { + inout_vel->x *= -elasticity; + if(bounces) + (*bounces)++; + affected++; + } + + if(col_check_point(pos.x, pos.y + vel.y)) + { + inout_vel->y *= -elasticity; + if(bounces) + (*bounces)++; + affected++; + } + + if(affected == 0) + { + inout_vel->x *= -elasticity; + inout_vel->y *= -elasticity; + } + } + else + { + *inout_pos = pos + vel; + } +} + +bool test_box(vec2 pos, vec2 size) +{ + size *= 0.5f; + if(col_check_point(pos.x-size.x, pos.y-size.y)) + return true; + if(col_check_point(pos.x+size.x, pos.y-size.y)) + return true; + if(col_check_point(pos.x-size.x, pos.y+size.y)) + return true; + if(col_check_point(pos.x+size.x, pos.y+size.y)) + return true; + return false; +} + +void move_box(vec2 *inout_pos, vec2 *inout_vel, vec2 size, float elasticity) +{ + // do the move + vec2 pos = *inout_pos; + vec2 vel = *inout_vel; + + float distance = length(vel); + int max = (int)distance; + + if(distance > 0.00001f) + { + //vec2 old_pos = pos; + float fraction = 1.0f/(float)(max+1); + for(int i = 0; i <= max; i++) + { + //float amount = i/(float)max; + //if(max == 0) + //amount = 0; + + vec2 new_pos = pos + vel*fraction; // TODO: this row is not nice + + if(test_box(vec2(new_pos.x, new_pos.y), size)) + { + int hits = 0; + + if(test_box(vec2(pos.x, new_pos.y), size)) + { + new_pos.y = pos.y; + vel.y *= -elasticity; + hits++; + } + + if(test_box(vec2(new_pos.x, pos.y), size)) + { + new_pos.x = pos.x; + vel.x *= -elasticity; + hits++; + } + + // neither of the tests got a collision. + // this is a real _corner case_! + if(hits == 0) + { + new_pos.y = pos.y; + vel.y *= -elasticity; + new_pos.x = pos.x; + vel.x *= -elasticity; + } + } + + pos = new_pos; + } + } + + *inout_pos = pos; + *inout_vel = vel; +} + +void player_core::tick() +{ + float phys_size = 28.0f; + triggered_events = 0; + + #define MACRO_CHECK_VELOCITY { dbg_assert(length(vel) < 1000.0f, "velocity error"); } + + MACRO_CHECK_VELOCITY + + bool grounded = false; + if(col_check_point((int)(pos.x+phys_size/2), (int)(pos.y+phys_size/2+5))) + grounded = true; + if(col_check_point((int)(pos.x-phys_size/2), (int)(pos.y+phys_size/2+5))) + grounded = true; + + vec2 direction = normalize(vec2(input.target_x, input.target_y)); + + vel.y += gravity; + + MACRO_CHECK_VELOCITY + + float max_speed = grounded ? ground_control_speed : air_control_speed; + float accel = grounded ? ground_control_accel : air_control_accel; + float friction = grounded ? ground_friction : air_friction; + + // handle movement + if(input.left) + vel.x = saturated_add(-max_speed, max_speed, vel.x, -accel); + if(input.right) + vel.x = saturated_add(-max_speed, max_speed, vel.x, accel); + + MACRO_CHECK_VELOCITY + + if(!input.left && !input.right) + vel.x *= friction; + + MACRO_CHECK_VELOCITY + + // handle jumping + // 1 bit = to keep track if a jump has been made on this input + // 2 bit = to keep track if a air-jump has been made + if(grounded) + jumped &= ~2; + + if(input.jump) + { + if(!(jumped&1)) + { + if(grounded) + { + triggered_events |= COREEVENT_GROUND_JUMP; + vel.y = -ground_jump_speed; + jumped |= 1; + } + else if(!(jumped&2)) + { + triggered_events |= COREEVENT_AIR_JUMP; + vel.y = -ground_air_speed; + jumped |= 3; + } + } + } + else + jumped &= ~1; + + MACRO_CHECK_VELOCITY + + // do hook + if(input.hook) + { + if(hook_state == HOOK_IDLE) + { + hook_state = HOOK_FLYING; + hook_pos = pos; + hook_dir = direction; + hooked_player = -1; + hook_tick = 0; + triggered_events |= COREEVENT_HOOK_LAUNCH; + } + else if(hook_state == HOOK_FLYING) + { + vec2 new_pos = hook_pos+hook_dir*hook_fire_speed; + + // Check against other players first + for(int i = 0; i < MAX_CLIENTS; i++) + { + player_core *p = world->players[i]; + if(!p || p == this) + continue; + + //if(p != this && !p->dead && distance(p->pos, new_pos) < p->phys_size) + if(distance(p->pos, new_pos) < phys_size) + { + triggered_events |= COREEVENT_HOOK_ATTACH_PLAYER; + hook_state = HOOK_GRABBED; + hooked_player = i; + break; + } + } + /* + for(entity *ent = world.first_entity; ent; ent = ent->next_entity) + { + if(ent && ent->objtype == OBJTYPE_PLAYER) + { + player *p = (player*)ent; + if(p != this && !p->dead && distance(p->pos, new_pos) < p->phys_size) + { + hook_state = HOOK_GRABBED; + hooked_player = p; + break; + } + } + }*/ + + if(hook_state == HOOK_FLYING) + { + // check against ground + if(col_intersect_line(hook_pos, new_pos, &new_pos)) + { + triggered_events |= COREEVENT_HOOK_ATTACH_GROUND; + hook_state = HOOK_GRABBED; + hook_pos = new_pos; + } + else if(distance(pos, new_pos) > hook_length) + { + triggered_events |= COREEVENT_HOOK_RETRACT; + hook_state = HOOK_RETRACTED; + } + else + hook_pos = new_pos; + } + + if(hook_state == HOOK_GRABBED) + { + //create_sound(pos, SOUND_HOOK_ATTACH); + //hook_tick = server_tick(); + } + } + } + else + { + //release_hooked(); + hooked_player = -1; + hook_state = HOOK_IDLE; + hook_pos = pos; + } + + if(hook_state == HOOK_GRABBED) + { + if(hooked_player != -1) + { + player_core *p = world->players[hooked_player]; + if(p) + hook_pos = p->pos; + else + { + // release hook + hooked_player = -1; + hook_state = HOOK_RETRACTED; + hook_pos = pos; + } + + // keep players hooked for a max of 1.5sec + //if(server_tick() > hook_tick+(server_tickspeed()*3)/2) + //release_hooked(); + } + + // Old version feels much better (to me atleast) + if(distance(hook_pos, pos) > 46.0f) + { + vec2 hookvel = normalize(hook_pos-pos)*hook_drag_accel; + // the hook as more power to drag you up then down. + // this makes it easier to get on top of an platform + if(hookvel.y > 0) + hookvel.y *= 0.3f; + + // the hook will boost it's power if the player wants to move + // in that direction. otherwise it will dampen everything abit + if((hookvel.x < 0 && input.left) || (hookvel.x > 0 && input.right)) + hookvel.x *= 0.95f; + else + hookvel.x *= 0.75f; + + vec2 new_vel = vel+hookvel; + + // check if we are under the legal limit for the hook + if(length(new_vel) < hook_drag_speed || length(new_vel) < length(vel)) + vel = new_vel; // no problem. apply + + } + + // release hook + hook_tick++; + if(hooked_player != -1 && hook_tick > SERVER_TICK_SPEED*2) + { + hooked_player = -1; + hook_state = HOOK_RETRACTED; + hook_pos = pos; + } + } + + MACRO_CHECK_VELOCITY + + if(true) + { + for(int i = 0; i < MAX_CLIENTS; i++) + { + player_core *p = world->players[i]; + if(!p) + continue; + + //player *p = (player*)ent; + if(p == this) // || !(p->flags&FLAG_ALIVE) + continue; // make sure that we don't nudge our self + + // handle player <-> player collision + float d = distance(pos, p->pos); + vec2 dir = normalize(pos - p->pos); + if(d < phys_size*1.25f && d > 1.0f) + { + float a = phys_size*1.25f - d; + vel = vel + dir*a; + } + + MACRO_CHECK_VELOCITY + + // handle hook influence + if(hooked_player == i) + { + if(d > phys_size*1.50f) // TODO: fix tweakable variable + { + float accel = hook_drag_accel * (d/hook_length); + vel.x = saturated_add(-hook_drag_speed, hook_drag_speed, vel.x, -accel*dir.x); + vel.y = saturated_add(-hook_drag_speed, hook_drag_speed, vel.y, -accel*dir.y); + + MACRO_CHECK_VELOCITY + } + } + } + } +} + +void player_core::move() +{ + move_box(&pos, &vel, vec2(28.0f, 28.0f), 0); +} + +void player_core::write(obj_player_core *obj_core) +{ + obj_core->x = (int)pos.x; + obj_core->y = (int)pos.y; + obj_core->vx = (int)(vel.x*256.0f); + obj_core->vy = (int)(vel.y*256.0f); + obj_core->hook_state = hook_state; + obj_core->hook_tick = hook_tick; + obj_core->hook_x = (int)hook_pos.x; + obj_core->hook_y = (int)hook_pos.y; + obj_core->hook_dx = (int)(hook_dir.x*256.0f); + obj_core->hook_dy = (int)(hook_dir.y*256.0f); + obj_core->hooked_player = hooked_player; + obj_core->jumped = jumped; + + float a = 0; + if(input.target_x == 0) + a = atan((float)input.target_y); + else + a = atan((float)input.target_y/(float)input.target_x); + + if(input.target_x < 0) + a = a+pi; + + obj_core->angle = (int)(a*256.0f); +} + +void player_core::read(const obj_player_core *obj_core) +{ + pos.x = obj_core->x; + pos.y = obj_core->y; + vel.x = obj_core->vx/256.0f; + vel.y = obj_core->vy/256.0f; + hook_state = obj_core->hook_state; + hook_tick = obj_core->hook_tick; + hook_pos.x = obj_core->hook_x; + hook_pos.y = obj_core->hook_y; + hook_dir.x = obj_core->hook_dx/256.0f; + hook_dir.y = obj_core->hook_dy/256.0f; + hooked_player = obj_core->hooked_player; + jumped = obj_core->jumped; +} + +void player_core::quantize() +{ + obj_player_core c; + write(&c); + read(&c); +} + diff --git a/src/game/g_game.h b/src/game/g_game.h new file mode 100644 index 00000000..70a15a19 --- /dev/null +++ b/src/game/g_game.h @@ -0,0 +1,171 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +#include +#include +#include +#include "../engine/e_interface.h" +#include "g_mapres_col.h" + +#include "g_protocol.h" + +inline vec2 get_direction(int angle) +{ + float a = angle/256.0f; + return vec2(cosf(a), sinf(a)); +} + +inline vec2 get_dir(float a) +{ + return vec2(cosf(a), sinf(a)); +} + +inline float get_angle(vec2 dir) +{ + float a = atan(dir.y/dir.x); + if(dir.x < 0) + a = a+pi; + return a; +} + + +inline vec2 calc_pos(vec2 p, vec2 v, float gravity, float t) +{ + vec2 n; + n.x = p.x + v.x*t; + n.y = p.y + v.y*t - gravity*(t*t); + return n; +} + + +template +inline T saturated_add(T min, T max, T current, T modifier) +{ + if(modifier < 0) + { + if(current < min) + return current; + current += modifier; + if(current < min) + current = min; + return current; + } + else + { + if(current > max) + return current; + current += modifier; + if(current > max) + current = max; + return current; + } +} + +void move_point(vec2 *inout_pos, vec2 *inout_vel, float elasticity, int *bounces); +void move_box(vec2 *inout_pos, vec2 *inout_vel, vec2 size, float elasticity); +bool test_box(vec2 pos, vec2 size); + + +// hooking stuff +enum +{ + HOOK_RETRACTED=-1, + HOOK_IDLE=0, + HOOK_FLYING, + HOOK_GRABBED, + + COREEVENT_GROUND_JUMP=0x01, + COREEVENT_AIR_JUMP=0x02, + COREEVENT_HOOK_LAUNCH=0x04, + COREEVENT_HOOK_ATTACH_PLAYER=0x08, + COREEVENT_HOOK_ATTACH_GROUND=0x10, + COREEVENT_HOOK_RETRACT=0x20, +}; + +class world_core +{ +public: + world_core() + { + mem_zero(players, sizeof(players)); + } + + class player_core *players[MAX_CLIENTS]; +}; + +class player_core +{ +public: + world_core *world; + + vec2 pos; + vec2 vel; + + vec2 hook_pos; + vec2 hook_dir; + int hook_tick; + int hook_state; + int hooked_player; + + int jumped; + player_input input; + + int triggered_events; + + void tick(); + void move(); + + void read(const obj_player_core *obj_core); + void write(obj_player_core *obj_core); + void quantize(); +}; + + +#define LERP(a,b,t) (a + (b-a) * t) +#define min(a, b) ( a > b ? b : a) +#define max(a, b) ( a > b ? a : b) + +inline bool col_check_point(float x, float y) { return col_check_point((int)x, (int)y) != 0; } +inline bool col_check_point(vec2 p) { return col_check_point(p.x, p.y); } + +struct mapres_entity +{ + int x, y; + int data[1]; +}; + +struct mapres_spawnpoint +{ + int x, y; +}; + +struct mapres_item +{ + int x, y; + int type; +}; + +struct mapres_flagstand +{ + int x, y; +}; + +enum +{ + MAPRES_ENTS_START=1, + MAPRES_SPAWNPOINT=1, + MAPRES_ITEM=2, + MAPRES_SPAWNPOINT_RED=3, + MAPRES_SPAWNPOINT_BLUE=4, + MAPRES_FLAGSTAND_RED=5, + MAPRES_FLAGSTAND_BLUE=6, + MAPRES_ENTS_END, + + ITEM_NULL=0, + ITEM_WEAPON_GUN=0x00010001, + ITEM_WEAPON_SHOTGUN=0x00010002, + ITEM_WEAPON_ROCKET=0x00010003, + ITEM_WEAPON_SNIPER=0x00010004, + ITEM_WEAPON_HAMMER=0x00010005, + ITEM_HEALTH =0x00020001, + ITEM_ARMOR=0x00030001, + ITEM_NINJA=0x00040001, +}; diff --git a/src/game/g_mapres.h b/src/game/g_mapres.h new file mode 100644 index 00000000..77a3ccaa --- /dev/null +++ b/src/game/g_mapres.h @@ -0,0 +1,14 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +enum +{ + MAPRES_REGISTERED=0x8000, + MAPRES_IMAGE=0x8001, + MAPRES_TILEMAP=0x8002, + MAPRES_COLLISIONMAP=0x8003, + MAPRES_TEMP_THEME=0x8fff, +}; + +struct mapres_theme +{ + int id; +}; diff --git a/src/game/g_mapres_col.cpp b/src/game/g_mapres_col.cpp new file mode 100644 index 00000000..4b391204 --- /dev/null +++ b/src/game/g_mapres_col.cpp @@ -0,0 +1,69 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +#include +#include +#include +#include +#include "../engine/e_interface.h" +#include "g_mapres_col.h" +#include "g_mapres.h" + +/* + Simple collision rutines! +*/ +struct collision +{ + int w, h; + unsigned char *data; +}; + +static collision col; +static int global_dividor; + +int col_init(int dividor) +{ + mapres_collision *c = (mapres_collision*)map_find_item(MAPRES_COLLISIONMAP,0); + if(!c) + { + dbg_msg("mapres_col", "failed!"); + return 0; + } + col.w = c->width; + col.h = c->height; + global_dividor = dividor; + col.data = (unsigned char *)map_get_data(c->data_index); + return col.data ? 1 : 0; +} + +int col_check_point(int x, int y) +{ + int nx = x/global_dividor; + int ny = y/global_dividor; + if(nx < 0 || nx >= col.w || ny >= col.h) + return 1; + + if(y < 0) + return 0; // up == sky == free + + return col.data[ny*col.w+nx]; +} + +// TODO: rewrite this smarter! +bool col_intersect_line(vec2 pos0, vec2 pos1, vec2 *out) +{ + float d = distance(pos0, pos1); + + for(float f = 0; f < d; f++) + { + float a = f/d; + vec2 pos = mix(pos0, pos1, a); + if(col_check_point((int)pos.x, (int)pos.y)) + { + if(out) + *out = pos; + return true; + } + } + if(out) + *out = pos1; + return false; +} diff --git a/src/game/g_mapres_col.h b/src/game/g_mapres_col.h new file mode 100644 index 00000000..5d4427ab --- /dev/null +++ b/src/game/g_mapres_col.h @@ -0,0 +1,13 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +#include + +struct mapres_collision +{ + int width; + int height; + int data_index; +}; + +int col_init(int dividor); +int col_check_point(int x, int y); +bool col_intersect_line(vec2 pos0, vec2 pos1, vec2 *out); diff --git a/src/game/g_math.h b/src/game/g_math.h new file mode 100644 index 00000000..e2cf6bd8 --- /dev/null +++ b/src/game/g_math.h @@ -0,0 +1,30 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +#ifndef BASE_MATH_H +#define BASE_MATH_H + +template +inline T clamp(T val, T min, T max) +{ + if(val < min) + return min; + if(val > max) + return max; + return val; +} + +inline float sign(float f) +{ + return f<0.0f?-1.0f:1.0f; +} + +template +inline T mix(const T a, const T b, TB amount) +{ + return a + (b-a)*amount; +} +const float pi = 3.1415926535897932384626433f; + +template inline T min(T a, T b) { return a inline T max(T a, T b) { return a>b?a:b; } + +#endif // BASE_MATH_H diff --git a/src/game/g_protocol.h b/src/game/g_protocol.h new file mode 100644 index 00000000..4fcc1e5f --- /dev/null +++ b/src/game/g_protocol.h @@ -0,0 +1,215 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +// NOTE: Be very careful when editing this file as it will change the network version + +// --------- PHYSICS TWEAK! -------- +const float ground_control_speed = 7.0f; +const float ground_control_accel = 2.0f; +const float ground_friction = 0.5f; +const float ground_jump_speed = 12.6f; +const float ground_air_speed = 11.5f; +const float air_control_speed = 5.0f; +const float air_control_accel = 1.5f; +const float air_friction = 0.95f; +const float hook_length = 34*10.0f; +const float hook_fire_speed = 45.0f; +const float hook_drag_accel = 3.0f; +const float hook_drag_speed = 15.0f; +const float gravity = 0.5f; +const float wall_friction = 0.80f; +const float wall_jump_speed_up = ground_jump_speed*0.8f; +const float wall_jump_speed_out = ground_jump_speed*0.8f; + +// Network stuff +enum +{ + OBJTYPE_NULL=0, + OBJTYPE_GAME, + OBJTYPE_PLAYER_INFO, + OBJTYPE_PLAYER_CHARACTER, // use this if you are searching for the player entity + OBJTYPE_PROJECTILE, + OBJTYPE_POWERUP, + OBJTYPE_FLAG, + EVENT_EXPLOSION, + EVENT_DAMAGEINDICATION, + EVENT_SOUND_WORLD, + EVENT_SMOKE, + EVENT_SPAWN, + EVENT_DEATH, + EVENT_AIR_JUMP, + + EVENT_DUMMY +}; + +enum +{ + MSG_NULL=0, + MSG_SAY, // client -> server + MSG_CHAT, // server -> client + MSG_SETINFO, // server -> client - contains name, skin and color info + MSG_KILLMSG, // server -> client + MSG_SETTEAM, + MSG_JOIN, + MSG_QUIT, + MSG_EMOTICON, + MSG_STARTINFO, // client -> server + MSG_CHANGEINFO, // client -> server + MSG_READY_TO_ENTER, // server -> client + MSG_WEAPON_PICKUP, + MSG_SOUND_GLOBAL, +}; + +enum +{ + EMOTE_NORMAL=0, + EMOTE_PAIN, + EMOTE_HAPPY, + EMOTE_SURPRISE, + EMOTE_ANGRY, + EMOTE_BLINK, +}; + +enum +{ + INPUT_STATE_MASK=0x1f, +}; + +enum +{ + STATE_UNKNOWN=0, + STATE_PLAYING, + STATE_IN_MENU, + STATE_CHATTING, + + GAMETYPE_DM=0, + GAMETYPE_TDM, + GAMETYPE_CTF, +}; + +struct player_input +{ + int left; + int right; + + int target_x; + int target_y; + + int jump; + int fire; + int hook; + int blink; + int state; + + int wanted_weapon; + int next_weapon; + int prev_weapon; +}; + +struct ev_common +{ + int x, y; +}; + +struct ev_explosion : public ev_common +{ +}; + +struct ev_spawn : public ev_common +{ +}; + +struct ev_death : public ev_common +{ +}; + +struct ev_sound : public ev_common +{ + int sound; +}; + +struct ev_damageind : public ev_common +{ + int angle; +}; + +struct obj_game +{ + int round_start_tick; + int game_over; + int sudden_death; + int paused; + + int score_limit; + int time_limit; + int gametype; + + int warmup; + + int teamscore[2]; +}; + +struct obj_projectile +{ + int type; + int tick; + int x, y; + int vx, vy; // should be an angle instead + int start_tick; +}; + +struct obj_powerup +{ + int x, y; + int type; // why do we need two types? + int subtype; +}; + +struct obj_flag +{ + int x, y; + int team; + int carried_by; // is set if the local player has the flag +}; + +// core object needed for physics +struct obj_player_core +{ + int x, y; + int vx, vy; + int angle; + int jumped; + + int hooked_player; + int hook_state; + int hook_tick; + int hook_x, hook_y; + int hook_dx, hook_dy; +}; + +// info about the player that is only needed when it's on screen +struct obj_player_character : public obj_player_core +{ + int state; + + int health; + int armor; + int ammocount; + int weaponstage; + + int weapon; // current active weapon + + int emote; + + int attacktick; // num attack ticks left of current attack +}; + +// information about the player that is always needed +struct obj_player_info +{ + int local; + int clientid; + + int team; + int score; + int latency; + int latency_flux; +}; diff --git a/src/game/g_variables.h b/src/game/g_variables.h new file mode 100644 index 00000000..44e52c6f --- /dev/null +++ b/src/game/g_variables.h @@ -0,0 +1,64 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +MACRO_CONFIG_INT(key_move_left, 'A', 32, 512) +MACRO_CONFIG_INT(key_move_right, 'D', 32, 512) +MACRO_CONFIG_INT(key_jump, 32, 32, 512) +MACRO_CONFIG_INT(key_fire, 384, 32, 512) +MACRO_CONFIG_INT(key_hook, 385, 32, 512) +MACRO_CONFIG_INT(key_weapon1, '1', 32, 512) +MACRO_CONFIG_INT(key_weapon2, '2', 32, 512) +MACRO_CONFIG_INT(key_weapon3, '3', 32, 512) +MACRO_CONFIG_INT(key_weapon4, '4', 32, 512) +MACRO_CONFIG_INT(key_weapon5, '5', 32, 512) +MACRO_CONFIG_INT(key_weapon6, '6', 32, 512) +MACRO_CONFIG_INT(key_weapon7, '7', 32, 512) + +MACRO_CONFIG_INT(key_next_weapon, 382, 32, 512) +MACRO_CONFIG_INT(key_prev_weapon, 383, 32, 512) + +MACRO_CONFIG_INT(key_emoticon, 'E', 32, 512) + +MACRO_CONFIG_INT(key_chat, 'T', 32, 512) +MACRO_CONFIG_INT(key_teamchat, 'Y', 32, 512) + +MACRO_CONFIG_INT(key_console, 256+2, 32, 512) +MACRO_CONFIG_INT(key_remoteconsole, 256+3, 32, 512) + + +MACRO_CONFIG_INT(dbg_bots, 0, 0, 7) + +MACRO_CONFIG_INT(cl_predict, 1, 0, 1) +MACRO_CONFIG_INT(cl_nameplates, 0, 0, 1) +MACRO_CONFIG_INT(cl_nameplates_always, 0, 0, 1) +MACRO_CONFIG_INT(cl_dynamic_camera, 1, 0, 1) +MACRO_CONFIG_INT(cl_team, -10, -1, 0) +MACRO_CONFIG_INT(cl_autoswitch_weapons, 0, 0, 1) + +MACRO_CONFIG_INT(player_use_custom_color, 0, 0, 1) +MACRO_CONFIG_INT(player_color_body, 65408, 0, 0) +MACRO_CONFIG_INT(player_color_feet, 65408, 0, 0) +MACRO_CONFIG_STR(player_skin, 64, "default") + +MACRO_CONFIG_INT(dbg_new_gui, 0, 0, 1) + +MACRO_CONFIG_INT(ui_page, 1, 0, 5) +MACRO_CONFIG_STR(ui_server_address, 128, "localhost:8303") +MACRO_CONFIG_INT(ui_scale, 100, 1, 100000) + +MACRO_CONFIG_INT(ui_color_hue, 160, 0, 255) +MACRO_CONFIG_INT(ui_color_sat, 70, 0, 255) +MACRO_CONFIG_INT(ui_color_lht, 175, 0, 255) +MACRO_CONFIG_INT(ui_color_alpha, 228, 0, 255) + + +MACRO_CONFIG_INT(sv_warmup, 0, 0, 0) +MACRO_CONFIG_STR(sv_msg, 512, "") +MACRO_CONFIG_INT(sv_teamdamage, 0, 0, 1) +MACRO_CONFIG_STR(sv_maprotation, 512, "") +MACRO_CONFIG_INT(sv_powerups, 1, 0, 1) +MACRO_CONFIG_INT(sv_scorelimit, 20, 0, 1000) +MACRO_CONFIG_INT(sv_timelimit, 0, 0, 1000) +MACRO_CONFIG_STR(sv_gametype, 32, "dm") +MACRO_CONFIG_INT(sv_restart, 0, 0, 120) + + + diff --git a/src/game/g_version.h b/src/game/g_version.h new file mode 100644 index 00000000..8f261c0f --- /dev/null +++ b/src/game/g_version.h @@ -0,0 +1,4 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +#include "generated/nethash.c" +#define TEEWARS_VERSION "0.3.0-dev" +#define TEEWARS_NETVERSION "0.3 " TEEWARS_NETVERSION_HASH diff --git a/src/game/g_vmath.h b/src/game/g_vmath.h new file mode 100644 index 00000000..65f94776 --- /dev/null +++ b/src/game/g_vmath.h @@ -0,0 +1,182 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +#ifndef BASE_VMATH_H +#define BASE_VMATH_H + +//#include + +// ------------------------------------ + +template +class vector2_base +{ +public: + union { T x,u; }; + union { T y,v; }; + + vector2_base() {} + vector2_base(float nx, float ny) + { + x = nx; + y = ny; + } + + vector2_base operator -() const { return vector2_base(-x, -y); } + vector2_base operator -(const vector2_base &v) const { return vector2_base(x-v.x, y-v.y); } + vector2_base operator +(const vector2_base &v) const { return vector2_base(x+v.x, y+v.y); } + vector2_base operator *(const T v) const { return vector2_base(x*v, y*v); } + + const vector2_base &operator =(const vector2_base &v) { x = v.x; y = v.y; return *this; } + + const vector2_base &operator +=(const vector2_base &v) { x += v.x; y += v.y; return *this; } + const vector2_base &operator -=(const vector2_base &v) { x -= v.x; y -= v.y; return *this; } + const vector2_base &operator *=(const T v) { x *= v; y *= v; return *this; } + + bool operator ==(const vector2_base &v) const { return x == v.x && y == v.y; } //TODO: do this with an eps instead + + operator const T* () { return &x; } +}; + + +template +inline T length(const vector2_base &a) +{ + return sqrtf(a.x*a.x + a.y*a.y); +} + +template +inline T distance(const vector2_base a, const vector2_base &b) +{ + return length(a-b); +} + +template +inline T dot(const vector2_base a, const vector2_base &b) +{ + return a.x*b.x + a.y*b.y; +} + +template +inline vector2_base normalize(const vector2_base &v) +{ + T l = (T)(1.0f/sqrtf(v.x*v.x + v.y*v.y)); + return vector2_base(v.x*l, v.y*l); +} + +typedef vector2_base vec2; +typedef vector2_base bvec2; +typedef vector2_base ivec2; + +// ------------------------------------ +template +class vector3_base +{ +public: + union { T x,r,h; }; + union { T y,g,s; }; + union { T z,b,v,l; }; + + vector3_base() {} + vector3_base(float nx, float ny, float nz) + { + x = nx; + y = ny; + z = nz; + } + + const vector3_base &operator =(const vector3_base &v) { x = v.x; y = v.y; z = v.z; return *this; } + + vector3_base operator -(const vector3_base &v) const { return vector3_base(x-v.x, y-v.y, z-v.z); } + vector3_base operator -() const { return vector3_base(-x, -y, -z); } + vector3_base operator +(const vector3_base &v) const { return vector3_base(x+v.x, y+v.y, z+v.z); } + vector3_base operator *(const T v) const { return vector3_base(x*v, y*v, z*v); } + vector3_base operator *(const vector3_base &v) const { return vector3_base(x*v.x, y*v.y, z*v.z); } + vector3_base operator /(const T v) const { return vector3_base(x/v, y/v, z/v); } + + const vector3_base &operator +=(const vector3_base &v) { x += v.x; y += v.y; z += v.z; return *this; } + const vector3_base &operator -=(const vector3_base &v) { x -= v.x; y -= v.y; z -= v.z; return *this; } + const vector3_base &operator *=(const T v) { x *= v; y *= v; z *= v; return *this; } + + bool operator ==(const vector3_base &v) const { return x == v.x && y == v.y && z == v.z; } //TODO: do this with an eps instead + + operator const T* () { return &x; } +}; + +template +inline T length(const vector3_base &a) +{ + return sqrtf(a.x*a.x + a.y*a.y + a.z*a.z); +} + +template +inline T distance(const vector3_base &a, const vector3_base &b) +{ + return length(a-b); +} + +template +inline T dot(const vector3_base &a, const vector3_base &b) +{ + return a.x*b.x + a.y*b.y + a.z*b.z; +} + +template +inline vector3_base normalize(const vector3_base &v) +{ + T l = (T)(1.0f/sqrtf(v.x*v.x + v.y*v.y + v.z*v.z)); + return vector3_base(v.x*l, v.y*l, v.z*l); +} + +template +inline vector3_base cross(const vector3_base &a, const vector3_base &b) +{ + return vector3_base( + a.y*b.z - a.z*b.y, + a.z*b.x - a.x*b.z, + a.x*b.y - a.y*b.x); +} + +typedef vector3_base vec3; +typedef vector3_base bvec3; +typedef vector3_base ivec3; + +// ------------------------------------ + +template +class vector4_base +{ +public: + union { T x,r; }; + union { T y,g; }; + union { T z,b; }; + union { T w,a; }; + + vector4_base() {} + vector4_base(float nx, float ny, float nz, float nw) + { + x = nx; + y = ny; + z = nz; + w = nw; + } + + vector4_base operator +(const vector4_base &v) const { return vector4_base(x+v.x, y+v.y, z+v.z, w+v.w); } + vector4_base operator -(const vector4_base &v) const { return vector4_base(x-v.x, y-v.y, z-v.z, w-v.w); } + vector4_base operator -() const { return vector4_base(-x, -y, -z, -w); } + vector4_base operator *(const T v) const { return vector4_base(x*v, y*v, z*v, w*v); } + + const vector4_base &operator =(const vector4_base &v) { x = v.x; y = v.y; z = v.z; w = v.w; return *this; } + + const vector4_base &operator +=(const vector4_base &v) { x += v.x; y += v.y; z += v.z; w += v.w; return *this; } + const vector4_base &operator -=(const vector4_base &v) { x -= v.x; y -= v.y; z -= v.z; w -= v.w; return *this; } + const vector4_base &operator *=(const T v) { x *= v; y *= v; z *= v; w *= v; return *this; } + + bool operator ==(const vector4_base &v) const { return x == v.x && y == v.y && z == v.z && w == v.w; } //TODO: do this with an eps instead + + operator const T* () { return &x; } +}; + +typedef vector4_base vec4; +typedef vector4_base bvec4; +typedef vector4_base ivec4; + +#endif diff --git a/src/game/game.cpp b/src/game/game.cpp deleted file mode 100644 index 29e4da14..00000000 --- a/src/game/game.cpp +++ /dev/null @@ -1,411 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#include "game.h" - -// TODO: OPT: rewrite this smarter! -void move_point(vec2 *inout_pos, vec2 *inout_vel, float elasticity, int *bounces) -{ - if(bounces) - *bounces = 0; - - vec2 pos = *inout_pos; - vec2 vel = *inout_vel; - if(col_check_point(pos + vel)) - { - int affected = 0; - if(col_check_point(pos.x + vel.x, pos.y)) - { - inout_vel->x *= -elasticity; - if(bounces) - (*bounces)++; - affected++; - } - - if(col_check_point(pos.x, pos.y + vel.y)) - { - inout_vel->y *= -elasticity; - if(bounces) - (*bounces)++; - affected++; - } - - if(affected == 0) - { - inout_vel->x *= -elasticity; - inout_vel->y *= -elasticity; - } - } - else - { - *inout_pos = pos + vel; - } -} - -bool test_box(vec2 pos, vec2 size) -{ - size *= 0.5f; - if(col_check_point(pos.x-size.x, pos.y-size.y)) - return true; - if(col_check_point(pos.x+size.x, pos.y-size.y)) - return true; - if(col_check_point(pos.x-size.x, pos.y+size.y)) - return true; - if(col_check_point(pos.x+size.x, pos.y+size.y)) - return true; - return false; -} - -void move_box(vec2 *inout_pos, vec2 *inout_vel, vec2 size, float elasticity) -{ - // do the move - vec2 pos = *inout_pos; - vec2 vel = *inout_vel; - - float distance = length(vel); - int max = (int)distance; - - if(distance > 0.00001f) - { - //vec2 old_pos = pos; - float fraction = 1.0f/(float)(max+1); - for(int i = 0; i <= max; i++) - { - //float amount = i/(float)max; - //if(max == 0) - //amount = 0; - - vec2 new_pos = pos + vel*fraction; // TODO: this row is not nice - - if(test_box(vec2(new_pos.x, new_pos.y), size)) - { - int hits = 0; - - if(test_box(vec2(pos.x, new_pos.y), size)) - { - new_pos.y = pos.y; - vel.y *= -elasticity; - hits++; - } - - if(test_box(vec2(new_pos.x, pos.y), size)) - { - new_pos.x = pos.x; - vel.x *= -elasticity; - hits++; - } - - // neither of the tests got a collision. - // this is a real _corner case_! - if(hits == 0) - { - new_pos.y = pos.y; - vel.y *= -elasticity; - new_pos.x = pos.x; - vel.x *= -elasticity; - } - } - - pos = new_pos; - } - } - - *inout_pos = pos; - *inout_vel = vel; -} - -void player_core::tick() -{ - float phys_size = 28.0f; - triggered_events = 0; - - #define MACRO_CHECK_VELOCITY { dbg_assert(length(vel) < 1000.0f, "velocity error"); } - - MACRO_CHECK_VELOCITY - - bool grounded = false; - if(col_check_point((int)(pos.x+phys_size/2), (int)(pos.y+phys_size/2+5))) - grounded = true; - if(col_check_point((int)(pos.x-phys_size/2), (int)(pos.y+phys_size/2+5))) - grounded = true; - - vec2 direction = normalize(vec2(input.target_x, input.target_y)); - - vel.y += gravity; - - MACRO_CHECK_VELOCITY - - float max_speed = grounded ? ground_control_speed : air_control_speed; - float accel = grounded ? ground_control_accel : air_control_accel; - float friction = grounded ? ground_friction : air_friction; - - // handle movement - if(input.left) - vel.x = saturated_add(-max_speed, max_speed, vel.x, -accel); - if(input.right) - vel.x = saturated_add(-max_speed, max_speed, vel.x, accel); - - MACRO_CHECK_VELOCITY - - if(!input.left && !input.right) - vel.x *= friction; - - MACRO_CHECK_VELOCITY - - // handle jumping - // 1 bit = to keep track if a jump has been made on this input - // 2 bit = to keep track if a air-jump has been made - if(grounded) - jumped &= ~2; - - if(input.jump) - { - if(!(jumped&1)) - { - if(grounded) - { - triggered_events |= COREEVENT_GROUND_JUMP; - vel.y = -ground_jump_speed; - jumped |= 1; - } - else if(!(jumped&2)) - { - triggered_events |= COREEVENT_AIR_JUMP; - vel.y = -ground_air_speed; - jumped |= 3; - } - } - } - else - jumped &= ~1; - - MACRO_CHECK_VELOCITY - - // do hook - if(input.hook) - { - if(hook_state == HOOK_IDLE) - { - hook_state = HOOK_FLYING; - hook_pos = pos; - hook_dir = direction; - hooked_player = -1; - hook_tick = 0; - triggered_events |= COREEVENT_HOOK_LAUNCH; - } - else if(hook_state == HOOK_FLYING) - { - vec2 new_pos = hook_pos+hook_dir*hook_fire_speed; - - // Check against other players first - for(int i = 0; i < MAX_CLIENTS; i++) - { - player_core *p = world->players[i]; - if(!p || p == this) - continue; - - //if(p != this && !p->dead && distance(p->pos, new_pos) < p->phys_size) - if(distance(p->pos, new_pos) < phys_size) - { - triggered_events |= COREEVENT_HOOK_ATTACH_PLAYER; - hook_state = HOOK_GRABBED; - hooked_player = i; - break; - } - } - /* - for(entity *ent = world.first_entity; ent; ent = ent->next_entity) - { - if(ent && ent->objtype == OBJTYPE_PLAYER) - { - player *p = (player*)ent; - if(p != this && !p->dead && distance(p->pos, new_pos) < p->phys_size) - { - hook_state = HOOK_GRABBED; - hooked_player = p; - break; - } - } - }*/ - - if(hook_state == HOOK_FLYING) - { - // check against ground - if(col_intersect_line(hook_pos, new_pos, &new_pos)) - { - triggered_events |= COREEVENT_HOOK_ATTACH_GROUND; - hook_state = HOOK_GRABBED; - hook_pos = new_pos; - } - else if(distance(pos, new_pos) > hook_length) - { - triggered_events |= COREEVENT_HOOK_RETRACT; - hook_state = HOOK_RETRACTED; - } - else - hook_pos = new_pos; - } - - if(hook_state == HOOK_GRABBED) - { - //create_sound(pos, SOUND_HOOK_ATTACH); - //hook_tick = server_tick(); - } - } - } - else - { - //release_hooked(); - hooked_player = -1; - hook_state = HOOK_IDLE; - hook_pos = pos; - } - - if(hook_state == HOOK_GRABBED) - { - if(hooked_player != -1) - { - player_core *p = world->players[hooked_player]; - if(p) - hook_pos = p->pos; - else - { - // release hook - hooked_player = -1; - hook_state = HOOK_RETRACTED; - hook_pos = pos; - } - - // keep players hooked for a max of 1.5sec - //if(server_tick() > hook_tick+(server_tickspeed()*3)/2) - //release_hooked(); - } - - // Old version feels much better (to me atleast) - if(distance(hook_pos, pos) > 46.0f) - { - vec2 hookvel = normalize(hook_pos-pos)*hook_drag_accel; - // the hook as more power to drag you up then down. - // this makes it easier to get on top of an platform - if(hookvel.y > 0) - hookvel.y *= 0.3f; - - // the hook will boost it's power if the player wants to move - // in that direction. otherwise it will dampen everything abit - if((hookvel.x < 0 && input.left) || (hookvel.x > 0 && input.right)) - hookvel.x *= 0.95f; - else - hookvel.x *= 0.75f; - - vec2 new_vel = vel+hookvel; - - // check if we are under the legal limit for the hook - if(length(new_vel) < hook_drag_speed || length(new_vel) < length(vel)) - vel = new_vel; // no problem. apply - - } - - // release hook - hook_tick++; - if(hooked_player != -1 && hook_tick > SERVER_TICK_SPEED*2) - { - hooked_player = -1; - hook_state = HOOK_RETRACTED; - hook_pos = pos; - } - } - - MACRO_CHECK_VELOCITY - - if(true) - { - for(int i = 0; i < MAX_CLIENTS; i++) - { - player_core *p = world->players[i]; - if(!p) - continue; - - //player *p = (player*)ent; - if(p == this) // || !(p->flags&FLAG_ALIVE) - continue; // make sure that we don't nudge our self - - // handle player <-> player collision - float d = distance(pos, p->pos); - vec2 dir = normalize(pos - p->pos); - if(d < phys_size*1.25f && d > 1.0f) - { - float a = phys_size*1.25f - d; - vel = vel + dir*a; - } - - MACRO_CHECK_VELOCITY - - // handle hook influence - if(hooked_player == i) - { - if(d > phys_size*1.50f) // TODO: fix tweakable variable - { - float accel = hook_drag_accel * (d/hook_length); - vel.x = saturated_add(-hook_drag_speed, hook_drag_speed, vel.x, -accel*dir.x); - vel.y = saturated_add(-hook_drag_speed, hook_drag_speed, vel.y, -accel*dir.y); - - MACRO_CHECK_VELOCITY - } - } - } - } -} - -void player_core::move() -{ - move_box(&pos, &vel, vec2(28.0f, 28.0f), 0); -} - -void player_core::write(obj_player_core *obj_core) -{ - obj_core->x = (int)pos.x; - obj_core->y = (int)pos.y; - obj_core->vx = (int)(vel.x*256.0f); - obj_core->vy = (int)(vel.y*256.0f); - obj_core->hook_state = hook_state; - obj_core->hook_tick = hook_tick; - obj_core->hook_x = (int)hook_pos.x; - obj_core->hook_y = (int)hook_pos.y; - obj_core->hook_dx = (int)(hook_dir.x*256.0f); - obj_core->hook_dy = (int)(hook_dir.y*256.0f); - obj_core->hooked_player = hooked_player; - obj_core->jumped = jumped; - - float a = 0; - if(input.target_x == 0) - a = atan((float)input.target_y); - else - a = atan((float)input.target_y/(float)input.target_x); - - if(input.target_x < 0) - a = a+pi; - - obj_core->angle = (int)(a*256.0f); -} - -void player_core::read(const obj_player_core *obj_core) -{ - pos.x = obj_core->x; - pos.y = obj_core->y; - vel.x = obj_core->vx/256.0f; - vel.y = obj_core->vy/256.0f; - hook_state = obj_core->hook_state; - hook_tick = obj_core->hook_tick; - hook_pos.x = obj_core->hook_x; - hook_pos.y = obj_core->hook_y; - hook_dir.x = obj_core->hook_dx/256.0f; - hook_dir.y = obj_core->hook_dy/256.0f; - hooked_player = obj_core->hooked_player; - jumped = obj_core->jumped; -} - -void player_core::quantize() -{ - obj_player_core c; - write(&c); - read(&c); -} - diff --git a/src/game/game.h b/src/game/game.h deleted file mode 100644 index c30543d9..00000000 --- a/src/game/game.h +++ /dev/null @@ -1,171 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#include -#include -#include -#include "../engine/interface.h" -#include "mapres_col.h" - -#include "game_protocol.h" - -inline vec2 get_direction(int angle) -{ - float a = angle/256.0f; - return vec2(cosf(a), sinf(a)); -} - -inline vec2 get_dir(float a) -{ - return vec2(cosf(a), sinf(a)); -} - -inline float get_angle(vec2 dir) -{ - float a = atan(dir.y/dir.x); - if(dir.x < 0) - a = a+pi; - return a; -} - - -inline vec2 calc_pos(vec2 p, vec2 v, float gravity, float t) -{ - vec2 n; - n.x = p.x + v.x*t; - n.y = p.y + v.y*t - gravity*(t*t); - return n; -} - - -template -inline T saturated_add(T min, T max, T current, T modifier) -{ - if(modifier < 0) - { - if(current < min) - return current; - current += modifier; - if(current < min) - current = min; - return current; - } - else - { - if(current > max) - return current; - current += modifier; - if(current > max) - current = max; - return current; - } -} - -void move_point(vec2 *inout_pos, vec2 *inout_vel, float elasticity, int *bounces); -void move_box(vec2 *inout_pos, vec2 *inout_vel, vec2 size, float elasticity); -bool test_box(vec2 pos, vec2 size); - - -// hooking stuff -enum -{ - HOOK_RETRACTED=-1, - HOOK_IDLE=0, - HOOK_FLYING, - HOOK_GRABBED, - - COREEVENT_GROUND_JUMP=0x01, - COREEVENT_AIR_JUMP=0x02, - COREEVENT_HOOK_LAUNCH=0x04, - COREEVENT_HOOK_ATTACH_PLAYER=0x08, - COREEVENT_HOOK_ATTACH_GROUND=0x10, - COREEVENT_HOOK_RETRACT=0x20, -}; - -class world_core -{ -public: - world_core() - { - mem_zero(players, sizeof(players)); - } - - class player_core *players[MAX_CLIENTS]; -}; - -class player_core -{ -public: - world_core *world; - - vec2 pos; - vec2 vel; - - vec2 hook_pos; - vec2 hook_dir; - int hook_tick; - int hook_state; - int hooked_player; - - int jumped; - player_input input; - - int triggered_events; - - void tick(); - void move(); - - void read(const obj_player_core *obj_core); - void write(obj_player_core *obj_core); - void quantize(); -}; - - -#define LERP(a,b,t) (a + (b-a) * t) -#define min(a, b) ( a > b ? b : a) -#define max(a, b) ( a > b ? a : b) - -inline bool col_check_point(float x, float y) { return col_check_point((int)x, (int)y) != 0; } -inline bool col_check_point(vec2 p) { return col_check_point(p.x, p.y); } - -struct mapres_entity -{ - int x, y; - int data[1]; -}; - -struct mapres_spawnpoint -{ - int x, y; -}; - -struct mapres_item -{ - int x, y; - int type; -}; - -struct mapres_flagstand -{ - int x, y; -}; - -enum -{ - MAPRES_ENTS_START=1, - MAPRES_SPAWNPOINT=1, - MAPRES_ITEM=2, - MAPRES_SPAWNPOINT_RED=3, - MAPRES_SPAWNPOINT_BLUE=4, - MAPRES_FLAGSTAND_RED=5, - MAPRES_FLAGSTAND_BLUE=6, - MAPRES_ENTS_END, - - ITEM_NULL=0, - ITEM_WEAPON_GUN=0x00010001, - ITEM_WEAPON_SHOTGUN=0x00010002, - ITEM_WEAPON_ROCKET=0x00010003, - ITEM_WEAPON_SNIPER=0x00010004, - ITEM_WEAPON_HAMMER=0x00010005, - ITEM_HEALTH =0x00020001, - ITEM_ARMOR=0x00030001, - ITEM_NINJA=0x00040001, -}; diff --git a/src/game/game_protocol.h b/src/game/game_protocol.h deleted file mode 100644 index 4fcc1e5f..00000000 --- a/src/game/game_protocol.h +++ /dev/null @@ -1,215 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -// NOTE: Be very careful when editing this file as it will change the network version - -// --------- PHYSICS TWEAK! -------- -const float ground_control_speed = 7.0f; -const float ground_control_accel = 2.0f; -const float ground_friction = 0.5f; -const float ground_jump_speed = 12.6f; -const float ground_air_speed = 11.5f; -const float air_control_speed = 5.0f; -const float air_control_accel = 1.5f; -const float air_friction = 0.95f; -const float hook_length = 34*10.0f; -const float hook_fire_speed = 45.0f; -const float hook_drag_accel = 3.0f; -const float hook_drag_speed = 15.0f; -const float gravity = 0.5f; -const float wall_friction = 0.80f; -const float wall_jump_speed_up = ground_jump_speed*0.8f; -const float wall_jump_speed_out = ground_jump_speed*0.8f; - -// Network stuff -enum -{ - OBJTYPE_NULL=0, - OBJTYPE_GAME, - OBJTYPE_PLAYER_INFO, - OBJTYPE_PLAYER_CHARACTER, // use this if you are searching for the player entity - OBJTYPE_PROJECTILE, - OBJTYPE_POWERUP, - OBJTYPE_FLAG, - EVENT_EXPLOSION, - EVENT_DAMAGEINDICATION, - EVENT_SOUND_WORLD, - EVENT_SMOKE, - EVENT_SPAWN, - EVENT_DEATH, - EVENT_AIR_JUMP, - - EVENT_DUMMY -}; - -enum -{ - MSG_NULL=0, - MSG_SAY, // client -> server - MSG_CHAT, // server -> client - MSG_SETINFO, // server -> client - contains name, skin and color info - MSG_KILLMSG, // server -> client - MSG_SETTEAM, - MSG_JOIN, - MSG_QUIT, - MSG_EMOTICON, - MSG_STARTINFO, // client -> server - MSG_CHANGEINFO, // client -> server - MSG_READY_TO_ENTER, // server -> client - MSG_WEAPON_PICKUP, - MSG_SOUND_GLOBAL, -}; - -enum -{ - EMOTE_NORMAL=0, - EMOTE_PAIN, - EMOTE_HAPPY, - EMOTE_SURPRISE, - EMOTE_ANGRY, - EMOTE_BLINK, -}; - -enum -{ - INPUT_STATE_MASK=0x1f, -}; - -enum -{ - STATE_UNKNOWN=0, - STATE_PLAYING, - STATE_IN_MENU, - STATE_CHATTING, - - GAMETYPE_DM=0, - GAMETYPE_TDM, - GAMETYPE_CTF, -}; - -struct player_input -{ - int left; - int right; - - int target_x; - int target_y; - - int jump; - int fire; - int hook; - int blink; - int state; - - int wanted_weapon; - int next_weapon; - int prev_weapon; -}; - -struct ev_common -{ - int x, y; -}; - -struct ev_explosion : public ev_common -{ -}; - -struct ev_spawn : public ev_common -{ -}; - -struct ev_death : public ev_common -{ -}; - -struct ev_sound : public ev_common -{ - int sound; -}; - -struct ev_damageind : public ev_common -{ - int angle; -}; - -struct obj_game -{ - int round_start_tick; - int game_over; - int sudden_death; - int paused; - - int score_limit; - int time_limit; - int gametype; - - int warmup; - - int teamscore[2]; -}; - -struct obj_projectile -{ - int type; - int tick; - int x, y; - int vx, vy; // should be an angle instead - int start_tick; -}; - -struct obj_powerup -{ - int x, y; - int type; // why do we need two types? - int subtype; -}; - -struct obj_flag -{ - int x, y; - int team; - int carried_by; // is set if the local player has the flag -}; - -// core object needed for physics -struct obj_player_core -{ - int x, y; - int vx, vy; - int angle; - int jumped; - - int hooked_player; - int hook_state; - int hook_tick; - int hook_x, hook_y; - int hook_dx, hook_dy; -}; - -// info about the player that is only needed when it's on screen -struct obj_player_character : public obj_player_core -{ - int state; - - int health; - int armor; - int ammocount; - int weaponstage; - - int weapon; // current active weapon - - int emote; - - int attacktick; // num attack ticks left of current attack -}; - -// information about the player that is always needed -struct obj_player_info -{ - int local; - int clientid; - - int team; - int score; - int latency; - int latency_flux; -}; diff --git a/src/game/game_variables.h b/src/game/game_variables.h deleted file mode 100644 index 44e52c6f..00000000 --- a/src/game/game_variables.h +++ /dev/null @@ -1,64 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -MACRO_CONFIG_INT(key_move_left, 'A', 32, 512) -MACRO_CONFIG_INT(key_move_right, 'D', 32, 512) -MACRO_CONFIG_INT(key_jump, 32, 32, 512) -MACRO_CONFIG_INT(key_fire, 384, 32, 512) -MACRO_CONFIG_INT(key_hook, 385, 32, 512) -MACRO_CONFIG_INT(key_weapon1, '1', 32, 512) -MACRO_CONFIG_INT(key_weapon2, '2', 32, 512) -MACRO_CONFIG_INT(key_weapon3, '3', 32, 512) -MACRO_CONFIG_INT(key_weapon4, '4', 32, 512) -MACRO_CONFIG_INT(key_weapon5, '5', 32, 512) -MACRO_CONFIG_INT(key_weapon6, '6', 32, 512) -MACRO_CONFIG_INT(key_weapon7, '7', 32, 512) - -MACRO_CONFIG_INT(key_next_weapon, 382, 32, 512) -MACRO_CONFIG_INT(key_prev_weapon, 383, 32, 512) - -MACRO_CONFIG_INT(key_emoticon, 'E', 32, 512) - -MACRO_CONFIG_INT(key_chat, 'T', 32, 512) -MACRO_CONFIG_INT(key_teamchat, 'Y', 32, 512) - -MACRO_CONFIG_INT(key_console, 256+2, 32, 512) -MACRO_CONFIG_INT(key_remoteconsole, 256+3, 32, 512) - - -MACRO_CONFIG_INT(dbg_bots, 0, 0, 7) - -MACRO_CONFIG_INT(cl_predict, 1, 0, 1) -MACRO_CONFIG_INT(cl_nameplates, 0, 0, 1) -MACRO_CONFIG_INT(cl_nameplates_always, 0, 0, 1) -MACRO_CONFIG_INT(cl_dynamic_camera, 1, 0, 1) -MACRO_CONFIG_INT(cl_team, -10, -1, 0) -MACRO_CONFIG_INT(cl_autoswitch_weapons, 0, 0, 1) - -MACRO_CONFIG_INT(player_use_custom_color, 0, 0, 1) -MACRO_CONFIG_INT(player_color_body, 65408, 0, 0) -MACRO_CONFIG_INT(player_color_feet, 65408, 0, 0) -MACRO_CONFIG_STR(player_skin, 64, "default") - -MACRO_CONFIG_INT(dbg_new_gui, 0, 0, 1) - -MACRO_CONFIG_INT(ui_page, 1, 0, 5) -MACRO_CONFIG_STR(ui_server_address, 128, "localhost:8303") -MACRO_CONFIG_INT(ui_scale, 100, 1, 100000) - -MACRO_CONFIG_INT(ui_color_hue, 160, 0, 255) -MACRO_CONFIG_INT(ui_color_sat, 70, 0, 255) -MACRO_CONFIG_INT(ui_color_lht, 175, 0, 255) -MACRO_CONFIG_INT(ui_color_alpha, 228, 0, 255) - - -MACRO_CONFIG_INT(sv_warmup, 0, 0, 0) -MACRO_CONFIG_STR(sv_msg, 512, "") -MACRO_CONFIG_INT(sv_teamdamage, 0, 0, 1) -MACRO_CONFIG_STR(sv_maprotation, 512, "") -MACRO_CONFIG_INT(sv_powerups, 1, 0, 1) -MACRO_CONFIG_INT(sv_scorelimit, 20, 0, 1000) -MACRO_CONFIG_INT(sv_timelimit, 0, 0, 1000) -MACRO_CONFIG_STR(sv_gametype, 32, "dm") -MACRO_CONFIG_INT(sv_restart, 0, 0, 120) - - - diff --git a/src/game/generated/createdir.txt b/src/game/generated/createdir.txt new file mode 100644 index 00000000..e69de29b diff --git a/src/game/mapres.h b/src/game/mapres.h deleted file mode 100644 index 77a3ccaa..00000000 --- a/src/game/mapres.h +++ /dev/null @@ -1,14 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -enum -{ - MAPRES_REGISTERED=0x8000, - MAPRES_IMAGE=0x8001, - MAPRES_TILEMAP=0x8002, - MAPRES_COLLISIONMAP=0x8003, - MAPRES_TEMP_THEME=0x8fff, -}; - -struct mapres_theme -{ - int id; -}; diff --git a/src/game/mapres_col.cpp b/src/game/mapres_col.cpp deleted file mode 100644 index 046272ca..00000000 --- a/src/game/mapres_col.cpp +++ /dev/null @@ -1,69 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#include -#include -#include -#include -#include "../engine/interface.h" -#include "mapres_col.h" -#include "mapres.h" - -/* - Simple collision rutines! -*/ -struct collision -{ - int w, h; - unsigned char *data; -}; - -static collision col; -static int global_dividor; - -int col_init(int dividor) -{ - mapres_collision *c = (mapres_collision*)map_find_item(MAPRES_COLLISIONMAP,0); - if(!c) - { - dbg_msg("mapres_col", "failed!"); - return 0; - } - col.w = c->width; - col.h = c->height; - global_dividor = dividor; - col.data = (unsigned char *)map_get_data(c->data_index); - return col.data ? 1 : 0; -} - -int col_check_point(int x, int y) -{ - int nx = x/global_dividor; - int ny = y/global_dividor; - if(nx < 0 || nx >= col.w || ny >= col.h) - return 1; - - if(y < 0) - return 0; // up == sky == free - - return col.data[ny*col.w+nx]; -} - -// TODO: rewrite this smarter! -bool col_intersect_line(vec2 pos0, vec2 pos1, vec2 *out) -{ - float d = distance(pos0, pos1); - - for(float f = 0; f < d; f++) - { - float a = f/d; - vec2 pos = mix(pos0, pos1, a); - if(col_check_point((int)pos.x, (int)pos.y)) - { - if(out) - *out = pos; - return true; - } - } - if(out) - *out = pos1; - return false; -} diff --git a/src/game/mapres_col.h b/src/game/mapres_col.h deleted file mode 100644 index 9b7191c8..00000000 --- a/src/game/mapres_col.h +++ /dev/null @@ -1,13 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#include - -struct mapres_collision -{ - int width; - int height; - int data_index; -}; - -int col_init(int dividor); -int col_check_point(int x, int y); -bool col_intersect_line(vec2 pos0, vec2 pos1, vec2 *out); diff --git a/src/game/math.h b/src/game/math.h deleted file mode 100644 index e2cf6bd8..00000000 --- a/src/game/math.h +++ /dev/null @@ -1,30 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#ifndef BASE_MATH_H -#define BASE_MATH_H - -template -inline T clamp(T val, T min, T max) -{ - if(val < min) - return min; - if(val > max) - return max; - return val; -} - -inline float sign(float f) -{ - return f<0.0f?-1.0f:1.0f; -} - -template -inline T mix(const T a, const T b, TB amount) -{ - return a + (b-a)*amount; -} -const float pi = 3.1415926535897932384626433f; - -template inline T min(T a, T b) { return a inline T max(T a, T b) { return a>b?a:b; } - -#endif // BASE_MATH_H diff --git a/src/game/server/data/createdir.txt b/src/game/server/data/createdir.txt deleted file mode 100644 index 258b42d5..00000000 --- a/src/game/server/data/createdir.txt +++ /dev/null @@ -1 +0,0 @@ -this file is here to make sure that this directory gets created diff --git a/src/game/server/game_server.cpp b/src/game/server/game_server.cpp deleted file mode 100644 index 658e72c9..00000000 --- a/src/game/server/game_server.cpp +++ /dev/null @@ -1,1860 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#include -#include -#include -#include -#include "../version.h" -#include "game_server.h" -#include "srv_common.h" -#include "srv_ctf.h" -#include "srv_tdm.h" -#include "srv_dm.h" - -data_container *data = 0x0; - -class player* get_player(int index); -void create_damageind(vec2 p, float angle_mod, int amount); -void create_explosion(vec2 p, int owner, int weapon, bool bnodamage); -void create_smoke(vec2 p); -void create_spawn(vec2 p); -void create_death(vec2 p); -void create_sound(vec2 pos, int sound, int mask=-1); -class player *intersect_player(vec2 pos0, vec2 pos1, vec2 &new_pos, class entity *notthis = 0); - -game_world *world; - -////////////////////////////////////////////////// -// Event handler -////////////////////////////////////////////////// -event_handler::event_handler() -{ - clear(); -} - -void *event_handler::create(int type, int size, int mask) -{ - if(num_events == MAX_EVENTS) - return 0; - if(current_offset+size >= MAX_DATASIZE) - return 0; - - void *p = &data[current_offset]; - offsets[num_events] = current_offset; - types[num_events] = type; - sizes[num_events] = size; - client_masks[num_events] = mask; - current_offset += size; - num_events++; - return p; -} - -void event_handler::clear() -{ - num_events = 0; - current_offset = 0; -} - -void event_handler::snap(int snapping_client) -{ - for(int i = 0; i < num_events; i++) - { - if(cmask_is_set(client_masks[i], snapping_client)) - { - ev_common *ev = (ev_common *)&data[offsets[i]]; - if(distance(players[snapping_client].pos, vec2(ev->x, ev->y)) < 1500.0f) - { - void *d = snap_new_item(types[i], i, sizes[i]); - mem_copy(d, &data[offsets[i]], sizes[i]); - } - } - } -} - -event_handler events; - -////////////////////////////////////////////////// -// Entity -////////////////////////////////////////////////// -entity::entity(int objtype) -{ - this->objtype = objtype; - pos = vec2(0,0); - flags = FLAG_PHYSICS; - proximity_radius = 0; - - id = snap_new_id(); - - next_entity = 0; - prev_entity = 0; - prev_type_entity = 0; - next_type_entity = 0; -} - -entity::~entity() -{ - snap_free_id(id); -} - -////////////////////////////////////////////////// -// game world -////////////////////////////////////////////////// -game_world::game_world() -{ - paused = false; - reset_requested = false; - first_entity = 0x0; - for(int i = 0; i < NUM_ENT_TYPES; i++) - first_entity_types[i] = 0; -} - -int game_world::find_entities(vec2 pos, float radius, entity **ents, int max) -{ - int num = 0; - for(entity *ent = first_entity; ent; ent = ent->next_entity) - { - if(!(ent->flags&entity::FLAG_PHYSICS)) - continue; - - if(distance(ent->pos, pos) < radius+ent->proximity_radius) - { - ents[num] = ent; - num++; - if(num == max) - break; - } - } - - return num; -} - -int game_world::find_entities(vec2 pos, float radius, entity **ents, int max, const int* types, int maxtypes) -{ - int num = 0; - for(int t = 0; t < maxtypes; t++) - { - for(entity *ent = first_entity_types[types[t]]; ent; ent = ent->next_type_entity) - { - if(!(ent->flags&entity::FLAG_PHYSICS)) - continue; - - if(distance(ent->pos, pos) < radius+ent->proximity_radius) - { - ents[num] = ent; - num++; - if(num == max) - break; - } - } - } - - return num; -} - -void game_world::insert_entity(entity *ent) -{ - entity *cur = first_entity; - while(cur) - { - dbg_assert(cur != ent, "err"); - cur = cur->next_entity; - } - - // insert it - if(first_entity) - first_entity->prev_entity = ent; - ent->next_entity = first_entity; - ent->prev_entity = 0x0; - first_entity = ent; - - // into typelist aswell - if(first_entity_types[ent->objtype]) - first_entity_types[ent->objtype]->prev_type_entity = ent; - ent->next_type_entity = first_entity_types[ent->objtype]; - ent->prev_type_entity = 0x0; - first_entity_types[ent->objtype] = ent; -} - -void game_world::destroy_entity(entity *ent) -{ - ent->set_flag(entity::FLAG_DESTROY); -} - -void game_world::remove_entity(entity *ent) -{ - // not in the list - if(!ent->next_entity && !ent->prev_entity && first_entity != ent) - return; - - // remove - if(ent->prev_entity) - ent->prev_entity->next_entity = ent->next_entity; - else - first_entity = ent->next_entity; - if(ent->next_entity) - ent->next_entity->prev_entity = ent->prev_entity; - - if(ent->prev_type_entity) - ent->prev_type_entity->next_type_entity = ent->next_type_entity; - else - first_entity_types[ent->objtype] = ent->next_type_entity; - if(ent->next_type_entity) - ent->next_type_entity->prev_type_entity = ent->prev_type_entity; - - ent->next_entity = 0; - ent->prev_entity = 0; - ent->next_type_entity = 0; - ent->prev_type_entity = 0; -} - -// -void game_world::snap(int snapping_client) -{ - for(entity *ent = first_entity; ent; ent = ent->next_entity) - ent->snap(snapping_client); -} - -void game_world::reset() -{ - // reset all entities - for(entity *ent = first_entity; ent; ent = ent->next_entity) - ent->reset(); - remove_entities(); - - for(entity *ent = first_entity; ent; ent = ent->next_entity) - ent->post_reset(); - remove_entities(); - - reset_requested = false; -} - -void game_world::remove_entities() -{ - // destroy objects marked for destruction - entity *ent = first_entity; - while(ent) - { - entity *next = ent->next_entity; - if(ent->flags&entity::FLAG_DESTROY) - { - remove_entity(ent); - ent->destroy(); - } - ent = next; - } -} - -void game_world::tick() -{ - if(reset_requested) - reset(); - - if(!paused) - { - // update all objects - for(entity *ent = first_entity; ent; ent = ent->next_entity) - ent->tick(); - - for(entity *ent = first_entity; ent; ent = ent->next_entity) - ent->tick_defered(); - } - - remove_entities(); -} - -////////////////////////////////////////////////// -// projectile -////////////////////////////////////////////////// -projectile::projectile(int type, int owner, vec2 pos, vec2 vel, int span, entity* powner, - int damage, int flags, float force, int sound_impact, int weapon) -: entity(OBJTYPE_PROJECTILE) -{ - this->type = type; - this->pos = pos; - this->vel = vel * SERVER_TICK_SPEED; // TODO: remove this - this->lifespan = span; - this->owner = owner; - this->powner = powner; - this->flags = flags; - this->force = force; - this->damage = damage; - this->sound_impact = sound_impact; - this->weapon = weapon; - this->bounce = 0; - this->start_tick = server_tick(); - world->insert_entity(this); -} - -void projectile::reset() -{ - world->destroy_entity(this); -} - -void projectile::tick() -{ - float gravity = -400; - if(type != WEAPON_ROCKET) - gravity = -100; - - float pt = (server_tick()-start_tick-1)/(float)SERVER_TICK_SPEED; - float ct = (server_tick()-start_tick)/(float)SERVER_TICK_SPEED; - vec2 prevpos = calc_pos(pos, vel, gravity, pt); - vec2 curpos = calc_pos(pos, vel, gravity, ct); - - lifespan--; - - int collide = col_check_point((int)curpos.x, (int)curpos.y); - - vec2 new_pos; - entity *targetplayer = (entity*)intersect_player(prevpos, curpos, new_pos, powner); - - if(targetplayer || collide || lifespan < 0 ) - { - if (lifespan >= 0 || weapon == WEAPON_ROCKET) - create_sound(pos, sound_impact); - - if (flags & PROJECTILE_FLAGS_EXPLODE) - create_explosion(prevpos, owner, weapon, false); - else if (targetplayer) - { - targetplayer->take_damage(normalize(vel) * max(0.001f, force), damage, owner, weapon); - } - - world->destroy_entity(this); - } -} - -void projectile::snap(int snapping_client) -{ - float ct = (server_tick()-start_tick)/(float)SERVER_TICK_SPEED; - vec2 curpos = calc_pos(pos, vel, -7.5f*SERVER_TICK_SPEED, ct); - - if(distance(players[snapping_client].pos, curpos) > 1000.0f) - return; - - obj_projectile *proj = (obj_projectile *)snap_new_item(OBJTYPE_PROJECTILE, id, sizeof(obj_projectile)); - proj->x = (int)pos.x; - proj->y = (int)pos.y; - proj->vx = (int)vel.x; - proj->vy = (int)vel.y; - proj->start_tick = start_tick; - proj->type = type; -} - -////////////////////////////////////////////////// -// player -////////////////////////////////////////////////// -// TODO: move to separate file -player::player() -: entity(OBJTYPE_PLAYER_CHARACTER) -{ - init(); -} - -void player::init() -{ - proximity_radius = phys_size; - client_id = -1; - team = -1; // -1 == spectator - extrapowerflags = 0; - - latency_accum = 0; - latency_accum_min = 0; - latency_accum_max = 0; - latency_avg = 0; - latency_min = 0; - latency_max = 0; - - reset(); -} - -void player::reset() -{ - pos = vec2(0.0f, 0.0f); - core.vel = vec2(0.0f, 0.0f); - //direction = vec2(0.0f, 1.0f); - score = 0; - dead = true; - clear_flag(entity::FLAG_PHYSICS); - spawning = false; - die_tick = 0; - damage_taken = 0; - state = STATE_UNKNOWN; - - mem_zero(&input, sizeof(input)); - mem_zero(&previnput, sizeof(previnput)); - num_inputs = 0; - - last_action = -1; - - emote_stop = 0; - damage_taken_tick = 0; - attack_tick = 0; - numobjectshit = 0; - ninjaactivationtick = 0; - currentmovetime = 0; - - active_weapon = WEAPON_GUN; - last_weapon = WEAPON_HAMMER; - wanted_weapon = WEAPON_GUN; -} - -void player::destroy() { } - -void player::set_weapon(int w) -{ - last_weapon = active_weapon; - active_weapon = w; - if(active_weapon < 0 || active_weapon >= NUM_WEAPONS) - active_weapon = 0; -} - -void player::respawn() -{ - spawning = true; -} - - -void player::set_team(int new_team) -{ - team = new_team; - die(client_id, -1); - - dbg_msg("game", "cid=%d team=%d", client_id, team); - - if(team == -1) - clear_flag(FLAG_PHYSICS); - else - set_flag(FLAG_PHYSICS); -} - - -bool try_spawntype(int t, vec2 *outpos) -{ - // get spawn point - int start, num; - map_get_type(t, &start, &num); - if(!num) - return false; - - int id = rand()%num; - mapres_spawnpoint *sp = (mapres_spawnpoint*)map_get_item(start + id, NULL, NULL); - *outpos = vec2((float)sp->x, (float)sp->y); - return true; -} - -void player::try_respawn() -{ - vec2 spawnpos = vec2(100.0f, -60.0f); - - // get spawn point - if(gameobj->gametype == GAMETYPE_CTF) - { - // try first try own team spawn, then normal spawn and then enemy - if(!try_spawntype(MAPRES_SPAWNPOINT_RED+(team&1), &spawnpos)) - { - if(!try_spawntype(MAPRES_SPAWNPOINT, &spawnpos)) - try_spawntype(MAPRES_SPAWNPOINT_RED+((team+1)&1), &spawnpos); - } - } - else - { - if(!try_spawntype(MAPRES_SPAWNPOINT, &spawnpos)) - try_spawntype(MAPRES_SPAWNPOINT_RED+(rand()&1), &spawnpos); - } - - // check if the position is occupado - entity *ents[2] = {0}; - int types[] = {OBJTYPE_PLAYER_CHARACTER}; - int num_ents = world->find_entities(spawnpos, 64, ents, 2, types, 1); - for(int i = 0; i < num_ents; i++) - { - if(ents[i] != this) - return; - } - - spawning = false; - pos = spawnpos; - - core.pos = pos; - core.vel = vec2(0,0); - core.hooked_player = -1; - - health = 10; - armor = 0; - jumped = 0; - dead = false; - set_flag(entity::FLAG_PHYSICS); - state = STATE_PLAYING; - - core.hook_state = HOOK_IDLE; - - mem_zero(&input, sizeof(input)); - - // init weapons - mem_zero(&weapons, sizeof(weapons)); - weapons[WEAPON_HAMMER].got = true; - weapons[WEAPON_HAMMER].ammo = -1; - weapons[WEAPON_GUN].got = true; - weapons[WEAPON_GUN].ammo = data->weapons[WEAPON_GUN].maxammo; - - //weapons[WEAPON_SNIPER].got = true; - //weapons[WEAPON_SNIPER].ammo = data->weapons[WEAPON_SNIPER].maxammo; - active_weapon = WEAPON_GUN; - last_weapon = WEAPON_HAMMER; - wanted_weapon = WEAPON_GUN; - - reload_timer = 0; - - // Create sound and spawn effects - create_sound(pos, SOUND_PLAYER_SPAWN); - create_spawn(pos); - - gameobj->on_player_spawn(this); -} - -bool player::is_grounded() -{ - if(col_check_point((int)(pos.x+phys_size/2), (int)(pos.y+phys_size/2+5))) - return true; - if(col_check_point((int)(pos.x-phys_size/2), (int)(pos.y+phys_size/2+5))) - return true; - return false; -} - -struct input_count -{ - int presses; - int releases; -}; - -static input_count count_input(int prev, int cur) -{ - input_count c = {0,0}; - prev &= INPUT_STATE_MASK; - cur &= INPUT_STATE_MASK; - int i = prev; - while(i != cur) - { - i = (i+1)&INPUT_STATE_MASK; - if(i&1) - c.presses++; - else - c.releases++; - } - - return c; -} - - -int player::handle_ninja() -{ - vec2 direction = normalize(vec2(input.target_x, input.target_y)); - - if ((server_tick() - ninjaactivationtick) > (data->weapons[WEAPON_NINJA].duration * server_tickspeed() / 1000)) - { - // time's up, return - weapons[WEAPON_NINJA].got = false; - active_weapon = last_weapon; - set_weapon(active_weapon); - return 0; - } - - // Check if it should activate - if (count_input(previnput.fire, input.fire).presses && (server_tick() > currentcooldown)) - { - // ok then, activate ninja - attack_tick = server_tick(); - activationdir = direction; - currentmovetime = data->weapons[WEAPON_NINJA].movetime * server_tickspeed() / 1000; - currentcooldown = data->weapons[WEAPON_NINJA].firedelay * server_tickspeed() / 1000 + server_tick(); - - // reset hit objects - numobjectshit = 0; - - create_sound(pos, SOUND_NINJA_FIRE); - - // release all hooks when ninja is activated - //release_hooked(); - //release_hooks(); - } - - currentmovetime--; - - if (currentmovetime == 0) - { - // reset player velocity - core.vel *= 0.2f; - //return MODIFIER_RETURNFLAGS_OVERRIDEWEAPON; - } - - if (currentmovetime > 0) - { - // Set player velocity - core.vel = activationdir * data->weapons[WEAPON_NINJA].velocity; - vec2 oldpos = pos; - move_box(&core.pos, &core.vel, vec2(phys_size, phys_size), 0.0f); - // reset velocity so the client doesn't predict stuff - core.vel = vec2(0.0f,0.0f); - if ((currentmovetime % 2) == 0) - { - create_smoke(pos); - } - - // check if we hit anything along the way - { - int type = OBJTYPE_PLAYER_CHARACTER; - entity *ents[64]; - vec2 dir = pos - oldpos; - float radius = phys_size * 2.0f; //length(dir * 0.5f); - vec2 center = oldpos + dir * 0.5f; - int num = world->find_entities(center, radius, ents, 64, &type, 1); - - for (int i = 0; i < num; i++) - { - // Check if entity is a player - if (ents[i] == this) - continue; - // make sure we haven't hit this object before - bool balreadyhit = false; - for (int j = 0; j < numobjectshit; j++) - { - if (hitobjects[j] == ents[i]) - balreadyhit = true; - } - if (balreadyhit) - continue; - - // check so we are sufficiently close - if (distance(ents[i]->pos, pos) > (phys_size * 2.0f)) - continue; - - // hit a player, give him damage and stuffs... - create_sound(ents[i]->pos, SOUND_NINJA_HIT); - // set his velocity to fast upward (for now) - if(numobjectshit < 10) - hitobjects[numobjectshit++] = ents[i]; - ents[i]->take_damage(vec2(0,10.0f), data->weapons[WEAPON_NINJA].meleedamage, client_id,WEAPON_NINJA); - } - } - return MODIFIER_RETURNFLAGS_OVERRIDEVELOCITY | MODIFIER_RETURNFLAGS_OVERRIDEPOSITION | MODIFIER_RETURNFLAGS_OVERRIDEGRAVITY; - } - - return 0; -} - -int player::handle_sniper() -{ - return 0; - - struct input_count button = count_input(previnput.fire, input.fire); - if (button.releases) - { - vec2 direction = normalize(vec2(input.target_x, input.target_y)); - // Check if we were charging, if so fire - if (weapons[WEAPON_SNIPER].weaponstage >= WEAPONSTAGE_SNIPER_CHARGING) - { - new projectile(projectile::WEAPON_PROJECTILETYPE_SNIPER, - client_id, pos+vec2(0,0), direction*50.0f, - 100 + weapons[WEAPON_SNIPER].weaponstage * 20,this, weapons[WEAPON_SNIPER].weaponstage, 0, 0, -1, WEAPON_SNIPER); - create_sound(pos, SOUND_SNIPER_FIRE); - } - // Add blowback - core.vel = -direction * 10.0f * weapons[WEAPON_SNIPER].weaponstage; - - // update ammo and stuff - weapons[WEAPON_SNIPER].ammo = max(0,weapons[WEAPON_SNIPER].ammo - weapons[WEAPON_SNIPER].weaponstage); - weapons[WEAPON_SNIPER].weaponstage = WEAPONSTAGE_SNIPER_NEUTRAL; - weapons[WEAPON_SNIPER].chargetick = 0; - } - else if (input.fire & 1) - { - // Charge!! (if we are on the ground) - if (is_grounded() && weapons[WEAPON_SNIPER].ammo > 0) - { - if (!weapons[WEAPON_SNIPER].chargetick) - { - weapons[WEAPON_SNIPER].chargetick = server_tick(); - dbg_msg("game", "Chargetick='%d:'", server_tick()); - } - if ((server_tick() - weapons[WEAPON_SNIPER].chargetick) > server_tickspeed() * data->weapons[active_weapon].chargetime) - { - if (weapons[WEAPON_SNIPER].ammo > weapons[WEAPON_SNIPER].weaponstage) - { - weapons[WEAPON_SNIPER].weaponstage++; - weapons[WEAPON_SNIPER].chargetick = server_tick(); - } - else if ((server_tick() - weapons[WEAPON_SNIPER].chargetick) > server_tickspeed() * data->weapons[active_weapon].overchargetime) - { - // Ooopsie, weapon exploded - create_explosion(pos, client_id, WEAPON_SNIPER, false); - create_sound(pos, SOUND_ROCKET_EXPLODE); - // remove this weapon and change weapon to gun - weapons[WEAPON_SNIPER].got = false; - weapons[WEAPON_SNIPER].ammo = 0; - last_weapon = active_weapon; - active_weapon = WEAPON_GUN; - return 0; - } - } - - // While charging, don't move - return MODIFIER_RETURNFLAGS_OVERRIDEVELOCITY|MODIFIER_RETURNFLAGS_NOHOOK; - } - else if (weapons[WEAPON_SNIPER].weaponstage) - weapons[WEAPON_SNIPER].weaponstage = WEAPONSTAGE_SNIPER_NEUTRAL; - } - return 0; -} - -int player::handle_weapons() -{ - vec2 direction = normalize(vec2(input.target_x, input.target_y)); - - if(config.dbg_stress) - { - for(int i = 0; i < NUM_WEAPONS; i++) - { - weapons[i].got = true; - weapons[i].ammo = 10; - } - - if(reload_timer) // twice as fast reload - reload_timer--; - } - - // check reload timer - if(reload_timer) - { - reload_timer--; - return 0; - } - - if (active_weapon == WEAPON_NINJA) - { - // don't update other weapons while ninja is active - return handle_ninja(); - } - - // select weapon - int next = count_input(previnput.next_weapon, input.next_weapon).presses; - int prev = count_input(previnput.prev_weapon, input.prev_weapon).presses; - - if(next < 128) // make sure we only try sane stuff - { - while(next) // next weapon selection - { - wanted_weapon = (wanted_weapon+1)%NUM_WEAPONS; - if(weapons[wanted_weapon].got) - next--; - } - } - - if(prev < 128) // make sure we only try sane stuff - { - while(prev) // prev weapon selection - { - wanted_weapon = (wanted_weapon-1)<0?NUM_WEAPONS-1:wanted_weapon-1; - if(weapons[wanted_weapon].got) - prev--; - } - } - - if(input.wanted_weapon) // direct weapon selection - wanted_weapon = input.wanted_weapon-1; - - if(wanted_weapon < 0 || wanted_weapon >= NUM_WEAPONS) - wanted_weapon = 0; - - // switch weapon if wanted - if(data->weapons[active_weapon].duration <= 0) - { - if(wanted_weapon != -1 && wanted_weapon != active_weapon && wanted_weapon >= 0 && wanted_weapon < NUM_WEAPONS && weapons[wanted_weapon].got) - { - if(active_weapon != wanted_weapon) - create_sound(pos, SOUND_WEAPON_SWITCH); - - set_weapon(wanted_weapon); - } - } - - if (active_weapon == WEAPON_SNIPER) - { - // don't update other weapons while sniper is active - return handle_sniper(); - } - - if(count_input(previnput.fire, input.fire).presses) //previnput.fire != input.fire && (input.fire&1)) - { - if(reload_timer == 0) - { - // fire! - if(weapons[active_weapon].ammo) - { - switch(active_weapon) - { - case WEAPON_HAMMER: - // reset objects hit - numobjectshit = 0; - create_sound(pos, SOUND_HAMMER_FIRE); - break; - - case WEAPON_GUN: - new projectile(WEAPON_GUN, - client_id, - pos+vec2(0,0), - direction*30.0f, - 100, - this, - 1, 0, 0, -1, WEAPON_GUN); - create_sound(pos, SOUND_GUN_FIRE); - break; - case WEAPON_ROCKET: - { - new projectile(WEAPON_ROCKET, - client_id, - pos+vec2(0,0), - direction*15.0f, - 100, - this, - 1, projectile::PROJECTILE_FLAGS_EXPLODE, 0, SOUND_ROCKET_EXPLODE, WEAPON_ROCKET); - create_sound(pos, SOUND_ROCKET_FIRE); - break; - } - case WEAPON_SHOTGUN: - { - int shotspread = 2; - for(int i = -shotspread; i <= shotspread; i++) - { - float spreading[] = {-0.12f, -0.05f, 0, 0.05f, 0.12f}; - float a = get_angle(direction); - float v = 1.0f-fabs(i/(float)shotspread); - a += spreading[i+2]; - new projectile(WEAPON_SHOTGUN, - client_id, - pos+vec2(0,0), - vec2(cosf(a), sinf(a))*(30.0f + 15.0f*v), - //vec2(cosf(a), sinf(a))*20.0f, - (int)(server_tickspeed()*0.3f), - this, - 1, 0, 0, -1, WEAPON_SHOTGUN); - } - create_sound(pos, SOUND_SHOTGUN_FIRE); - break; - } - } - - weapons[active_weapon].ammo--; - attack_tick = server_tick(); - reload_timer = data->weapons[active_weapon].firedelay * server_tickspeed() / 1000; - } - else - { - create_sound(pos, SOUND_WEAPON_NOAMMO); - } - } - } - - // Update weapons - if (active_weapon == WEAPON_HAMMER && reload_timer > 0) - { - // Handle collisions - // only one that needs update (for now) - // do selection for the weapon and bash anything in it - // check if we hit anything along the way - int type = OBJTYPE_PLAYER_CHARACTER; - entity *ents[64]; - vec2 lookdir(direction.x > 0.0f ? 1.0f : -1.0f, 0.0f); - vec2 dir = lookdir * data->weapons[active_weapon].meleereach; - float radius = length(dir * 0.5f); - vec2 center = pos + dir * 0.5f; - int num = world->find_entities(center, radius, ents, 64, &type, 1); - - for (int i = 0; i < num; i++) - { - // Check if entity is a player - if (ents[i] == this) - continue; - // make sure we haven't hit this object before - bool balreadyhit = false; - for (int j = 0; j < numobjectshit; j++) - { - if (hitobjects[j] == ents[i]) - balreadyhit = true; - } - if (balreadyhit) - continue; - - // check so we are sufficiently close - if (distance(ents[i]->pos, pos) > (phys_size * 2.0f)) - continue; - - // hit a player, give him damage and stuffs... - // create sound for bash - //create_sound(ents[i]->pos, sound_impact); - vec2 fdir = normalize(ents[i]->pos- pos); - - // set his velocity to fast upward (for now) - create_smoke(ents[i]->pos); - create_sound(pos, SOUND_HAMMER_HIT); - if(numobjectshit < 10) - hitobjects[numobjectshit++] = ents[i]; - ents[i]->take_damage(vec2(0,-1.0f), data->weapons[active_weapon].meleedamage, client_id, active_weapon); - player* target = (player*)ents[i]; - vec2 dir; - if (length(target->pos - pos) > 0.0f) - dir = normalize(target->pos - pos); - else - dir = vec2(0,-1); - - target->core.vel += normalize(dir + vec2(0,-1.1f)) * 10.0f; - } - } - - if (data->weapons[active_weapon].ammoregentime) - { - // If equipped and not active, regen ammo? - if (reload_timer <= 0) - { - if (weapons[active_weapon].ammoregenstart < 0) - weapons[active_weapon].ammoregenstart = server_tick(); - - if ((server_tick() - weapons[active_weapon].ammoregenstart) >= data->weapons[active_weapon].ammoregentime * server_tickspeed() / 1000) - { - // Add some ammo - weapons[active_weapon].ammo = min(weapons[active_weapon].ammo + 1, data->weapons[active_weapon].maxammo); - weapons[active_weapon].ammoregenstart = -1; - } - } - else - { - weapons[active_weapon].ammoregenstart = -1; - } - } - - return 0; -} - -void player::tick() -{ - // check if we have enough input - // this is to prevent initial weird clicks - if(num_inputs < 2) - previnput = input; - - // do latency stuff - { - CLIENT_INFO info; - if(server_getclientinfo(client_id, &info)) - { - latency_accum += info.latency; - latency_accum_max = max(latency_accum_max, info.latency); - latency_accum_min = min(latency_accum_min, info.latency); - } - - if(server_tick()%server_tickspeed() == 0) - { - latency_avg = latency_accum/server_tickspeed(); - latency_max = latency_accum_max; - latency_min = latency_accum_min; - latency_accum = 0; - latency_accum_min = 1000; - latency_accum_max = 0; - } - } - - // enable / disable physics - if(team == -1 || dead) - world->core.players[client_id] = 0; - else - world->core.players[client_id] = &core; - - // spectator - if(team == -1) - return; - - if(spawning) - try_respawn(); - - // TODO: rework the input to be more robust - if(dead) - { - if(server_tick()-die_tick >= server_tickspeed()*5) // auto respawn after 3 sec - respawn(); - if((input.fire&1) && server_tick()-die_tick >= server_tickspeed()/2) // auto respawn after 0.5 sec - respawn(); - return; - } - - //player_core core; - //core.pos = pos; - //core.jumped = jumped; - core.input = input; - core.tick(); - - // handle weapons - handle_weapons(); - - state = input.state; - - // Previnput - previnput = input; - return; -} - -void player::tick_defered() -{ - if(!dead) - { - vec2 start_pos = core.pos; - vec2 start_vel = core.vel; - bool stuck_before = test_box(core.pos, vec2(28.0f, 28.0f)); - core.move(); - bool stuck_after_move = test_box(core.pos, vec2(28.0f, 28.0f)); - core.quantize(); - bool stuck_after_quant = test_box(core.pos, vec2(28.0f, 28.0f)); - pos = core.pos; - - if(!stuck_before && (stuck_after_move || stuck_after_quant)) - { - dbg_msg("player", "STUCK!!! %d %d %d %f %f %f %f %x %x %x %x", - stuck_before, - stuck_after_move, - stuck_after_quant, - start_pos.x, start_pos.y, - start_vel.x, start_vel.y, - *((unsigned *)&start_pos.x), *((unsigned *)&start_pos.y), - *((unsigned *)&start_vel.x), *((unsigned *)&start_vel.y)); - } - - - int events = core.triggered_events; - int mask = cmask_all_except_one(client_id); - - if(events&COREEVENT_GROUND_JUMP) create_sound(pos, SOUND_PLAYER_JUMP, mask); - if(events&COREEVENT_AIR_JUMP) - { - create_sound(pos, SOUND_PLAYER_JUMP, mask); - ev_common *c = (ev_common *)::events.create(EVENT_AIR_JUMP, sizeof(ev_common), mask); - if(c) - { - c->x = (int)pos.x; - c->y = (int)pos.y; - } - } - - //if(events&COREEVENT_HOOK_LAUNCH) snd_play_random(CHN_WORLD, SOUND_HOOK_LOOP, 1.0f, pos); - if(events&COREEVENT_HOOK_ATTACH_PLAYER) create_sound(pos, SOUND_HOOK_ATTACH_PLAYER, mask); - if(events&COREEVENT_HOOK_ATTACH_GROUND) create_sound(pos, SOUND_HOOK_ATTACH_GROUND, mask); - //if(events&COREEVENT_HOOK_RETRACT) snd_play_random(CHN_WORLD, SOUND_PLAYER_JUMP, 1.0f, pos); - - } - - if(team == -1) - { - pos.x = input.target_x; - pos.y = input.target_y; - } -} - -void player::die(int killer, int weapon) -{ - int mode_special = gameobj->on_player_death(this, get_player(killer), weapon); - - dbg_msg("game", "kill killer='%d:%s' victim='%d:%s' weapon=%d special=%d", - killer, server_clientname(killer), - client_id, server_clientname(client_id), weapon, mode_special); - - // send the kill message - msg_pack_start(MSG_KILLMSG, MSGFLAG_VITAL); - msg_pack_int(killer); - msg_pack_int(client_id); - msg_pack_int(weapon); - msg_pack_int(mode_special); - msg_pack_end(); - server_send_msg(-1); - - // a nice sound - create_sound(pos, SOUND_PLAYER_DIE); - - // set dead state - dead = true; - die_tick = server_tick(); - clear_flag(FLAG_PHYSICS); - create_death(pos); -} - -bool player::take_damage(vec2 force, int dmg, int from, int weapon) -{ - core.vel += force; - - if(gameobj->is_friendly_fire(client_id, from) && !config.sv_teamdamage) - return false; - - // player only inflicts half damage on self - if(from == client_id) - dmg = max(1, dmg/2); - - // CTF and TDM (TODO: check for FF) - //if (gameobj->gametype != GAMETYPE_DM && from >= 0 && players[from].team == team) - //return false; - - damage_taken++; - - // create healthmod indicator - if(server_tick() < damage_taken_tick+25) - { - // make sure that the damage indicators doesn't group together - create_damageind(pos, damage_taken*0.25f, dmg); - } - else - { - damage_taken = 0; - create_damageind(pos, 0, dmg); - } - - if(armor) - { - armor -= 1; - dmg--; - } - - if(dmg > armor) - { - dmg -= armor; - armor = 0; - health -= dmg; - } - else - armor -= dmg; - - damage_taken_tick = server_tick(); - - // do damage hit sound - if(from >= 0) - create_sound(get_player(from)->pos, SOUND_HIT, cmask_one(from)); - - // check for death - if(health <= 0) - { - die(from, weapon); - - // set attacker's face to happy (taunt!) - if (from >= 0 && from != client_id) - { - player *p = get_player(from); - - p->emote_type = EMOTE_HAPPY; - p->emote_stop = server_tick() + server_tickspeed(); - } - - return false; - } - - if (dmg > 2) - create_sound(pos, SOUND_PLAYER_PAIN_LONG); - else - create_sound(pos, SOUND_PLAYER_PAIN_SHORT); - - emote_type = EMOTE_PAIN; - emote_stop = server_tick() + 500 * server_tickspeed() / 1000; - - // spawn blood? - return true; -} - -void player::snap(int snaping_client) -{ - if(1) - { - obj_player_info *info = (obj_player_info *)snap_new_item(OBJTYPE_PLAYER_INFO, client_id, sizeof(obj_player_info)); - - info->latency = latency_avg; - info->latency_flux = latency_max-latency_min; - info->local = 0; - info->clientid = client_id; - info->score = score; - info->team = team; - - if(client_id == snaping_client) - info->local = 1; - } - - if(health > 0 && distance(players[snaping_client].pos, pos) < 1000.0f) - { - obj_player_character *character = (obj_player_character *)snap_new_item(OBJTYPE_PLAYER_CHARACTER, client_id, sizeof(obj_player_character)); - - core.write(character); - - // this is to make sure that players that are just standing still - // isn't sent. this is because the physics keep bouncing between - // 0-128 when just standing. - // TODO: fix the physics so this isn't needed - if(snaping_client != client_id && abs(character->vy) < 256.0f) - character->vy = 0; - - if (emote_stop < server_tick()) - { - emote_type = EMOTE_NORMAL; - emote_stop = -1; - } - - character->emote = emote_type; - - character->ammocount = weapons[active_weapon].ammo; - character->health = 0; - character->armor = 0; - character->weapon = active_weapon; - character->weaponstage = weapons[active_weapon].weaponstage; - character->attacktick = attack_tick; - - if(client_id == snaping_client) - { - character->health = health; - character->armor = armor; - } - - if(dead) - character->health = -1; - - //if(length(vel) > 15.0f) - // player->emote = EMOTE_HAPPY; - - //if(damage_taken_tick+50 > server_tick()) - // player->emote = EMOTE_PAIN; - - if (character->emote == EMOTE_NORMAL) - { - if(250 - ((server_tick() - last_action)%(250)) < 5) - character->emote = EMOTE_BLINK; - } - - character->state = state; - } -} - -player *players; - -////////////////////////////////////////////////// -// powerup -////////////////////////////////////////////////// -powerup::powerup(int _type, int _subtype) -: entity(OBJTYPE_POWERUP) -{ - type = _type; - subtype = _subtype; - proximity_radius = phys_size; - - reset(); - - // TODO: should this be done here? - world->insert_entity(this); -} - -void powerup::reset() -{ - if (data->powerupinfo[type].startspawntime > 0) - spawntick = server_tick() + server_tickspeed() * data->powerupinfo[type].startspawntime; - else - spawntick = -1; -} - - -void send_weapon_pickup(int cid, int weapon); - -void powerup::tick() -{ - // wait for respawn - if(spawntick > 0) - { - if(server_tick() > spawntick) - { - // respawn - spawntick = -1; - - if(type == POWERUP_WEAPON) - create_sound(pos, SOUND_WEAPON_SPAWN); - } - else - return; - } - // Check if a player intersected us - vec2 meh; - player* pplayer = intersect_player(pos, pos + vec2(0,16), meh, 0); - if (pplayer) - { - // player picked us up, is someone was hooking us, let them go - int respawntime = -1; - switch (type) - { - case POWERUP_HEALTH: - if(pplayer->health < 10) - { - create_sound(pos, SOUND_PICKUP_HEALTH); - pplayer->health = min(10, pplayer->health + data->powerupinfo[type].amount); - respawntime = data->powerupinfo[type].respawntime; - } - break; - case POWERUP_ARMOR: - if(pplayer->armor < 10) - { - create_sound(pos, SOUND_PICKUP_ARMOR); - pplayer->armor = min(10, pplayer->armor + data->powerupinfo[type].amount); - respawntime = data->powerupinfo[type].respawntime; - } - break; - - case POWERUP_WEAPON: - if(subtype >= 0 && subtype < NUM_WEAPONS) - { - if(pplayer->weapons[subtype].ammo < 10 || !pplayer->weapons[subtype].got) - { - pplayer->weapons[subtype].got = true; - pplayer->weapons[subtype].ammo = min(10, pplayer->weapons[subtype].ammo + data->powerupinfo[type].amount); - respawntime = data->powerupinfo[type].respawntime; - - // TODO: data compiler should take care of stuff like this - if(subtype == WEAPON_ROCKET) - create_sound(pos, SOUND_PICKUP_ROCKET); - else if(subtype == WEAPON_SHOTGUN) - create_sound(pos, SOUND_PICKUP_SHOTGUN); - - send_weapon_pickup(pplayer->client_id, subtype); - } - } - break; - case POWERUP_NINJA: - { - // activate ninja on target player - pplayer->ninjaactivationtick = server_tick(); - pplayer->weapons[WEAPON_NINJA].got = true; - pplayer->last_weapon = pplayer->active_weapon; - pplayer->active_weapon = WEAPON_NINJA; - respawntime = data->powerupinfo[type].respawntime; - create_sound(pos, SOUND_PICKUP_NINJA); - - // loop through all players, setting their emotes - entity *ents[64]; - const int types[] = {OBJTYPE_PLAYER_CHARACTER}; - int num = world->find_entities(vec2(0, 0), 1000000, ents, 64, types, 1); - for (int i = 0; i < num; i++) - { - player *p = (player *)ents[i]; - if (p != pplayer) - { - p->emote_type = EMOTE_SURPRISE; - p->emote_stop = server_tick() + server_tickspeed(); - } - } - - pplayer->emote_type = EMOTE_ANGRY; - pplayer->emote_stop = server_tick() + 1200 * server_tickspeed() / 1000; - - break; - } - default: - break; - }; - - if(respawntime >= 0) - { - dbg_msg("game", "pickup player='%d:%s' item=%d/%d", - pplayer->client_id, server_clientname(pplayer->client_id), type, subtype); - spawntick = server_tick() + server_tickspeed() * respawntime; - } - } -} - -void powerup::snap(int snapping_client) -{ - if(spawntick != -1) - return; - - obj_powerup *up = (obj_powerup *)snap_new_item(OBJTYPE_POWERUP, id, sizeof(obj_powerup)); - up->x = (int)pos.x; - up->y = (int)pos.y; - up->type = type; // TODO: two diffrent types? what gives? - up->subtype = subtype; -} - -// POWERUP END /////////////////////// - -player *get_player(int index) -{ - return &players[index]; -} - -void create_damageind(vec2 p, float angle, int amount) -{ - float a = 3 * 3.14159f / 2 + angle; - //float a = get_angle(dir); - float s = a-pi/3; - float e = a+pi/3; - for(int i = 0; i < amount; i++) - { - float f = mix(s, e, float(i+1)/float(amount+2)); - ev_damageind *ev = (ev_damageind *)events.create(EVENT_DAMAGEINDICATION, sizeof(ev_damageind)); - if(ev) - { - ev->x = (int)p.x; - ev->y = (int)p.y; - ev->angle = (int)(f*256.0f); - } - } -} - -void create_explosion(vec2 p, int owner, int weapon, bool bnodamage) -{ - // create the event - ev_explosion *ev = (ev_explosion *)events.create(EVENT_EXPLOSION, sizeof(ev_explosion)); - if(ev) - { - ev->x = (int)p.x; - ev->y = (int)p.y; - } - - if (!bnodamage) - { - // deal damage - entity *ents[64]; - const float radius = 128.0f; - const float innerradius = 42.0f; - int num = world->find_entities(p, radius, ents, 64); - for(int i = 0; i < num; i++) - { - vec2 diff = ents[i]->pos - p; - vec2 forcedir(0,1); - float l = length(diff); - if(l) - forcedir = normalize(diff); - l = 1-clamp((l-innerradius)/(radius-innerradius), 0.0f, 1.0f); - float dmg = 6 * l; - if((int)dmg) - ents[i]->take_damage(forcedir*dmg*2, (int)dmg, owner, weapon); - } - } -} - -void create_smoke(vec2 p) -{ - // create the event - ev_explosion *ev = (ev_explosion *)events.create(EVENT_SMOKE, sizeof(ev_explosion)); - if(ev) - { - ev->x = (int)p.x; - ev->y = (int)p.y; - } -} - -void create_spawn(vec2 p) -{ - // create the event - ev_spawn *ev = (ev_spawn *)events.create(EVENT_SPAWN, sizeof(ev_spawn)); - if(ev) - { - ev->x = (int)p.x; - ev->y = (int)p.y; - } -} - -void create_death(vec2 p) -{ - // create the event - ev_death *ev = (ev_death *)events.create(EVENT_DEATH, sizeof(ev_death)); - if(ev) - { - ev->x = (int)p.x; - ev->y = (int)p.y; - } -} - -void create_sound(vec2 pos, int sound, int mask) -{ - if (sound < 0) - return; - - // create a sound - ev_sound *ev = (ev_sound *)events.create(EVENT_SOUND_WORLD, sizeof(ev_sound), mask); - if(ev) - { - ev->x = (int)pos.x; - ev->y = (int)pos.y; - ev->sound = sound; - } -} - -void create_sound_global(int sound, int target) -{ - if (sound < 0) - return; - - msg_pack_start(MSG_SOUND_GLOBAL, MSGFLAG_VITAL); - msg_pack_int(sound); - server_send_msg(-1); -} - -// TODO: should be more general -player* intersect_player(vec2 pos0, vec2 pos1, vec2& new_pos, entity* notthis) -{ - // Find other players - entity *ents[64]; - vec2 dir = pos1 - pos0; - float radius = length(dir * 0.5f); - vec2 center = pos0 + dir * 0.5f; - const int types[] = {OBJTYPE_PLAYER_CHARACTER}; - int num = world->find_entities(center, radius, ents, 64, types, 1); - for (int i = 0; i < num; i++) - { - // Check if entity is a player - if (ents[i] != notthis) - { - new_pos = ents[i]->pos; - return (player*)ents[i]; - } - } - - return 0; -} - - -void send_chat(int cid, int team, const char *msg) -{ - if(cid >= 0 && cid < MAX_CLIENTS) - dbg_msg("chat", "%d:%d:%s: %s", cid, team, server_clientname(cid), msg); - else - dbg_msg("chat", "*** %s", msg); - - if(team == -1) - { - msg_pack_start(MSG_CHAT, MSGFLAG_VITAL); - msg_pack_int(cid); - msg_pack_int(0); - msg_pack_string(msg, 512); - msg_pack_end(); - server_send_msg(-1); - } - else - { - msg_pack_start(MSG_CHAT, MSGFLAG_VITAL); - msg_pack_int(cid); - msg_pack_int(1); - msg_pack_string(msg, 512); - msg_pack_end(); - - for(int i = 0; i < MAX_CLIENTS; i++) - { - if(players[i].client_id != -1 && players[i].team == team) - server_send_msg(i); - } - } -} - - -// Server hooks -void mods_tick() -{ - // clear all events - events.clear(); - world->tick(); - - if(world->paused) // make sure that the game object always updates - gameobj->tick(); - - if(config.sv_restart) - { - if(config.sv_restart > 1) - gameobj->do_warmup(config.sv_restart); - else - gameobj->startround(); - - config.sv_restart = 0; - } - - if(config.sv_msg[0] != 0) - { - send_chat(-1, 0, config.sv_msg); - config.sv_msg[0] = 0; - } -} - -void mods_snap(int client_id) -{ - world->snap(client_id); - events.snap(client_id); -} - -void mods_client_input(int client_id, void *input) -{ - if(!world->paused) - { - if (memcmp(&players[client_id].input, input, sizeof(player_input)) != 0) - players[client_id].last_action = server_tick(); - - //players[client_id].previnput = players[client_id].input; - players[client_id].input = *(player_input*)input; - players[client_id].num_inputs++; - } -} - -void send_info(int who, int to_who) -{ - msg_pack_start(MSG_SETINFO, MSGFLAG_VITAL); - msg_pack_int(who); - msg_pack_string(server_clientname(who), 64); - msg_pack_string(players[who].skin_name, 64); - msg_pack_int(players[who].use_custom_color); - msg_pack_int(players[who].color_body); - msg_pack_int(players[who].color_feet); - msg_pack_end(); - server_send_msg(to_who); -} - -void send_emoticon(int cid, int emoticon) -{ - msg_pack_start(MSG_EMOTICON, MSGFLAG_VITAL); - msg_pack_int(cid); - msg_pack_int(emoticon % 16); - msg_pack_end(); - server_send_msg(-1); -} - -void send_weapon_pickup(int cid, int weapon) -{ - msg_pack_start(MSG_WEAPON_PICKUP, MSGFLAG_VITAL); - msg_pack_int(weapon); - msg_pack_end(); - server_send_msg(cid); -} - -void mods_client_enter(int client_id) -{ - world->insert_entity(&players[client_id]); - players[client_id].respawn(); - dbg_msg("game", "join player='%d:%s'", client_id, server_clientname(client_id)); - - char buf[512]; - sprintf(buf, "%s has joined the game", server_clientname(client_id)); - send_chat(-1, -1, buf); -} - -void mods_connected(int client_id) -{ - players[client_id].init(); - players[client_id].client_id = client_id; - - //dbg_msg("game", "connected player='%d:%s'", client_id, server_clientname(client_id)); - - // Check which team the player should be on - if(gameobj->gametype == GAMETYPE_DM) - players[client_id].team = 0; - else - players[client_id].team = gameobj->getteam(client_id); -} - -void mods_client_drop(int client_id) -{ - char buf[512]; - sprintf(buf, "%s has left the game", server_clientname(client_id)); - send_chat(-1, -1, buf); - - dbg_msg("game", "leave player='%d:%s'", client_id, server_clientname(client_id)); - - gameobj->on_player_death(&players[client_id], 0, -1); - world->remove_entity(&players[client_id]); - world->core.players[client_id] = 0x0; - players[client_id].client_id = -1; -} - -void mods_message(int msg, int client_id) -{ - if(msg == MSG_SAY) - { - int team = msg_unpack_int(); - const char *text = msg_unpack_string(); - if(team) - team = players[client_id].team; - else - team = -1; - send_chat(client_id, team, text); - } - else if (msg == MSG_SETTEAM) - { - // Switch team on given client and kill/respawn him - players[client_id].set_team(msg_unpack_int()); - gameobj->on_player_info_change(&players[client_id]); - - // send all info to this client - for(int i = 0; i < MAX_CLIENTS; i++) - { - if(players[i].client_id != -1) - send_info(i, -1); - } - } - else if (msg == MSG_CHANGEINFO || msg == MSG_STARTINFO) - { - const char *name = msg_unpack_string(); - const char *skin_name = msg_unpack_string(); - players[client_id].use_custom_color = msg_unpack_int(); - players[client_id].color_body = msg_unpack_int(); - players[client_id].color_feet = msg_unpack_int(); - - // check for invalid chars - const char *p = name; - while (*p) - { - if(*p < 32) - return; - p++; - } - - // - if(msg == MSG_CHANGEINFO && strcmp(name, server_clientname(client_id)) != 0) - { - char msg[256]; - sprintf(msg, "*** %s changed name to %s", server_clientname(client_id), name); - send_chat(-1, -1, msg); - } - - //send_set_name(client_id, players[client_id].name, name); - strncpy(players[client_id].skin_name, skin_name, 64); - server_setclientname(client_id, name); - - gameobj->on_player_info_change(&players[client_id]); - - if(msg == MSG_STARTINFO) - { - // send all info to this client - for(int i = 0; i < MAX_CLIENTS; i++) - { - if(players[i].client_id != -1) - send_info(i, client_id); - } - - msg_pack_start(MSG_READY_TO_ENTER, MSGFLAG_VITAL); - msg_pack_end(); - server_send_msg(client_id); - } - - send_info(client_id, -1); - } - else if (msg == MSG_EMOTICON) - { - int emoteicon = msg_unpack_int(); - send_emoticon(client_id, emoteicon % 16); - } -} - -extern unsigned char internal_data[]; - -void mods_init() -{ - if(!data) /* only load once */ - data = load_data_from_memory(internal_data); - - col_init(32); - - world = new game_world; - players = new player[MAX_CLIENTS]; - - // select gametype - if(strcmp(config.sv_gametype, "ctf") == 0) - gameobj = new gameobject_ctf; - else if(strcmp(config.sv_gametype, "tdm") == 0) - gameobj = new gameobject_tdm; - else - gameobj = new gameobject_dm; - - // setup core world - for(int i = 0; i < MAX_CLIENTS; i++) - players[i].core.world = &world->core; - - // - int start, num; - map_get_type(MAPRES_ITEM, &start, &num); - - // TODO: this is way more complicated then it should be - for(int i = 0; i < num; i++) - { - mapres_item *it = (mapres_item *)map_get_item(start+i, 0, 0); - - int type = -1; - int subtype = 0; - - switch(it->type) - { - case ITEM_WEAPON_GUN: - type = POWERUP_WEAPON; - subtype = WEAPON_GUN; - break; - case ITEM_WEAPON_SHOTGUN: - type = POWERUP_WEAPON; - subtype = WEAPON_SHOTGUN; - break; - case ITEM_WEAPON_ROCKET: - type = POWERUP_WEAPON; - subtype = WEAPON_ROCKET; - break; - case ITEM_WEAPON_HAMMER: - type = POWERUP_WEAPON; - subtype = WEAPON_HAMMER; - break; - - case ITEM_HEALTH: - type = POWERUP_HEALTH; - break; - - case ITEM_ARMOR: - type = POWERUP_ARMOR; - break; - - case ITEM_NINJA: - if(config.sv_powerups) - { - type = POWERUP_NINJA; - subtype = WEAPON_NINJA; - } - break; - }; - - if(type != -1) - { - // LOL, the only new in the entire game code - // perhaps we can get rid of it. seems like a stupid thing to have - powerup *ppower = new powerup(type, subtype); - ppower->pos = vec2(it->x, it->y); - } - } - - if(gameobj->gametype == GAMETYPE_CTF) - { - } - - world->insert_entity(gameobj); - - - if(config.dbg_bots) - { - - for(int i = 0; i < config.dbg_bots ; i++) - { - mods_connected(MAX_CLIENTS-i-1); - mods_client_enter(MAX_CLIENTS-i-1); - if(gameobj->gametype != GAMETYPE_DM) - players[MAX_CLIENTS-i-1].team = i&1; - } - } -} - -void mods_shutdown() -{ - delete [] players; - delete gameobj; - delete world; - gameobj = 0; - players = 0; - world = 0; -} - -void mods_presnap() {} -void mods_postsnap() {} - -extern "C" const char *mods_net_version() { return TEEWARS_NETVERSION; } diff --git a/src/game/server/game_server.h b/src/game/server/game_server.h deleted file mode 100644 index 65ef58f4..00000000 --- a/src/game/server/game_server.h +++ /dev/null @@ -1,2 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ - diff --git a/src/game/server/gs_common.cpp b/src/game/server/gs_common.cpp new file mode 100644 index 00000000..dc0d5c05 --- /dev/null +++ b/src/game/server/gs_common.cpp @@ -0,0 +1,264 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +#include +#include "gs_common.h" +#include + +gameobject::gameobject() +: entity(OBJTYPE_GAME) +{ + // select gametype + if(strcmp(config.sv_gametype, "ctf") == 0) + { + gametype = GAMETYPE_CTF; + dbg_msg("game", "-- Capture The Flag --"); + } + else if(strcmp(config.sv_gametype, "tdm") == 0) + { + gametype = GAMETYPE_TDM; + dbg_msg("game", "-- Team Death Match --"); + } + else + { + gametype = GAMETYPE_DM; + dbg_msg("game", "-- Death Match --"); + } + + // + do_warmup(config.sv_warmup); + game_over_tick = -1; + sudden_death = 0; + round_start_tick = server_tick(); + round_count = 0; + is_teamplay = false; + teamscore[0] = 0; + teamscore[1] = 0; +} + +void gameobject::endround() +{ + if(warmup) // game can't end when we are running warmup + return; + + world->paused = true; + game_over_tick = server_tick(); + sudden_death = 0; +} + +void gameobject::resetgame() +{ + world->reset_requested = true; +} + +static bool is_separator(char c) +{ + return c == ';' || c == ' ' || c == ',' || c == '\t'; +} + +void gameobject::startround() +{ + resetgame(); + + round_start_tick = server_tick(); + sudden_death = 0; + game_over_tick = -1; + world->paused = false; + teamscore[0] = 0; + teamscore[1] = 0; + round_count++; +} + +void gameobject::cyclemap() +{ + if(!strlen(config.sv_maprotation)) + return; + // handle maprotation + char buf[512]; + const char *s = strstr(config.sv_maprotation, config.sv_map); + if(s == 0) + s = config.sv_maprotation; // restart rotation + else + { + s += strlen(config.sv_map); // skip this map + while(is_separator(s[0])) + s++; + if(s[0] == 0) + s = config.sv_maprotation; // restart rotation + } + + int i = 0; + for(; i < 512; i++) + { + buf[i] = s[i]; + if(is_separator(s[i]) || s[i] == 0) + { + buf[i] = 0; + break; + } + } + + i = 0; // skip spaces + while(is_separator(buf[i])) + i++; + + dbg_msg("game", "rotating map to %s", &buf[i]); + strcpy(config.sv_map, &buf[i]); +} + +void gameobject::post_reset() +{ + for(int i = 0; i < MAX_CLIENTS; i++) + { + if(players[i].client_id != -1) + players[i].respawn(); + } +} + +void gameobject::on_player_info_change(class player *p) +{ + const int team_colors[2] = {65387, 10223467}; + if(is_teamplay) + { + if(p->team >= 0 || p->team <= 1) + { + p->use_custom_color = 1; + p->color_body = team_colors[p->team]; + p->color_feet = team_colors[p->team]; + } + } +} + + +int gameobject::on_player_death(class player *victim, class player *killer, int weapon) +{ + // do scoreing + if(!killer) + return 0; + if(killer == victim) + victim->score--; // suicide + else + { + if(is_teamplay && victim->team == killer->team) + killer->score--; // teamkill + else + killer->score++; // normal kill + } + return 0; +} + +void gameobject::do_warmup(int seconds) +{ + warmup = seconds*SERVER_TICK_SPEED; +} + +bool gameobject::is_friendly_fire(int cid1, int cid2) +{ + if(cid1 == cid2) + return false; + + if(is_teamplay) + { + if(players[cid1].team == players[cid2].team) + return true; + } + + return false; +} + +void gameobject::tick() +{ + // do warmup + if(warmup) + { + warmup--; + if(!warmup) + startround(); + } + + if(game_over_tick != -1) + { + // game over.. wait for restart + if(server_tick() > game_over_tick+server_tickspeed()*10) + { + cyclemap(); + startround(); + } + } + + + // update browse info + int prog = -1; + if(config.sv_timelimit > 0) + prog = max(prog, (server_tick()-round_start_tick) * 100 / (config.sv_timelimit*server_tickspeed()*60)); + + if(config.sv_scorelimit) + { + if(is_teamplay) + { + prog = max(prog, (teamscore[0]*100)/config.sv_scorelimit); + prog = max(prog, (teamscore[1]*100)/config.sv_scorelimit); + } + else + { + for(int i = 0; i < MAX_CLIENTS; i++) + { + if(players[i].client_id != -1) + prog = max(prog, (players[i].score*100)/config.sv_scorelimit); + } + } + } + + if(warmup) + prog = -1; + + server_setbrowseinfo(gametype, prog); +} + +void gameobject::snap(int snapping_client) +{ + obj_game *game = (obj_game *)snap_new_item(OBJTYPE_GAME, 0, sizeof(obj_game)); + game->paused = world->paused; + game->game_over = game_over_tick==-1?0:1; + game->sudden_death = sudden_death; + + game->score_limit = config.sv_scorelimit; + game->time_limit = config.sv_timelimit; + game->round_start_tick = round_start_tick; + game->gametype = gametype; + + game->warmup = warmup; + + game->teamscore[0] = teamscore[0]; + game->teamscore[1] = teamscore[1]; +} + +int gameobject::getteam(int notthisid) +{ + int numplayers[2] = {0,0}; + for(int i = 0; i < MAX_CLIENTS; i++) + { + if(players[i].client_id != -1 && players[i].client_id != notthisid) + { + numplayers[players[i].team]++; + } + } + + return numplayers[0] > numplayers[1] ? 1 : 0; +} + +void gameobject::do_team_wincheck() +{ + if(game_over_tick == -1 && !warmup) + { + // check score win condition + if((config.sv_scorelimit > 0 && (teamscore[0] >= config.sv_scorelimit || teamscore[1] >= config.sv_scorelimit)) || + (config.sv_timelimit > 0 && (server_tick()-round_start_tick) >= config.sv_timelimit*server_tickspeed()*60)) + { + if(teamscore[0] != teamscore[1]) + endround(); + else + sudden_death = 1; + } + } +} + +gameobject *gameobj = 0; diff --git a/src/game/server/gs_common.h b/src/game/server/gs_common.h new file mode 100644 index 00000000..93264f9f --- /dev/null +++ b/src/game/server/gs_common.h @@ -0,0 +1,327 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +#include "../g_game.h" +#include "../generated/gs_data.h" + + +void create_sound_global(int sound, int target=-1); + +inline int cmask_all() { return -1; } +inline int cmask_one(int cid) { return 1<stand_pos = vec2(stand->x, stand->y); + f->pos = f->stand_pos; + flags[i] = f; + //dbg_msg("game", "flag at %f,%f", f->pos.x, f->pos.y); + } + else + { + // report massive failure + flags[i] = 0; + } + } + + is_teamplay = true; +} + +void gameobject_ctf::on_player_spawn(class player *p) +{ +} + +int gameobject_ctf::on_player_death(class player *victim, class player *killer, int weaponid) +{ + gameobject::on_player_death(victim, killer, weaponid); + int had_flag = 0; + + // drop flags + for(int fi = 0; fi < 2; fi++) + { + flag *f = flags[fi]; + if(f && f->carrying_player == killer) + had_flag |= 2; + if(f && f->carrying_player == victim) + { + create_sound_global(SOUND_CTF_DROP); + f->drop_tick = server_tick(); + f->carrying_player = 0; + f->vel = vec2(0,0); + + if(killer && killer->team != victim->team) + killer->score++; + + had_flag |= 1; + } + } + + return had_flag; +} + +void gameobject_ctf::tick() +{ + gameobject::tick(); + + do_team_wincheck(); + + // do flags + for(int fi = 0; fi < 2; fi++) + { + flag *f = flags[fi]; + + if(!f) + continue; + + // + if(f->carrying_player) + { + // update flag position + f->pos = f->carrying_player->pos; + + if(flags[fi^1]->at_stand) + { + if(distance(f->pos, flags[fi^1]->pos) < 24) + { + // CAPTURE! \o/ + teamscore[fi^1] += 100; + f->carrying_player->score += 5; + for(int i = 0; i < 2; i++) + flags[i]->reset(); + + dbg_msg("", "capture sound %d", SOUND_CTF_CAPTURE); + create_sound_global(SOUND_CTF_CAPTURE); + } + } + } + else + { + player *players[MAX_CLIENTS]; + int types[] = {OBJTYPE_PLAYER_CHARACTER}; + int num = world->find_entities(f->pos, 32.0f, (entity**)players, MAX_CLIENTS, types, 1); + for(int i = 0; i < num; i++) + { + if(players[i]->team == f->team) + { + // return the flag + if(!f->at_stand) + { + players[i]->score += 1; + create_sound_global(SOUND_CTF_RETURN); + f->reset(); + } + } + else + { + // take the flag + if(f->at_stand) + teamscore[fi^1]++; + f->at_stand = 0; + f->carrying_player = players[i]; + f->carrying_player->score += 1; + create_sound_global(SOUND_CTF_GRAB); + break; + } + } + + if(!f->carrying_player && !f->at_stand) + { + if(server_tick() > f->drop_tick + SERVER_TICK_SPEED*30) + { + create_sound_global(SOUND_CTF_RETURN); + f->reset(); + } + else + { + f->vel.y += gravity; + move_box(&f->pos, &f->vel, vec2(f->phys_size, f->phys_size), 0.5f); + } + } + } + } +} + +// Flag +flag::flag(int _team) +: entity(OBJTYPE_FLAG) +{ + team = _team; + proximity_radius = phys_size; + carrying_player = 0x0; + + reset(); + + // TODO: should this be done here? + world->insert_entity(this); +} + +void flag::reset() +{ + carrying_player = 0; + at_stand = 1; + pos = stand_pos; + vel = vec2(0,0); +} + +void flag::snap(int snapping_client) +{ + obj_flag *flag = (obj_flag *)snap_new_item(OBJTYPE_FLAG, team, sizeof(obj_flag)); + flag->x = (int)pos.x; + flag->y = (int)pos.y; + flag->team = team; + flag->carried_by = -1; + + if(at_stand) + flag->carried_by = -2; + else if(carrying_player) + flag->carried_by = carrying_player->client_id; +} diff --git a/src/game/server/gs_game_ctf.h b/src/game/server/gs_game_ctf.h new file mode 100644 index 00000000..02acef37 --- /dev/null +++ b/src/game/server/gs_game_ctf.h @@ -0,0 +1,33 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ + +// game object +class gameobject_ctf : public gameobject +{ +public: + class flag *flags[2]; + + gameobject_ctf(); + virtual void tick(); + + virtual void on_player_spawn(class player *p); + virtual int on_player_death(class player *victim, class player *killer, int weapon); +}; + +// TODO: move to seperate file +class flag : public entity +{ +public: + static const int phys_size = 14; + player *carrying_player; + vec2 vel; + vec2 stand_pos; + + int team; + int at_stand; + int drop_tick; + + flag(int _team); + + virtual void reset(); + virtual void snap(int snapping_client); +}; diff --git a/src/game/server/gs_game_dm.cpp b/src/game/server/gs_game_dm.cpp new file mode 100644 index 00000000..98317578 --- /dev/null +++ b/src/game/server/gs_game_dm.cpp @@ -0,0 +1,42 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +#include +#include "gs_common.h" +#include "gs_game_dm.h" + +void gameobject_dm::tick() +{ + if(game_over_tick == -1) + { + // game is running + + // gather some stats + int topscore = 0; + int topscore_count = 0; + for(int i = 0; i < MAX_CLIENTS; i++) + { + if(players[i].client_id != -1) + { + if(players[i].score > topscore) + { + topscore = players[i].score; + topscore_count = 1; + } + else if(players[i].score == topscore) + topscore_count++; + } + } + + // check score win condition + if((config.sv_scorelimit > 0 && topscore >= config.sv_scorelimit) || + (config.sv_timelimit > 0 && (server_tick()-round_start_tick) >= config.sv_timelimit*server_tickspeed()*60)) + { + if(topscore_count == 1) + endround(); + else + sudden_death = 1; + } + } + + gameobject::tick(); +} + diff --git a/src/game/server/gs_game_dm.h b/src/game/server/gs_game_dm.h new file mode 100644 index 00000000..96bff3ae --- /dev/null +++ b/src/game/server/gs_game_dm.h @@ -0,0 +1,7 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +// game object +class gameobject_dm : public gameobject +{ +public: + virtual void tick(); +}; diff --git a/src/game/server/gs_game_tdm.cpp b/src/game/server/gs_game_tdm.cpp new file mode 100644 index 00000000..7aa12e2b --- /dev/null +++ b/src/game/server/gs_game_tdm.cpp @@ -0,0 +1,32 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +#include +#include "gs_common.h" +#include "gs_game_tdm.h" + +gameobject_tdm::gameobject_tdm() +{ + is_teamplay = true; +} + + +int gameobject_tdm::on_player_death(class player *victim, class player *killer, int weapon) +{ + gameobject::on_player_death(victim, killer, weapon); + + if(weapon >= 0) + { + // do team scoring + if(killer == victim) + teamscore[killer->team&1]--; // klant arschel + else + teamscore[killer->team&1]++; // good shit + } + return 0; +} + +void gameobject_tdm::tick() +{ + do_team_wincheck(); + + gameobject::tick(); +} diff --git a/src/game/server/gs_game_tdm.h b/src/game/server/gs_game_tdm.h new file mode 100644 index 00000000..70b4646e --- /dev/null +++ b/src/game/server/gs_game_tdm.h @@ -0,0 +1,10 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +// game object +class gameobject_tdm : public gameobject +{ +public: + gameobject_tdm(); + + int on_player_death(class player *victim, class player *killer, int weapon); + virtual void tick(); +}; diff --git a/src/game/server/gs_server.cpp b/src/game/server/gs_server.cpp new file mode 100644 index 00000000..65985371 --- /dev/null +++ b/src/game/server/gs_server.cpp @@ -0,0 +1,1859 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +#include +#include +#include +#include +#include "../g_version.h" +#include "gs_common.h" +#include "gs_game_ctf.h" +#include "gs_game_tdm.h" +#include "gs_game_dm.h" + +data_container *data = 0x0; + +class player* get_player(int index); +void create_damageind(vec2 p, float angle_mod, int amount); +void create_explosion(vec2 p, int owner, int weapon, bool bnodamage); +void create_smoke(vec2 p); +void create_spawn(vec2 p); +void create_death(vec2 p); +void create_sound(vec2 pos, int sound, int mask=-1); +class player *intersect_player(vec2 pos0, vec2 pos1, vec2 &new_pos, class entity *notthis = 0); + +game_world *world; + +////////////////////////////////////////////////// +// Event handler +////////////////////////////////////////////////// +event_handler::event_handler() +{ + clear(); +} + +void *event_handler::create(int type, int size, int mask) +{ + if(num_events == MAX_EVENTS) + return 0; + if(current_offset+size >= MAX_DATASIZE) + return 0; + + void *p = &data[current_offset]; + offsets[num_events] = current_offset; + types[num_events] = type; + sizes[num_events] = size; + client_masks[num_events] = mask; + current_offset += size; + num_events++; + return p; +} + +void event_handler::clear() +{ + num_events = 0; + current_offset = 0; +} + +void event_handler::snap(int snapping_client) +{ + for(int i = 0; i < num_events; i++) + { + if(cmask_is_set(client_masks[i], snapping_client)) + { + ev_common *ev = (ev_common *)&data[offsets[i]]; + if(distance(players[snapping_client].pos, vec2(ev->x, ev->y)) < 1500.0f) + { + void *d = snap_new_item(types[i], i, sizes[i]); + mem_copy(d, &data[offsets[i]], sizes[i]); + } + } + } +} + +event_handler events; + +////////////////////////////////////////////////// +// Entity +////////////////////////////////////////////////// +entity::entity(int objtype) +{ + this->objtype = objtype; + pos = vec2(0,0); + flags = FLAG_PHYSICS; + proximity_radius = 0; + + id = snap_new_id(); + + next_entity = 0; + prev_entity = 0; + prev_type_entity = 0; + next_type_entity = 0; +} + +entity::~entity() +{ + snap_free_id(id); +} + +////////////////////////////////////////////////// +// game world +////////////////////////////////////////////////// +game_world::game_world() +{ + paused = false; + reset_requested = false; + first_entity = 0x0; + for(int i = 0; i < NUM_ENT_TYPES; i++) + first_entity_types[i] = 0; +} + +int game_world::find_entities(vec2 pos, float radius, entity **ents, int max) +{ + int num = 0; + for(entity *ent = first_entity; ent; ent = ent->next_entity) + { + if(!(ent->flags&entity::FLAG_PHYSICS)) + continue; + + if(distance(ent->pos, pos) < radius+ent->proximity_radius) + { + ents[num] = ent; + num++; + if(num == max) + break; + } + } + + return num; +} + +int game_world::find_entities(vec2 pos, float radius, entity **ents, int max, const int* types, int maxtypes) +{ + int num = 0; + for(int t = 0; t < maxtypes; t++) + { + for(entity *ent = first_entity_types[types[t]]; ent; ent = ent->next_type_entity) + { + if(!(ent->flags&entity::FLAG_PHYSICS)) + continue; + + if(distance(ent->pos, pos) < radius+ent->proximity_radius) + { + ents[num] = ent; + num++; + if(num == max) + break; + } + } + } + + return num; +} + +void game_world::insert_entity(entity *ent) +{ + entity *cur = first_entity; + while(cur) + { + dbg_assert(cur != ent, "err"); + cur = cur->next_entity; + } + + // insert it + if(first_entity) + first_entity->prev_entity = ent; + ent->next_entity = first_entity; + ent->prev_entity = 0x0; + first_entity = ent; + + // into typelist aswell + if(first_entity_types[ent->objtype]) + first_entity_types[ent->objtype]->prev_type_entity = ent; + ent->next_type_entity = first_entity_types[ent->objtype]; + ent->prev_type_entity = 0x0; + first_entity_types[ent->objtype] = ent; +} + +void game_world::destroy_entity(entity *ent) +{ + ent->set_flag(entity::FLAG_DESTROY); +} + +void game_world::remove_entity(entity *ent) +{ + // not in the list + if(!ent->next_entity && !ent->prev_entity && first_entity != ent) + return; + + // remove + if(ent->prev_entity) + ent->prev_entity->next_entity = ent->next_entity; + else + first_entity = ent->next_entity; + if(ent->next_entity) + ent->next_entity->prev_entity = ent->prev_entity; + + if(ent->prev_type_entity) + ent->prev_type_entity->next_type_entity = ent->next_type_entity; + else + first_entity_types[ent->objtype] = ent->next_type_entity; + if(ent->next_type_entity) + ent->next_type_entity->prev_type_entity = ent->prev_type_entity; + + ent->next_entity = 0; + ent->prev_entity = 0; + ent->next_type_entity = 0; + ent->prev_type_entity = 0; +} + +// +void game_world::snap(int snapping_client) +{ + for(entity *ent = first_entity; ent; ent = ent->next_entity) + ent->snap(snapping_client); +} + +void game_world::reset() +{ + // reset all entities + for(entity *ent = first_entity; ent; ent = ent->next_entity) + ent->reset(); + remove_entities(); + + for(entity *ent = first_entity; ent; ent = ent->next_entity) + ent->post_reset(); + remove_entities(); + + reset_requested = false; +} + +void game_world::remove_entities() +{ + // destroy objects marked for destruction + entity *ent = first_entity; + while(ent) + { + entity *next = ent->next_entity; + if(ent->flags&entity::FLAG_DESTROY) + { + remove_entity(ent); + ent->destroy(); + } + ent = next; + } +} + +void game_world::tick() +{ + if(reset_requested) + reset(); + + if(!paused) + { + // update all objects + for(entity *ent = first_entity; ent; ent = ent->next_entity) + ent->tick(); + + for(entity *ent = first_entity; ent; ent = ent->next_entity) + ent->tick_defered(); + } + + remove_entities(); +} + +////////////////////////////////////////////////// +// projectile +////////////////////////////////////////////////// +projectile::projectile(int type, int owner, vec2 pos, vec2 vel, int span, entity* powner, + int damage, int flags, float force, int sound_impact, int weapon) +: entity(OBJTYPE_PROJECTILE) +{ + this->type = type; + this->pos = pos; + this->vel = vel * SERVER_TICK_SPEED; // TODO: remove this + this->lifespan = span; + this->owner = owner; + this->powner = powner; + this->flags = flags; + this->force = force; + this->damage = damage; + this->sound_impact = sound_impact; + this->weapon = weapon; + this->bounce = 0; + this->start_tick = server_tick(); + world->insert_entity(this); +} + +void projectile::reset() +{ + world->destroy_entity(this); +} + +void projectile::tick() +{ + float gravity = -400; + if(type != WEAPON_ROCKET) + gravity = -100; + + float pt = (server_tick()-start_tick-1)/(float)SERVER_TICK_SPEED; + float ct = (server_tick()-start_tick)/(float)SERVER_TICK_SPEED; + vec2 prevpos = calc_pos(pos, vel, gravity, pt); + vec2 curpos = calc_pos(pos, vel, gravity, ct); + + lifespan--; + + int collide = col_check_point((int)curpos.x, (int)curpos.y); + + vec2 new_pos; + entity *targetplayer = (entity*)intersect_player(prevpos, curpos, new_pos, powner); + + if(targetplayer || collide || lifespan < 0 ) + { + if (lifespan >= 0 || weapon == WEAPON_ROCKET) + create_sound(pos, sound_impact); + + if (flags & PROJECTILE_FLAGS_EXPLODE) + create_explosion(prevpos, owner, weapon, false); + else if (targetplayer) + { + targetplayer->take_damage(normalize(vel) * max(0.001f, force), damage, owner, weapon); + } + + world->destroy_entity(this); + } +} + +void projectile::snap(int snapping_client) +{ + float ct = (server_tick()-start_tick)/(float)SERVER_TICK_SPEED; + vec2 curpos = calc_pos(pos, vel, -7.5f*SERVER_TICK_SPEED, ct); + + if(distance(players[snapping_client].pos, curpos) > 1000.0f) + return; + + obj_projectile *proj = (obj_projectile *)snap_new_item(OBJTYPE_PROJECTILE, id, sizeof(obj_projectile)); + proj->x = (int)pos.x; + proj->y = (int)pos.y; + proj->vx = (int)vel.x; + proj->vy = (int)vel.y; + proj->start_tick = start_tick; + proj->type = type; +} + +////////////////////////////////////////////////// +// player +////////////////////////////////////////////////// +// TODO: move to separate file +player::player() +: entity(OBJTYPE_PLAYER_CHARACTER) +{ + init(); +} + +void player::init() +{ + proximity_radius = phys_size; + client_id = -1; + team = -1; // -1 == spectator + extrapowerflags = 0; + + latency_accum = 0; + latency_accum_min = 0; + latency_accum_max = 0; + latency_avg = 0; + latency_min = 0; + latency_max = 0; + + reset(); +} + +void player::reset() +{ + pos = vec2(0.0f, 0.0f); + core.vel = vec2(0.0f, 0.0f); + //direction = vec2(0.0f, 1.0f); + score = 0; + dead = true; + clear_flag(entity::FLAG_PHYSICS); + spawning = false; + die_tick = 0; + damage_taken = 0; + state = STATE_UNKNOWN; + + mem_zero(&input, sizeof(input)); + mem_zero(&previnput, sizeof(previnput)); + num_inputs = 0; + + last_action = -1; + + emote_stop = 0; + damage_taken_tick = 0; + attack_tick = 0; + numobjectshit = 0; + ninjaactivationtick = 0; + currentmovetime = 0; + + active_weapon = WEAPON_GUN; + last_weapon = WEAPON_HAMMER; + wanted_weapon = WEAPON_GUN; +} + +void player::destroy() { } + +void player::set_weapon(int w) +{ + last_weapon = active_weapon; + active_weapon = w; + if(active_weapon < 0 || active_weapon >= NUM_WEAPONS) + active_weapon = 0; +} + +void player::respawn() +{ + spawning = true; +} + + +void player::set_team(int new_team) +{ + team = new_team; + die(client_id, -1); + + dbg_msg("game", "cid=%d team=%d", client_id, team); + + if(team == -1) + clear_flag(FLAG_PHYSICS); + else + set_flag(FLAG_PHYSICS); +} + + +bool try_spawntype(int t, vec2 *outpos) +{ + // get spawn point + int start, num; + map_get_type(t, &start, &num); + if(!num) + return false; + + int id = rand()%num; + mapres_spawnpoint *sp = (mapres_spawnpoint*)map_get_item(start + id, NULL, NULL); + *outpos = vec2((float)sp->x, (float)sp->y); + return true; +} + +void player::try_respawn() +{ + vec2 spawnpos = vec2(100.0f, -60.0f); + + // get spawn point + if(gameobj->gametype == GAMETYPE_CTF) + { + // try first try own team spawn, then normal spawn and then enemy + if(!try_spawntype(MAPRES_SPAWNPOINT_RED+(team&1), &spawnpos)) + { + if(!try_spawntype(MAPRES_SPAWNPOINT, &spawnpos)) + try_spawntype(MAPRES_SPAWNPOINT_RED+((team+1)&1), &spawnpos); + } + } + else + { + if(!try_spawntype(MAPRES_SPAWNPOINT, &spawnpos)) + try_spawntype(MAPRES_SPAWNPOINT_RED+(rand()&1), &spawnpos); + } + + // check if the position is occupado + entity *ents[2] = {0}; + int types[] = {OBJTYPE_PLAYER_CHARACTER}; + int num_ents = world->find_entities(spawnpos, 64, ents, 2, types, 1); + for(int i = 0; i < num_ents; i++) + { + if(ents[i] != this) + return; + } + + spawning = false; + pos = spawnpos; + + core.pos = pos; + core.vel = vec2(0,0); + core.hooked_player = -1; + + health = 10; + armor = 0; + jumped = 0; + dead = false; + set_flag(entity::FLAG_PHYSICS); + state = STATE_PLAYING; + + core.hook_state = HOOK_IDLE; + + mem_zero(&input, sizeof(input)); + + // init weapons + mem_zero(&weapons, sizeof(weapons)); + weapons[WEAPON_HAMMER].got = true; + weapons[WEAPON_HAMMER].ammo = -1; + weapons[WEAPON_GUN].got = true; + weapons[WEAPON_GUN].ammo = data->weapons[WEAPON_GUN].maxammo; + + //weapons[WEAPON_SNIPER].got = true; + //weapons[WEAPON_SNIPER].ammo = data->weapons[WEAPON_SNIPER].maxammo; + active_weapon = WEAPON_GUN; + last_weapon = WEAPON_HAMMER; + wanted_weapon = WEAPON_GUN; + + reload_timer = 0; + + // Create sound and spawn effects + create_sound(pos, SOUND_PLAYER_SPAWN); + create_spawn(pos); + + gameobj->on_player_spawn(this); +} + +bool player::is_grounded() +{ + if(col_check_point((int)(pos.x+phys_size/2), (int)(pos.y+phys_size/2+5))) + return true; + if(col_check_point((int)(pos.x-phys_size/2), (int)(pos.y+phys_size/2+5))) + return true; + return false; +} + +struct input_count +{ + int presses; + int releases; +}; + +static input_count count_input(int prev, int cur) +{ + input_count c = {0,0}; + prev &= INPUT_STATE_MASK; + cur &= INPUT_STATE_MASK; + int i = prev; + while(i != cur) + { + i = (i+1)&INPUT_STATE_MASK; + if(i&1) + c.presses++; + else + c.releases++; + } + + return c; +} + + +int player::handle_ninja() +{ + vec2 direction = normalize(vec2(input.target_x, input.target_y)); + + if ((server_tick() - ninjaactivationtick) > (data->weapons[WEAPON_NINJA].duration * server_tickspeed() / 1000)) + { + // time's up, return + weapons[WEAPON_NINJA].got = false; + active_weapon = last_weapon; + set_weapon(active_weapon); + return 0; + } + + // Check if it should activate + if (count_input(previnput.fire, input.fire).presses && (server_tick() > currentcooldown)) + { + // ok then, activate ninja + attack_tick = server_tick(); + activationdir = direction; + currentmovetime = data->weapons[WEAPON_NINJA].movetime * server_tickspeed() / 1000; + currentcooldown = data->weapons[WEAPON_NINJA].firedelay * server_tickspeed() / 1000 + server_tick(); + + // reset hit objects + numobjectshit = 0; + + create_sound(pos, SOUND_NINJA_FIRE); + + // release all hooks when ninja is activated + //release_hooked(); + //release_hooks(); + } + + currentmovetime--; + + if (currentmovetime == 0) + { + // reset player velocity + core.vel *= 0.2f; + //return MODIFIER_RETURNFLAGS_OVERRIDEWEAPON; + } + + if (currentmovetime > 0) + { + // Set player velocity + core.vel = activationdir * data->weapons[WEAPON_NINJA].velocity; + vec2 oldpos = pos; + move_box(&core.pos, &core.vel, vec2(phys_size, phys_size), 0.0f); + // reset velocity so the client doesn't predict stuff + core.vel = vec2(0.0f,0.0f); + if ((currentmovetime % 2) == 0) + { + create_smoke(pos); + } + + // check if we hit anything along the way + { + int type = OBJTYPE_PLAYER_CHARACTER; + entity *ents[64]; + vec2 dir = pos - oldpos; + float radius = phys_size * 2.0f; //length(dir * 0.5f); + vec2 center = oldpos + dir * 0.5f; + int num = world->find_entities(center, radius, ents, 64, &type, 1); + + for (int i = 0; i < num; i++) + { + // Check if entity is a player + if (ents[i] == this) + continue; + // make sure we haven't hit this object before + bool balreadyhit = false; + for (int j = 0; j < numobjectshit; j++) + { + if (hitobjects[j] == ents[i]) + balreadyhit = true; + } + if (balreadyhit) + continue; + + // check so we are sufficiently close + if (distance(ents[i]->pos, pos) > (phys_size * 2.0f)) + continue; + + // hit a player, give him damage and stuffs... + create_sound(ents[i]->pos, SOUND_NINJA_HIT); + // set his velocity to fast upward (for now) + if(numobjectshit < 10) + hitobjects[numobjectshit++] = ents[i]; + ents[i]->take_damage(vec2(0,10.0f), data->weapons[WEAPON_NINJA].meleedamage, client_id,WEAPON_NINJA); + } + } + return MODIFIER_RETURNFLAGS_OVERRIDEVELOCITY | MODIFIER_RETURNFLAGS_OVERRIDEPOSITION | MODIFIER_RETURNFLAGS_OVERRIDEGRAVITY; + } + + return 0; +} + +int player::handle_sniper() +{ + return 0; + + struct input_count button = count_input(previnput.fire, input.fire); + if (button.releases) + { + vec2 direction = normalize(vec2(input.target_x, input.target_y)); + // Check if we were charging, if so fire + if (weapons[WEAPON_SNIPER].weaponstage >= WEAPONSTAGE_SNIPER_CHARGING) + { + new projectile(projectile::WEAPON_PROJECTILETYPE_SNIPER, + client_id, pos+vec2(0,0), direction*50.0f, + 100 + weapons[WEAPON_SNIPER].weaponstage * 20,this, weapons[WEAPON_SNIPER].weaponstage, 0, 0, -1, WEAPON_SNIPER); + create_sound(pos, SOUND_SNIPER_FIRE); + } + // Add blowback + core.vel = -direction * 10.0f * weapons[WEAPON_SNIPER].weaponstage; + + // update ammo and stuff + weapons[WEAPON_SNIPER].ammo = max(0,weapons[WEAPON_SNIPER].ammo - weapons[WEAPON_SNIPER].weaponstage); + weapons[WEAPON_SNIPER].weaponstage = WEAPONSTAGE_SNIPER_NEUTRAL; + weapons[WEAPON_SNIPER].chargetick = 0; + } + else if (input.fire & 1) + { + // Charge!! (if we are on the ground) + if (is_grounded() && weapons[WEAPON_SNIPER].ammo > 0) + { + if (!weapons[WEAPON_SNIPER].chargetick) + { + weapons[WEAPON_SNIPER].chargetick = server_tick(); + dbg_msg("game", "Chargetick='%d:'", server_tick()); + } + if ((server_tick() - weapons[WEAPON_SNIPER].chargetick) > server_tickspeed() * data->weapons[active_weapon].chargetime) + { + if (weapons[WEAPON_SNIPER].ammo > weapons[WEAPON_SNIPER].weaponstage) + { + weapons[WEAPON_SNIPER].weaponstage++; + weapons[WEAPON_SNIPER].chargetick = server_tick(); + } + else if ((server_tick() - weapons[WEAPON_SNIPER].chargetick) > server_tickspeed() * data->weapons[active_weapon].overchargetime) + { + // Ooopsie, weapon exploded + create_explosion(pos, client_id, WEAPON_SNIPER, false); + create_sound(pos, SOUND_ROCKET_EXPLODE); + // remove this weapon and change weapon to gun + weapons[WEAPON_SNIPER].got = false; + weapons[WEAPON_SNIPER].ammo = 0; + last_weapon = active_weapon; + active_weapon = WEAPON_GUN; + return 0; + } + } + + // While charging, don't move + return MODIFIER_RETURNFLAGS_OVERRIDEVELOCITY|MODIFIER_RETURNFLAGS_NOHOOK; + } + else if (weapons[WEAPON_SNIPER].weaponstage) + weapons[WEAPON_SNIPER].weaponstage = WEAPONSTAGE_SNIPER_NEUTRAL; + } + return 0; +} + +int player::handle_weapons() +{ + vec2 direction = normalize(vec2(input.target_x, input.target_y)); + + if(config.dbg_stress) + { + for(int i = 0; i < NUM_WEAPONS; i++) + { + weapons[i].got = true; + weapons[i].ammo = 10; + } + + if(reload_timer) // twice as fast reload + reload_timer--; + } + + // check reload timer + if(reload_timer) + { + reload_timer--; + return 0; + } + + if (active_weapon == WEAPON_NINJA) + { + // don't update other weapons while ninja is active + return handle_ninja(); + } + + // select weapon + int next = count_input(previnput.next_weapon, input.next_weapon).presses; + int prev = count_input(previnput.prev_weapon, input.prev_weapon).presses; + + if(next < 128) // make sure we only try sane stuff + { + while(next) // next weapon selection + { + wanted_weapon = (wanted_weapon+1)%NUM_WEAPONS; + if(weapons[wanted_weapon].got) + next--; + } + } + + if(prev < 128) // make sure we only try sane stuff + { + while(prev) // prev weapon selection + { + wanted_weapon = (wanted_weapon-1)<0?NUM_WEAPONS-1:wanted_weapon-1; + if(weapons[wanted_weapon].got) + prev--; + } + } + + if(input.wanted_weapon) // direct weapon selection + wanted_weapon = input.wanted_weapon-1; + + if(wanted_weapon < 0 || wanted_weapon >= NUM_WEAPONS) + wanted_weapon = 0; + + // switch weapon if wanted + if(data->weapons[active_weapon].duration <= 0) + { + if(wanted_weapon != -1 && wanted_weapon != active_weapon && wanted_weapon >= 0 && wanted_weapon < NUM_WEAPONS && weapons[wanted_weapon].got) + { + if(active_weapon != wanted_weapon) + create_sound(pos, SOUND_WEAPON_SWITCH); + + set_weapon(wanted_weapon); + } + } + + if (active_weapon == WEAPON_SNIPER) + { + // don't update other weapons while sniper is active + return handle_sniper(); + } + + if(count_input(previnput.fire, input.fire).presses) //previnput.fire != input.fire && (input.fire&1)) + { + if(reload_timer == 0) + { + // fire! + if(weapons[active_weapon].ammo) + { + switch(active_weapon) + { + case WEAPON_HAMMER: + // reset objects hit + numobjectshit = 0; + create_sound(pos, SOUND_HAMMER_FIRE); + break; + + case WEAPON_GUN: + new projectile(WEAPON_GUN, + client_id, + pos+vec2(0,0), + direction*30.0f, + 100, + this, + 1, 0, 0, -1, WEAPON_GUN); + create_sound(pos, SOUND_GUN_FIRE); + break; + case WEAPON_ROCKET: + { + new projectile(WEAPON_ROCKET, + client_id, + pos+vec2(0,0), + direction*15.0f, + 100, + this, + 1, projectile::PROJECTILE_FLAGS_EXPLODE, 0, SOUND_ROCKET_EXPLODE, WEAPON_ROCKET); + create_sound(pos, SOUND_ROCKET_FIRE); + break; + } + case WEAPON_SHOTGUN: + { + int shotspread = 2; + for(int i = -shotspread; i <= shotspread; i++) + { + float spreading[] = {-0.12f, -0.05f, 0, 0.05f, 0.12f}; + float a = get_angle(direction); + float v = 1.0f-fabs(i/(float)shotspread); + a += spreading[i+2]; + new projectile(WEAPON_SHOTGUN, + client_id, + pos+vec2(0,0), + vec2(cosf(a), sinf(a))*(30.0f + 15.0f*v), + //vec2(cosf(a), sinf(a))*20.0f, + (int)(server_tickspeed()*0.3f), + this, + 1, 0, 0, -1, WEAPON_SHOTGUN); + } + create_sound(pos, SOUND_SHOTGUN_FIRE); + break; + } + } + + weapons[active_weapon].ammo--; + attack_tick = server_tick(); + reload_timer = data->weapons[active_weapon].firedelay * server_tickspeed() / 1000; + } + else + { + create_sound(pos, SOUND_WEAPON_NOAMMO); + } + } + } + + // Update weapons + if (active_weapon == WEAPON_HAMMER && reload_timer > 0) + { + // Handle collisions + // only one that needs update (for now) + // do selection for the weapon and bash anything in it + // check if we hit anything along the way + int type = OBJTYPE_PLAYER_CHARACTER; + entity *ents[64]; + vec2 lookdir(direction.x > 0.0f ? 1.0f : -1.0f, 0.0f); + vec2 dir = lookdir * data->weapons[active_weapon].meleereach; + float radius = length(dir * 0.5f); + vec2 center = pos + dir * 0.5f; + int num = world->find_entities(center, radius, ents, 64, &type, 1); + + for (int i = 0; i < num; i++) + { + // Check if entity is a player + if (ents[i] == this) + continue; + // make sure we haven't hit this object before + bool balreadyhit = false; + for (int j = 0; j < numobjectshit; j++) + { + if (hitobjects[j] == ents[i]) + balreadyhit = true; + } + if (balreadyhit) + continue; + + // check so we are sufficiently close + if (distance(ents[i]->pos, pos) > (phys_size * 2.0f)) + continue; + + // hit a player, give him damage and stuffs... + // create sound for bash + //create_sound(ents[i]->pos, sound_impact); + vec2 fdir = normalize(ents[i]->pos- pos); + + // set his velocity to fast upward (for now) + create_smoke(ents[i]->pos); + create_sound(pos, SOUND_HAMMER_HIT); + if(numobjectshit < 10) + hitobjects[numobjectshit++] = ents[i]; + ents[i]->take_damage(vec2(0,-1.0f), data->weapons[active_weapon].meleedamage, client_id, active_weapon); + player* target = (player*)ents[i]; + vec2 dir; + if (length(target->pos - pos) > 0.0f) + dir = normalize(target->pos - pos); + else + dir = vec2(0,-1); + + target->core.vel += normalize(dir + vec2(0,-1.1f)) * 10.0f; + } + } + + if (data->weapons[active_weapon].ammoregentime) + { + // If equipped and not active, regen ammo? + if (reload_timer <= 0) + { + if (weapons[active_weapon].ammoregenstart < 0) + weapons[active_weapon].ammoregenstart = server_tick(); + + if ((server_tick() - weapons[active_weapon].ammoregenstart) >= data->weapons[active_weapon].ammoregentime * server_tickspeed() / 1000) + { + // Add some ammo + weapons[active_weapon].ammo = min(weapons[active_weapon].ammo + 1, data->weapons[active_weapon].maxammo); + weapons[active_weapon].ammoregenstart = -1; + } + } + else + { + weapons[active_weapon].ammoregenstart = -1; + } + } + + return 0; +} + +void player::tick() +{ + // check if we have enough input + // this is to prevent initial weird clicks + if(num_inputs < 2) + previnput = input; + + // do latency stuff + { + CLIENT_INFO info; + if(server_getclientinfo(client_id, &info)) + { + latency_accum += info.latency; + latency_accum_max = max(latency_accum_max, info.latency); + latency_accum_min = min(latency_accum_min, info.latency); + } + + if(server_tick()%server_tickspeed() == 0) + { + latency_avg = latency_accum/server_tickspeed(); + latency_max = latency_accum_max; + latency_min = latency_accum_min; + latency_accum = 0; + latency_accum_min = 1000; + latency_accum_max = 0; + } + } + + // enable / disable physics + if(team == -1 || dead) + world->core.players[client_id] = 0; + else + world->core.players[client_id] = &core; + + // spectator + if(team == -1) + return; + + if(spawning) + try_respawn(); + + // TODO: rework the input to be more robust + if(dead) + { + if(server_tick()-die_tick >= server_tickspeed()*5) // auto respawn after 3 sec + respawn(); + if((input.fire&1) && server_tick()-die_tick >= server_tickspeed()/2) // auto respawn after 0.5 sec + respawn(); + return; + } + + //player_core core; + //core.pos = pos; + //core.jumped = jumped; + core.input = input; + core.tick(); + + // handle weapons + handle_weapons(); + + state = input.state; + + // Previnput + previnput = input; + return; +} + +void player::tick_defered() +{ + if(!dead) + { + vec2 start_pos = core.pos; + vec2 start_vel = core.vel; + bool stuck_before = test_box(core.pos, vec2(28.0f, 28.0f)); + core.move(); + bool stuck_after_move = test_box(core.pos, vec2(28.0f, 28.0f)); + core.quantize(); + bool stuck_after_quant = test_box(core.pos, vec2(28.0f, 28.0f)); + pos = core.pos; + + if(!stuck_before && (stuck_after_move || stuck_after_quant)) + { + dbg_msg("player", "STUCK!!! %d %d %d %f %f %f %f %x %x %x %x", + stuck_before, + stuck_after_move, + stuck_after_quant, + start_pos.x, start_pos.y, + start_vel.x, start_vel.y, + *((unsigned *)&start_pos.x), *((unsigned *)&start_pos.y), + *((unsigned *)&start_vel.x), *((unsigned *)&start_vel.y)); + } + + + int events = core.triggered_events; + int mask = cmask_all_except_one(client_id); + + if(events&COREEVENT_GROUND_JUMP) create_sound(pos, SOUND_PLAYER_JUMP, mask); + if(events&COREEVENT_AIR_JUMP) + { + create_sound(pos, SOUND_PLAYER_JUMP, mask); + ev_common *c = (ev_common *)::events.create(EVENT_AIR_JUMP, sizeof(ev_common), mask); + if(c) + { + c->x = (int)pos.x; + c->y = (int)pos.y; + } + } + + //if(events&COREEVENT_HOOK_LAUNCH) snd_play_random(CHN_WORLD, SOUND_HOOK_LOOP, 1.0f, pos); + if(events&COREEVENT_HOOK_ATTACH_PLAYER) create_sound(pos, SOUND_HOOK_ATTACH_PLAYER, mask); + if(events&COREEVENT_HOOK_ATTACH_GROUND) create_sound(pos, SOUND_HOOK_ATTACH_GROUND, mask); + //if(events&COREEVENT_HOOK_RETRACT) snd_play_random(CHN_WORLD, SOUND_PLAYER_JUMP, 1.0f, pos); + + } + + if(team == -1) + { + pos.x = input.target_x; + pos.y = input.target_y; + } +} + +void player::die(int killer, int weapon) +{ + int mode_special = gameobj->on_player_death(this, get_player(killer), weapon); + + dbg_msg("game", "kill killer='%d:%s' victim='%d:%s' weapon=%d special=%d", + killer, server_clientname(killer), + client_id, server_clientname(client_id), weapon, mode_special); + + // send the kill message + msg_pack_start(MSG_KILLMSG, MSGFLAG_VITAL); + msg_pack_int(killer); + msg_pack_int(client_id); + msg_pack_int(weapon); + msg_pack_int(mode_special); + msg_pack_end(); + server_send_msg(-1); + + // a nice sound + create_sound(pos, SOUND_PLAYER_DIE); + + // set dead state + dead = true; + die_tick = server_tick(); + clear_flag(FLAG_PHYSICS); + create_death(pos); +} + +bool player::take_damage(vec2 force, int dmg, int from, int weapon) +{ + core.vel += force; + + if(gameobj->is_friendly_fire(client_id, from) && !config.sv_teamdamage) + return false; + + // player only inflicts half damage on self + if(from == client_id) + dmg = max(1, dmg/2); + + // CTF and TDM (TODO: check for FF) + //if (gameobj->gametype != GAMETYPE_DM && from >= 0 && players[from].team == team) + //return false; + + damage_taken++; + + // create healthmod indicator + if(server_tick() < damage_taken_tick+25) + { + // make sure that the damage indicators doesn't group together + create_damageind(pos, damage_taken*0.25f, dmg); + } + else + { + damage_taken = 0; + create_damageind(pos, 0, dmg); + } + + if(armor) + { + armor -= 1; + dmg--; + } + + if(dmg > armor) + { + dmg -= armor; + armor = 0; + health -= dmg; + } + else + armor -= dmg; + + damage_taken_tick = server_tick(); + + // do damage hit sound + if(from >= 0) + create_sound(get_player(from)->pos, SOUND_HIT, cmask_one(from)); + + // check for death + if(health <= 0) + { + die(from, weapon); + + // set attacker's face to happy (taunt!) + if (from >= 0 && from != client_id) + { + player *p = get_player(from); + + p->emote_type = EMOTE_HAPPY; + p->emote_stop = server_tick() + server_tickspeed(); + } + + return false; + } + + if (dmg > 2) + create_sound(pos, SOUND_PLAYER_PAIN_LONG); + else + create_sound(pos, SOUND_PLAYER_PAIN_SHORT); + + emote_type = EMOTE_PAIN; + emote_stop = server_tick() + 500 * server_tickspeed() / 1000; + + // spawn blood? + return true; +} + +void player::snap(int snaping_client) +{ + if(1) + { + obj_player_info *info = (obj_player_info *)snap_new_item(OBJTYPE_PLAYER_INFO, client_id, sizeof(obj_player_info)); + + info->latency = latency_avg; + info->latency_flux = latency_max-latency_min; + info->local = 0; + info->clientid = client_id; + info->score = score; + info->team = team; + + if(client_id == snaping_client) + info->local = 1; + } + + if(health > 0 && distance(players[snaping_client].pos, pos) < 1000.0f) + { + obj_player_character *character = (obj_player_character *)snap_new_item(OBJTYPE_PLAYER_CHARACTER, client_id, sizeof(obj_player_character)); + + core.write(character); + + // this is to make sure that players that are just standing still + // isn't sent. this is because the physics keep bouncing between + // 0-128 when just standing. + // TODO: fix the physics so this isn't needed + if(snaping_client != client_id && abs(character->vy) < 256.0f) + character->vy = 0; + + if (emote_stop < server_tick()) + { + emote_type = EMOTE_NORMAL; + emote_stop = -1; + } + + character->emote = emote_type; + + character->ammocount = weapons[active_weapon].ammo; + character->health = 0; + character->armor = 0; + character->weapon = active_weapon; + character->weaponstage = weapons[active_weapon].weaponstage; + character->attacktick = attack_tick; + + if(client_id == snaping_client) + { + character->health = health; + character->armor = armor; + } + + if(dead) + character->health = -1; + + //if(length(vel) > 15.0f) + // player->emote = EMOTE_HAPPY; + + //if(damage_taken_tick+50 > server_tick()) + // player->emote = EMOTE_PAIN; + + if (character->emote == EMOTE_NORMAL) + { + if(250 - ((server_tick() - last_action)%(250)) < 5) + character->emote = EMOTE_BLINK; + } + + character->state = state; + } +} + +player *players; + +////////////////////////////////////////////////// +// powerup +////////////////////////////////////////////////// +powerup::powerup(int _type, int _subtype) +: entity(OBJTYPE_POWERUP) +{ + type = _type; + subtype = _subtype; + proximity_radius = phys_size; + + reset(); + + // TODO: should this be done here? + world->insert_entity(this); +} + +void powerup::reset() +{ + if (data->powerupinfo[type].startspawntime > 0) + spawntick = server_tick() + server_tickspeed() * data->powerupinfo[type].startspawntime; + else + spawntick = -1; +} + + +void send_weapon_pickup(int cid, int weapon); + +void powerup::tick() +{ + // wait for respawn + if(spawntick > 0) + { + if(server_tick() > spawntick) + { + // respawn + spawntick = -1; + + if(type == POWERUP_WEAPON) + create_sound(pos, SOUND_WEAPON_SPAWN); + } + else + return; + } + // Check if a player intersected us + vec2 meh; + player* pplayer = intersect_player(pos, pos + vec2(0,16), meh, 0); + if (pplayer) + { + // player picked us up, is someone was hooking us, let them go + int respawntime = -1; + switch (type) + { + case POWERUP_HEALTH: + if(pplayer->health < 10) + { + create_sound(pos, SOUND_PICKUP_HEALTH); + pplayer->health = min(10, pplayer->health + data->powerupinfo[type].amount); + respawntime = data->powerupinfo[type].respawntime; + } + break; + case POWERUP_ARMOR: + if(pplayer->armor < 10) + { + create_sound(pos, SOUND_PICKUP_ARMOR); + pplayer->armor = min(10, pplayer->armor + data->powerupinfo[type].amount); + respawntime = data->powerupinfo[type].respawntime; + } + break; + + case POWERUP_WEAPON: + if(subtype >= 0 && subtype < NUM_WEAPONS) + { + if(pplayer->weapons[subtype].ammo < 10 || !pplayer->weapons[subtype].got) + { + pplayer->weapons[subtype].got = true; + pplayer->weapons[subtype].ammo = min(10, pplayer->weapons[subtype].ammo + data->powerupinfo[type].amount); + respawntime = data->powerupinfo[type].respawntime; + + // TODO: data compiler should take care of stuff like this + if(subtype == WEAPON_ROCKET) + create_sound(pos, SOUND_PICKUP_ROCKET); + else if(subtype == WEAPON_SHOTGUN) + create_sound(pos, SOUND_PICKUP_SHOTGUN); + + send_weapon_pickup(pplayer->client_id, subtype); + } + } + break; + case POWERUP_NINJA: + { + // activate ninja on target player + pplayer->ninjaactivationtick = server_tick(); + pplayer->weapons[WEAPON_NINJA].got = true; + pplayer->last_weapon = pplayer->active_weapon; + pplayer->active_weapon = WEAPON_NINJA; + respawntime = data->powerupinfo[type].respawntime; + create_sound(pos, SOUND_PICKUP_NINJA); + + // loop through all players, setting their emotes + entity *ents[64]; + const int types[] = {OBJTYPE_PLAYER_CHARACTER}; + int num = world->find_entities(vec2(0, 0), 1000000, ents, 64, types, 1); + for (int i = 0; i < num; i++) + { + player *p = (player *)ents[i]; + if (p != pplayer) + { + p->emote_type = EMOTE_SURPRISE; + p->emote_stop = server_tick() + server_tickspeed(); + } + } + + pplayer->emote_type = EMOTE_ANGRY; + pplayer->emote_stop = server_tick() + 1200 * server_tickspeed() / 1000; + + break; + } + default: + break; + }; + + if(respawntime >= 0) + { + dbg_msg("game", "pickup player='%d:%s' item=%d/%d", + pplayer->client_id, server_clientname(pplayer->client_id), type, subtype); + spawntick = server_tick() + server_tickspeed() * respawntime; + } + } +} + +void powerup::snap(int snapping_client) +{ + if(spawntick != -1) + return; + + obj_powerup *up = (obj_powerup *)snap_new_item(OBJTYPE_POWERUP, id, sizeof(obj_powerup)); + up->x = (int)pos.x; + up->y = (int)pos.y; + up->type = type; // TODO: two diffrent types? what gives? + up->subtype = subtype; +} + +// POWERUP END /////////////////////// + +player *get_player(int index) +{ + return &players[index]; +} + +void create_damageind(vec2 p, float angle, int amount) +{ + float a = 3 * 3.14159f / 2 + angle; + //float a = get_angle(dir); + float s = a-pi/3; + float e = a+pi/3; + for(int i = 0; i < amount; i++) + { + float f = mix(s, e, float(i+1)/float(amount+2)); + ev_damageind *ev = (ev_damageind *)events.create(EVENT_DAMAGEINDICATION, sizeof(ev_damageind)); + if(ev) + { + ev->x = (int)p.x; + ev->y = (int)p.y; + ev->angle = (int)(f*256.0f); + } + } +} + +void create_explosion(vec2 p, int owner, int weapon, bool bnodamage) +{ + // create the event + ev_explosion *ev = (ev_explosion *)events.create(EVENT_EXPLOSION, sizeof(ev_explosion)); + if(ev) + { + ev->x = (int)p.x; + ev->y = (int)p.y; + } + + if (!bnodamage) + { + // deal damage + entity *ents[64]; + const float radius = 128.0f; + const float innerradius = 42.0f; + int num = world->find_entities(p, radius, ents, 64); + for(int i = 0; i < num; i++) + { + vec2 diff = ents[i]->pos - p; + vec2 forcedir(0,1); + float l = length(diff); + if(l) + forcedir = normalize(diff); + l = 1-clamp((l-innerradius)/(radius-innerradius), 0.0f, 1.0f); + float dmg = 6 * l; + if((int)dmg) + ents[i]->take_damage(forcedir*dmg*2, (int)dmg, owner, weapon); + } + } +} + +void create_smoke(vec2 p) +{ + // create the event + ev_explosion *ev = (ev_explosion *)events.create(EVENT_SMOKE, sizeof(ev_explosion)); + if(ev) + { + ev->x = (int)p.x; + ev->y = (int)p.y; + } +} + +void create_spawn(vec2 p) +{ + // create the event + ev_spawn *ev = (ev_spawn *)events.create(EVENT_SPAWN, sizeof(ev_spawn)); + if(ev) + { + ev->x = (int)p.x; + ev->y = (int)p.y; + } +} + +void create_death(vec2 p) +{ + // create the event + ev_death *ev = (ev_death *)events.create(EVENT_DEATH, sizeof(ev_death)); + if(ev) + { + ev->x = (int)p.x; + ev->y = (int)p.y; + } +} + +void create_sound(vec2 pos, int sound, int mask) +{ + if (sound < 0) + return; + + // create a sound + ev_sound *ev = (ev_sound *)events.create(EVENT_SOUND_WORLD, sizeof(ev_sound), mask); + if(ev) + { + ev->x = (int)pos.x; + ev->y = (int)pos.y; + ev->sound = sound; + } +} + +void create_sound_global(int sound, int target) +{ + if (sound < 0) + return; + + msg_pack_start(MSG_SOUND_GLOBAL, MSGFLAG_VITAL); + msg_pack_int(sound); + server_send_msg(-1); +} + +// TODO: should be more general +player* intersect_player(vec2 pos0, vec2 pos1, vec2& new_pos, entity* notthis) +{ + // Find other players + entity *ents[64]; + vec2 dir = pos1 - pos0; + float radius = length(dir * 0.5f); + vec2 center = pos0 + dir * 0.5f; + const int types[] = {OBJTYPE_PLAYER_CHARACTER}; + int num = world->find_entities(center, radius, ents, 64, types, 1); + for (int i = 0; i < num; i++) + { + // Check if entity is a player + if (ents[i] != notthis) + { + new_pos = ents[i]->pos; + return (player*)ents[i]; + } + } + + return 0; +} + + +void send_chat(int cid, int team, const char *msg) +{ + if(cid >= 0 && cid < MAX_CLIENTS) + dbg_msg("chat", "%d:%d:%s: %s", cid, team, server_clientname(cid), msg); + else + dbg_msg("chat", "*** %s", msg); + + if(team == -1) + { + msg_pack_start(MSG_CHAT, MSGFLAG_VITAL); + msg_pack_int(cid); + msg_pack_int(0); + msg_pack_string(msg, 512); + msg_pack_end(); + server_send_msg(-1); + } + else + { + msg_pack_start(MSG_CHAT, MSGFLAG_VITAL); + msg_pack_int(cid); + msg_pack_int(1); + msg_pack_string(msg, 512); + msg_pack_end(); + + for(int i = 0; i < MAX_CLIENTS; i++) + { + if(players[i].client_id != -1 && players[i].team == team) + server_send_msg(i); + } + } +} + + +// Server hooks +void mods_tick() +{ + // clear all events + events.clear(); + world->tick(); + + if(world->paused) // make sure that the game object always updates + gameobj->tick(); + + if(config.sv_restart) + { + if(config.sv_restart > 1) + gameobj->do_warmup(config.sv_restart); + else + gameobj->startround(); + + config.sv_restart = 0; + } + + if(config.sv_msg[0] != 0) + { + send_chat(-1, 0, config.sv_msg); + config.sv_msg[0] = 0; + } +} + +void mods_snap(int client_id) +{ + world->snap(client_id); + events.snap(client_id); +} + +void mods_client_input(int client_id, void *input) +{ + if(!world->paused) + { + if (memcmp(&players[client_id].input, input, sizeof(player_input)) != 0) + players[client_id].last_action = server_tick(); + + //players[client_id].previnput = players[client_id].input; + players[client_id].input = *(player_input*)input; + players[client_id].num_inputs++; + } +} + +void send_info(int who, int to_who) +{ + msg_pack_start(MSG_SETINFO, MSGFLAG_VITAL); + msg_pack_int(who); + msg_pack_string(server_clientname(who), 64); + msg_pack_string(players[who].skin_name, 64); + msg_pack_int(players[who].use_custom_color); + msg_pack_int(players[who].color_body); + msg_pack_int(players[who].color_feet); + msg_pack_end(); + server_send_msg(to_who); +} + +void send_emoticon(int cid, int emoticon) +{ + msg_pack_start(MSG_EMOTICON, MSGFLAG_VITAL); + msg_pack_int(cid); + msg_pack_int(emoticon % 16); + msg_pack_end(); + server_send_msg(-1); +} + +void send_weapon_pickup(int cid, int weapon) +{ + msg_pack_start(MSG_WEAPON_PICKUP, MSGFLAG_VITAL); + msg_pack_int(weapon); + msg_pack_end(); + server_send_msg(cid); +} + +void mods_client_enter(int client_id) +{ + world->insert_entity(&players[client_id]); + players[client_id].respawn(); + dbg_msg("game", "join player='%d:%s'", client_id, server_clientname(client_id)); + + char buf[512]; + sprintf(buf, "%s has joined the game", server_clientname(client_id)); + send_chat(-1, -1, buf); +} + +void mods_connected(int client_id) +{ + players[client_id].init(); + players[client_id].client_id = client_id; + + //dbg_msg("game", "connected player='%d:%s'", client_id, server_clientname(client_id)); + + // Check which team the player should be on + if(gameobj->gametype == GAMETYPE_DM) + players[client_id].team = 0; + else + players[client_id].team = gameobj->getteam(client_id); +} + +void mods_client_drop(int client_id) +{ + char buf[512]; + sprintf(buf, "%s has left the game", server_clientname(client_id)); + send_chat(-1, -1, buf); + + dbg_msg("game", "leave player='%d:%s'", client_id, server_clientname(client_id)); + + gameobj->on_player_death(&players[client_id], 0, -1); + world->remove_entity(&players[client_id]); + world->core.players[client_id] = 0x0; + players[client_id].client_id = -1; +} + +void mods_message(int msg, int client_id) +{ + if(msg == MSG_SAY) + { + int team = msg_unpack_int(); + const char *text = msg_unpack_string(); + if(team) + team = players[client_id].team; + else + team = -1; + send_chat(client_id, team, text); + } + else if (msg == MSG_SETTEAM) + { + // Switch team on given client and kill/respawn him + players[client_id].set_team(msg_unpack_int()); + gameobj->on_player_info_change(&players[client_id]); + + // send all info to this client + for(int i = 0; i < MAX_CLIENTS; i++) + { + if(players[i].client_id != -1) + send_info(i, -1); + } + } + else if (msg == MSG_CHANGEINFO || msg == MSG_STARTINFO) + { + const char *name = msg_unpack_string(); + const char *skin_name = msg_unpack_string(); + players[client_id].use_custom_color = msg_unpack_int(); + players[client_id].color_body = msg_unpack_int(); + players[client_id].color_feet = msg_unpack_int(); + + // check for invalid chars + const char *p = name; + while (*p) + { + if(*p < 32) + return; + p++; + } + + // + if(msg == MSG_CHANGEINFO && strcmp(name, server_clientname(client_id)) != 0) + { + char msg[256]; + sprintf(msg, "*** %s changed name to %s", server_clientname(client_id), name); + send_chat(-1, -1, msg); + } + + //send_set_name(client_id, players[client_id].name, name); + strncpy(players[client_id].skin_name, skin_name, 64); + server_setclientname(client_id, name); + + gameobj->on_player_info_change(&players[client_id]); + + if(msg == MSG_STARTINFO) + { + // send all info to this client + for(int i = 0; i < MAX_CLIENTS; i++) + { + if(players[i].client_id != -1) + send_info(i, client_id); + } + + msg_pack_start(MSG_READY_TO_ENTER, MSGFLAG_VITAL); + msg_pack_end(); + server_send_msg(client_id); + } + + send_info(client_id, -1); + } + else if (msg == MSG_EMOTICON) + { + int emoteicon = msg_unpack_int(); + send_emoticon(client_id, emoteicon % 16); + } +} + +extern unsigned char internal_data[]; + +void mods_init() +{ + if(!data) /* only load once */ + data = load_data_from_memory(internal_data); + + col_init(32); + + world = new game_world; + players = new player[MAX_CLIENTS]; + + // select gametype + if(strcmp(config.sv_gametype, "ctf") == 0) + gameobj = new gameobject_ctf; + else if(strcmp(config.sv_gametype, "tdm") == 0) + gameobj = new gameobject_tdm; + else + gameobj = new gameobject_dm; + + // setup core world + for(int i = 0; i < MAX_CLIENTS; i++) + players[i].core.world = &world->core; + + // + int start, num; + map_get_type(MAPRES_ITEM, &start, &num); + + // TODO: this is way more complicated then it should be + for(int i = 0; i < num; i++) + { + mapres_item *it = (mapres_item *)map_get_item(start+i, 0, 0); + + int type = -1; + int subtype = 0; + + switch(it->type) + { + case ITEM_WEAPON_GUN: + type = POWERUP_WEAPON; + subtype = WEAPON_GUN; + break; + case ITEM_WEAPON_SHOTGUN: + type = POWERUP_WEAPON; + subtype = WEAPON_SHOTGUN; + break; + case ITEM_WEAPON_ROCKET: + type = POWERUP_WEAPON; + subtype = WEAPON_ROCKET; + break; + case ITEM_WEAPON_HAMMER: + type = POWERUP_WEAPON; + subtype = WEAPON_HAMMER; + break; + + case ITEM_HEALTH: + type = POWERUP_HEALTH; + break; + + case ITEM_ARMOR: + type = POWERUP_ARMOR; + break; + + case ITEM_NINJA: + if(config.sv_powerups) + { + type = POWERUP_NINJA; + subtype = WEAPON_NINJA; + } + break; + }; + + if(type != -1) + { + // LOL, the only new in the entire game code + // perhaps we can get rid of it. seems like a stupid thing to have + powerup *ppower = new powerup(type, subtype); + ppower->pos = vec2(it->x, it->y); + } + } + + if(gameobj->gametype == GAMETYPE_CTF) + { + } + + world->insert_entity(gameobj); + + + if(config.dbg_bots) + { + + for(int i = 0; i < config.dbg_bots ; i++) + { + mods_connected(MAX_CLIENTS-i-1); + mods_client_enter(MAX_CLIENTS-i-1); + if(gameobj->gametype != GAMETYPE_DM) + players[MAX_CLIENTS-i-1].team = i&1; + } + } +} + +void mods_shutdown() +{ + delete [] players; + delete gameobj; + delete world; + gameobj = 0; + players = 0; + world = 0; +} + +void mods_presnap() {} +void mods_postsnap() {} + +extern "C" const char *mods_net_version() { return TEEWARS_NETVERSION; } diff --git a/src/game/server/srv_common.cpp b/src/game/server/srv_common.cpp deleted file mode 100644 index 83754780..00000000 --- a/src/game/server/srv_common.cpp +++ /dev/null @@ -1,264 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#include -#include "srv_common.h" -#include - -gameobject::gameobject() -: entity(OBJTYPE_GAME) -{ - // select gametype - if(strcmp(config.sv_gametype, "ctf") == 0) - { - gametype = GAMETYPE_CTF; - dbg_msg("game", "-- Capture The Flag --"); - } - else if(strcmp(config.sv_gametype, "tdm") == 0) - { - gametype = GAMETYPE_TDM; - dbg_msg("game", "-- Team Death Match --"); - } - else - { - gametype = GAMETYPE_DM; - dbg_msg("game", "-- Death Match --"); - } - - // - do_warmup(config.sv_warmup); - game_over_tick = -1; - sudden_death = 0; - round_start_tick = server_tick(); - round_count = 0; - is_teamplay = false; - teamscore[0] = 0; - teamscore[1] = 0; -} - -void gameobject::endround() -{ - if(warmup) // game can't end when we are running warmup - return; - - world->paused = true; - game_over_tick = server_tick(); - sudden_death = 0; -} - -void gameobject::resetgame() -{ - world->reset_requested = true; -} - -static bool is_separator(char c) -{ - return c == ';' || c == ' ' || c == ',' || c == '\t'; -} - -void gameobject::startround() -{ - resetgame(); - - round_start_tick = server_tick(); - sudden_death = 0; - game_over_tick = -1; - world->paused = false; - teamscore[0] = 0; - teamscore[1] = 0; - round_count++; -} - -void gameobject::cyclemap() -{ - if(!strlen(config.sv_maprotation)) - return; - // handle maprotation - char buf[512]; - const char *s = strstr(config.sv_maprotation, config.sv_map); - if(s == 0) - s = config.sv_maprotation; // restart rotation - else - { - s += strlen(config.sv_map); // skip this map - while(is_separator(s[0])) - s++; - if(s[0] == 0) - s = config.sv_maprotation; // restart rotation - } - - int i = 0; - for(; i < 512; i++) - { - buf[i] = s[i]; - if(is_separator(s[i]) || s[i] == 0) - { - buf[i] = 0; - break; - } - } - - i = 0; // skip spaces - while(is_separator(buf[i])) - i++; - - dbg_msg("game", "rotating map to %s", &buf[i]); - strcpy(config.sv_map, &buf[i]); -} - -void gameobject::post_reset() -{ - for(int i = 0; i < MAX_CLIENTS; i++) - { - if(players[i].client_id != -1) - players[i].respawn(); - } -} - -void gameobject::on_player_info_change(class player *p) -{ - const int team_colors[2] = {65387, 10223467}; - if(is_teamplay) - { - if(p->team >= 0 || p->team <= 1) - { - p->use_custom_color = 1; - p->color_body = team_colors[p->team]; - p->color_feet = team_colors[p->team]; - } - } -} - - -int gameobject::on_player_death(class player *victim, class player *killer, int weapon) -{ - // do scoreing - if(!killer) - return 0; - if(killer == victim) - victim->score--; // suicide - else - { - if(is_teamplay && victim->team == killer->team) - killer->score--; // teamkill - else - killer->score++; // normal kill - } - return 0; -} - -void gameobject::do_warmup(int seconds) -{ - warmup = seconds*SERVER_TICK_SPEED; -} - -bool gameobject::is_friendly_fire(int cid1, int cid2) -{ - if(cid1 == cid2) - return false; - - if(is_teamplay) - { - if(players[cid1].team == players[cid2].team) - return true; - } - - return false; -} - -void gameobject::tick() -{ - // do warmup - if(warmup) - { - warmup--; - if(!warmup) - startround(); - } - - if(game_over_tick != -1) - { - // game over.. wait for restart - if(server_tick() > game_over_tick+server_tickspeed()*10) - { - cyclemap(); - startround(); - } - } - - - // update browse info - int prog = -1; - if(config.sv_timelimit > 0) - prog = max(prog, (server_tick()-round_start_tick) * 100 / (config.sv_timelimit*server_tickspeed()*60)); - - if(config.sv_scorelimit) - { - if(is_teamplay) - { - prog = max(prog, (teamscore[0]*100)/config.sv_scorelimit); - prog = max(prog, (teamscore[1]*100)/config.sv_scorelimit); - } - else - { - for(int i = 0; i < MAX_CLIENTS; i++) - { - if(players[i].client_id != -1) - prog = max(prog, (players[i].score*100)/config.sv_scorelimit); - } - } - } - - if(warmup) - prog = -1; - - server_setbrowseinfo(gametype, prog); -} - -void gameobject::snap(int snapping_client) -{ - obj_game *game = (obj_game *)snap_new_item(OBJTYPE_GAME, 0, sizeof(obj_game)); - game->paused = world->paused; - game->game_over = game_over_tick==-1?0:1; - game->sudden_death = sudden_death; - - game->score_limit = config.sv_scorelimit; - game->time_limit = config.sv_timelimit; - game->round_start_tick = round_start_tick; - game->gametype = gametype; - - game->warmup = warmup; - - game->teamscore[0] = teamscore[0]; - game->teamscore[1] = teamscore[1]; -} - -int gameobject::getteam(int notthisid) -{ - int numplayers[2] = {0,0}; - for(int i = 0; i < MAX_CLIENTS; i++) - { - if(players[i].client_id != -1 && players[i].client_id != notthisid) - { - numplayers[players[i].team]++; - } - } - - return numplayers[0] > numplayers[1] ? 1 : 0; -} - -void gameobject::do_team_wincheck() -{ - if(game_over_tick == -1 && !warmup) - { - // check score win condition - if((config.sv_scorelimit > 0 && (teamscore[0] >= config.sv_scorelimit || teamscore[1] >= config.sv_scorelimit)) || - (config.sv_timelimit > 0 && (server_tick()-round_start_tick) >= config.sv_timelimit*server_tickspeed()*60)) - { - if(teamscore[0] != teamscore[1]) - endround(); - else - sudden_death = 1; - } - } -} - -gameobject *gameobj = 0; diff --git a/src/game/server/srv_common.h b/src/game/server/srv_common.h deleted file mode 100644 index b28d3066..00000000 --- a/src/game/server/srv_common.h +++ /dev/null @@ -1,327 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#include "../game.h" -#include "data.h" - - -void create_sound_global(int sound, int target=-1); - -inline int cmask_all() { return -1; } -inline int cmask_one(int cid) { return 1<stand_pos = vec2(stand->x, stand->y); - f->pos = f->stand_pos; - flags[i] = f; - //dbg_msg("game", "flag at %f,%f", f->pos.x, f->pos.y); - } - else - { - // report massive failure - flags[i] = 0; - } - } - - is_teamplay = true; -} - -void gameobject_ctf::on_player_spawn(class player *p) -{ -} - -int gameobject_ctf::on_player_death(class player *victim, class player *killer, int weaponid) -{ - gameobject::on_player_death(victim, killer, weaponid); - int had_flag = 0; - - // drop flags - for(int fi = 0; fi < 2; fi++) - { - flag *f = flags[fi]; - if(f && f->carrying_player == killer) - had_flag |= 2; - if(f && f->carrying_player == victim) - { - create_sound_global(SOUND_CTF_DROP); - f->drop_tick = server_tick(); - f->carrying_player = 0; - f->vel = vec2(0,0); - - if(killer && killer->team != victim->team) - killer->score++; - - had_flag |= 1; - } - } - - return had_flag; -} - -void gameobject_ctf::tick() -{ - gameobject::tick(); - - do_team_wincheck(); - - // do flags - for(int fi = 0; fi < 2; fi++) - { - flag *f = flags[fi]; - - if(!f) - continue; - - // - if(f->carrying_player) - { - // update flag position - f->pos = f->carrying_player->pos; - - if(flags[fi^1]->at_stand) - { - if(distance(f->pos, flags[fi^1]->pos) < 24) - { - // CAPTURE! \o/ - teamscore[fi^1] += 100; - f->carrying_player->score += 5; - for(int i = 0; i < 2; i++) - flags[i]->reset(); - - dbg_msg("", "capture sound %d", SOUND_CTF_CAPTURE); - create_sound_global(SOUND_CTF_CAPTURE); - } - } - } - else - { - player *players[MAX_CLIENTS]; - int types[] = {OBJTYPE_PLAYER_CHARACTER}; - int num = world->find_entities(f->pos, 32.0f, (entity**)players, MAX_CLIENTS, types, 1); - for(int i = 0; i < num; i++) - { - if(players[i]->team == f->team) - { - // return the flag - if(!f->at_stand) - { - players[i]->score += 1; - create_sound_global(SOUND_CTF_RETURN); - f->reset(); - } - } - else - { - // take the flag - if(f->at_stand) - teamscore[fi^1]++; - f->at_stand = 0; - f->carrying_player = players[i]; - f->carrying_player->score += 1; - create_sound_global(SOUND_CTF_GRAB); - break; - } - } - - if(!f->carrying_player && !f->at_stand) - { - if(server_tick() > f->drop_tick + SERVER_TICK_SPEED*30) - { - create_sound_global(SOUND_CTF_RETURN); - f->reset(); - } - else - { - f->vel.y += gravity; - move_box(&f->pos, &f->vel, vec2(f->phys_size, f->phys_size), 0.5f); - } - } - } - } -} - -// Flag -flag::flag(int _team) -: entity(OBJTYPE_FLAG) -{ - team = _team; - proximity_radius = phys_size; - carrying_player = 0x0; - - reset(); - - // TODO: should this be done here? - world->insert_entity(this); -} - -void flag::reset() -{ - carrying_player = 0; - at_stand = 1; - pos = stand_pos; - vel = vec2(0,0); -} - -void flag::snap(int snapping_client) -{ - obj_flag *flag = (obj_flag *)snap_new_item(OBJTYPE_FLAG, team, sizeof(obj_flag)); - flag->x = (int)pos.x; - flag->y = (int)pos.y; - flag->team = team; - flag->carried_by = -1; - - if(at_stand) - flag->carried_by = -2; - else if(carrying_player) - flag->carried_by = carrying_player->client_id; -} diff --git a/src/game/server/srv_ctf.h b/src/game/server/srv_ctf.h deleted file mode 100644 index 02acef37..00000000 --- a/src/game/server/srv_ctf.h +++ /dev/null @@ -1,33 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ - -// game object -class gameobject_ctf : public gameobject -{ -public: - class flag *flags[2]; - - gameobject_ctf(); - virtual void tick(); - - virtual void on_player_spawn(class player *p); - virtual int on_player_death(class player *victim, class player *killer, int weapon); -}; - -// TODO: move to seperate file -class flag : public entity -{ -public: - static const int phys_size = 14; - player *carrying_player; - vec2 vel; - vec2 stand_pos; - - int team; - int at_stand; - int drop_tick; - - flag(int _team); - - virtual void reset(); - virtual void snap(int snapping_client); -}; diff --git a/src/game/server/srv_dm.cpp b/src/game/server/srv_dm.cpp deleted file mode 100644 index d5720856..00000000 --- a/src/game/server/srv_dm.cpp +++ /dev/null @@ -1,42 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#include -#include "srv_common.h" -#include "srv_dm.h" - -void gameobject_dm::tick() -{ - if(game_over_tick == -1) - { - // game is running - - // gather some stats - int topscore = 0; - int topscore_count = 0; - for(int i = 0; i < MAX_CLIENTS; i++) - { - if(players[i].client_id != -1) - { - if(players[i].score > topscore) - { - topscore = players[i].score; - topscore_count = 1; - } - else if(players[i].score == topscore) - topscore_count++; - } - } - - // check score win condition - if((config.sv_scorelimit > 0 && topscore >= config.sv_scorelimit) || - (config.sv_timelimit > 0 && (server_tick()-round_start_tick) >= config.sv_timelimit*server_tickspeed()*60)) - { - if(topscore_count == 1) - endround(); - else - sudden_death = 1; - } - } - - gameobject::tick(); -} - diff --git a/src/game/server/srv_dm.h b/src/game/server/srv_dm.h deleted file mode 100644 index 96bff3ae..00000000 --- a/src/game/server/srv_dm.h +++ /dev/null @@ -1,7 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -// game object -class gameobject_dm : public gameobject -{ -public: - virtual void tick(); -}; diff --git a/src/game/server/srv_tdm.cpp b/src/game/server/srv_tdm.cpp deleted file mode 100644 index bcae397f..00000000 --- a/src/game/server/srv_tdm.cpp +++ /dev/null @@ -1,32 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#include -#include "srv_common.h" -#include "srv_tdm.h" - -gameobject_tdm::gameobject_tdm() -{ - is_teamplay = true; -} - - -int gameobject_tdm::on_player_death(class player *victim, class player *killer, int weapon) -{ - gameobject::on_player_death(victim, killer, weapon); - - if(weapon >= 0) - { - // do team scoring - if(killer == victim) - teamscore[killer->team&1]--; // klant arschel - else - teamscore[killer->team&1]++; // good shit - } - return 0; -} - -void gameobject_tdm::tick() -{ - do_team_wincheck(); - - gameobject::tick(); -} diff --git a/src/game/server/srv_tdm.h b/src/game/server/srv_tdm.h deleted file mode 100644 index 70b4646e..00000000 --- a/src/game/server/srv_tdm.h +++ /dev/null @@ -1,10 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -// game object -class gameobject_tdm : public gameobject -{ -public: - gameobject_tdm(); - - int on_player_death(class player *victim, class player *killer, int weapon); - virtual void tick(); -}; diff --git a/src/game/version.h b/src/game/version.h deleted file mode 100644 index a2167ecd..00000000 --- a/src/game/version.h +++ /dev/null @@ -1,4 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#include "nethash.c" -#define TEEWARS_VERSION "0.3.0-dev" -#define TEEWARS_NETVERSION "0.3 " TEEWARS_NETVERSION_HASH diff --git a/src/game/vmath.h b/src/game/vmath.h deleted file mode 100644 index 65f94776..00000000 --- a/src/game/vmath.h +++ /dev/null @@ -1,182 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#ifndef BASE_VMATH_H -#define BASE_VMATH_H - -//#include - -// ------------------------------------ - -template -class vector2_base -{ -public: - union { T x,u; }; - union { T y,v; }; - - vector2_base() {} - vector2_base(float nx, float ny) - { - x = nx; - y = ny; - } - - vector2_base operator -() const { return vector2_base(-x, -y); } - vector2_base operator -(const vector2_base &v) const { return vector2_base(x-v.x, y-v.y); } - vector2_base operator +(const vector2_base &v) const { return vector2_base(x+v.x, y+v.y); } - vector2_base operator *(const T v) const { return vector2_base(x*v, y*v); } - - const vector2_base &operator =(const vector2_base &v) { x = v.x; y = v.y; return *this; } - - const vector2_base &operator +=(const vector2_base &v) { x += v.x; y += v.y; return *this; } - const vector2_base &operator -=(const vector2_base &v) { x -= v.x; y -= v.y; return *this; } - const vector2_base &operator *=(const T v) { x *= v; y *= v; return *this; } - - bool operator ==(const vector2_base &v) const { return x == v.x && y == v.y; } //TODO: do this with an eps instead - - operator const T* () { return &x; } -}; - - -template -inline T length(const vector2_base &a) -{ - return sqrtf(a.x*a.x + a.y*a.y); -} - -template -inline T distance(const vector2_base a, const vector2_base &b) -{ - return length(a-b); -} - -template -inline T dot(const vector2_base a, const vector2_base &b) -{ - return a.x*b.x + a.y*b.y; -} - -template -inline vector2_base normalize(const vector2_base &v) -{ - T l = (T)(1.0f/sqrtf(v.x*v.x + v.y*v.y)); - return vector2_base(v.x*l, v.y*l); -} - -typedef vector2_base vec2; -typedef vector2_base bvec2; -typedef vector2_base ivec2; - -// ------------------------------------ -template -class vector3_base -{ -public: - union { T x,r,h; }; - union { T y,g,s; }; - union { T z,b,v,l; }; - - vector3_base() {} - vector3_base(float nx, float ny, float nz) - { - x = nx; - y = ny; - z = nz; - } - - const vector3_base &operator =(const vector3_base &v) { x = v.x; y = v.y; z = v.z; return *this; } - - vector3_base operator -(const vector3_base &v) const { return vector3_base(x-v.x, y-v.y, z-v.z); } - vector3_base operator -() const { return vector3_base(-x, -y, -z); } - vector3_base operator +(const vector3_base &v) const { return vector3_base(x+v.x, y+v.y, z+v.z); } - vector3_base operator *(const T v) const { return vector3_base(x*v, y*v, z*v); } - vector3_base operator *(const vector3_base &v) const { return vector3_base(x*v.x, y*v.y, z*v.z); } - vector3_base operator /(const T v) const { return vector3_base(x/v, y/v, z/v); } - - const vector3_base &operator +=(const vector3_base &v) { x += v.x; y += v.y; z += v.z; return *this; } - const vector3_base &operator -=(const vector3_base &v) { x -= v.x; y -= v.y; z -= v.z; return *this; } - const vector3_base &operator *=(const T v) { x *= v; y *= v; z *= v; return *this; } - - bool operator ==(const vector3_base &v) const { return x == v.x && y == v.y && z == v.z; } //TODO: do this with an eps instead - - operator const T* () { return &x; } -}; - -template -inline T length(const vector3_base &a) -{ - return sqrtf(a.x*a.x + a.y*a.y + a.z*a.z); -} - -template -inline T distance(const vector3_base &a, const vector3_base &b) -{ - return length(a-b); -} - -template -inline T dot(const vector3_base &a, const vector3_base &b) -{ - return a.x*b.x + a.y*b.y + a.z*b.z; -} - -template -inline vector3_base normalize(const vector3_base &v) -{ - T l = (T)(1.0f/sqrtf(v.x*v.x + v.y*v.y + v.z*v.z)); - return vector3_base(v.x*l, v.y*l, v.z*l); -} - -template -inline vector3_base cross(const vector3_base &a, const vector3_base &b) -{ - return vector3_base( - a.y*b.z - a.z*b.y, - a.z*b.x - a.x*b.z, - a.x*b.y - a.y*b.x); -} - -typedef vector3_base vec3; -typedef vector3_base bvec3; -typedef vector3_base ivec3; - -// ------------------------------------ - -template -class vector4_base -{ -public: - union { T x,r; }; - union { T y,g; }; - union { T z,b; }; - union { T w,a; }; - - vector4_base() {} - vector4_base(float nx, float ny, float nz, float nw) - { - x = nx; - y = ny; - z = nz; - w = nw; - } - - vector4_base operator +(const vector4_base &v) const { return vector4_base(x+v.x, y+v.y, z+v.z, w+v.w); } - vector4_base operator -(const vector4_base &v) const { return vector4_base(x-v.x, y-v.y, z-v.z, w-v.w); } - vector4_base operator -() const { return vector4_base(-x, -y, -z, -w); } - vector4_base operator *(const T v) const { return vector4_base(x*v, y*v, z*v, w*v); } - - const vector4_base &operator =(const vector4_base &v) { x = v.x; y = v.y; z = v.z; w = v.w; return *this; } - - const vector4_base &operator +=(const vector4_base &v) { x += v.x; y += v.y; z += v.z; w += v.w; return *this; } - const vector4_base &operator -=(const vector4_base &v) { x -= v.x; y -= v.y; z -= v.z; w -= v.w; return *this; } - const vector4_base &operator *=(const T v) { x *= v; y *= v; z *= v; w *= v; return *this; } - - bool operator ==(const vector4_base &v) const { return x == v.x && y == v.y && z == v.z && w == v.w; } //TODO: do this with an eps instead - - operator const T* () { return &x; } -}; - -typedef vector4_base vec4; -typedef vector4_base bvec4; -typedef vector4_base ivec4; - -#endif -- cgit 1.4.1