Added TeeWorlds - it compiles but does not work yet (crashed my Evo yay!)

This commit is contained in:
pelya
2010-09-03 18:39:44 +03:00
parent ab7d052202
commit 59e15688fc
213 changed files with 48972 additions and 1 deletions

View File

@@ -0,0 +1,95 @@
#include <base/math.hpp>
#include <engine/e_client_interface.h>
#include <game/generated/g_protocol.hpp>
#include <game/generated/gc_data.hpp>
#include "animstate.hpp"
static void anim_seq_eval(ANIM_SEQUENCE *seq, float time, ANIM_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
ANIM_KEYFRAME *frame1 = 0;
ANIM_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);
}
}
}
static void anim_add_keyframe(ANIM_KEYFRAME *seq, ANIM_KEYFRAME *added, float amount)
{
seq->x += added->x*amount;
seq->y += added->y*amount;
seq->angle += added->angle*amount;
}
static 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 ANIMSTATE::set(ANIMATION *anim, float time)
{
anim_seq_eval(&anim->body, time, &body);
anim_seq_eval(&anim->back_foot, time, &back_foot);
anim_seq_eval(&anim->front_foot, time, &front_foot);
anim_seq_eval(&anim->attach, time, &attach);
}
void ANIMSTATE::add(ANIMATION *anim, float time, float amount)
{
ANIMSTATE add;
add.set(anim, time);
anim_add(this, &add, amount);
}
ANIMSTATE *ANIMSTATE::get_idle()
{
static ANIMSTATE state;
static bool init = true;
if(init)
{
state.set(&data->animations[ANIM_BASE], 0);
state.add(&data->animations[ANIM_IDLE], 0, 1.0f);
init = false;
}
return &state;
}

View File

@@ -0,0 +1,24 @@
#ifndef GAME_CLIENT_ANIMATION_H
#define GAME_CLIENT_ANIMATION_H
class ANIMSTATE
{
public:
ANIM_KEYFRAME body;
ANIM_KEYFRAME back_foot;
ANIM_KEYFRAME front_foot;
ANIM_KEYFRAME attach;
void set(ANIMATION *anim, float time);
void add(ANIMATION *added, float time, float amount);
static ANIMSTATE *get_idle();
};
//void anim_seq_eval(ANIM_SEQUENCE *seq, float time, ANIM_KEYFRAME *frame);
//void anim_eval(ANIMATION *anim, float time, ANIM_STATE *state);
//void anim_add_keyframe(ANIM_KEYFRAME *seq, ANIM_KEYFRAME *added, float amount);
//void anim_add(ANIM_STATE *state, ANIM_STATE *added, float amount);
//void anim_eval_add(ANIM_STATE *state, ANIMATION *anim, float time, float amount);
#endif

View File

@@ -0,0 +1,28 @@
#include <string.h>
#include <engine/e_client_interface.h>
#include <game/version.hpp>
#include "gameclient.hpp"
#include "components/console.hpp"
// clean hooks
extern "C" void modc_entergame() {}
extern "C" void modc_shutdown() {}
extern "C" void modc_console_init() { gameclient.on_console_init(); }
extern "C" void modc_save_config() { gameclient.on_save(); }
extern "C" void modc_init() { gameclient.on_init(); }
extern "C" void modc_connected() { gameclient.on_connected(); }
extern "C" void modc_predict() { gameclient.on_predict(); }
extern "C" void modc_newsnapshot() { gameclient.on_snapshot(); }
extern "C" int modc_snap_input(int *data) { return gameclient.on_snapinput(data); }
extern "C" void modc_statechange(int state, int old) { gameclient.on_statechange(state, old); }
extern "C" void modc_render() { gameclient.on_render(); }
extern "C" void modc_message(int msgtype) { gameclient.on_message(msgtype); }
extern "C" void modc_rcon_line(const char *line) { gameclient.console->print_line(1, line); }
extern "C" const char *modc_net_version() { return GAME_NETVERSION; }
extern "C" const char *modc_getitemname(int type) { return netobj_get_name(type); }

View File

@@ -0,0 +1,27 @@
#ifndef GAME_CLIENT_GAMESYSTEM_H
#define GAME_CLIENT_GAMESYSTEM_H
#include <engine/e_client_interface.h>
class GAMECLIENT;
class COMPONENT
{
protected:
GAMECLIENT *client;
public:
virtual ~COMPONENT() {}
virtual void on_statechange(int new_state, int old_state) {};
virtual void on_console_init() {};
virtual void on_init() {};
virtual void on_save() {};
virtual void on_reset() {};
virtual void on_render() {};
virtual void on_mapload() {};
virtual void on_message(int msg, void *rawmsg) {}
virtual bool on_mousemove(float x, float y) { return false; }
virtual bool on_input(INPUT_EVENT e) { return false; }
};
#endif

View File

@@ -0,0 +1,222 @@
#include <stdlib.h> // atoi
#include <string.h> // strcmp
#include <engine/e_client_interface.h>
#include "binds.hpp"
bool BINDS::BINDS_SPECIAL::on_input(INPUT_EVENT e)
{
// don't handle invalid events and keys that arn't set to anything
if(e.key >= KEY_F1 && e.key <= KEY_F15 && binds->keybindings[e.key][0] != 0)
{
int stroke = 0;
if(e.flags&INPFLAG_PRESS)
stroke = 1;
console_execute_line_stroked(stroke, binds->keybindings[e.key]);
return true;
}
return false;
}
BINDS::BINDS()
{
mem_zero(keybindings, sizeof(keybindings));
special_binds.binds = this;
}
void BINDS::bind(int keyid, const char *str)
{
if(keyid < 0 || keyid >= KEY_LAST)
return;
str_copy(keybindings[keyid], str, sizeof(keybindings[keyid]));
if(!keybindings[keyid][0])
dbg_msg("binds", "unbound %s (%d)", inp_key_name(keyid), keyid);
else
dbg_msg("binds", "bound %s (%d) = %s", inp_key_name(keyid), keyid, keybindings[keyid]);
}
bool BINDS::on_input(INPUT_EVENT e)
{
// don't handle invalid events and keys that arn't set to anything
if(e.key <= 0 || e.key >= KEY_LAST || keybindings[e.key][0] == 0)
return false;
int stroke = 0;
if(e.flags&INPFLAG_PRESS)
stroke = 1;
console_execute_line_stroked(stroke, keybindings[e.key]);
return true;
}
void BINDS::unbindall()
{
for(int i = 0; i < KEY_LAST; i++)
keybindings[i][0] = 0;
}
const char *BINDS::get(int keyid)
{
if(keyid > 0 && keyid < KEY_LAST)
return keybindings[keyid];
return "";
}
const char *BINDS::get_key(const char *bindstr)
{
for(int keyid = 0; keyid < KEY_LAST; keyid++)
{
const char *bind = get(keyid);
if(!bind[0])
continue;
if(strcmp(bind, bindstr) == 0)
return inp_key_name(keyid);
}
return "";
}
void BINDS::set_defaults()
{
// set default key bindings
unbindall();
bind(KEY_F1, "toggle_local_console");
bind(KEY_F2, "toggle_remote_console");
bind(KEY_TAB, "+scoreboard");
bind(KEY_F10, "screenshot");
bind('a', "+left");
bind('d', "+right");
bind(KEY_SPACE, "+jump");
bind(KEY_MOUSE_1, "+fire");
bind(KEY_MOUSE_2, "+hook");
bind(KEY_LSHIFT, "+emote");
bind('1', "+weapon1");
bind('2', "+weapon2");
bind('3', "+weapon3");
bind('4', "+weapon4");
bind('5', "+weapon5");
bind(KEY_MOUSE_WHEEL_UP, "+prevweapon");
bind(KEY_MOUSE_WHEEL_DOWN, "+nextweapon");
bind('t', "chat all");
bind('y', "chat team");
bind(KEY_F3, "vote yes");
bind(KEY_F4, "vote no");
}
void BINDS::on_console_init()
{
// bindings
MACRO_REGISTER_COMMAND("bind", "sr", CFGFLAG_CLIENT, con_bind, this, "Bind key to execute the command");
MACRO_REGISTER_COMMAND("unbind", "s", CFGFLAG_CLIENT, con_unbind, this, "Unbind key");
MACRO_REGISTER_COMMAND("unbindall", "", CFGFLAG_CLIENT, con_unbindall, this, "Unbind all keys");
MACRO_REGISTER_COMMAND("dump_binds", "", CFGFLAG_CLIENT, con_dump_binds, this, "Dump binds");
// default bindings
set_defaults();
}
void BINDS::con_bind(void *result, void *user_data)
{
BINDS *binds = (BINDS *)user_data;
const char *key_name = console_arg_string(result, 0);
int id = binds->get_key_id(key_name);
if(!id)
{
dbg_msg("binds", "key %s not found", key_name);
return;
}
binds->bind(id, console_arg_string(result, 1));
}
void BINDS::con_unbind(void *result, void *user_data)
{
BINDS *binds = (BINDS *)user_data;
const char *key_name = console_arg_string(result, 0);
int id = binds->get_key_id(key_name);
if(!id)
{
dbg_msg("binds", "key %s not found", key_name);
return;
}
binds->bind(id, "");
}
void BINDS::con_unbindall(void *result, void *user_data)
{
BINDS *binds = (BINDS *)user_data;
binds->unbindall();
}
void BINDS::con_dump_binds(void *result, void *user_data)
{
BINDS *binds = (BINDS *)user_data;
for(int i = 0; i < KEY_LAST; i++)
{
if(binds->keybindings[i][0] == 0)
continue;
dbg_msg("binds", "%s (%d) = %s", inp_key_name(i), i, binds->keybindings[i]);
}
}
int BINDS::get_key_id(const char *key_name)
{
// check for numeric
if(key_name[0] == '&')
{
int i = atoi(key_name+1);
if(i > 0 && i < KEY_LAST)
return i; // numeric
}
// search for key
for(int i = 0; i < KEY_LAST; i++)
{
if(strcmp(key_name, inp_key_name(i)) == 0)
return i;
}
return 0;
}
void BINDS::on_save()
{
char buffer[256];
char *end = buffer+sizeof(buffer)-8;
client_save_line("unbindall");
for(int i = 0; i < KEY_LAST; i++)
{
if(keybindings[i][0] == 0)
continue;
str_format(buffer, sizeof(buffer), "bind %s ", inp_key_name(i));
// process the string. we need to escape some characters
const char *src = keybindings[i];
char *dst = buffer + strlen(buffer);
*dst++ = '"';
while(*src && dst < end)
{
if(*src == '"' || *src == '\\') // escape \ and "
*dst++ = '\\';
*dst++ = *src++;
}
*dst++ = '"';
*dst++ = 0;
client_save_line(buffer);
}
}

View File

@@ -0,0 +1,35 @@
#include <game/client/component.hpp>
class BINDS : public COMPONENT
{
char keybindings[KEY_LAST][128];
int get_key_id(const char *key_name);
static void con_bind(void *result, void *user_data);
static void con_unbind(void *result, void *user_data);
static void con_unbindall(void *result, void *user_data);
static void con_dump_binds(void *result, void *user_data);
public:
BINDS();
class BINDS_SPECIAL : public COMPONENT
{
public:
BINDS *binds;
virtual bool on_input(INPUT_EVENT e);
};
BINDS_SPECIAL special_binds;
void bind(int keyid, const char *str);
void set_defaults();
void unbindall();
const char *get(int keyid);
const char *get_key(const char *bindstr);
virtual void on_save();
virtual void on_console_init();
virtual bool on_input(INPUT_EVENT e);
};

View File

@@ -0,0 +1,35 @@
#include <engine/e_client_interface.h>
#include <engine/e_config.h>
#include <game/generated/g_protocol.hpp>
#include <game/generated/gc_data.hpp>
#include <game/client/gameclient.hpp>
#include "broadcast.hpp"
void BROADCAST::on_reset()
{
broadcast_time = 0;
}
void BROADCAST::on_render()
{
gfx_mapscreen(0, 0, 300*gfx_screenaspect(), 300);
if(time_get() < broadcast_time)
{
float w = gfx_text_width(0, 14, broadcast_text, -1);
gfx_text(0, 150*gfx_screenaspect()-w/2, 35, 14, broadcast_text, -1);
}
}
void BROADCAST::on_message(int msgtype, void *rawmsg)
{
if(msgtype == NETMSGTYPE_SV_BROADCAST)
{
NETMSG_SV_BROADCAST *msg = (NETMSG_SV_BROADCAST *)rawmsg;
str_copy(broadcast_text, msg->message, sizeof(broadcast_text));
broadcast_time = time_get()+time_freq()*10;
}
}

View File

@@ -0,0 +1,14 @@
#include <game/client/component.hpp>
class BROADCAST : public COMPONENT
{
public:
// broadcasts
char broadcast_text[1024];
int64 broadcast_time;
virtual void on_reset();
virtual void on_render();
virtual void on_message(int msgtype, void *rawmsg);
};

View File

@@ -0,0 +1,40 @@
extern "C" {
#include <engine/e_config.h>
#include <engine/e_client_interface.h>
}
#include <base/math.hpp>
#include <game/collision.hpp>
#include <game/client/gameclient.hpp>
#include <game/client/component.hpp>
#include "camera.hpp"
#include "controls.hpp"
CAMERA::CAMERA()
{
}
void CAMERA::on_render()
{
//vec2 center;
zoom = 1.0f;
// update camera center
if(gameclient.snap.spectate)
center = gameclient.controls->mouse_pos;
else
{
float l = length(gameclient.controls->mouse_pos);
float deadzone = config.cl_mouse_deadzone;
float follow_factor = config.cl_mouse_followfactor/100.0f;
vec2 camera_offset(0, 0);
float offset_amount = max(l-deadzone, 0.0f) * follow_factor;
if(l > 0.0001f) // make sure that this isn't 0
camera_offset = normalize(gameclient.controls->mouse_pos)*offset_amount;
center = gameclient.local_character_pos + camera_offset;
}
}

View File

@@ -0,0 +1,13 @@
#include <base/vmath.hpp>
#include <game/client/component.hpp>
class CAMERA : public COMPONENT
{
public:
vec2 center;
float zoom;
CAMERA();
virtual void on_render();
};

View File

@@ -0,0 +1,223 @@
#include <string.h> // strcmp
#include <engine/e_client_interface.h>
#include <game/generated/g_protocol.hpp>
#include <game/generated/gc_data.hpp>
#include <game/client/gameclient.hpp>
#include <game/client/components/sounds.hpp>
#include "chat.hpp"
void CHAT::on_statechange(int new_state, int old_state)
{
if(old_state <= CLIENTSTATE_CONNECTING)
{
mode = MODE_NONE;
for(int i = 0; i < MAX_LINES; i++)
lines[i].time = 0;
current_line = 0;
}
}
void CHAT::con_say(void *result, void *user_data)
{
((CHAT*)user_data)->say(0, console_arg_string(result, 0));
}
void CHAT::con_sayteam(void *result, void *user_data)
{
((CHAT*)user_data)->say(1, console_arg_string(result, 0));
}
void CHAT::con_chat(void *result, void *user_data)
{
const char *mode = console_arg_string(result, 0);
if(strcmp(mode, "all") == 0)
((CHAT*)user_data)->enable_mode(0);
else if(strcmp(mode, "team") == 0)
((CHAT*)user_data)->enable_mode(1);
else
dbg_msg("console", "expected all or team as mode");
}
void CHAT::on_console_init()
{
MACRO_REGISTER_COMMAND("say", "r", CFGFLAG_CLIENT, con_say, this, "Say in chat");
MACRO_REGISTER_COMMAND("say_team", "r", CFGFLAG_CLIENT, con_sayteam, this, "Say in team chat");
MACRO_REGISTER_COMMAND("chat", "s", CFGFLAG_CLIENT, con_chat, this, "Enable chat with all/team mode");
}
bool CHAT::on_input(INPUT_EVENT e)
{
if(mode == MODE_NONE)
return false;
if(e.flags&INPFLAG_PRESS && e.key == KEY_ESCAPE)
mode = MODE_NONE;
else if(e.flags&INPFLAG_PRESS && (e.key == KEY_RETURN || e.key == KEY_KP_ENTER))
{
if(input.get_string()[0])
gameclient.chat->say(mode == MODE_ALL ? 0 : 1, input.get_string());
mode = MODE_NONE;
}
else
input.process_input(e);
return true;
}
void CHAT::enable_mode(int team)
{
if(mode == MODE_NONE)
{
if(team)
mode = MODE_TEAM;
else
mode = MODE_ALL;
input.clear();
inp_clear_events();
}
}
void CHAT::on_message(int msgtype, void *rawmsg)
{
if(msgtype == NETMSGTYPE_SV_CHAT)
{
NETMSG_SV_CHAT *msg = (NETMSG_SV_CHAT *)rawmsg;
add_line(msg->cid, msg->team, msg->message);
if(msg->cid >= 0)
gameclient.sounds->play(SOUNDS::CHN_GUI, SOUND_CHAT_CLIENT, 0, vec2(0,0));
else
gameclient.sounds->play(SOUNDS::CHN_GUI, SOUND_CHAT_SERVER, 0, vec2(0,0));
}
}
void CHAT::add_line(int client_id, int team, const char *line)
{
current_line = (current_line+1)%MAX_LINES;
lines[current_line].time = time_get();
lines[current_line].client_id = client_id;
lines[current_line].team = team;
lines[current_line].name_color = -2;
if(client_id == -1) // server message
{
str_copy(lines[current_line].name, "*** ", sizeof(lines[current_line].name));
str_format(lines[current_line].text, sizeof(lines[current_line].text), "%s", line);
}
else
{
if(gameclient.clients[client_id].team == -1)
lines[current_line].name_color = -1;
if(gameclient.snap.gameobj && gameclient.snap.gameobj->flags&GAMEFLAG_TEAMS)
{
if(gameclient.clients[client_id].team == 0)
lines[current_line].name_color = 0;
else if(gameclient.clients[client_id].team == 1)
lines[current_line].name_color = 1;
}
str_copy(lines[current_line].name, gameclient.clients[client_id].name, sizeof(lines[current_line].name));
str_format(lines[current_line].text, sizeof(lines[current_line].text), ": %s", line);
}
dbg_msg("chat", "%s%s", lines[current_line].name, lines[current_line].text);
}
void CHAT::on_render()
{
gfx_mapscreen(0,0,300*gfx_screenaspect(),300);
float x = 10.0f;
float y = 300.0f-20.0f;
if(mode != MODE_NONE)
{
// render chat input
TEXT_CURSOR cursor;
gfx_text_set_cursor(&cursor, x, y, 8.0f, TEXTFLAG_RENDER);
cursor.line_width = 200.0f;
if(mode == MODE_ALL)
gfx_text_ex(&cursor, "All: ", -1);
else if(mode == MODE_TEAM)
gfx_text_ex(&cursor, "Team: ", -1);
else
gfx_text_ex(&cursor, "Chat: ", -1);
gfx_text_ex(&cursor, input.get_string(), input.cursor_offset());
TEXT_CURSOR marker = cursor;
gfx_text_ex(&marker, "|", -1);
gfx_text_ex(&cursor, input.get_string()+input.cursor_offset(), -1);
}
y -= 8;
int i;
for(i = 0; i < MAX_LINES; i++)
{
int r = ((current_line-i)+MAX_LINES)%MAX_LINES;
if(time_get() > lines[r].time+15*time_freq())
break;
float begin = x;
float fontsize = 7.0f;
// get the y offset
TEXT_CURSOR cursor;
gfx_text_set_cursor(&cursor, begin, 0, fontsize, 0);
cursor.line_width = 200.0f;
gfx_text_ex(&cursor, lines[r].name, -1);
gfx_text_ex(&cursor, lines[r].text, -1);
y -= cursor.y + cursor.font_size;
// cut off if msgs waste too much space
if(y < 200.0f)
break;
// reset the cursor
gfx_text_set_cursor(&cursor, begin, y, fontsize, TEXTFLAG_RENDER);
cursor.line_width = 200.0f;
// render name
gfx_text_color(0.8f,0.8f,0.8f,1);
if(lines[r].client_id == -1)
gfx_text_color(1,1,0.5f,1); // system
else if(lines[r].team)
gfx_text_color(0.45f,0.9f,0.45f,1); // team message
else if(lines[r].name_color == 0)
gfx_text_color(1.0f,0.5f,0.5f,1); // red
else if(lines[r].name_color == 1)
gfx_text_color(0.7f,0.7f,1.0f,1); // blue
else if(lines[r].name_color == -1)
gfx_text_color(0.75f,0.5f,0.75f, 1); // spectator
// render name
gfx_text_ex(&cursor, lines[r].name, -1);
// render line
gfx_text_color(1,1,1,1);
if(lines[r].client_id == -1)
gfx_text_color(1,1,0.5f,1); // system
else if(lines[r].team)
gfx_text_color(0.65f,1,0.65f,1); // team message
gfx_text_ex(&cursor, lines[r].text, -1);
}
gfx_text_color(1,1,1,1);
}
void CHAT::say(int team, const char *line)
{
// send chat message
NETMSG_CL_SAY msg;
msg.team = team;
msg.message = line;
msg.pack(MSGFLAG_VITAL);
client_send_msg();
}

View File

@@ -0,0 +1,54 @@
#include <game/client/component.hpp>
#include <game/client/lineinput.hpp>
class CHAT : public COMPONENT
{
LINEINPUT input;
enum
{
MAX_LINES = 10,
};
struct LINE
{
int64 time;
int client_id;
int team;
int name_color;
char name[64];
char text[512];
};
LINE lines[MAX_LINES];
int current_line;
// chat
enum
{
MODE_NONE=0,
MODE_ALL,
MODE_TEAM,
};
int mode;
static void con_say(void *result, void *user_data);
static void con_sayteam(void *result, void *user_data);
static void con_chat(void *result, void *user_data);
public:
bool is_active() const { return mode != MODE_NONE; }
void add_line(int client_id, int team, const char *line);
void enable_mode(int team);
void say(int team, const char *line);
virtual void on_console_init();
virtual void on_statechange(int new_state, int old_state);
virtual void on_render();
virtual void on_message(int msgtype, void *rawmsg);
virtual bool on_input(INPUT_EVENT e);
};

View File

@@ -0,0 +1,538 @@
//#include "gc_console.hpp"
#include <math.h>
#include <game/generated/gc_data.hpp>
#include <base/system.h>
#include <engine/e_client_interface.h>
extern "C" {
#include <engine/e_ringbuffer.h>
#include <engine/client/ec_font.h>
}
#include <cstring>
#include <cstdio>
#include <game/client/ui.hpp>
#include <game/version.hpp>
#include <game/client/lineinput.hpp>
#include <game/client/render.hpp>
#include "console.hpp"
enum
{
CONSOLE_CLOSED,
CONSOLE_OPENING,
CONSOLE_OPEN,
CONSOLE_CLOSING,
};
CONSOLE::INSTANCE::INSTANCE(int t)
{
// init ringbuffers
history = ringbuf_init(history_data, sizeof(history_data), RINGBUF_FLAG_RECYCLE);
backlog = ringbuf_init(backlog_data, sizeof(backlog_data), RINGBUF_FLAG_RECYCLE);
history_entry = 0x0;
type = t;
if(t == 0)
completion_flagmask = CFGFLAG_CLIENT;
else
completion_flagmask = CFGFLAG_SERVER;
completion_buffer[0] = 0;
completion_chosen = -1;
command = 0x0;
}
void CONSOLE::INSTANCE::execute_line(const char *line)
{
if(type == 0)
console_execute_line(line);
else
{
if(client_rcon_authed())
client_rcon(line);
else
client_rcon_auth("", line);
}
}
void CONSOLE::INSTANCE::possible_commands_complete_callback(const char *str, void *user)
{
CONSOLE::INSTANCE *instance = (CONSOLE::INSTANCE *)user;
if(instance->completion_chosen == instance->completion_enumeration_count)
instance->input.set(str);
instance->completion_enumeration_count++;
}
void CONSOLE::INSTANCE::on_input(INPUT_EVENT e)
{
bool handled = false;
if(e.flags&INPFLAG_PRESS)
{
if(e.key == KEY_RETURN || e.key == KEY_KP_ENTER)
{
if(input.get_string()[0])
{
char *entry = (char *)ringbuf_allocate(history, input.get_length()+1);
mem_copy(entry, input.get_string(), input.get_length()+1);
execute_line(input.get_string());
input.clear();
history_entry = 0x0;
}
handled = true;
}
else if (e.key == KEY_UP)
{
if (history_entry)
{
char *test = (char *)ringbuf_prev(history, history_entry);
if (test)
history_entry = test;
}
else
history_entry = (char *)ringbuf_last(history);
if (history_entry)
{
unsigned int len = strlen(history_entry);
if (len < sizeof(input) - 1)
input.set(history_entry);
}
handled = true;
}
else if (e.key == KEY_DOWN)
{
if (history_entry)
history_entry = (char *)ringbuf_next(history, history_entry);
if (history_entry)
{
unsigned int len = strlen(history_entry);
if (len < sizeof(input) - 1)
input.set(history_entry);
}
else
input.clear();
handled = true;
}
else if(e.key == KEY_TAB)
{
completion_chosen++;
completion_enumeration_count = 0;
console_possible_commands(completion_buffer, completion_flagmask, possible_commands_complete_callback, this);
// handle wrapping
if(completion_enumeration_count && completion_chosen >= completion_enumeration_count)
{
completion_chosen %= completion_enumeration_count;
completion_enumeration_count = 0;
console_possible_commands(completion_buffer, completion_flagmask, possible_commands_complete_callback, this);
}
}
if(e.key != KEY_TAB)
{
completion_chosen = -1;
str_copy(completion_buffer, input.get_string(), sizeof(completion_buffer));
}
// find the current command
{
char buf[64] = {0};
const char *src = get_string();
int i = 0;
for(; i < (int)sizeof(buf) && *src && *src != ' ' && *src != ' '; i++, src++)
buf[i] = *src;
buf[i] = 0;
command = console_get_command(buf);
}
}
if(!handled)
input.process_input(e);
}
void CONSOLE::INSTANCE::print_line(const char *line)
{
int len = strlen(line);
if (len > 255)
len = 255;
char *entry = (char *)ringbuf_allocate(backlog, len+1);
mem_copy(entry, line, len+1);
}
CONSOLE::CONSOLE()
: local_console(0), remote_console(1)
{
console_type = 0;
console_state = CONSOLE_CLOSED;
state_change_end = 0.0f;
state_change_duration = 0.1f;
}
float CONSOLE::time_now()
{
static long long time_start = time_get();
return float(time_get()-time_start)/float(time_freq());
}
CONSOLE::INSTANCE *CONSOLE::current_console()
{
if(console_type != 0)
return &remote_console;
return &local_console;
}
void CONSOLE::on_reset()
{
}
// only defined for 0<=t<=1
static float console_scale_func(float t)
{
//return t;
return sinf(acosf(1.0f-t));
}
struct RENDERINFO
{
TEXT_CURSOR cursor;
const char *current_cmd;
int wanted_completion;
int enum_count;
};
void CONSOLE::possible_commands_render_callback(const char *str, void *user)
{
RENDERINFO *info = (RENDERINFO *)user;
if(info->enum_count == info->wanted_completion)
{
float tw = gfx_text_width(info->cursor.font_set, info->cursor.font_size, str, -1);
gfx_texture_set(-1);
gfx_quads_begin();
gfx_setcolor(229.0f/255.0f,185.0f/255.0f,4.0f/255.0f,0.85f);
draw_round_rect(info->cursor.x-3, info->cursor.y, tw+5, info->cursor.font_size+4, info->cursor.font_size/3);
gfx_quads_end();
gfx_text_color(0.05f, 0.05f, 0.05f,1);
gfx_text_ex(&info->cursor, str, -1);
}
else
{
const char *match_start = str_find_nocase(str, info->current_cmd);
if(match_start)
{
gfx_text_color(0.5f,0.5f,0.5f,1);
gfx_text_ex(&info->cursor, str, match_start-str);
gfx_text_color(229.0f/255.0f,185.0f/255.0f,4.0f/255.0f,1);
gfx_text_ex(&info->cursor, match_start, strlen(info->current_cmd));
gfx_text_color(0.5f,0.5f,0.5f,1);
gfx_text_ex(&info->cursor, match_start+strlen(info->current_cmd), -1);
}
else
{
gfx_text_color(0.75f,0.75f,0.75f,1);
gfx_text_ex(&info->cursor, str, -1);
}
}
info->enum_count++;
info->cursor.x += 7.0f;
}
void CONSOLE::on_render()
{
RECT screen = *ui_screen();
float console_max_height = screen.h*3/5.0f;
float console_height;
float progress = (time_now()-(state_change_end-state_change_duration))/float(state_change_duration);
if (progress >= 1.0f)
{
if (console_state == CONSOLE_CLOSING)
console_state = CONSOLE_CLOSED;
else if (console_state == CONSOLE_OPENING)
console_state = CONSOLE_OPEN;
progress = 1.0f;
}
if (console_state == CONSOLE_OPEN && config.cl_editor)
toggle(0);
if (console_state == CONSOLE_CLOSED)
return;
if (console_state == CONSOLE_OPEN)
inp_mouse_mode_absolute();
float console_height_scale;
if (console_state == CONSOLE_OPENING)
console_height_scale = console_scale_func(progress);
else if (console_state == CONSOLE_CLOSING)
console_height_scale = console_scale_func(1.0f-progress);
else //if (console_state == CONSOLE_OPEN)
console_height_scale = console_scale_func(1.0f);
console_height = console_height_scale*console_max_height;
gfx_mapscreen(screen.x, screen.y, screen.w, screen.h);
// do console shadow
gfx_texture_set(-1);
gfx_quads_begin();
gfx_setcolorvertex(0, 0,0,0, 0.5f);
gfx_setcolorvertex(1, 0,0,0, 0.5f);
gfx_setcolorvertex(2, 0,0,0, 0.0f);
gfx_setcolorvertex(3, 0,0,0, 0.0f);
gfx_quads_drawTL(0,console_height,screen.w,10.0f);
gfx_quads_end();
// do background
gfx_texture_set(data->images[IMAGE_CONSOLE_BG].id);
gfx_quads_begin();
gfx_setcolor(0.2f, 0.2f, 0.2f,0.9f);
if(console_type != 0)
gfx_setcolor(0.4f, 0.2f, 0.2f,0.9f);
gfx_quads_setsubset(0,-console_height*0.075f,screen.w*0.075f*0.5f,0);
gfx_quads_drawTL(0,0,screen.w,console_height);
gfx_quads_end();
// do small bar shadow
gfx_texture_set(-1);
gfx_quads_begin();
gfx_setcolorvertex(0, 0,0,0, 0.0f);
gfx_setcolorvertex(1, 0,0,0, 0.0f);
gfx_setcolorvertex(2, 0,0,0, 0.25f);
gfx_setcolorvertex(3, 0,0,0, 0.25f);
gfx_quads_drawTL(0,console_height-20,screen.w,10);
gfx_quads_end();
// do the lower bar
gfx_texture_set(data->images[IMAGE_CONSOLE_BAR].id);
gfx_quads_begin();
gfx_setcolor(1.0f, 1.0f, 1.0f, 0.9f);
gfx_quads_setsubset(0,0.1f,screen.w*0.015f,1-0.1f);
gfx_quads_drawTL(0,console_height-10.0f,screen.w,10.0f);
gfx_quads_end();
console_height -= 22.0f;
INSTANCE *console = current_console();
{
float font_size = 10.0f;
float row_height = font_size*1.25f;
float x = 3;
float y = console_height - row_height - 2;
// render prompt
TEXT_CURSOR cursor;
gfx_text_set_cursor(&cursor, x, y, font_size, TEXTFLAG_RENDER);
RENDERINFO info;
info.wanted_completion = console->completion_chosen;
info.enum_count = 0;
info.current_cmd = console->completion_buffer;
gfx_text_set_cursor(&info.cursor, x, y+12.0f, font_size, TEXTFLAG_RENDER);
const char *prompt = "> ";
if(console_type)
{
if(client_state() == CLIENTSTATE_ONLINE)
{
if(client_rcon_authed())
prompt = "rcon> ";
else
prompt = "ENTER PASSWORD> ";
}
else
prompt = "NOT CONNECTED> ";
}
gfx_text_ex(&cursor, prompt, -1);
// render console input
gfx_text_ex(&cursor, console->input.get_string(), console->input.cursor_offset());
TEXT_CURSOR marker = cursor;
gfx_text_ex(&marker, "|", -1);
gfx_text_ex(&cursor, console->input.get_string()+console->input.cursor_offset(), -1);
// render version
char buf[128];
str_format(buf, sizeof(buf), "v%s", GAME_VERSION);
float version_width = gfx_text_width(0, font_size, buf, -1);
gfx_text(0, screen.w-version_width-5, y, font_size, buf, -1);
// render possible commands
if(console->input.get_string()[0] != 0)
{
console_possible_commands(console->completion_buffer, console->completion_flagmask, possible_commands_render_callback, &info);
if(info.enum_count <= 0)
{
if(console->command)
{
char buf[512];
str_format(buf, sizeof(buf), "Help: %s ", console->command->help);
gfx_text_ex(&info.cursor, buf, -1);
gfx_text_color(0.75f, 0.75f, 0.75f, 1);
str_format(buf, sizeof(buf), "Syntax: %s %s", console->command->name, console->command->params);
gfx_text_ex(&info.cursor, buf, -1);
}
}
}
gfx_text_color(1,1,1,1);
// render log
y -= row_height;
char *entry = (char *)ringbuf_last(console->backlog);
while (y > 0.0f && entry)
{
gfx_text(0, x, y, font_size, entry, -1);
y -= row_height;
entry = (char *)ringbuf_prev(console->backlog, entry);
}
}
}
void CONSOLE::on_message(int msgtype, void *rawmsg)
{
}
bool CONSOLE::on_input(INPUT_EVENT e)
{
if(console_state == CONSOLE_CLOSED)
return false;
if(e.key >= KEY_F1 && e.key <= KEY_F15)
return false;
if(e.key == KEY_ESCAPE && (e.flags&INPFLAG_PRESS))
toggle(console_type);
else
current_console()->on_input(e);
return true;
}
void CONSOLE::toggle(int type)
{
if(console_type != type && (console_state == CONSOLE_OPEN || console_state == CONSOLE_OPENING))
{
// don't toggle console, just switch what console to use
}
else
{
if (console_state == CONSOLE_CLOSED || console_state == CONSOLE_OPEN)
{
state_change_end = time_now()+state_change_duration;
}
else
{
float progress = state_change_end-time_now();
float reversed_progress = state_change_duration-progress;
state_change_end = time_now()+reversed_progress;
}
if (console_state == CONSOLE_CLOSED || console_state == CONSOLE_CLOSING)
{
inp_mouse_mode_absolute();
console_state = CONSOLE_OPENING;
}
else
{
inp_mouse_mode_relative();
console_state = CONSOLE_CLOSING;
}
}
console_type = type;
}
void CONSOLE::con_toggle_local_console(void *result, void *user_data)
{
((CONSOLE *)user_data)->toggle(0);
}
void CONSOLE::con_toggle_remote_console(void *result, void *user_data)
{
((CONSOLE *)user_data)->toggle(1);
}
void CONSOLE::client_console_print_callback(const char *str, void *user_data)
{
((CONSOLE *)user_data)->local_console.print_line(str);
}
void CONSOLE::print_line(int type, const char *line)
{
if(type == 0)
local_console.print_line(line);
else if(type == 1)
remote_console.print_line(line);
}
void CONSOLE::on_console_init()
{
//
console_register_print_callback(client_console_print_callback, this);
MACRO_REGISTER_COMMAND("toggle_local_console", "", CFGFLAG_CLIENT, con_toggle_local_console, this, "Toggle local console");
MACRO_REGISTER_COMMAND("toggle_remote_console", "", CFGFLAG_CLIENT, con_toggle_remote_console, this, "Toggle remote console");
}
/*
static void con_team(void *result, void *user_data)
{
send_switch_team(console_arg_int(result, 0));
}
static void con_kill(void *result, void *user_data)
{
send_kill(-1);
}
void send_kill(int client_id);
static void con_emote(void *result, void *user_data)
{
send_emoticon(console_arg_int(result, 0));
}
extern void con_chat(void *result, void *user_data);
void client_console_init()
{
//
MACRO_REGISTER_COMMAND("team", "i", con_team, 0x0);
MACRO_REGISTER_COMMAND("kill", "", con_kill, 0x0);
// chatting
MACRO_REGISTER_COMMAND("emote", "i", con_emote, 0);
MACRO_REGISTER_COMMAND("+emote", "", con_key_input_state, &emoticon_selector_active);
}
*/

View File

@@ -0,0 +1,70 @@
#include <engine/e_client_interface.h>
#include <game/client/component.hpp>
#include <game/client/lineinput.hpp>
class CONSOLE : public COMPONENT
{
class INSTANCE
{
public:
char history_data[65536];
struct RINGBUFFER *history;
char *history_entry;
char backlog_data[65536];
struct RINGBUFFER *backlog;
LINEINPUT input;
int type;
int completion_enumeration_count;
public:
char completion_buffer[128];
int completion_chosen;
int completion_flagmask;
COMMAND *command;
INSTANCE(int t);
void execute_line(const char *line);
void on_input(INPUT_EVENT e);
void print_line(const char *line);
const char *get_string() const { return input.get_string(); }
static void possible_commands_complete_callback(const char *str, void *user);
};
INSTANCE local_console;
INSTANCE remote_console;
INSTANCE *current_console();
float time_now();
int console_type;
int console_state;
float state_change_end;
float state_change_duration;
void toggle(int type);
static void possible_commands_render_callback(const char *str, void *user);
static void client_console_print_callback(const char *str, void *user_data);
static void con_toggle_local_console(void *result, void *user_data);
static void con_toggle_remote_console(void *result, void *user_data);
public:
CONSOLE();
void print_line(int type, const char *line);
virtual void on_console_init();
virtual void on_reset();
virtual void on_render();
virtual void on_message(int msgtype, void *rawmsg);
virtual bool on_input(INPUT_EVENT e);
};

View File

@@ -0,0 +1,215 @@
#include <engine/e_client_interface.h>
#include <base/math.hpp>
#include <game/collision.hpp>
#include <game/client/gameclient.hpp>
#include <game/client/component.hpp>
#include <game/client/components/chat.hpp>
#include <game/client/components/menus.hpp>
#include "controls.hpp"
CONTROLS::CONTROLS()
{
}
static void con_key_input_state(void *result, void *user_data)
{
((int *)user_data)[0] = console_arg_int(result, 0);
}
static void con_key_input_counter(void *result, void *user_data)
{
int *v = (int *)user_data;
if(((*v)&1) != console_arg_int(result, 0))
(*v)++;
*v &= INPUT_STATE_MASK;
}
struct INPUTSET
{
CONTROLS *controls;
int *variable;
int value;
};
static void con_key_input_set(void *result, void *user_data)
{
INPUTSET *set = (INPUTSET *)user_data;
*set->variable = console_arg_int(result, 0) ? *set->variable = set->value : 0;
}
static void con_key_input_nextprev_weapon(void *result, void *user_data)
{
INPUTSET *set = (INPUTSET *)user_data;
con_key_input_counter(result, set->variable);
set->controls->input_data.wanted_weapon = 0;
}
void CONTROLS::on_console_init()
{
// game commands
MACRO_REGISTER_COMMAND("+left", "", CFGFLAG_CLIENT, con_key_input_state, &input_direction_left, "Move left");
MACRO_REGISTER_COMMAND("+right", "", CFGFLAG_CLIENT, con_key_input_state, &input_direction_right, "Move right");
MACRO_REGISTER_COMMAND("+jump", "", CFGFLAG_CLIENT, con_key_input_state, &input_data.jump, "Jump");
MACRO_REGISTER_COMMAND("+hook", "", CFGFLAG_CLIENT, con_key_input_state, &input_data.hook, "Hook");
MACRO_REGISTER_COMMAND("+fire", "", CFGFLAG_CLIENT, con_key_input_counter, &input_data.fire, "Fire");
{ static INPUTSET set = {this, &input_data.wanted_weapon, 1}; MACRO_REGISTER_COMMAND("+weapon1", "", CFGFLAG_CLIENT, con_key_input_set, (void *)&set, "Switch to hammer"); }
{ static INPUTSET set = {this, &input_data.wanted_weapon, 2}; MACRO_REGISTER_COMMAND("+weapon2", "", CFGFLAG_CLIENT, con_key_input_set, (void *)&set, "Switch to gun"); }
{ static INPUTSET set = {this, &input_data.wanted_weapon, 3}; MACRO_REGISTER_COMMAND("+weapon3", "", CFGFLAG_CLIENT, con_key_input_set, (void *)&set, "Switch to shotgun"); }
{ static INPUTSET set = {this, &input_data.wanted_weapon, 4}; MACRO_REGISTER_COMMAND("+weapon4", "", CFGFLAG_CLIENT, con_key_input_set, (void *)&set, "Switch to grenade"); }
{ static INPUTSET set = {this, &input_data.wanted_weapon, 5}; MACRO_REGISTER_COMMAND("+weapon5", "", CFGFLAG_CLIENT, con_key_input_set, (void *)&set, "Switch to rifle"); }
{ static INPUTSET set = {this, &input_data.next_weapon, 0}; MACRO_REGISTER_COMMAND("+nextweapon", "", CFGFLAG_CLIENT, con_key_input_nextprev_weapon, (void *)&set, "Switch to next weapon"); }
{ static INPUTSET set = {this, &input_data.prev_weapon, 0}; MACRO_REGISTER_COMMAND("+prevweapon", "", CFGFLAG_CLIENT, con_key_input_nextprev_weapon, (void *)&set, "Switch to previous weapon"); }
}
void CONTROLS::on_message(int msg, void *rawmsg)
{
if(msg == NETMSGTYPE_SV_WEAPONPICKUP)
{
NETMSG_SV_WEAPONPICKUP *msg = (NETMSG_SV_WEAPONPICKUP *)rawmsg;
if(config.cl_autoswitch_weapons)
input_data.wanted_weapon = msg->weapon+1;
}
}
int CONTROLS::snapinput(int *data)
{
static NETOBJ_PLAYER_INPUT last_data = {0};
static int64 last_send_time = 0;
bool send = false;
// update player state
if(gameclient.chat->is_active())
input_data.player_state = PLAYERSTATE_CHATTING;
else if(gameclient.menus->is_active())
input_data.player_state = PLAYERSTATE_IN_MENU;
else
input_data.player_state = PLAYERSTATE_PLAYING;
if(last_data.player_state != input_data.player_state)
send = true;
last_data.player_state = input_data.player_state;
// we freeze the input if chat or menu is activated
if(input_data.player_state != PLAYERSTATE_PLAYING)
{
last_data.direction = 0;
last_data.hook = 0;
last_data.jump = 0;
input_data = last_data;
input_direction_left = 0;
input_direction_right = 0;
mem_copy(data, &input_data, sizeof(input_data));
// send once a second just to be sure
if(time_get() > last_send_time + time_freq())
send = true;
}
else
{
input_data.target_x = (int)mouse_pos.x;
input_data.target_y = (int)mouse_pos.y;
if(!input_data.target_x && !input_data.target_y)
input_data.target_y = 1;
// set direction
input_data.direction = 0;
if(input_direction_left && !input_direction_right)
input_data.direction = -1;
if(!input_direction_left && input_direction_right)
input_data.direction = 1;
// stress testing
if(config.dbg_stress)
{
float t = client_localtime();
mem_zero(&input_data, sizeof(input_data));
input_data.direction = ((int)t/2)&1;
input_data.jump = ((int)t);
input_data.fire = ((int)(t*10));
input_data.hook = ((int)(t*2))&1;
input_data.wanted_weapon = ((int)t)%NUM_WEAPONS;
input_data.target_x = (int)(sinf(t*3)*100.0f);
input_data.target_y = (int)(cosf(t*3)*100.0f);
}
// check if we need to send input
if(input_data.direction != last_data.direction) send = true;
else if(input_data.jump != last_data.jump) send = true;
else if(input_data.fire != last_data.fire) send = true;
else if(input_data.hook != last_data.hook) send = true;
else if(input_data.player_state != last_data.player_state) send = true;
else if(input_data.wanted_weapon != last_data.wanted_weapon) send = true;
else if(input_data.next_weapon != last_data.next_weapon) send = true;
else if(input_data.prev_weapon != last_data.prev_weapon) send = true;
// send at at least 10hz
if(time_get() > last_send_time + time_freq()/25)
send = true;
}
// copy and return size
last_data = input_data;
if(!send)
return 0;
last_send_time = time_get();
mem_copy(data, &input_data, sizeof(input_data));
return sizeof(input_data);
}
void CONTROLS::on_render()
{
// update target pos
if(!((gameclient.snap.gameobj && gameclient.snap.gameobj->paused) || gameclient.snap.spectate))
target_pos = gameclient.local_character_pos + mouse_pos;
}
bool CONTROLS::on_mousemove(float x, float y)
{
if(gameclient.snap.gameobj && gameclient.snap.gameobj->paused)
return false;
mouse_pos += vec2(x, y); // TODO: ugly
//
float camera_max_distance = 200.0f;
float follow_factor = config.cl_mouse_followfactor/100.0f;
float deadzone = config.cl_mouse_deadzone;
float mouse_max = min(camera_max_distance/follow_factor + deadzone, (float)config.cl_mouse_max_distance);
//vec2 camera_offset(0, 0);
if(gameclient.snap.spectate)
{
if(mouse_pos.x < 200.0f) mouse_pos.x = 200.0f;
if(mouse_pos.y < 200.0f) mouse_pos.y = 200.0f;
if(mouse_pos.x > col_width()*32-200.0f) mouse_pos.x = col_width()*32-200.0f;
if(mouse_pos.y > col_height()*32-200.0f) mouse_pos.y = col_height()*32-200.0f;
target_pos = mouse_pos;
}
else
{
float l = length(mouse_pos);
if(l > mouse_max)
{
mouse_pos = normalize(mouse_pos)*mouse_max;
l = mouse_max;
}
//float offset_amount = max(l-deadzone, 0.0f) * follow_factor;
//if(l > 0.0001f) // make sure that this isn't 0
//camera_offset = normalize(mouse_pos)*offset_amount;
}
return true;
}

View File

@@ -0,0 +1,22 @@
#include <base/vmath.hpp>
#include <game/client/component.hpp>
class CONTROLS : public COMPONENT
{
public:
vec2 mouse_pos;
vec2 target_pos;
NETOBJ_PLAYER_INPUT input_data;
int input_direction_left;
int input_direction_right;
CONTROLS();
virtual void on_render();
virtual void on_message(int msg, void *rawmsg);
virtual bool on_mousemove(float x, float y);
virtual void on_console_init();
int snapinput(int *data);
};

View File

@@ -0,0 +1,66 @@
#include <engine/e_client_interface.h>
#include <game/generated/g_protocol.hpp>
#include <game/generated/gc_data.hpp>
#include <game/gamecore.hpp> // get_angle
#include <game/client/ui.hpp>
#include <game/client/render.hpp>
#include "damageind.hpp"
DAMAGEIND::DAMAGEIND()
{
lastupdate = 0;
num_items = 0;
}
DAMAGEIND::ITEM *DAMAGEIND::create_i()
{
if (num_items < MAX_ITEMS)
{
ITEM *p = &items[num_items];
num_items++;
return p;
}
return 0;
}
void DAMAGEIND::destroy_i(DAMAGEIND::ITEM *i)
{
num_items--;
*i = items[num_items];
}
void DAMAGEIND::create(vec2 pos, vec2 dir)
{
ITEM *i = create_i();
if (i)
{
i->pos = pos;
i->life = 0.75f;
i->dir = dir*-1;
i->startangle = (( (float)rand()/(float)RAND_MAX) - 1.0f) * 2.0f * pi;
}
}
void DAMAGEIND::on_render()
{
gfx_texture_set(data->images[IMAGE_GAME].id);
gfx_quads_begin();
for(int i = 0; i < num_items;)
{
vec2 pos = mix(items[i].pos+items[i].dir*75.0f, items[i].pos, clamp((items[i].life-0.60f)/0.15f, 0.0f, 1.0f));
items[i].life -= client_frametime();
if(items[i].life < 0.0f)
destroy_i(&items[i]);
else
{
gfx_setcolor(1.0f,1.0f,1.0f, items[i].life/0.1f);
gfx_quads_setrotation(items[i].startangle + items[i].life * 2.0f);
select_sprite(SPRITE_STAR1);
draw_sprite(pos.x, pos.y, 48.0f);
i++;
}
}
gfx_quads_end();
}

View File

@@ -0,0 +1,31 @@
#include <base/vmath.hpp>
#include <game/client/component.hpp>
class DAMAGEIND : public COMPONENT
{
int64 lastupdate;
struct ITEM
{
vec2 pos;
vec2 dir;
float life;
float startangle;
};
enum
{
MAX_ITEMS=64,
};
ITEM items[MAX_ITEMS];
int num_items;
ITEM *create_i();
void destroy_i(ITEM *i);
public:
DAMAGEIND();
void create(vec2 pos, vec2 dir);
virtual void on_render();
};

View File

@@ -0,0 +1,112 @@
#include <memory.h> // memcmp
extern "C" {
#include <engine/e_config.h>
}
#include <engine/e_client_interface.h>
#include <game/generated/g_protocol.hpp>
#include <game/generated/gc_data.hpp>
#include <game/layers.hpp>
#include <game/client/gameclient.hpp>
#include <game/client/animstate.hpp>
#include <game/client/render.hpp>
//#include "controls.hpp"
//#include "camera.hpp"
#include "debughud.hpp"
void DEBUGHUD::render_netcorrections()
{
if(!config.debug || !gameclient.snap.local_character || !gameclient.snap.local_prev_character)
return;
gfx_mapscreen(0, 0, 300*gfx_screenaspect(), 300);
/*float speed = distance(vec2(netobjects.local_prev_character->x, netobjects.local_prev_character->y),
vec2(netobjects.local_character->x, netobjects.local_character->y));*/
float velspeed = length(vec2(gameclient.snap.local_character->vx/256.0f, gameclient.snap.local_character->vy/256.0f))*50;
float ramp = velocity_ramp(velspeed, gameclient.tuning.velramp_start, gameclient.tuning.velramp_range, gameclient.tuning.velramp_curvature);
char buf[512];
str_format(buf, sizeof(buf), "%.0f\n%.0f\n%.2f\n%d %s\n%d %d",
velspeed, velspeed*ramp, ramp,
netobj_num_corrections(), netobj_corrected_on(),
gameclient.snap.local_character->x,
gameclient.snap.local_character->y
);
gfx_text(0, 150, 50, 12, buf, -1);
}
void DEBUGHUD::render_tuning()
{
// render tuning debugging
if(!config.dbg_tuning)
return;
TUNING_PARAMS standard_tuning;
gfx_mapscreen(0, 0, 300*gfx_screenaspect(), 300);
float y = 50.0f;
int count = 0;
for(int i = 0; i < gameclient.tuning.num(); i++)
{
char buf[128];
float current, standard;
gameclient.tuning.get(i, &current);
standard_tuning.get(i, &standard);
if(standard == current)
gfx_text_color(1,1,1,1.0f);
else
gfx_text_color(1,0.25f,0.25f,1.0f);
float w;
float x = 5.0f;
str_format(buf, sizeof(buf), "%.2f", standard);
x += 20.0f;
w = gfx_text_width(0, 5, buf, -1);
gfx_text(0x0, x-w, y+count*6, 5, buf, -1);
str_format(buf, sizeof(buf), "%.2f", current);
x += 20.0f;
w = gfx_text_width(0, 5, buf, -1);
gfx_text(0x0, x-w, y+count*6, 5, buf, -1);
x += 5.0f;
gfx_text(0x0, x, y+count*6, 5, gameclient.tuning.names[i], -1);
count++;
}
y = y+count*6;
gfx_texture_set(-1);
gfx_blend_normal();
gfx_lines_begin();
float height = 50.0f;
float pv = 1;
for(int i = 0; i < 100; i++)
{
float speed = i/100.0f * 3000;
float ramp = velocity_ramp(speed, gameclient.tuning.velramp_start, gameclient.tuning.velramp_range, gameclient.tuning.velramp_curvature);
float rampedspeed = (speed * ramp)/1000.0f;
gfx_lines_draw((i-1)*2, y+height-pv*height, i*2, y+height-rampedspeed*height);
//gfx_lines_draw((i-1)*2, 200, i*2, 200);
pv = rampedspeed;
}
gfx_lines_end();
gfx_text_color(1,1,1,1);
}
void DEBUGHUD::on_render()
{
render_tuning();
render_netcorrections();
}

View File

@@ -0,0 +1,10 @@
#include <game/client/component.hpp>
class DEBUGHUD : public COMPONENT
{
void render_netcorrections();
void render_tuning();
public:
virtual void on_render();
};

View File

@@ -0,0 +1,261 @@
#include <engine/e_client_interface.h>
//#include <gc_client.hpp>
#include <game/generated/gc_data.hpp>
#include <game/client/components/particles.hpp>
#include <game/client/components/skins.hpp>
#include <game/client/components/flow.hpp>
#include <game/client/components/damageind.hpp>
#include <game/client/components/sounds.hpp>
#include <game/client/gameclient.hpp>
#include "effects.hpp"
inline vec2 random_dir() { return normalize(vec2(frandom()-0.5f, frandom()-0.5f)); }
EFFECTS::EFFECTS()
{
add_50hz = false;
add_100hz = false;
}
void EFFECTS::air_jump(vec2 pos)
{
PARTICLE p;
p.set_default();
p.spr = SPRITE_PART_AIRJUMP;
p.pos = pos + vec2(-6.0f, 16.0f);
p.vel = vec2(0, -200);
p.life_span = 0.5f;
p.start_size = 48.0f;
p.end_size = 0;
p.rot = frandom()*pi*2;
p.rotspeed = pi*2;
p.gravity = 500;
p.friction = 0.7f;
p.flow_affected = 0.0f;
gameclient.particles->add(PARTICLES::GROUP_GENERAL, &p);
p.pos = pos + vec2(6.0f, 16.0f);
gameclient.particles->add(PARTICLES::GROUP_GENERAL, &p);
gameclient.sounds->play(SOUNDS::CHN_WORLD, SOUND_PLAYER_AIRJUMP, 1.0f, pos);
}
void EFFECTS::damage_indicator(vec2 pos, vec2 dir)
{
gameclient.damageind->create(pos, dir);
}
void EFFECTS::powerupshine(vec2 pos, vec2 size)
{
if(!add_50hz)
return;
PARTICLE p;
p.set_default();
p.spr = SPRITE_PART_SLICE;
p.pos = pos + vec2((frandom()-0.5f)*size.x, (frandom()-0.5f)*size.y);
p.vel = vec2(0, 0);
p.life_span = 0.5f;
p.start_size = 16.0f;
p.end_size = 0;
p.rot = frandom()*pi*2;
p.rotspeed = pi*2;
p.gravity = 500;
p.friction = 0.9f;
p.flow_affected = 0.0f;
gameclient.particles->add(PARTICLES::GROUP_GENERAL, &p);
}
void EFFECTS::smoketrail(vec2 pos, vec2 vel)
{
if(!add_50hz)
return;
PARTICLE p;
p.set_default();
p.spr = SPRITE_PART_SMOKE;
p.pos = pos;
p.vel = vel + random_dir()*50.0f;
p.life_span = 0.5f + frandom()*0.5f;
p.start_size = 12.0f + frandom()*8;
p.end_size = 0;
p.friction = 0.7;
p.gravity = frandom()*-500.0f;
gameclient.particles->add(PARTICLES::GROUP_PROJECTILE_TRAIL, &p);
}
void EFFECTS::skidtrail(vec2 pos, vec2 vel)
{
if(!add_100hz)
return;
PARTICLE p;
p.set_default();
p.spr = SPRITE_PART_SMOKE;
p.pos = pos;
p.vel = vel + random_dir()*50.0f;
p.life_span = 0.5f + frandom()*0.5f;
p.start_size = 24.0f + frandom()*12;
p.end_size = 0;
p.friction = 0.7f;
p.gravity = frandom()*-500.0f;
p.color = vec4(0.75f,0.75f,0.75f,1.0f);
gameclient.particles->add(PARTICLES::GROUP_GENERAL, &p);
}
void EFFECTS::bullettrail(vec2 pos)
{
if(!add_100hz)
return;
PARTICLE p;
p.set_default();
p.spr = SPRITE_PART_BALL;
p.pos = pos;
p.life_span = 0.25f + frandom()*0.25f;
p.start_size = 8.0f;
p.end_size = 0;
p.friction = 0.7f;
gameclient.particles->add(PARTICLES::GROUP_PROJECTILE_TRAIL, &p);
}
void EFFECTS::playerspawn(vec2 pos)
{
for(int i = 0; i < 32; i++)
{
PARTICLE p;
p.set_default();
p.spr = SPRITE_PART_SHELL;
p.pos = pos;
p.vel = random_dir() * (pow(frandom(), 3)*600.0f);
p.life_span = 0.3f + frandom()*0.3f;
p.start_size = 64.0f + frandom()*32;
p.end_size = 0;
p.rot = frandom()*pi*2;
p.rotspeed = frandom();
p.gravity = frandom()*-400.0f;
p.friction = 0.7f;
p.color = vec4(0xb5/255.0f, 0x50/255.0f, 0xcb/255.0f, 1.0f);
gameclient.particles->add(PARTICLES::GROUP_GENERAL, &p);
}
gameclient.sounds->play(SOUNDS::CHN_WORLD, SOUND_PLAYER_SPAWN, 1.0f, pos);
}
void EFFECTS::playerdeath(vec2 pos, int cid)
{
vec3 blood_color(1.0f,1.0f,1.0f);
if(cid >= 0)
{
const SKINS::SKIN *s = gameclient.skins->get(gameclient.clients[cid].skin_id);
if(s)
blood_color = s->blood_color;
}
for(int i = 0; i < 64; i++)
{
PARTICLE p;
p.set_default();
p.spr = SPRITE_PART_SPLAT01 + (rand()%3);
p.pos = pos;
p.vel = random_dir() * ((frandom()+0.1f)*900.0f);
p.life_span = 0.3f + frandom()*0.3f;
p.start_size = 24.0f + frandom()*16;
p.end_size = 0;
p.rot = frandom()*pi*2;
p.rotspeed = (frandom()-0.5f) * pi;
p.gravity = 800.0f;
p.friction = 0.8f;
vec3 c = blood_color * (0.75f + frandom()*0.25f);
p.color = vec4(c.r, c.g, c.b, 0.75f);
gameclient.particles->add(PARTICLES::GROUP_GENERAL, &p);
}
}
void EFFECTS::explosion(vec2 pos)
{
// add to flow
for(int y = -8; y <= 8; y++)
for(int x = -8; x <= 8; x++)
{
if(x == 0 && y == 0)
continue;
float a = 1 - (length(vec2(x,y)) / length(vec2(8,8)));
gameclient.flow->add(pos+vec2(x,y)*16, normalize(vec2(x,y))*5000.0f*a, 10.0f);
}
// add the explosion
PARTICLE p;
p.set_default();
p.spr = SPRITE_PART_EXPL01;
p.pos = pos;
p.life_span = 0.4f;
p.start_size = 150.0f;
p.end_size = 0;
p.rot = frandom()*pi*2;
gameclient.particles->add(PARTICLES::GROUP_EXPLOSIONS, &p);
// add the smoke
for(int i = 0; i < 24; i++)
{
PARTICLE p;
p.set_default();
p.spr = SPRITE_PART_SMOKE;
p.pos = pos;
p.vel = random_dir() * ((1.0f + frandom()*0.2f) * 1000.0f);
p.life_span = 0.5f + frandom()*0.4f;
p.start_size = 32.0f + frandom()*8;
p.end_size = 0;
p.gravity = frandom()*-800.0f;
p.friction = 0.4f;
p.color = mix(vec4(0.75f,0.75f,0.75f,1.0f), vec4(0.5f,0.5f,0.5f,1.0f), frandom());
gameclient.particles->add(PARTICLES::GROUP_GENERAL, &p);
}
}
void EFFECTS::hammerhit(vec2 pos)
{
// add the explosion
PARTICLE p;
p.set_default();
p.spr = SPRITE_PART_EXPL01;
p.pos = pos;
p.life_span = 0.4f;
p.start_size = 150.0f;
p.end_size = 0;
p.rot = frandom()*pi*2;
gameclient.particles->add(PARTICLES::GROUP_EXPLOSIONS, &p);
gameclient.sounds->play(SOUNDS::CHN_WORLD, SOUND_HAMMER_HIT, 1.0f, pos);
}
void EFFECTS::on_render()
{
static int64 last_update_100hz = 0;
static int64 last_update_50hz = 0;
if(time_get()-last_update_100hz > time_freq()/100)
{
add_100hz = true;
last_update_100hz = time_get();
}
else
add_100hz = false;
if(time_get()-last_update_50hz > time_freq()/100)
{
add_50hz = true;
last_update_50hz = time_get();
}
else
add_50hz = false;
if(add_50hz)
gameclient.flow->update();
}

View File

@@ -0,0 +1,24 @@
#include <game/client/component.hpp>
class EFFECTS : public COMPONENT
{
bool add_50hz;
bool add_100hz;
public:
EFFECTS();
virtual void on_render();
void bullettrail(vec2 pos);
void smoketrail(vec2 pos, vec2 vel);
void skidtrail(vec2 pos, vec2 vel);
void explosion(vec2 pos);
void hammerhit(vec2 pos);
void air_jump(vec2 pos);
void damage_indicator(vec2 pos, vec2 dir);
void playerspawn(vec2 pos);
void playerdeath(vec2 pos, int cid);
void powerupshine(vec2 pos, vec2 size);
void update();
};

View File

@@ -0,0 +1,156 @@
#include <engine/e_client_interface.h>
#include <game/generated/g_protocol.hpp>
#include <game/generated/gc_data.hpp>
#include <game/gamecore.hpp> // get_angle
#include <game/client/gameclient.hpp>
#include <game/client/ui.hpp>
#include <game/client/render.hpp>
#include "emoticon.hpp"
EMOTICON::EMOTICON()
{
on_reset();
}
void EMOTICON::con_key_emoticon(void *result, void *user_data)
{
((EMOTICON *)user_data)->active = console_arg_int(result, 0) != 0;
}
void EMOTICON::con_emote(void *result, void *user_data)
{
((EMOTICON *)user_data)->emote(console_arg_int(result, 0));
}
void EMOTICON::on_console_init()
{
MACRO_REGISTER_COMMAND("+emote", "", CFGFLAG_CLIENT, con_key_emoticon, this, "Open emote selector");
MACRO_REGISTER_COMMAND("emote", "i", CFGFLAG_CLIENT, con_emote, this, "Use emote");
}
void EMOTICON::on_reset()
{
was_active = false;
active = false;
selected_emote = -1;
}
void EMOTICON::on_message(int msgtype, void *rawmsg)
{
if(msgtype == NETMSGTYPE_SV_EMOTICON)
{
NETMSG_SV_EMOTICON *msg = (NETMSG_SV_EMOTICON *)rawmsg;
gameclient.clients[msg->cid].emoticon = msg->emoticon;
gameclient.clients[msg->cid].emoticon_start = client_tick();
}
}
bool EMOTICON::on_mousemove(float x, float y)
{
if(!active)
return false;
selector_mouse += vec2(x,y);
return true;
}
void EMOTICON::draw_circle(float x, float y, float r, int segments)
{
float f_segments = (float)segments;
for(int i = 0; i < segments; i+=2)
{
float a1 = i/f_segments * 2*pi;
float a2 = (i+1)/f_segments * 2*pi;
float a3 = (i+2)/f_segments * 2*pi;
float ca1 = cosf(a1);
float ca2 = cosf(a2);
float ca3 = cosf(a3);
float sa1 = sinf(a1);
float sa2 = sinf(a2);
float sa3 = sinf(a3);
gfx_quads_draw_freeform(
x, y,
x+ca1*r, y+sa1*r,
x+ca3*r, y+sa3*r,
x+ca2*r, y+sa2*r);
}
}
void EMOTICON::on_render()
{
if(!active)
{
if(was_active && selected_emote != -1)
emote(selected_emote);
was_active = false;
return;
}
was_active = true;
int x, y;
inp_mouse_relative(&x, &y);
selector_mouse.x += x;
selector_mouse.y += y;
if (length(selector_mouse) > 140)
selector_mouse = normalize(selector_mouse) * 140;
float selected_angle = get_angle(selector_mouse) + 2*pi/24;
if (selected_angle < 0)
selected_angle += 2*pi;
if (length(selector_mouse) > 100)
selected_emote = (int)(selected_angle / (2*pi) * 12.0f);
RECT screen = *ui_screen();
gfx_mapscreen(screen.x, screen.y, screen.w, screen.h);
gfx_blend_normal();
gfx_texture_set(-1);
gfx_quads_begin();
gfx_setcolor(0,0,0,0.3f);
draw_circle(screen.w/2, screen.h/2, 160, 64);
gfx_quads_end();
gfx_texture_set(data->images[IMAGE_EMOTICONS].id);
gfx_quads_begin();
for (int i = 0; i < 12; i++)
{
float angle = 2*pi*i/12.0;
if (angle > pi)
angle -= 2*pi;
bool selected = selected_emote == i;
float size = selected ? 96 : 64;
float nudge_x = 120 * cos(angle);
float nudge_y = 120 * sin(angle);
select_sprite(SPRITE_OOP + i);
gfx_quads_draw(screen.w/2 + nudge_x, screen.h/2 + nudge_y, size, size);
}
gfx_quads_end();
gfx_texture_set(data->images[IMAGE_CURSOR].id);
gfx_quads_begin();
gfx_setcolor(1,1,1,1);
gfx_quads_drawTL(selector_mouse.x+screen.w/2,selector_mouse.y+screen.h/2,24,24);
gfx_quads_end();
}
void EMOTICON::emote(int emoticon)
{
NETMSG_CL_EMOTICON msg;
msg.emoticon = emoticon;
msg.pack(MSGFLAG_VITAL);
client_send_msg();
}

View File

@@ -0,0 +1,28 @@
#include <base/vmath.hpp>
#include <game/client/component.hpp>
class EMOTICON : public COMPONENT
{
void draw_circle(float x, float y, float r, int segments);
bool was_active;
bool active;
vec2 selector_mouse;
int selected_emote;
static void con_key_emoticon(void *result, void *user_data);
static void con_emote(void *result, void *user_data);
public:
EMOTICON();
virtual void on_reset();
virtual void on_console_init();
virtual void on_render();
virtual void on_message(int msgtype, void *rawmsg);
virtual bool on_mousemove(float x, float y);
void emote(int emoticon);
};

View File

@@ -0,0 +1,84 @@
#include <game/mapitems.hpp>
#include <game/layers.hpp>
#include "flow.hpp"
FLOW::FLOW()
{
cells = 0;
height = 0;
width = 0;
spacing = 16;
}
void FLOW::dbg_render()
{
if(!cells)
return;
gfx_texture_set(-1);
gfx_lines_begin();
for(int y = 0; y < height; y++)
for(int x = 0; x < width; x++)
{
vec2 pos(x*spacing, y*spacing);
vec2 vel = cells[y*width+x].vel * 0.01f;
gfx_lines_draw(pos.x, pos.y, pos.x+vel.x, pos.y+vel.y);
}
gfx_lines_end();
}
void FLOW::init()
{
if(cells)
{
mem_free(cells);
cells = 0;
}
MAPITEM_LAYER_TILEMAP *tilemap = layers_game_layer();
width = tilemap->width*32/spacing;
height = tilemap->height*32/spacing;
// allocate and clear
cells = (CELL *)mem_alloc(sizeof(CELL)*width*height, 1);
for(int y = 0; y < height; y++)
for(int x = 0; x < width; x++)
cells[y*width+x].vel = vec2(0.0f, 0.0f);
}
void FLOW::update()
{
if(!cells)
return;
for(int y = 0; y < height; y++)
for(int x = 0; x < width; x++)
cells[y*width+x].vel *= 0.85f;
}
vec2 FLOW::get(vec2 pos)
{
if(!cells)
return vec2(0,0);
int x = (int)(pos.x / spacing);
int y = (int)(pos.y / spacing);
if(x < 0 || y < 0 || x >= width || y >= height)
return vec2(0,0);
return cells[y*width+x].vel;
}
void FLOW::add(vec2 pos, vec2 vel, float size)
{
if(!cells)
return;
int x = (int)(pos.x / spacing);
int y = (int)(pos.y / spacing);
if(x < 0 || y < 0 || x >= width || y >= height)
return;
cells[y*width+x].vel += vel;
}

View File

@@ -0,0 +1,25 @@
#include <base/vmath.hpp>
#include <game/client/component.hpp>
class FLOW : public COMPONENT
{
struct CELL
{
vec2 vel;
};
CELL *cells;
int height;
int width;
int spacing;
void dbg_render();
void init();
public:
FLOW();
vec2 get(vec2 pos);
void add(vec2 pos, vec2 vel, float size);
void update();
};

View File

@@ -0,0 +1,307 @@
#include <memory.h> // memcmp
#include <engine/e_client_interface.h>
#include <game/generated/g_protocol.hpp>
#include <game/generated/gc_data.hpp>
#include <game/layers.hpp>
#include <game/client/gameclient.hpp>
#include <game/client/animstate.hpp>
#include <game/client/render.hpp>
#include "controls.hpp"
#include "camera.hpp"
#include "hud.hpp"
#include "voting.hpp"
#include "binds.hpp"
HUD::HUD()
{
}
void HUD::on_reset()
{
}
void HUD::render_goals()
{
// TODO: split this up into these:
// render_gametimer
// render_suddendeath
// render_scorehud
// render_warmuptimer
int gameflags = gameclient.snap.gameobj->flags;
float whole = 300*gfx_screenaspect();
float half = whole/2.0f;
gfx_mapscreen(0,0,300*gfx_screenaspect(),300);
if(!gameclient.snap.gameobj->sudden_death)
{
char buf[32];
int time = 0;
if(gameclient.snap.gameobj->time_limit)
{
time = gameclient.snap.gameobj->time_limit*60 - ((client_tick()-gameclient.snap.gameobj->round_start_tick)/client_tickspeed());
if(gameclient.snap.gameobj->game_over)
time = 0;
}
else
time = (client_tick()-gameclient.snap.gameobj->round_start_tick)/client_tickspeed();
str_format(buf, sizeof(buf), "%d:%02d", time /60, time %60);
float w = gfx_text_width(0, 16, buf, -1);
gfx_text(0, half-w/2, 2, 16, buf, -1);
}
if(gameclient.snap.gameobj->sudden_death)
{
const char *text = "Sudden Death";
float w = gfx_text_width(0, 16, text, -1);
gfx_text(0, half-w/2, 2, 16, text, -1);
}
// render small score hud
if(!(gameclient.snap.gameobj && gameclient.snap.gameobj->game_over) && (gameflags&GAMEFLAG_TEAMS))
{
for(int t = 0; t < 2; t++)
{
gfx_blend_normal();
gfx_texture_set(-1);
gfx_quads_begin();
if(t == 0)
gfx_setcolor(1,0,0,0.25f);
else
gfx_setcolor(0,0,1,0.25f);
draw_round_rect(whole-40, 300-40-15+t*20, 50, 18, 5.0f);
gfx_quads_end();
char buf[32];
str_format(buf, sizeof(buf), "%d", t?gameclient.snap.gameobj->teamscore_blue:gameclient.snap.gameobj->teamscore_red);
float w = gfx_text_width(0, 14, buf, -1);
if(gameflags&GAMEFLAG_FLAGS)
{
gfx_text(0, whole-20-w/2+5, 300-40-15+t*20, 14, buf, -1);
if(gameclient.snap.flags[t])
{
if(gameclient.snap.flags[t]->carried_by == -2 || (gameclient.snap.flags[t]->carried_by == -1 && ((client_tick()/10)&1)))
{
gfx_blend_normal();
gfx_texture_set(data->images[IMAGE_GAME].id);
gfx_quads_begin();
if(t == 0) select_sprite(SPRITE_FLAG_RED);
else select_sprite(SPRITE_FLAG_BLUE);
float size = 16;
gfx_quads_drawTL(whole-40+5, 300-40-15+t*20+1, size/2, size);
gfx_quads_end();
}
else if(gameclient.snap.flags[t]->carried_by >= 0)
{
int id = gameclient.snap.flags[t]->carried_by%MAX_CLIENTS;
const char *name = gameclient.clients[id].name;
float w = gfx_text_width(0, 10, name, -1);
gfx_text(0, whole-40-5-w, 300-40-15+t*20+2, 10, name, -1);
TEE_RENDER_INFO info = gameclient.clients[id].render_info;
info.size = 18.0f;
render_tee(ANIMSTATE::get_idle(), &info, EMOTE_NORMAL, vec2(1,0),
vec2(whole-40+10, 300-40-15+9+t*20+1));
}
}
}
else
gfx_text(0, whole-20-w/2, 300-40-15+t*20, 14, buf, -1);
}
}
// render warmup timer
if(gameclient.snap.gameobj->warmup)
{
char buf[256];
float w = gfx_text_width(0, 24, "Warmup", -1);
gfx_text(0, 150*gfx_screenaspect()+-w/2, 50, 24, "Warmup", -1);
int seconds = gameclient.snap.gameobj->warmup/SERVER_TICK_SPEED;
if(seconds < 5)
str_format(buf, sizeof(buf), "%d.%d", seconds, (gameclient.snap.gameobj->warmup*10/SERVER_TICK_SPEED)%10);
else
str_format(buf, sizeof(buf), "%d", seconds);
w = gfx_text_width(0, 24, buf, -1);
gfx_text(0, 150*gfx_screenaspect()+-w/2, 75, 24, buf, -1);
}
}
static void mapscreen_to_group(float center_x, float center_y, MAPITEM_GROUP *group)
{
float points[4];
mapscreen_to_world(center_x, center_y, group->parallax_x/100.0f, group->parallax_y/100.0f,
group->offset_x, group->offset_y, gfx_screenaspect(), 1.0f, points);
gfx_mapscreen(points[0], points[1], points[2], points[3]);
}
void HUD::render_fps()
{
if(config.cl_showfps)
{
char buf[512];
str_format(buf, sizeof(buf), "%d", (int)(1.0f/client_frametime()));
gfx_text(0, width-10-gfx_text_width(0,12,buf,-1), 5, 12, buf, -1);
}
}
void HUD::render_connectionwarning()
{
if(client_connection_problems())
{
const char *text = "Connection Problems...";
float w = gfx_text_width(0, 24, text, -1);
gfx_text(0, 150*gfx_screenaspect()-w/2, 50, 24, text, -1);
}
}
void HUD::render_teambalancewarning()
{
// render prompt about team-balance
bool flash = time_get()/(time_freq()/2)%2 == 0;
if (gameclient.snap.gameobj && (gameclient.snap.gameobj->flags&GAMEFLAG_TEAMS) != 0)
{
if (config.cl_warning_teambalance && abs(gameclient.snap.team_size[0]-gameclient.snap.team_size[1]) >= 2)
{
const char *text = "Please balance teams!";
if(flash)
gfx_text_color(1,1,0.5f,1);
else
gfx_text_color(0.7f,0.7f,0.2f,1.0f);
gfx_text(0x0, 5, 50, 6, text, -1);
gfx_text_color(1,1,1,1);
}
}
}
void HUD::render_voting()
{
if(!gameclient.voting->is_voting())
return;
gfx_texture_set(-1);
gfx_quads_begin();
gfx_setcolor(0,0,0,0.40f);
draw_round_rect(-10, 60-2, 100+10+4+5, 28, 5.0f);
gfx_quads_end();
gfx_text_color(1,1,1,1);
char buf[512];
gfx_text(0x0, 5, 60, 6, gameclient.voting->vote_description(), -1);
str_format(buf, sizeof(buf), "%ds left", gameclient.voting->seconds_left());
float tw = gfx_text_width(0x0, 6, buf, -1);
gfx_text(0x0, 5+100-tw, 60, 6, buf, -1);
RECT base = {5, 70, 100, 4};
gameclient.voting->render_bars(base, false);
const char *yes_key = gameclient.binds->get_key("vote yes");
const char *no_key = gameclient.binds->get_key("vote no");
str_format(buf, sizeof(buf), "%s - Vote Yes", yes_key);
base.y += base.h+1;
ui_do_label(&base, buf, 6.0f, -1);
str_format(buf, sizeof(buf), "Vote No - %s", no_key);
ui_do_label(&base, buf, 6.0f, 1);
}
void HUD::render_cursor()
{
if(!gameclient.snap.local_character)
return;
mapscreen_to_group(gameclient.camera->center.x, gameclient.camera->center.y, layers_game_group());
gfx_texture_set(data->images[IMAGE_GAME].id);
gfx_quads_begin();
// render cursor
select_sprite(data->weapons.id[gameclient.snap.local_character->weapon%NUM_WEAPONS].sprite_cursor);
float cursorsize = 64;
draw_sprite(gameclient.controls->target_pos.x, gameclient.controls->target_pos.y, cursorsize);
gfx_quads_end();
}
void HUD::render_healthandammo()
{
//mapscreen_to_group(gacenter_x, center_y, layers_game_group());
float x = 5;
float y = 5;
// render ammo count
// render gui stuff
gfx_texture_set(data->images[IMAGE_GAME].id);
gfx_mapscreen(0,0,width,300);
gfx_quads_begin();
// if weaponstage is active, put a "glow" around the stage ammo
select_sprite(data->weapons.id[gameclient.snap.local_character->weapon%NUM_WEAPONS].sprite_proj);
for (int i = 0; i < min(gameclient.snap.local_character->ammocount, 10); i++)
gfx_quads_drawTL(x+i*12,y+24,10,10);
gfx_quads_end();
gfx_quads_begin();
int h = 0;
// render health
select_sprite(SPRITE_HEALTH_FULL);
for(; h < gameclient.snap.local_character->health; h++)
gfx_quads_drawTL(x+h*12,y,10,10);
select_sprite(SPRITE_HEALTH_EMPTY);
for(; h < 10; h++)
gfx_quads_drawTL(x+h*12,y,10,10);
// render armor meter
h = 0;
select_sprite(SPRITE_ARMOR_FULL);
for(; h < gameclient.snap.local_character->armor; h++)
gfx_quads_drawTL(x+h*12,y+12,10,10);
select_sprite(SPRITE_ARMOR_EMPTY);
for(; h < 10; h++)
gfx_quads_drawTL(x+h*12,y+12,10,10);
gfx_quads_end();
}
void HUD::on_render()
{
if(!gameclient.snap.gameobj)
return;
width = 300*gfx_screenaspect();
bool spectate = false;
if(gameclient.snap.local_info && gameclient.snap.local_info->team == -1)
spectate = true;
if(gameclient.snap.local_character && !spectate && !(gameclient.snap.gameobj && gameclient.snap.gameobj->game_over))
render_healthandammo();
render_goals();
render_fps();
if(client_state() != CLIENTSTATE_DEMOPLAYBACK)
render_connectionwarning();
render_teambalancewarning();
render_voting();
render_cursor();
}

View File

@@ -0,0 +1,22 @@
#include <game/client/component.hpp>
class HUD : public COMPONENT
{
float width;
void render_cursor();
void render_fps();
void render_connectionwarning();
void render_teambalancewarning();
void render_voting();
void render_healthandammo();
void render_goals();
public:
HUD();
virtual void on_reset();
virtual void on_render();
};

View File

@@ -0,0 +1,258 @@
#include <engine/e_client_interface.h>
#include <game/generated/g_protocol.hpp>
#include <game/generated/gc_data.hpp>
#include <game/gamecore.hpp> // get_angle
#include <game/client/gameclient.hpp>
#include <game/client/ui.hpp>
#include <game/client/render.hpp>
#include <game/client/components/flow.hpp>
#include <game/client/components/effects.hpp>
#include "items.hpp"
void ITEMS::render_projectile(const NETOBJ_PROJECTILE *current, int itemid)
{
// get positions
float curvature = 0;
float speed = 0;
if(current->type == WEAPON_GRENADE)
{
curvature = gameclient.tuning.grenade_curvature;
speed = gameclient.tuning.grenade_speed;
}
else if(current->type == WEAPON_SHOTGUN)
{
curvature = gameclient.tuning.shotgun_curvature;
speed = gameclient.tuning.shotgun_speed;
}
else if(current->type == WEAPON_GUN)
{
curvature = gameclient.tuning.gun_curvature;
speed = gameclient.tuning.gun_speed;
}
float ct = (client_prevtick()-current->start_tick)/(float)SERVER_TICK_SPEED + client_ticktime();
if(ct < 0)
return; // projectile havn't been shot yet
vec2 startpos(current->x, current->y);
vec2 startvel(current->vx/100.0f, current->vy/100.0f);
vec2 pos = calc_pos(startpos, startvel, curvature, speed, ct);
vec2 prevpos = calc_pos(startpos, startvel, curvature, speed, ct-0.001f);
gfx_texture_set(data->images[IMAGE_GAME].id);
gfx_quads_begin();
select_sprite(data->weapons.id[clamp(current->type, 0, NUM_WEAPONS-1)].sprite_proj);
vec2 vel = pos-prevpos;
//vec2 pos = mix(vec2(prev->x, prev->y), vec2(current->x, current->y), client_intratick());
// add particle for this projectile
if(current->type == WEAPON_GRENADE)
{
gameclient.effects->smoketrail(pos, vel*-1);
gameclient.flow->add(pos, vel*1000*client_frametime(), 10.0f);
gfx_quads_setrotation(client_localtime()*pi*2*2 + itemid);
}
else
{
gameclient.effects->bullettrail(pos);
gameclient.flow->add(pos, vel*1000*client_frametime(), 10.0f);
if(length(vel) > 0.00001f)
gfx_quads_setrotation(get_angle(vel));
else
gfx_quads_setrotation(0);
}
gfx_quads_draw(pos.x, pos.y, 32, 32);
gfx_quads_setrotation(0);
gfx_quads_end();
}
void ITEMS::render_pickup(const NETOBJ_PICKUP *prev, const NETOBJ_PICKUP *current)
{
gfx_texture_set(data->images[IMAGE_GAME].id);
gfx_quads_begin();
vec2 pos = mix(vec2(prev->x, prev->y), vec2(current->x, current->y), client_intratick());
float angle = 0.0f;
float size = 64.0f;
if (current->type == POWERUP_WEAPON)
{
angle = 0; //-pi/6;//-0.25f * pi * 2.0f;
select_sprite(data->weapons.id[clamp(current->subtype, 0, NUM_WEAPONS-1)].sprite_body);
size = data->weapons.id[clamp(current->subtype, 0, NUM_WEAPONS-1)].visual_size;
}
else
{
const int c[] = {
SPRITE_PICKUP_HEALTH,
SPRITE_PICKUP_ARMOR,
SPRITE_PICKUP_WEAPON,
SPRITE_PICKUP_NINJA
};
select_sprite(c[current->type]);
if(c[current->type] == SPRITE_PICKUP_NINJA)
{
gameclient.effects->powerupshine(pos, vec2(96,18));
size *= 2.0f;
pos.x += 10.0f;
}
}
gfx_quads_setrotation(angle);
float offset = pos.y/32.0f + pos.x/32.0f;
pos.x += cosf(client_localtime()*2.0f+offset)*2.5f;
pos.y += sinf(client_localtime()*2.0f+offset)*2.5f;
draw_sprite(pos.x, pos.y, size);
gfx_quads_end();
}
void ITEMS::render_flag(const NETOBJ_FLAG *prev, const NETOBJ_FLAG *current)
{
float angle = 0.0f;
float size = 42.0f;
gfx_blend_normal();
gfx_texture_set(data->images[IMAGE_GAME].id);
gfx_quads_begin();
if(current->team == 0) // red team
select_sprite(SPRITE_FLAG_RED);
else
select_sprite(SPRITE_FLAG_BLUE);
gfx_quads_setrotation(angle);
vec2 pos = mix(vec2(prev->x, prev->y), vec2(current->x, current->y), client_intratick());
// make sure that the flag isn't interpolated between capture and return
if(prev->carried_by != current->carried_by)
pos = vec2(current->x, current->y);
// make sure to use predicted position if we are the carrier
if(gameclient.snap.local_info && current->carried_by == gameclient.snap.local_info->cid)
pos = gameclient.local_character_pos;
gfx_quads_draw(pos.x, pos.y-size*0.75f, size, size*2);
gfx_quads_end();
}
void ITEMS::render_laser(const struct NETOBJ_LASER *current)
{
vec2 pos = vec2(current->x, current->y);
vec2 from = vec2(current->from_x, current->from_y);
vec2 dir = normalize(pos-from);
float ticks = client_tick() + client_intratick() - current->start_tick;
float ms = (ticks/50.0f) * 1000.0f;
float a = ms / gameclient.tuning.laser_bounce_delay;
a = clamp(a, 0.0f, 1.0f);
float ia = 1-a;
vec2 out, border;
gfx_blend_normal();
gfx_texture_set(-1);
gfx_quads_begin();
//vec4 inner_color(0.15f,0.35f,0.75f,1.0f);
//vec4 outer_color(0.65f,0.85f,1.0f,1.0f);
// do outline
vec4 outer_color(0.075f,0.075f,0.25f,1.0f);
gfx_setcolor(outer_color.r,outer_color.g,outer_color.b,1.0f);
out = vec2(dir.y, -dir.x) * (7.0f*ia);
gfx_quads_draw_freeform(
from.x-out.x, from.y-out.y,
from.x+out.x, from.y+out.y,
pos.x-out.x, pos.y-out.y,
pos.x+out.x, pos.y+out.y
);
// do inner
vec4 inner_color(0.5f,0.5f,1.0f,1.0f);
out = vec2(dir.y, -dir.x) * (5.0f*ia);
gfx_setcolor(inner_color.r, inner_color.g, inner_color.b, 1.0f); // center
gfx_quads_draw_freeform(
from.x-out.x, from.y-out.y,
from.x+out.x, from.y+out.y,
pos.x-out.x, pos.y-out.y,
pos.x+out.x, pos.y+out.y
);
gfx_quads_end();
// render head
{
gfx_blend_normal();
gfx_texture_set(data->images[IMAGE_PARTICLES].id);
gfx_quads_begin();
int sprites[] = {SPRITE_PART_SPLAT01, SPRITE_PART_SPLAT02, SPRITE_PART_SPLAT03};
select_sprite(sprites[client_tick()%3]);
gfx_quads_setrotation(client_tick());
gfx_setcolor(outer_color.r,outer_color.g,outer_color.b,1.0f);
gfx_quads_draw(pos.x, pos.y, 24,24);
gfx_setcolor(inner_color.r, inner_color.g, inner_color.b, 1.0f);
gfx_quads_draw(pos.x, pos.y, 20,20);
gfx_quads_end();
}
gfx_blend_normal();
}
void ITEMS::on_render()
{
int num = snap_num_items(SNAP_CURRENT);
for(int i = 0; i < num; i++)
{
SNAP_ITEM item;
const void *data = snap_get_item(SNAP_CURRENT, i, &item);
if(item.type == NETOBJTYPE_PROJECTILE)
{
render_projectile((const NETOBJ_PROJECTILE *)data, item.id);
}
else if(item.type == NETOBJTYPE_PICKUP)
{
const void *prev = snap_find_item(SNAP_PREV, item.type, item.id);
if(prev)
render_pickup((const NETOBJ_PICKUP *)prev, (const NETOBJ_PICKUP *)data);
}
else if(item.type == NETOBJTYPE_LASER)
{
render_laser((const NETOBJ_LASER *)data);
}
else if(item.type == NETOBJTYPE_FLAG)
{
const void *prev = snap_find_item(SNAP_PREV, item.type, item.id);
if (prev)
render_flag((const NETOBJ_FLAG *)prev, (const NETOBJ_FLAG *)data);
}
}
// render extra projectiles
/*
for(int i = 0; i < extraproj_num; i++)
{
if(extraproj_projectiles[i].start_tick < client_tick())
{
extraproj_projectiles[i] = extraproj_projectiles[extraproj_num-1];
extraproj_num--;
}
else
render_projectile(&extraproj_projectiles[i], 0);
}*/
}

View File

@@ -0,0 +1,13 @@
#include <game/client/component.hpp>
class ITEMS : public COMPONENT
{
void render_projectile(const NETOBJ_PROJECTILE *current, int itemid);
void render_pickup(const NETOBJ_PICKUP *prev, const NETOBJ_PICKUP *current);
void render_flag(const NETOBJ_FLAG *prev, const NETOBJ_FLAG *current);
void render_laser(const struct NETOBJ_LASER *current);
public:
virtual void on_render();
};

View File

@@ -0,0 +1,128 @@
#include <engine/e_client_interface.h>
#include <game/generated/g_protocol.hpp>
#include <game/generated/gc_data.hpp>
#include <game/client/gameclient.hpp>
#include <game/client/animstate.hpp>
#include "killmessages.hpp"
void KILLMESSAGES::on_reset()
{
killmsg_current = 0;
for(int i = 0; i < killmsg_max; i++)
killmsgs[i].tick = -100000;
}
void KILLMESSAGES::on_message(int msgtype, void *rawmsg)
{
if(msgtype == NETMSGTYPE_SV_KILLMSG)
{
NETMSG_SV_KILLMSG *msg = (NETMSG_SV_KILLMSG *)rawmsg;
// unpack messages
KILLMSG kill;
kill.killer = msg->killer;
kill.victim = msg->victim;
kill.weapon = msg->weapon;
kill.mode_special = msg->mode_special;
kill.tick = client_tick();
// add the message
killmsg_current = (killmsg_current+1)%killmsg_max;
killmsgs[killmsg_current] = kill;
}
}
void KILLMESSAGES::on_render()
{
float width = 400*3.0f*gfx_screenaspect();
float height = 400*3.0f;
gfx_mapscreen(0, 0, width*1.5f, height*1.5f);
float startx = width*1.5f-10.0f;
float y = 20.0f;
for(int i = 0; i < killmsg_max; i++)
{
int r = (killmsg_current+i+1)%killmsg_max;
if(client_tick() > killmsgs[r].tick+50*10)
continue;
float font_size = 36.0f;
float killername_w = gfx_text_width(0, font_size, gameclient.clients[killmsgs[r].killer].name, -1);
float victimname_w = gfx_text_width(0, font_size, gameclient.clients[killmsgs[r].victim].name, -1);
float x = startx;
// render victim name
x -= victimname_w;
gfx_text(0, x, y, font_size, gameclient.clients[killmsgs[r].victim].name, -1);
// render victim tee
x -= 24.0f;
if(gameclient.snap.gameobj && gameclient.snap.gameobj->flags&GAMEFLAG_FLAGS)
{
if(killmsgs[r].mode_special&1)
{
gfx_blend_normal();
gfx_texture_set(data->images[IMAGE_GAME].id);
gfx_quads_begin();
if(gameclient.clients[killmsgs[r].victim].team == 0) select_sprite(SPRITE_FLAG_BLUE);
else select_sprite(SPRITE_FLAG_RED);
float size = 56.0f;
gfx_quads_drawTL(x, y-16, size/2, size);
gfx_quads_end();
}
}
render_tee(ANIMSTATE::get_idle(), &gameclient.clients[killmsgs[r].victim].render_info, EMOTE_PAIN, vec2(-1,0), vec2(x, y+28));
x -= 32.0f;
// render weapon
x -= 44.0f;
if (killmsgs[r].weapon >= 0)
{
gfx_texture_set(data->images[IMAGE_GAME].id);
gfx_quads_begin();
select_sprite(data->weapons.id[killmsgs[r].weapon].sprite_body);
draw_sprite(x, y+28, 96);
gfx_quads_end();
}
x -= 52.0f;
if(killmsgs[r].victim != killmsgs[r].killer)
{
if(gameclient.snap.gameobj && gameclient.snap.gameobj->flags&GAMEFLAG_FLAGS)
{
if(killmsgs[r].mode_special&2)
{
gfx_blend_normal();
gfx_texture_set(data->images[IMAGE_GAME].id);
gfx_quads_begin();
if(gameclient.clients[killmsgs[r].killer].team == 0) select_sprite(SPRITE_FLAG_BLUE, SPRITE_FLAG_FLIP_X);
else select_sprite(SPRITE_FLAG_RED, SPRITE_FLAG_FLIP_X);
float size = 56.0f;
gfx_quads_drawTL(x-56, y-16, size/2, size);
gfx_quads_end();
}
}
// render killer tee
x -= 24.0f;
render_tee(ANIMSTATE::get_idle(), &gameclient.clients[killmsgs[r].killer].render_info, EMOTE_ANGRY, vec2(1,0), vec2(x, y+28));
x -= 32.0f;
// render killer name
x -= killername_w;
gfx_text(0, x, y, font_size, gameclient.clients[killmsgs[r].killer].name, -1);
}
y += 44;
}
}

View File

@@ -0,0 +1,24 @@
#include <game/client/component.hpp>
class KILLMESSAGES : public COMPONENT
{
public:
// kill messages
struct KILLMSG
{
int weapon;
int victim;
int killer;
int mode_special; // for CTF, if the guy is carrying a flag for example
int tick;
};
static const int killmsg_max = 5;
KILLMSG killmsgs[killmsg_max];
int killmsg_current;
virtual void on_reset();
virtual void on_render();
virtual void on_message(int msgtype, void *rawmsg);
};

View File

@@ -0,0 +1,45 @@
#include <game/client/component.hpp>
#include <game/mapitems.hpp>
#include "mapimages.hpp"
MAPIMAGES::MAPIMAGES()
{
count = 0;
}
void MAPIMAGES::on_mapload()
{
// unload all textures
for(int i = 0; i < count; i++)
{
gfx_unload_texture(textures[i]);
textures[i] = -1;
}
count = 0;
int start;
map_get_type(MAPITEMTYPE_IMAGE, &start, &count);
// load new textures
for(int i = 0; i < count; i++)
{
textures[i] = 0;
MAPITEM_IMAGE *img = (MAPITEM_IMAGE *)map_get_item(start+i, 0, 0);
if(img->external)
{
char buf[256];
char *name = (char *)map_get_data(img->image_name);
str_format(buf, sizeof(buf), "mapres/%s.png", name);
textures[i] = gfx_load_texture(buf, IMG_AUTO, 0);
}
else
{
void *data = map_get_data(img->image_data);
textures[i] = gfx_load_texture_raw(img->width, img->height, IMG_RGBA, data, IMG_RGBA, 0);
map_unload_data(img->image_data);
}
}
}

View File

@@ -0,0 +1,15 @@
#include <game/client/component.hpp>
class MAPIMAGES : public COMPONENT
{
int textures[64];
int count;
public:
MAPIMAGES();
int get(int index) const { return textures[index]; }
int num() const { return count; }
virtual void on_mapload();
};

View File

@@ -0,0 +1,163 @@
#include <game/layers.hpp>
#include <game/client/gameclient.hpp>
#include <game/client/component.hpp>
#include <game/client/render.hpp>
#include <game/client/components/camera.hpp>
#include <game/client/components/mapimages.hpp>
#include "maplayers.hpp"
MAPLAYERS::MAPLAYERS(int t)
{
type = t;
}
static void mapscreen_to_group(float center_x, float center_y, MAPITEM_GROUP *group)
{
float points[4];
mapscreen_to_world(center_x, center_y, group->parallax_x/100.0f, group->parallax_y/100.0f,
group->offset_x, group->offset_y, gfx_screenaspect(), 1.0f, points);
gfx_mapscreen(points[0], points[1], points[2], points[3]);
}
static void envelope_eval(float time_offset, int env, float *channels)
{
channels[0] = 0;
channels[1] = 0;
channels[2] = 0;
channels[3] = 0;
ENVPOINT *points;
{
int start, num;
map_get_type(MAPITEMTYPE_ENVPOINTS, &start, &num);
if(num)
points = (ENVPOINT *)map_get_item(start, 0, 0);
}
int start, num;
map_get_type(MAPITEMTYPE_ENVELOPE, &start, &num);
if(env >= num)
return;
MAPITEM_ENVELOPE *item = (MAPITEM_ENVELOPE *)map_get_item(start+env, 0, 0);
render_eval_envelope(points+item->start_point, item->num_points, 4, client_localtime()+time_offset, channels);
}
void MAPLAYERS::on_render()
{
if(client_state() != CLIENTSTATE_ONLINE && client_state() != CLIENTSTATE_DEMOPLAYBACK)
return;
RECT screen;
gfx_getscreen(&screen.x, &screen.y, &screen.w, &screen.h);
vec2 center = gameclient.camera->center;
//float center_x = gameclient.camera->center.x;
//float center_y = gameclient.camera->center.y;
bool passed_gamelayer = false;
for(int g = 0; g < layers_num_groups(); g++)
{
MAPITEM_GROUP *group = layers_get_group(g);
if(!config.gfx_noclip && group->version >= 2 && group->use_clipping)
{
// set clipping
float points[4];
mapscreen_to_group(center.x, center.y, layers_game_group());
gfx_getscreen(&points[0], &points[1], &points[2], &points[3]);
float x0 = (group->clip_x - points[0]) / (points[2]-points[0]);
float y0 = (group->clip_y - points[1]) / (points[3]-points[1]);
float x1 = ((group->clip_x+group->clip_w) - points[0]) / (points[2]-points[0]);
float y1 = ((group->clip_y+group->clip_h) - points[1]) / (points[3]-points[1]);
gfx_clip_enable((int)(x0*gfx_screenwidth()), (int)(y0*gfx_screenheight()),
(int)((x1-x0)*gfx_screenwidth()), (int)((y1-y0)*gfx_screenheight()));
}
mapscreen_to_group(center.x, center.y, group);
for(int l = 0; l < group->num_layers; l++)
{
MAPITEM_LAYER *layer = layers_get_layer(group->start_layer+l);
bool render = false;
bool is_game_layer = false;
if(layer == (MAPITEM_LAYER*)layers_game_layer())
{
is_game_layer = true;
passed_gamelayer = 1;
}
// skip rendering if detail layers if not wanted
if(layer->flags&LAYERFLAG_DETAIL && !config.gfx_high_detail && !is_game_layer)
continue;
if(type == -1)
render = true;
else if(type == 0)
{
if(passed_gamelayer)
return;
render = true;
}
else
{
if(passed_gamelayer && !is_game_layer)
render = true;
}
if(render && !is_game_layer)
{
//layershot_begin();
if(layer->type == LAYERTYPE_TILES)
{
MAPITEM_LAYER_TILEMAP *tmap = (MAPITEM_LAYER_TILEMAP *)layer;
if(tmap->image == -1)
gfx_texture_set(-1);
else
gfx_texture_set(gameclient.mapimages->get(tmap->image));
TILE *tiles = (TILE *)map_get_data(tmap->data);
gfx_blend_none();
render_tilemap(tiles, tmap->width, tmap->height, 32.0f, vec4(1,1,1,1), TILERENDERFLAG_EXTEND|LAYERRENDERFLAG_OPAQUE);
gfx_blend_normal();
render_tilemap(tiles, tmap->width, tmap->height, 32.0f, vec4(1,1,1,1), TILERENDERFLAG_EXTEND|LAYERRENDERFLAG_TRANSPARENT);
}
else if(layer->type == LAYERTYPE_QUADS)
{
MAPITEM_LAYER_QUADS *qlayer = (MAPITEM_LAYER_QUADS *)layer;
if(qlayer->image == -1)
gfx_texture_set(-1);
else
gfx_texture_set(gameclient.mapimages->get(qlayer->image));
QUAD *quads = (QUAD *)map_get_data_swapped(qlayer->data);
gfx_blend_none();
render_quads(quads, qlayer->num_quads, envelope_eval, LAYERRENDERFLAG_OPAQUE);
gfx_blend_normal();
render_quads(quads, qlayer->num_quads, envelope_eval, LAYERRENDERFLAG_TRANSPARENT);
}
//layershot_end();
}
}
if(!config.gfx_noclip)
gfx_clip_disable();
}
if(!config.gfx_noclip)
gfx_clip_disable();
// reset the screen like it was before
gfx_mapscreen(screen.x, screen.y, screen.w, screen.h);
}

View File

@@ -0,0 +1,16 @@
#include <game/client/component.hpp>
class MAPLAYERS : public COMPONENT
{
int type;
public:
enum
{
TYPE_BACKGROUND=0,
TYPE_FOREGROUND,
};
MAPLAYERS(int type);
virtual void on_render();
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,183 @@
#include <base/vmath.hpp>
#include <game/client/component.hpp>
#include <game/client/ui.hpp>
// compnent to fetch keypresses, override all other input
class MENUS_KEYBINDER : public COMPONENT
{
public:
bool take_key;
bool got_key;
INPUT_EVENT key;
MENUS_KEYBINDER();
virtual bool on_input(INPUT_EVENT e);
};
class MENUS : public COMPONENT
{
static vec4 gui_color;
static vec4 color_tabbar_inactive_outgame;
static vec4 color_tabbar_active_outgame;
static vec4 color_tabbar_inactive_ingame;
static vec4 color_tabbar_active_ingame;
static vec4 color_tabbar_inactive;
static vec4 color_tabbar_active;
static vec4 button_color_mul(const void *id);
static void ui_draw_demoplayer_button(const void *id, const char *text, int checked, const RECT *r, const void *extra);
static void ui_draw_browse_icon(int what, const RECT *r);
static void ui_draw_menu_button(const void *id, const char *text, int checked, const RECT *r, const void *extra);
static void ui_draw_keyselect_button(const void *id, const char *text, int checked, const RECT *r, const void *extra);
static void ui_draw_menu_tab_button(const void *id, const char *text, int checked, const RECT *r, const void *extra);
static void ui_draw_settings_tab_button(const void *id, const char *text, int checked, const RECT *r, const void *extra);
static void ui_draw_grid_header(const void *id, const char *text, int checked, const RECT *r, const void *extra);
static void ui_draw_list_row(const void *id, const char *text, int checked, const RECT *r, const void *extra);
static void ui_draw_checkbox_common(const void *id, const char *text, const char *boxtext, const RECT *r);
static void ui_draw_checkbox(const void *id, const char *text, int checked, const RECT *r, const void *extra);
static void ui_draw_checkbox_number(const void *id, const char *text, int checked, const RECT *r, const void *extra);
static int ui_do_edit_box(void *id, const RECT *rect, char *str, int str_size, float font_size, bool hidden=false);
static float ui_do_scrollbar_v(const void *id, const RECT *rect, float current);
static float ui_do_scrollbar_h(const void *id, const RECT *rect, float current);
static int ui_do_key_reader(void *id, const RECT *rect, int key);
static void ui_do_getbuttons(int start, int stop, RECT view);
struct LISTBOXITEM
{
int visible;
int selected;
RECT rect;
};
static void ui_do_listbox_start(void *id, const RECT *rect, float row_height, const char *title, int num_items, int selected_index);
static LISTBOXITEM ui_do_listbox_nextitem(void *id);
static int ui_do_listbox_end();
//static void demolist_listdir_callback(const char *name, int is_dir, void *user);
//static void demolist_list_callback(const RECT *rect, int index, void *user);
enum
{
POPUP_NONE=0,
POPUP_FIRST_LAUNCH,
POPUP_CONNECTING,
POPUP_DISCONNECTED,
POPUP_PURE,
POPUP_PASSWORD,
POPUP_QUIT,
};
enum
{
PAGE_NEWS=1,
PAGE_GAME,
PAGE_SERVER_INFO,
PAGE_CALLVOTE,
PAGE_INTERNET,
PAGE_LAN,
PAGE_FAVORITES,
PAGE_DEMOS,
PAGE_SETTINGS,
PAGE_SYSTEM,
};
int game_page;
int popup;
int active_page;
bool menu_active;
vec2 mouse_pos;
int64 last_input;
// TODO: this is a bit ugly but.. well.. yeah
enum { MAX_INPUTEVENTS = 32 };
static INPUT_EVENT inputevents[MAX_INPUTEVENTS];
static int num_inputevents;
// some settings
static float button_height;
static float listheader_height;
static float fontmod_height;
// for graphic settings
bool need_restart;
bool need_sendinfo;
//
bool escape_pressed;
bool enter_pressed;
// for call vote
int callvote_selectedoption;
int callvote_selectedplayer;
// demo
struct DEMOITEM
{
char filename[512];
char name[256];
};
DEMOITEM *demos;
int num_demos;
void demolist_populate();
static void demolist_count_callback(const char *name, int is_dir, void *user);
static void demolist_fetch_callback(const char *name, int is_dir, void *user);
// found in menus.cpp
int render();
void render_background();
//void render_loading(float percent);
int render_menubar(RECT r);
void render_news(RECT main_view);
// found in menus_demo.cpp
void render_demoplayer(RECT main_view);
void render_demolist(RECT main_view);
// found in menus_ingame.cpp
void render_game(RECT main_view);
void render_serverinfo(RECT main_view);
void render_servercontrol(RECT main_view);
void render_servercontrol_kick(RECT main_view);
void render_servercontrol_server(RECT main_view);
// found in menus_browser.cpp
int selected_index;
void render_serverbrowser_serverlist(RECT view);
void render_serverbrowser_serverdetail(RECT view);
void render_serverbrowser_filters(RECT view);
void render_serverbrowser(RECT main_view);
// found in menus_settings.cpp
void render_settings_player(RECT main_view);
void render_settings_controls(RECT main_view);
void render_settings_graphics(RECT main_view);
void render_settings_sound(RECT main_view);
void render_settings(RECT main_view);
void set_active(bool active);
public:
static MENUS_KEYBINDER binder;
MENUS();
void render_loading(float percent);
bool is_active() const { return menu_active; }
void init();
virtual void on_statechange(int new_state, int old_state);
virtual void on_reset();
virtual void on_render();
virtual bool on_input(INPUT_EVENT e);
virtual bool on_mousemove(float x, float y);
};

View File

@@ -0,0 +1,669 @@
#include <string.h> // strcmp, strlen, strncpy
#include <stdlib.h> // atoi
#include <engine/e_client_interface.h>
extern "C" {
#include <engine/client/ec_font.h>
}
#include <game/generated/g_protocol.hpp>
#include <game/generated/gc_data.hpp>
#include <game/client/ui.hpp>
#include <game/client/render.hpp>
#include "menus.hpp"
#include <game/version.hpp>
void MENUS::render_serverbrowser_serverlist(RECT view)
{
RECT headers;
RECT status;
ui_hsplit_t(&view, listheader_height, &headers, &view);
ui_hsplit_b(&view, 28.0f, &view, &status);
// split of the scrollbar
ui_draw_rect(&headers, vec4(1,1,1,0.25f), CORNER_T, 5.0f);
ui_vsplit_r(&headers, 20.0f, &headers, 0);
struct column
{
int id;
int sort;
const char *caption;
int direction;
float width;
int flags;
RECT rect;
RECT spacer;
};
enum
{
FIXED=1,
SPACER=2,
COL_FLAG_LOCK=0,
COL_FLAG_PURE,
COL_FLAG_FAV,
COL_NAME,
COL_GAMETYPE,
COL_MAP,
COL_PLAYERS,
COL_PING,
COL_PROGRESS,
COL_VERSION,
};
static column cols[] = {
{-1, -1, " ", -1, 2.0f, 0, {0}, {0}},
{COL_FLAG_LOCK, -1, " ", -1, 14.0f, 0, {0}, {0}},
{COL_FLAG_PURE, -1, " ", -1, 14.0f, 0, {0}, {0}},
{COL_FLAG_FAV, -1, " ", -1, 14.0f, 0, {0}, {0}},
{COL_NAME, BROWSESORT_NAME, "Name", 0, 300.0f, 0, {0}, {0}},
{COL_GAMETYPE, BROWSESORT_GAMETYPE, "Type", 1, 50.0f, 0, {0}, {0}},
{COL_MAP, BROWSESORT_MAP, "Map", 1, 100.0f, 0, {0}, {0}},
{COL_PLAYERS, BROWSESORT_NUMPLAYERS, "Players", 1, 60.0f, 0, {0}, {0}},
{-1, -1, " ", 1, 10.0f, 0, {0}, {0}},
{COL_PING, BROWSESORT_PING, "Ping", 1, 40.0f, FIXED, {0}, {0}},
};
int num_cols = sizeof(cols)/sizeof(column);
// do layout
for(int i = 0; i < num_cols; i++)
{
if(cols[i].direction == -1)
{
ui_vsplit_l(&headers, cols[i].width, &cols[i].rect, &headers);
if(i+1 < num_cols)
{
//cols[i].flags |= SPACER;
ui_vsplit_l(&headers, 2, &cols[i].spacer, &headers);
}
}
}
for(int i = num_cols-1; i >= 0; i--)
{
if(cols[i].direction == 1)
{
ui_vsplit_r(&headers, cols[i].width, &headers, &cols[i].rect);
ui_vsplit_r(&headers, 2, &headers, &cols[i].spacer);
}
}
for(int i = 0; i < num_cols; i++)
{
if(cols[i].direction == 0)
cols[i].rect = headers;
}
// do headers
for(int i = 0; i < num_cols; i++)
{
if(ui_do_button(cols[i].caption, cols[i].caption, config.b_sort == cols[i].sort, &cols[i].rect, ui_draw_grid_header, 0))
{
if(cols[i].sort != -1)
{
if(config.b_sort == cols[i].sort)
config.b_sort_order ^= 1;
else
config.b_sort_order = 0;
config.b_sort = cols[i].sort;
}
}
}
ui_draw_rect(&view, vec4(0,0,0,0.15f), 0, 0);
RECT scroll;
ui_vsplit_r(&view, 15, &view, &scroll);
int num_servers = client_serverbrowse_sorted_num();
// display important messages in the middle of the screen so no
// users misses it
{
RECT msgbox = view;
msgbox.y += view.h/3;
if(active_page == PAGE_INTERNET && client_serverbrowse_refreshingmasters())
ui_do_label(&msgbox, "Refreshing master servers", 16.0f, 0);
else if(!client_serverbrowse_num())
ui_do_label(&msgbox, "No servers found", 16.0f, 0);
else if(client_serverbrowse_num() && !num_servers)
ui_do_label(&msgbox, "No servers match your filter criteria", 16.0f, 0);
}
int num = (int)(view.h/cols[0].rect.h);
static int scrollbar = 0;
static float scrollvalue = 0;
//static int selected_index = -1;
ui_hmargin(&scroll, 5.0f, &scroll);
scrollvalue = ui_do_scrollbar_v(&scrollbar, &scroll, scrollvalue);
int scrollnum = num_servers-num+10;
if(scrollnum > 0)
{
if(inp_key_presses(KEY_MOUSE_WHEEL_UP))
scrollvalue -= 1.0f/scrollnum;
if(inp_key_presses(KEY_MOUSE_WHEEL_DOWN))
scrollvalue += 1.0f/scrollnum;
if(scrollvalue < 0) scrollvalue = 0;
if(scrollvalue > 1) scrollvalue = 1;
}
else
scrollnum = 0;
// set clipping
ui_clip_enable(&view);
int start = (int)(scrollnum*scrollvalue);
if(start < 0)
start = 0;
RECT original_view = view;
view.y -= scrollvalue*scrollnum*cols[0].rect.h;
int new_selected = -1;
int num_players = 0;
selected_index = -1;
for (int i = 0; i < num_servers; i++)
{
SERVER_INFO *item = client_serverbrowse_sorted_get(i);
num_players += item->num_players;
}
for (int i = 0; i < num_servers; i++)
{
int item_index = i;
SERVER_INFO *item = client_serverbrowse_sorted_get(item_index);
RECT row;
RECT select_hit_box;
int selected = strcmp(item->address, config.ui_server_address) == 0; //selected_index==item_index;
ui_hsplit_t(&view, 17.0f, &row, &view);
select_hit_box = row;
if(selected)
{
selected_index = i;
RECT r = row;
ui_margin(&r, 1.5f, &r);
ui_draw_rect(&r, vec4(1,1,1,0.5f), CORNER_ALL, 4.0f);
}
// make sure that only those in view can be selected
if(row.y+row.h > original_view.y)
{
if(select_hit_box.y < original_view.y) // clip the selection
{
select_hit_box.h -= original_view.y-select_hit_box.y;
select_hit_box.y = original_view.y;
}
if(ui_do_button(item, "", selected, &select_hit_box, 0, 0))
{
new_selected = item_index;
}
}
// check if we need to do more
if(row.y > original_view.y+original_view.h && !ui_active_item())
break;
for(int c = 0; c < num_cols; c++)
{
RECT button;
char temp[64];
button.x = cols[c].rect.x;
button.y = row.y;
button.h = row.h;
button.w = cols[c].rect.w;
//int s = 0;
int id = cols[c].id;
//s = ui_do_button(item, "L", l, &button, ui_draw_browse_icon, 0);
if(id == COL_FLAG_LOCK)
{
if(item->flags & SRVFLAG_PASSWORD)
ui_draw_browse_icon(SPRITE_BROWSE_LOCK, &button);
}
else if(id == COL_FLAG_PURE)
{
if(strcmp(item->gametype, "DM") == 0 || strcmp(item->gametype, "TDM") == 0 || strcmp(item->gametype, "CTF") == 0)
{
// pure server
}
else
{
// unpure
ui_draw_browse_icon(SPRITE_BROWSE_UNPURE, &button);
}
}
else if(id == COL_FLAG_FAV)
{
if(item->favorite)
ui_draw_browse_icon(SPRITE_BROWSE_HEART, &button);
}
else if(id == COL_NAME)
{
TEXT_CURSOR cursor;
gfx_text_set_cursor(&cursor, button.x, button.y, 12.0f*ui_scale(), TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END);
cursor.line_width = button.w;
if(config.b_filter_string[0] && (item->quicksearch_hit&BROWSEQUICK_SERVERNAME))
{
// highlight the parts that matches
const char *s = str_find_nocase(item->name, config.b_filter_string);
if(s)
{
gfx_text_ex(&cursor, item->name, (int)(s-item->name));
gfx_text_color(0.4f,0.4f,1.0f,1);
gfx_text_ex(&cursor, s, strlen(config.b_filter_string));
gfx_text_color(1,1,1,1);
gfx_text_ex(&cursor, s+strlen(config.b_filter_string), -1);
}
else
gfx_text_ex(&cursor, item->name, -1);
}
else
gfx_text_ex(&cursor, item->name, -1);
}
else if(id == COL_MAP)
ui_do_label(&button, item->map, 12.0f, -1);
else if(id == COL_PLAYERS)
{
str_format(temp, sizeof(temp), "%i/%i", item->num_players, item->max_players);
if(config.b_filter_string[0] && (item->quicksearch_hit&BROWSEQUICK_PLAYERNAME))
gfx_text_color(0.4f,0.4f,1.0f,1);
ui_do_label(&button, temp, 12.0f, 1);
gfx_text_color(1,1,1,1);
}
else if(id == COL_PING)
{
str_format(temp, sizeof(temp), "%i", item->latency);
ui_do_label(&button, temp, 12.0f, 1);
}
else if(id == COL_PROGRESS)
{
if(item->progression > 100)
item->progression = 100;
ui_draw_browse_icon(item->progression, &button);
}
else if(id == COL_VERSION)
{
const char *version = item->version;
if(strcmp(version, "0.3 e2d7973c6647a13c") == 0) // TODO: remove me later on
version = "0.3.0";
ui_do_label(&button, version, 12.0f, 1);
}
else if(id == COL_GAMETYPE)
{
ui_do_label(&button, item->gametype, 12.0f, 0);
}
}
}
ui_clip_disable();
if(new_selected != -1)
{
// select the new server
SERVER_INFO *item = client_serverbrowse_sorted_get(new_selected);
strncpy(config.ui_server_address, item->address, sizeof(config.ui_server_address));
if(inp_mouse_doubleclick())
client_connect(config.ui_server_address);
}
ui_draw_rect(&status, vec4(1,1,1,0.25f), CORNER_B, 5.0f);
ui_margin(&status, 5.0f, &status);
// render quick search
RECT quicksearch, button;
ui_vsplit_l(&status, 250.0f, &quicksearch, &status);
ui_do_label(&quicksearch, "Quick search: ", 14.0f, -1);
ui_vsplit_l(&quicksearch, gfx_text_width(0, 14.0f, "Quick search: ", -1), 0, &quicksearch);
ui_do_edit_box(&config.b_filter_string, &quicksearch, config.b_filter_string, sizeof(config.b_filter_string), 14.0f);
ui_vsplit_l(&quicksearch, 135.0f, &quicksearch, &button);
ui_vmargin(&button, 0.0f, &button);
static int clear_button = 0;
if(ui_do_button(&clear_button, "x", 0, &button, ui_draw_menu_button, 0))
config.b_filter_string[0] = 0;
// render status
char buf[128];
str_format(buf, sizeof(buf), "%d of %d servers, %d players", client_serverbrowse_sorted_num(), client_serverbrowse_num(), num_players);
ui_vsplit_r(&status, gfx_text_width(0, 14.0f, buf, -1), 0, &status);
ui_do_label(&status, buf, 14.0f, -1);
}
void MENUS::render_serverbrowser_filters(RECT view)
{
// filters
RECT button;
ui_hsplit_t(&view, 5.0f, 0, &view);
ui_vsplit_l(&view, 5.0f, 0, &view);
ui_vsplit_r(&view, 5.0f, &view, 0);
ui_hsplit_b(&view, 5.0f, &view, 0);
// render filters
ui_hsplit_t(&view, 20.0f, &button, &view);
if (ui_do_button(&config.b_filter_empty, "Has people playing", config.b_filter_empty, &button, ui_draw_checkbox, 0))
config.b_filter_empty ^= 1;
ui_hsplit_t(&view, 20.0f, &button, &view);
if (ui_do_button(&config.b_filter_full, "Server not full", config.b_filter_full, &button, ui_draw_checkbox, 0))
config.b_filter_full ^= 1;
ui_hsplit_t(&view, 20.0f, &button, &view);
if (ui_do_button(&config.b_filter_pw, "No password", config.b_filter_pw, &button, ui_draw_checkbox, 0))
config.b_filter_pw ^= 1;
ui_hsplit_t(&view, 20.0f, &button, &view);
if (ui_do_button((char *)&config.b_filter_compatversion, "Compatible Version", config.b_filter_compatversion, &button, ui_draw_checkbox, 0))
config.b_filter_compatversion ^= 1;
ui_hsplit_t(&view, 20.0f, &button, &view);
if (ui_do_button((char *)&config.b_filter_pure, "Standard gametype", config.b_filter_pure, &button, ui_draw_checkbox, 0))
config.b_filter_pure ^= 1;
ui_hsplit_t(&view, 20.0f, &button, &view);
/*ui_vsplit_l(&button, 20.0f, 0, &button);*/
if (ui_do_button((char *)&config.b_filter_pure_map, "Standard map", config.b_filter_pure_map, &button, ui_draw_checkbox, 0))
config.b_filter_pure_map ^= 1;
ui_hsplit_t(&view, 20.0f, &button, &view);
ui_do_label(&button, "Game types: ", 14.0f, -1);
ui_vsplit_l(&button, 95.0f, 0, &button);
ui_margin(&button, 1.0f, &button);
ui_do_edit_box(&config.b_filter_gametype, &button, config.b_filter_gametype, sizeof(config.b_filter_gametype), 14.0f);
{
ui_hsplit_t(&view, 20.0f, &button, &view);
RECT editbox;
ui_vsplit_l(&button, 40.0f, &editbox, &button);
ui_vsplit_l(&button, 5.0f, &button, &button);
char buf[8];
str_format(buf, sizeof(buf), "%d", config.b_filter_ping);
ui_do_edit_box(&config.b_filter_ping, &editbox, buf, sizeof(buf), 14.0f);
config.b_filter_ping = atoi(buf);
ui_do_label(&button, "Maximum ping", 14.0f, -1);
}
ui_hsplit_b(&view, button_height, &view, &button);
static int clear_button = 0;
if(ui_do_button(&clear_button, "Reset Filter", 0, &button, ui_draw_menu_button, 0))
{
config.b_filter_full = 0;
config.b_filter_empty = 0;
config.b_filter_pw = 0;
config.b_filter_ping = 999;
config.b_filter_gametype[0] = 0;
config.b_filter_compatversion = 1;
config.b_filter_string[0] = 0;
config.b_filter_pure = 1;
}
}
void MENUS::render_serverbrowser_serverdetail(RECT view)
{
RECT server_details = view;
RECT server_scoreboard, server_header;
SERVER_INFO *selected_server = client_serverbrowse_sorted_get(selected_index);
//ui_vsplit_l(&server_details, 10.0f, 0x0, &server_details);
// split off a piece to use for scoreboard
ui_hsplit_t(&server_details, 140.0f, &server_details, &server_scoreboard);
ui_hsplit_b(&server_details, 10.0f, &server_details, 0x0);
// server details
const float font_size = 12.0f;
ui_hsplit_t(&server_details, 20.0f, &server_header, &server_details);
ui_draw_rect(&server_header, vec4(1,1,1,0.25f), CORNER_T, 4.0f);
ui_draw_rect(&server_details, vec4(0,0,0,0.15f), CORNER_B, 4.0f);
ui_vsplit_l(&server_header, 8.0f, 0x0, &server_header);
ui_do_label(&server_header, "Server Details: ", font_size+2.0f, -1);
ui_vsplit_l(&server_details, 5.0f, 0x0, &server_details);
ui_margin(&server_details, 3.0f, &server_details);
if (selected_server)
{
RECT row;
static const char *labels[] = { "Version:", "Game Type:", "Progression:", "Ping:" };
RECT left_column;
RECT right_column;
//
{
RECT button;
ui_hsplit_b(&server_details, 20.0f, &server_details, &button);
static int add_fav_button = 0;
if (ui_do_button(&add_fav_button, "Favorite", selected_server->favorite, &button, ui_draw_checkbox, 0))
{
if(selected_server->favorite)
client_serverbrowse_removefavorite(selected_server->netaddr);
else
client_serverbrowse_addfavorite(selected_server->netaddr);
}
}
//ui_do_label(&row, temp, font_size, -1);
ui_vsplit_l(&server_details, 5.0f, 0x0, &server_details);
ui_vsplit_l(&server_details, 80.0f, &left_column, &right_column);
for (unsigned int i = 0; i < sizeof(labels) / sizeof(labels[0]); i++)
{
ui_hsplit_t(&left_column, 15.0f, &row, &left_column);
ui_do_label(&row, labels[i], font_size, -1);
}
ui_hsplit_t(&right_column, 15.0f, &row, &right_column);
ui_do_label(&row, selected_server->version, font_size, -1);
ui_hsplit_t(&right_column, 15.0f, &row, &right_column);
ui_do_label(&row, selected_server->gametype, font_size, -1);
char temp[16];
if(selected_server->progression < 0)
str_format(temp, sizeof(temp), "N/A");
else
str_format(temp, sizeof(temp), "%d%%", selected_server->progression);
ui_hsplit_t(&right_column, 15.0f, &row, &right_column);
ui_do_label(&row, temp, font_size, -1);
str_format(temp, sizeof(temp), "%d", selected_server->latency);
ui_hsplit_t(&right_column, 15.0f, &row, &right_column);
ui_do_label(&row, temp, font_size, -1);
}
// server scoreboard
ui_hsplit_b(&server_scoreboard, 10.0f, &server_scoreboard, 0x0);
ui_hsplit_t(&server_scoreboard, 20.0f, &server_header, &server_scoreboard);
ui_draw_rect(&server_header, vec4(1,1,1,0.25f), CORNER_T, 4.0f);
ui_draw_rect(&server_scoreboard, vec4(0,0,0,0.15f), CORNER_B, 4.0f);
ui_vsplit_l(&server_header, 8.0f, 0x0, &server_header);
ui_do_label(&server_header, "Scoreboard: ", font_size+2.0f, -1);
ui_vsplit_l(&server_scoreboard, 5.0f, 0x0, &server_scoreboard);
ui_margin(&server_scoreboard, 3.0f, &server_scoreboard);
if (selected_server)
{
for (int i = 0; i < selected_server->num_players; i++)
{
RECT row;
char temp[16];
ui_hsplit_t(&server_scoreboard, 16.0f, &row, &server_scoreboard);
str_format(temp, sizeof(temp), "%d", selected_server->players[i].score);
ui_do_label(&row, temp, font_size, -1);
ui_vsplit_l(&row, 25.0f, 0x0, &row);
TEXT_CURSOR cursor;
gfx_text_set_cursor(&cursor, row.x, row.y, 12.0f, TEXTFLAG_RENDER);
const char *name = selected_server->players[i].name;
if(config.b_filter_string[0])
{
// highlight the parts that matches
const char *s = str_find_nocase(name, config.b_filter_string);
if(s)
{
gfx_text_ex(&cursor, name, (int)(s-name));
gfx_text_color(0.4f,0.4f,1,1);
gfx_text_ex(&cursor, s, strlen(config.b_filter_string));
gfx_text_color(1,1,1,1);
gfx_text_ex(&cursor, s+strlen(config.b_filter_string), -1);
}
else
gfx_text_ex(&cursor, name, -1);
}
else
gfx_text_ex(&cursor, name, -1);
}
}
}
void MENUS::render_serverbrowser(RECT main_view)
{
ui_draw_rect(&main_view, color_tabbar_active, CORNER_ALL, 10.0f);
RECT view;
ui_margin(&main_view, 10.0f, &view);
/*
+-----------------+ +------+
| | | |
| | | tool |
| | | box |
| | | |
| | +------+
+-----------------+ button
status toolbar box
*/
//RECT filters;
RECT status_toolbar;
RECT toolbox;
RECT button_box;
// split off a piece for filters, details and scoreboard
ui_vsplit_r(&view, 200.0f, &view, &toolbox);
ui_hsplit_b(&toolbox, 80.0f, &toolbox, &button_box);
ui_hsplit_b(&view, button_height+5.0f, &view, &status_toolbar);
render_serverbrowser_serverlist(view);
int toolbox_page = config.ui_toolbox_page;
ui_vsplit_l(&toolbox, 5.0f, 0, &toolbox);
// do tabbar
{
RECT tab_bar;
RECT tabbutton0, tabbutton1;
ui_hsplit_t(&toolbox, 22.0f, &tab_bar, &toolbox);
ui_vsplit_mid(&tab_bar, &tabbutton0, &tabbutton1);
ui_vsplit_r(&tabbutton0, 5.0f, &tabbutton0, 0);
ui_vsplit_l(&tabbutton1, 5.0f, 0, &tabbutton1);
static int filters_tab = 0;
if (ui_do_button(&filters_tab, "Filter", toolbox_page==0, &tabbutton0, ui_draw_menu_tab_button, 0))
toolbox_page = 0;
static int info_tab = 0;
if (ui_do_button(&info_tab, "Info", toolbox_page==1, &tabbutton1, ui_draw_menu_tab_button, 0))
toolbox_page = 1;
}
config.ui_toolbox_page = toolbox_page;
ui_draw_rect(&toolbox, vec4(0,0,0,0.15f), 0, 0);
ui_hsplit_t(&toolbox, 5.0f, 0, &toolbox);
if(toolbox_page == 0)
render_serverbrowser_filters(toolbox);
else if(toolbox_page == 1)
render_serverbrowser_serverdetail(toolbox);
{
ui_hsplit_t(&status_toolbar, 5.0f, 0, &status_toolbar);
RECT button;
//ui_vsplit_r(&buttons, 20.0f, &buttons, &button);
ui_vsplit_r(&status_toolbar, 100.0f, &status_toolbar, &button);
ui_vmargin(&button, 2.0f, &button);
static int refresh_button = 0;
if(ui_do_button(&refresh_button, "Refresh", 0, &button, ui_draw_menu_button, 0))
{
if(config.ui_page == PAGE_INTERNET)
client_serverbrowse_refresh(BROWSETYPE_INTERNET);
else if(config.ui_page == PAGE_LAN)
client_serverbrowse_refresh(BROWSETYPE_LAN);
else if(config.ui_page == PAGE_FAVORITES)
client_serverbrowse_refresh(BROWSETYPE_FAVORITES);
}
char buf[512];
if(strcmp(client_latestversion(), "0") != 0)
str_format(buf, sizeof(buf), "Teeworlds %s is out! Download it at www.teeworlds.com!\nCurrent version: %s", client_latestversion(), GAME_VERSION);
else
str_format(buf, sizeof(buf), "Current version: %s", GAME_VERSION);
ui_do_label(&status_toolbar, buf, 14.0f, -1);
}
// do the button box
{
ui_vsplit_l(&button_box, 5.0f, 0, &button_box);
ui_vsplit_r(&button_box, 5.0f, &button_box, 0);
RECT button;
ui_hsplit_b(&button_box, button_height, &button_box, &button);
ui_vsplit_r(&button, 120.0f, 0, &button);
ui_vmargin(&button, 2.0f, &button);
//ui_vmargin(&button, 2.0f, &button);
static int join_button = 0;
if(ui_do_button(&join_button, "Connect", 0, &button, ui_draw_menu_button, 0) || enter_pressed)
{
client_connect(config.ui_server_address);
enter_pressed = false;
}
ui_hsplit_b(&button_box, 5.0f, &button_box, &button);
ui_hsplit_b(&button_box, 20.0f, &button_box, &button);
ui_do_edit_box(&config.ui_server_address, &button, config.ui_server_address, sizeof(config.ui_server_address), 14.0f);
ui_hsplit_b(&button_box, 20.0f, &button_box, &button);
ui_do_label(&button, "Host address:", 14.0f, -1);
}
}

View File

@@ -0,0 +1,408 @@
#include <base/math.hpp>
//#include <string.h> // strcmp, strlen, strncpy
//#include <stdlib.h> // atoi
#include <engine/e_client_interface.h>
#include <game/client/render.hpp>
#include <game/client/gameclient.hpp>
//#include <game/generated/g_protocol.hpp>
//#include <game/generated/gc_data.hpp>
#include <game/client/ui.hpp>
//#include <game/client/gameclient.hpp>
//#include <game/client/animstate.hpp>
#include "menus.hpp"
void MENUS::ui_draw_demoplayer_button(const void *id, const char *text, int checked, const RECT *r, const void *extra)
{
ui_draw_rect(r, vec4(1,1,1, checked ? 0.10f : 0.5f)*button_color_mul(id), CORNER_ALL, 5.0f);
ui_do_label(r, text, 14.0f, 0);
}
void MENUS::render_demoplayer(RECT main_view)
{
const DEMOPLAYBACK_INFO *info = client_demoplayer_getinfo();
const float seekbar_height = 15.0f;
const float buttonbar_height = 20.0f;
const float margins = 5.0f;
float total_height;
if(menu_active)
total_height = seekbar_height+buttonbar_height+margins*3;
else
total_height = seekbar_height+margins*2;
ui_hsplit_b(&main_view, total_height, 0, &main_view);
ui_vsplit_l(&main_view, 250.0f, 0, &main_view);
ui_vsplit_r(&main_view, 250.0f, &main_view, 0);
ui_draw_rect(&main_view, color_tabbar_active, CORNER_T, 10.0f);
ui_margin(&main_view, 5.0f, &main_view);
RECT seekbar, buttonbar;
if(menu_active)
{
ui_hsplit_t(&main_view, seekbar_height, &seekbar, &buttonbar);
ui_hsplit_t(&buttonbar, margins, 0, &buttonbar);
}
else
seekbar = main_view;
// do seekbar
{
static int seekbar_id = 0;
void *id = &seekbar_id;
char buffer[128];
ui_draw_rect(&seekbar, vec4(0,0,0,0.5f), CORNER_ALL, 5.0f);
int current_tick = info->current_tick - info->first_tick;
int total_ticks = info->last_tick - info->first_tick;
float amount = current_tick/(float)total_ticks;
RECT filledbar = seekbar;
filledbar.w = 10.0f + (filledbar.w-10.0f)*amount;
ui_draw_rect(&filledbar, vec4(1,1,1,0.5f), CORNER_ALL, 5.0f);
str_format(buffer, sizeof(buffer), "%d:%02d / %d:%02d",
current_tick/SERVER_TICK_SPEED/60, (current_tick/SERVER_TICK_SPEED)%60,
total_ticks/SERVER_TICK_SPEED/60, (total_ticks/SERVER_TICK_SPEED)%60);
ui_do_label(&seekbar, buffer, seekbar.h*0.70f, 0);
// do the logic
int inside = ui_mouse_inside(&seekbar);
if(ui_active_item() == id)
{
if(!ui_mouse_button(0))
ui_set_active_item(0);
else
{
float amount = (ui_mouse_x()-seekbar.x)/(float)seekbar.w;
if(amount > 0 && amount < 1.0f)
{
gameclient.on_reset();
gameclient.suppress_events = true;
client_demoplayer_setpos(amount);
gameclient.suppress_events = false;
}
}
}
else if(ui_hot_item() == id)
{
if(ui_mouse_button(0))
ui_set_active_item(id);
}
if(inside)
ui_set_hot_item(id);
}
if(menu_active)
{
// do buttons
RECT button;
// pause button
ui_vsplit_l(&buttonbar, buttonbar_height, &button, &buttonbar);
static int pause_button = 0;
if(ui_do_button(&pause_button, "| |", info->paused, &button, ui_draw_demoplayer_button, 0))
client_demoplayer_setpause(!info->paused);
// play button
ui_vsplit_l(&buttonbar, margins, 0, &buttonbar);
ui_vsplit_l(&buttonbar, buttonbar_height, &button, &buttonbar);
static int play_button = 0;
if(ui_do_button(&play_button, ">", !info->paused, &button, ui_draw_demoplayer_button, 0))
{
client_demoplayer_setpause(0);
client_demoplayer_setspeed(1.0f);
}
// slowdown
ui_vsplit_l(&buttonbar, margins, 0, &buttonbar);
ui_vsplit_l(&buttonbar, buttonbar_height, &button, &buttonbar);
static int slowdown_button = 0;
if(ui_do_button(&slowdown_button, "<<", 0, &button, ui_draw_demoplayer_button, 0))
{
if(info->speed > 4.0f) client_demoplayer_setspeed(4.0f);
else if(info->speed > 2.0f) client_demoplayer_setspeed(2.0f);
else if(info->speed > 1.0f) client_demoplayer_setspeed(1.0f);
else if(info->speed > 0.5f) client_demoplayer_setspeed(0.5f);
else client_demoplayer_setspeed(0.05f);
}
// fastforward
ui_vsplit_l(&buttonbar, margins, 0, &buttonbar);
ui_vsplit_l(&buttonbar, buttonbar_height, &button, &buttonbar);
static int fastforward_button = 0;
if(ui_do_button(&fastforward_button, ">>", 0, &button, ui_draw_demoplayer_button, 0))
{
if(info->speed < 0.5f) client_demoplayer_setspeed(0.5f);
else if(info->speed < 1.0f) client_demoplayer_setspeed(1.0f);
else if(info->speed < 2.0f) client_demoplayer_setspeed(2.0f);
else if(info->speed < 4.0f) client_demoplayer_setspeed(4.0f);
else client_demoplayer_setspeed(8.0f);
}
// speed meter
ui_vsplit_l(&buttonbar, margins*3, 0, &buttonbar);
char buffer[64];
if(info->speed >= 1.0f)
str_format(buffer, sizeof(buffer), "x%.0f", info->speed);
else
str_format(buffer, sizeof(buffer), "x%.1f", info->speed);
ui_do_label(&buttonbar, buffer, button.h*0.7f, -1);
// exit button
ui_vsplit_r(&buttonbar, buttonbar_height*3, &buttonbar, &button);
static int exit_button = 0;
if(ui_do_button(&exit_button, "Exit", 0, &button, ui_draw_demoplayer_button, 0))
client_disconnect();
}
}
static RECT listbox_originalview;
static RECT listbox_view;
static float listbox_rowheight;
static int listbox_itemindex;
static int listbox_selected_index;
static int listbox_new_selected;
void MENUS::ui_do_listbox_start(void *id, const RECT *rect, float row_height, const char *title, int num_items, int selected_index)
{
RECT scroll, row;
RECT view = *rect;
RECT header, footer;
// draw header
ui_hsplit_t(&view, listheader_height, &header, &view);
ui_draw_rect(&header, vec4(1,1,1,0.25f), CORNER_T, 5.0f);
ui_do_label(&header, title, header.h*fontmod_height, 0);
// draw footers
ui_hsplit_b(&view, listheader_height, &view, &footer);
ui_draw_rect(&footer, vec4(1,1,1,0.25f), CORNER_B, 5.0f);
ui_vsplit_l(&footer, 10.0f, 0, &footer);
// background
ui_draw_rect(&view, vec4(0,0,0,0.15f), 0, 0);
// prepare the scroll
ui_vsplit_r(&view, 15, &view, &scroll);
// setup the variables
listbox_originalview = view;
listbox_selected_index = selected_index;
listbox_new_selected = selected_index;
listbox_itemindex = 0;
listbox_rowheight = row_height;
//int num_servers = client_serverbrowse_sorted_num();
// do the scrollbar
ui_hsplit_t(&view, listbox_rowheight, &row, 0);
int num = (int)(listbox_originalview.h/row.h);
static float scrollvalue = 0;
ui_hmargin(&scroll, 5.0f, &scroll);
scrollvalue = ui_do_scrollbar_v(id, &scroll, scrollvalue);
int start = (int)(num*scrollvalue);
if(start < 0)
start = 0;
// the list
listbox_view = listbox_originalview;
ui_vmargin(&listbox_view, 5.0f, &listbox_view);
ui_clip_enable(&listbox_view);
listbox_view.y -= scrollvalue*num*row.h;
}
MENUS::LISTBOXITEM MENUS::ui_do_listbox_nextitem(void *id)
{
RECT row;
LISTBOXITEM item = {0};
ui_hsplit_t(&listbox_view, listbox_rowheight-2.0f, &row, &listbox_view);
ui_hsplit_t(&listbox_view, 2.0f, 0, &listbox_view);
RECT select_hit_box = row;
item.visible = 1;
if(listbox_selected_index == listbox_itemindex)
item.selected = 1;
// make sure that only those in view can be selected
if(row.y+row.h > listbox_originalview.y)
{
if(select_hit_box.y < listbox_originalview.y) // clip the selection
{
select_hit_box.h -= listbox_originalview.y-select_hit_box.y;
select_hit_box.y = listbox_originalview.y;
}
if(ui_do_button(id, "", listbox_selected_index==listbox_itemindex, &select_hit_box, 0, 0))
listbox_new_selected = listbox_itemindex;
}
else
item.visible = 0;
item.rect = row;
// check if we need to do more
if(row.y > listbox_originalview.y+listbox_originalview.h)
item.visible = 0;
if(listbox_selected_index==listbox_itemindex)
{
//selected_index = i;
RECT r = row;
ui_margin(&r, 1.5f, &r);
ui_draw_rect(&r, vec4(1,1,1,0.5f), CORNER_ALL, 4.0f);
}
listbox_itemindex++;
ui_vmargin(&item.rect, 5.0f, &item.rect);
return item;
}
int MENUS::ui_do_listbox_end()
{
ui_clip_disable();
return listbox_new_selected;
}
/*
void MENUS::demolist_listdir_callback(const char *name, int is_dir, void *user)
{
(*count)++;
LISTBOXITEM item = ui_do_listbox_nextitem((void*)(10+*count));
if(item.visible)
ui_do_label(&item.rect, name, item.rect.h*fontmod_height, -1);
}
DEMOITEM *demos;
int num_demos;
*/
void MENUS::demolist_count_callback(const char *name, int is_dir, void *user)
{
if(is_dir || name[0] == '.')
return;
(*(int *)user)++;
}
struct FETCH_CALLBACKINFO
{
MENUS *self;
const char *prefix;
int count;
};
void MENUS::demolist_fetch_callback(const char *name, int is_dir, void *user)
{
if(is_dir || name[0] == '.')
return;
FETCH_CALLBACKINFO *info = (FETCH_CALLBACKINFO *)user;
if(info->count == info->self->num_demos)
return;
str_format(info->self->demos[info->count].filename, sizeof(info->self->demos[info->count].filename), "%s/%s", info->prefix, name);
str_copy(info->self->demos[info->count].name, name, sizeof(info->self->demos[info->count].name));
info->count++;
}
void MENUS::demolist_populate()
{
if(demos)
mem_free(demos);
demos = 0;
num_demos = 0;
char buf[512];
str_format(buf, sizeof(buf), "%s/demos", client_user_directory());
fs_listdir(buf, demolist_count_callback, &num_demos);
fs_listdir("demos", demolist_count_callback, &num_demos);
demos = (DEMOITEM *)mem_alloc(sizeof(DEMOITEM)*num_demos, 1);
mem_zero(demos, sizeof(DEMOITEM)*num_demos);
FETCH_CALLBACKINFO info = {this, buf, 0};
fs_listdir(buf, demolist_fetch_callback, &info);
info.prefix = "demos";
fs_listdir("demos", demolist_fetch_callback, &info);
}
void MENUS::render_demolist(RECT main_view)
{
static int inited = 0;
if(!inited)
demolist_populate();
inited = 1;
// render background
ui_draw_rect(&main_view, color_tabbar_active, CORNER_ALL, 10.0f);
ui_margin(&main_view, 10.0f, &main_view);
RECT buttonbar;
ui_hsplit_b(&main_view, button_height+5.0f, &main_view, &buttonbar);
ui_hsplit_t(&buttonbar, 5.0f, 0, &buttonbar);
static int selected_item = -1;
static int num_items = 0;
static int demolist_id = 0;
ui_do_listbox_start(&demolist_id, &main_view, 17.0f, "Demos", num_items, selected_item);
for(int i = 0; i < num_demos; i++)
{
LISTBOXITEM item = ui_do_listbox_nextitem((void*)(10+i));
if(item.visible)
ui_do_label(&item.rect, demos[i].name, item.rect.h*fontmod_height, -1);
}
selected_item = ui_do_listbox_end();
if(selected_item >= 0 && selected_item < num_demos && inp_mouse_doubleclick())
{
ui_set_active_item(0);
client_demoplayer_play(demos[selected_item].filename);
}
RECT refresh_rect, play_rect;
ui_vsplit_r(&buttonbar, 250.0f, &buttonbar, &refresh_rect);
ui_vsplit_r(&refresh_rect, 130.0f, &refresh_rect, &play_rect);
ui_vsplit_r(&play_rect, 120.0f, 0x0, &play_rect);
static int refresh_button = 0;
if(ui_do_button(&refresh_button, "Refresh", 0, &refresh_rect, ui_draw_menu_button, 0))
{
demolist_populate();
}
static int play_button = 0;
if(ui_do_button(&play_button, "Play", 0, &play_rect, ui_draw_menu_button, 0))
{
if(selected_item >= 0 && selected_item < num_demos)
client_demoplayer_play(demos[selected_item].filename);
}
}

View File

@@ -0,0 +1,398 @@
#include <base/math.hpp>
#include <string.h> // strcmp, strlen, strncpy
#include <stdlib.h> // atoi
#include <engine/e_client_interface.h>
#include <game/generated/g_protocol.hpp>
#include <game/generated/gc_data.hpp>
#include <game/client/ui.hpp>
#include <game/client/gameclient.hpp>
#include <game/client/animstate.hpp>
#include "menus.hpp"
#include "motd.hpp"
#include "voting.hpp"
void MENUS::render_game(RECT main_view)
{
RECT button;
//RECT votearea;
ui_hsplit_t(&main_view, 45.0f, &main_view, 0);
ui_draw_rect(&main_view, color_tabbar_active, CORNER_ALL, 10.0f);
ui_hsplit_t(&main_view, 10.0f, 0, &main_view);
ui_hsplit_t(&main_view, 25.0f, &main_view, 0);
ui_vmargin(&main_view, 10.0f, &main_view);
ui_vsplit_r(&main_view, 120.0f, &main_view, &button);
static int disconnect_button = 0;
if(ui_do_button(&disconnect_button, "Disconnect", 0, &button, ui_draw_menu_button, 0))
client_disconnect();
if(gameclient.snap.local_info && gameclient.snap.gameobj)
{
if(gameclient.snap.local_info->team != -1)
{
ui_vsplit_l(&main_view, 10.0f, &button, &main_view);
ui_vsplit_l(&main_view, 120.0f, &button, &main_view);
static int spectate_button = 0;
if(ui_do_button(&spectate_button, "Spectate", 0, &button, ui_draw_menu_button, 0))
{
gameclient.send_switch_team(-1);
set_active(false);
}
}
if(gameclient.snap.gameobj->flags & GAMEFLAG_TEAMS)
{
if(gameclient.snap.local_info->team != 0)
{
ui_vsplit_l(&main_view, 10.0f, &button, &main_view);
ui_vsplit_l(&main_view, 120.0f, &button, &main_view);
static int spectate_button = 0;
if(ui_do_button(&spectate_button, "Join Red", 0, &button, ui_draw_menu_button, 0))
{
gameclient.send_switch_team(0);
set_active(false);
}
}
if(gameclient.snap.local_info->team != 1)
{
ui_vsplit_l(&main_view, 10.0f, &button, &main_view);
ui_vsplit_l(&main_view, 120.0f, &button, &main_view);
static int spectate_button = 0;
if(ui_do_button(&spectate_button, "Join Blue", 0, &button, ui_draw_menu_button, 0))
{
gameclient.send_switch_team(1);
set_active(false);
}
}
}
else
{
if(gameclient.snap.local_info->team != 0)
{
ui_vsplit_l(&main_view, 10.0f, &button, &main_view);
ui_vsplit_l(&main_view, 120.0f, &button, &main_view);
static int spectate_button = 0;
if(ui_do_button(&spectate_button, "Join Game", 0, &button, ui_draw_menu_button, 0))
{
gameclient.send_switch_team(0);
set_active(false);
}
}
}
}
/*
RECT bars;
ui_hsplit_t(&votearea, 10.0f, 0, &votearea);
ui_hsplit_t(&votearea, 25.0f + 10.0f*3 + 25.0f, &votearea, &bars);
ui_draw_rect(&votearea, color_tabbar_active, CORNER_ALL, 10.0f);
ui_vmargin(&votearea, 20.0f, &votearea);
ui_hmargin(&votearea, 10.0f, &votearea);
ui_hsplit_b(&votearea, 35.0f, &votearea, &bars);
if(gameclient.voting->is_voting())
{
// do yes button
ui_vsplit_l(&votearea, 50.0f, &button, &votearea);
static int yes_button = 0;
if(ui_do_button(&yes_button, "Yes", 0, &button, ui_draw_menu_button, 0))
gameclient.voting->vote(1);
// do no button
ui_vsplit_l(&votearea, 5.0f, 0, &votearea);
ui_vsplit_l(&votearea, 50.0f, &button, &votearea);
static int no_button = 0;
if(ui_do_button(&no_button, "No", 0, &button, ui_draw_menu_button, 0))
gameclient.voting->vote(-1);
// do time left
ui_vsplit_r(&votearea, 50.0f, &votearea, &button);
char buf[256];
str_format(buf, sizeof(buf), "%d", gameclient.voting->seconds_left());
ui_do_label(&button, buf, 24.0f, 0);
// do description and command
ui_vsplit_l(&votearea, 5.0f, 0, &votearea);
ui_do_label(&votearea, gameclient.voting->vote_description(), 14.0f, -1);
ui_hsplit_t(&votearea, 16.0f, 0, &votearea);
ui_do_label(&votearea, gameclient.voting->vote_command(), 10.0f, -1);
// do bars
ui_hsplit_t(&bars, 10.0f, 0, &bars);
ui_hmargin(&bars, 5.0f, &bars);
gameclient.voting->render_bars(bars, true);
}
else
{
ui_do_label(&votearea, "No vote in progress", 18.0f, -1);
}*/
}
void MENUS::render_serverinfo(RECT main_view)
{
// fetch server info
SERVER_INFO current_server_info;
client_serverinfo(&current_server_info);
if(!gameclient.snap.local_info)
return;
// count players for server info-box
int num_players = 0;
for(int i = 0; i < snap_num_items(SNAP_CURRENT); i++)
{
SNAP_ITEM item;
snap_get_item(SNAP_CURRENT, i, &item);
if(item.type == NETOBJTYPE_PLAYER_INFO)
{
num_players++;
}
}
// render background
ui_draw_rect(&main_view, color_tabbar_active, CORNER_ALL, 10.0f);
RECT view, serverinfo, gameinfo, motd;
float x = 0.0f;
float y = 0.0f;
char buf[1024];
// set view to use for all sub-modules
ui_margin(&main_view, 10.0f, &view);
/* serverinfo */
ui_hsplit_t(&view, view.h/2-5.0f, &serverinfo, &motd);
ui_vsplit_l(&serverinfo, view.w/2-5.0f, &serverinfo, &gameinfo);
ui_draw_rect(&serverinfo, vec4(1,1,1,0.25f), CORNER_ALL, 10.0f);
ui_margin(&serverinfo, 5.0f, &serverinfo);
x = 5.0f;
y = 0.0f;
gfx_text(0, serverinfo.x+x, serverinfo.y+y, 32, "Server info", 250);
y += 32.0f+5.0f;
mem_zero(buf, sizeof(buf));
str_format(
buf,
sizeof(buf),
"%s\n\n"
"Address: %s\n"
"Ping: %d\n"
"Version: %s\n"
"Password: %s\n",
current_server_info.name,
config.ui_server_address,
gameclient.snap.local_info->latency,
current_server_info.version,
current_server_info.flags&1 ? "Yes" : "No"
);
gfx_text(0, serverinfo.x+x, serverinfo.y+y, 20, buf, 250);
{
RECT button;
int is_favorite = client_serverbrowse_isfavorite(current_server_info.netaddr);
ui_hsplit_b(&serverinfo, 20.0f, &serverinfo, &button);
static int add_fav_button = 0;
if (ui_do_button(&add_fav_button, "Favorite", is_favorite, &button, ui_draw_checkbox, 0))
{
if(is_favorite)
client_serverbrowse_removefavorite(current_server_info.netaddr);
else
client_serverbrowse_addfavorite(current_server_info.netaddr);
}
}
/* gameinfo */
ui_vsplit_l(&gameinfo, 10.0f, 0x0, &gameinfo);
ui_draw_rect(&gameinfo, vec4(1,1,1,0.25f), CORNER_ALL, 10.0f);
ui_margin(&gameinfo, 5.0f, &gameinfo);
x = 5.0f;
y = 0.0f;
gfx_text(0, gameinfo.x+x, gameinfo.y+y, 32, "Game info", 250);
y += 32.0f+5.0f;
mem_zero(buf, sizeof(buf));
str_format(
buf,
sizeof(buf),
"\n\n"
"Gametype: %s\n"
"Map: %s\n"
"Score limit: %d\n"
"Time limit: %d\n"
"\n"
"Players: %d/%d\n",
current_server_info.gametype,
current_server_info.map,
gameclient.snap.gameobj->score_limit,
gameclient.snap.gameobj->time_limit,
gameclient.snap.num_players,
current_server_info.max_players
);
gfx_text(0, gameinfo.x+x, gameinfo.y+y, 20, buf, 250);
/* motd */
ui_hsplit_t(&motd, 10.0f, 0, &motd);
ui_draw_rect(&motd, vec4(1,1,1,0.25f), CORNER_ALL, 10.0f);
ui_margin(&motd, 5.0f, &motd);
y = 0.0f;
x = 5.0f;
gfx_text(0, motd.x+x, motd.y+y, 32, "MOTD", -1);
y += 32.0f+5.0f;
gfx_text(0, motd.x+x, motd.y+y, 16, gameclient.motd->server_motd, (int)motd.w);
}
static const char *format_command(const char *cmd)
{
return cmd;
}
void MENUS::render_servercontrol_server(RECT main_view)
{
int num_options = 0;
for(VOTING::VOTEOPTION *option = gameclient.voting->first; option; option = option->next)
num_options++;
static int votelist = 0;
RECT list = main_view;
ui_do_listbox_start(&votelist, &list, 24.0f, "Options", num_options, callvote_selectedoption);
for(VOTING::VOTEOPTION *option = gameclient.voting->first; option; option = option->next)
{
LISTBOXITEM item = ui_do_listbox_nextitem(option);
if(item.visible)
ui_do_label(&item.rect, format_command(option->command), 16.0f, -1);
}
callvote_selectedoption = ui_do_listbox_end();
}
void MENUS::render_servercontrol_kick(RECT main_view)
{
// draw header
RECT header, footer;
ui_hsplit_t(&main_view, 20, &header, &main_view);
ui_draw_rect(&header, vec4(1,1,1,0.25f), CORNER_T, 5.0f);
ui_do_label(&header, "Players", 18.0f, 0);
// draw footers
ui_hsplit_b(&main_view, 20, &main_view, &footer);
ui_draw_rect(&footer, vec4(1,1,1,0.25f), CORNER_B, 5.0f);
ui_vsplit_l(&footer, 10.0f, 0, &footer);
// players
ui_draw_rect(&main_view, vec4(0,0,0,0.15f), 0, 0);
RECT list = main_view;
for(int i = 0; i < MAX_CLIENTS; i++)
{
if(!gameclient.snap.player_infos[i])
continue;
RECT button;
ui_hsplit_t(&list, button_height, &button, &list);
if(ui_do_button((char *)&gameclient.snap+i, "", callvote_selectedplayer == i, &button, ui_draw_list_row, 0))
callvote_selectedplayer = i;
TEE_RENDER_INFO info = gameclient.clients[i].render_info;
info.size = button.h;
render_tee(ANIMSTATE::get_idle(), &info, EMOTE_NORMAL, vec2(1,0), vec2(button.x+button.h/2, button.y+button.h/2));
button.x += button.h;
ui_do_label(&button, gameclient.clients[i].name, 18.0f, -1);
}
}
void MENUS::render_servercontrol(RECT main_view)
{
static int control_page = 0;
// render background
RECT temp, tabbar;
ui_vsplit_r(&main_view, 120.0f, &main_view, &tabbar);
ui_draw_rect(&main_view, color_tabbar_active, CORNER_B|CORNER_TL, 10.0f);
ui_hsplit_t(&tabbar, 50.0f, &temp, &tabbar);
ui_draw_rect(&temp, color_tabbar_active, CORNER_R, 10.0f);
ui_hsplit_t(&main_view, 10.0f, 0, &main_view);
RECT button;
const char *tabs[] = {"Options", "Kick"};
int num_tabs = (int)(sizeof(tabs)/sizeof(*tabs));
for(int i = 0; i < num_tabs; i++)
{
ui_hsplit_t(&tabbar, 10, &button, &tabbar);
ui_hsplit_t(&tabbar, 26, &button, &tabbar);
if(ui_do_button(tabs[i], tabs[i], control_page == i, &button, ui_draw_settings_tab_button, 0))
{
control_page = i;
callvote_selectedplayer = -1;
callvote_selectedoption = -1;
}
}
ui_margin(&main_view, 10.0f, &main_view);
RECT bottom;
ui_hsplit_b(&main_view, button_height + 5*2, &main_view, &bottom);
ui_hmargin(&bottom, 5.0f, &bottom);
// render page
if(control_page == 0)
render_servercontrol_server(main_view);
else if(control_page == 1)
render_servercontrol_kick(main_view);
{
RECT button;
ui_vsplit_r(&bottom, 120.0f, &bottom, &button);
static int callvote_button = 0;
if(ui_do_button(&callvote_button, "Call vote", 0, &button, ui_draw_menu_button, 0))
{
if(control_page == 0)
{
//
gameclient.voting->callvote_option(callvote_selectedoption);
/*
if(callvote_selectedmap >= 0 && callvote_selectedmap < gameclient.maplist->num())
gameclient.voting->callvote_map(gameclient.maplist->name(callvote_selectedmap));*/
}
else if(control_page == 1)
{
if(callvote_selectedplayer >= 0 && callvote_selectedplayer < MAX_CLIENTS &&
gameclient.snap.player_infos[callvote_selectedplayer])
{
gameclient.voting->callvote_kick(callvote_selectedplayer);
set_active(false);
}
}
}
}
}

View File

@@ -0,0 +1,667 @@
#include <base/math.hpp>
#include <string.h> // strcmp, strlen, strncpy
#include <stdlib.h> // atoi
#include <engine/e_client_interface.h>
#include <game/generated/g_protocol.hpp>
#include <game/generated/gc_data.hpp>
#include <game/client/ui.hpp>
#include <game/client/render.hpp>
#include <game/client/gameclient.hpp>
#include <game/client/animstate.hpp>
#include "binds.hpp"
#include "menus.hpp"
#include "skins.hpp"
MENUS_KEYBINDER MENUS::binder;
MENUS_KEYBINDER::MENUS_KEYBINDER()
{
take_key = false;
got_key = false;
}
bool MENUS_KEYBINDER::on_input(INPUT_EVENT e)
{
if(take_key)
{
if(e.flags&INPFLAG_PRESS && e.key != KEY_ESCAPE)
{
key = e;
got_key = true;
take_key = false;
}
return true;
}
return false;
}
void MENUS::render_settings_player(RECT main_view)
{
RECT button;
RECT skinselection;
ui_vsplit_l(&main_view, 300.0f, &main_view, &skinselection);
ui_hsplit_t(&main_view, 20.0f, &button, &main_view);
// render settings
{
ui_hsplit_t(&main_view, 20.0f, &button, &main_view);
ui_do_label(&button, "Name:", 14.0, -1);
ui_vsplit_l(&button, 80.0f, 0, &button);
ui_vsplit_l(&button, 180.0f, &button, 0);
if(ui_do_edit_box(config.player_name, &button, config.player_name, sizeof(config.player_name), 14.0f))
need_sendinfo = true;
static int dynamic_camera_button = 0;
ui_hsplit_t(&main_view, 20.0f, &button, &main_view);
if(ui_do_button(&dynamic_camera_button, "Dynamic Camera", config.cl_mouse_deadzone != 0, &button, ui_draw_checkbox, 0))
{
if(config.cl_mouse_deadzone)
{
config.cl_mouse_followfactor = 0;
config.cl_mouse_max_distance = 400;
config.cl_mouse_deadzone = 0;
}
else
{
config.cl_mouse_followfactor = 60;
config.cl_mouse_max_distance = 1000;
config.cl_mouse_deadzone = 300;
}
}
ui_hsplit_t(&main_view, 20.0f, &button, &main_view);
if (ui_do_button(&config.cl_autoswitch_weapons, "Switch weapon on pickup", config.cl_autoswitch_weapons, &button, ui_draw_checkbox, 0))
config.cl_autoswitch_weapons ^= 1;
ui_hsplit_t(&main_view, 20.0f, &button, &main_view);
if (ui_do_button(&config.cl_nameplates, "Show name plates", config.cl_nameplates, &button, ui_draw_checkbox, 0))
config.cl_nameplates ^= 1;
//if(config.cl_nameplates)
{
ui_hsplit_t(&main_view, 20.0f, &button, &main_view);
ui_vsplit_l(&button, 15.0f, 0, &button);
if (ui_do_button(&config.cl_nameplates_always, "Always show name plates", config.cl_nameplates_always, &button, ui_draw_checkbox, 0))
config.cl_nameplates_always ^= 1;
}
ui_hsplit_t(&main_view, 20.0f, &button, &main_view);
ui_hsplit_t(&main_view, 20.0f, &button, &main_view);
if (ui_do_button(&config.player_color_body, "Custom colors", config.player_use_custom_color, &button, ui_draw_checkbox, 0))
{
config.player_use_custom_color = config.player_use_custom_color?0:1;
need_sendinfo = true;
}
if(config.player_use_custom_color)
{
int *colors[2];
colors[0] = &config.player_color_body;
colors[1] = &config.player_color_feet;
const char *parts[] = {"Body", "Feet"};
const char *labels[] = {"Hue", "Sat.", "Lht."};
static int color_slider[2][3] = {{0}};
//static float v[2][3] = {{0, 0.5f, 0.25f}, {0, 0.5f, 0.25f}};
for(int i = 0; i < 2; i++)
{
RECT text;
ui_hsplit_t(&main_view, 20.0f, &text, &main_view);
ui_vsplit_l(&text, 15.0f, 0, &text);
ui_do_label(&text, parts[i], 14.0f, -1);
int prevcolor = *colors[i];
int color = 0;
for(int s = 0; s < 3; s++)
{
RECT text;
ui_hsplit_t(&main_view, 19.0f, &button, &main_view);
ui_vsplit_l(&button, 30.0f, 0, &button);
ui_vsplit_l(&button, 30.0f, &text, &button);
ui_vsplit_r(&button, 5.0f, &button, 0);
ui_hsplit_t(&button, 4.0f, 0, &button);
float k = ((prevcolor>>((2-s)*8))&0xff) / 255.0f;
k = ui_do_scrollbar_h(&color_slider[i][s], &button, k);
color <<= 8;
color += clamp((int)(k*255), 0, 255);
ui_do_label(&text, labels[s], 15.0f, -1);
}
if(*colors[i] != color)
need_sendinfo = true;
*colors[i] = color;
ui_hsplit_t(&main_view, 5.0f, 0, &main_view);
}
}
}
// draw header
RECT header, footer;
ui_hsplit_t(&skinselection, 20, &header, &skinselection);
ui_draw_rect(&header, vec4(1,1,1,0.25f), CORNER_T, 5.0f);
ui_do_label(&header, "Skins", 18.0f, 0);
// draw footers
ui_hsplit_b(&skinselection, 20, &skinselection, &footer);
ui_draw_rect(&footer, vec4(1,1,1,0.25f), CORNER_B, 5.0f);
ui_vsplit_l(&footer, 10.0f, 0, &footer);
// modes
ui_draw_rect(&skinselection, vec4(0,0,0,0.15f), 0, 0);
RECT scroll;
ui_vsplit_r(&skinselection, 15, &skinselection, &scroll);
RECT list = skinselection;
ui_hsplit_t(&list, 50, &button, &list);
int num = (int)(skinselection.h/button.h);
static float scrollvalue = 0;
static int scrollbar = 0;
ui_hmargin(&scroll, 5.0f, &scroll);
scrollvalue = ui_do_scrollbar_v(&scrollbar, &scroll, scrollvalue);
int start = (int)((gameclient.skins->num()-num)*scrollvalue);
if(start < 0)
start = 0;
for(int i = start; i < start+num && i < gameclient.skins->num(); i++)
{
const SKINS::SKIN *s = gameclient.skins->get(i);
// no special skins
if(s->name[0] == 'x' && s->name[1] == '_')
{
num++;
continue;
}
char buf[128];
str_format(buf, sizeof(buf), "%s", s->name);
int selected = 0;
if(strcmp(s->name, config.player_skin) == 0)
selected = 1;
TEE_RENDER_INFO info;
info.texture = s->org_texture;
info.color_body = vec4(1,1,1,1);
info.color_feet = vec4(1,1,1,1);
if(config.player_use_custom_color)
{
info.color_body = gameclient.skins->get_color(config.player_color_body);
info.color_feet = gameclient.skins->get_color(config.player_color_feet);
info.texture = s->color_texture;
}
info.size = ui_scale()*50.0f;
RECT icon;
RECT text;
ui_vsplit_l(&button, 50.0f, &icon, &text);
if(ui_do_button(s, "", selected, &button, ui_draw_list_row, 0))
{
config_set_player_skin(&config, s->name);
need_sendinfo = true;
}
ui_hsplit_t(&text, 12.0f, 0, &text); // some margin from the top
ui_do_label(&text, buf, 18.0f, 0);
ui_hsplit_t(&icon, 5.0f, 0, &icon); // some margin from the top
render_tee(ANIMSTATE::get_idle(), &info, 0, vec2(1, 0), vec2(icon.x+icon.w/2, icon.y+icon.h/2));
if(config.debug)
{
gfx_texture_set(-1);
gfx_quads_begin();
gfx_setcolor(s->blood_color.r, s->blood_color.g, s->blood_color.b, 1.0f);
gfx_quads_drawTL(icon.x, icon.y, 12, 12);
gfx_quads_end();
}
ui_hsplit_t(&list, 50, &button, &list);
}
}
typedef void (*assign_func_callback)(CONFIGURATION *config, int value);
typedef struct
{
const char *name;
const char *command;
int keyid;
} KEYINFO;
KEYINFO keys[] =
{
{ "Move Left:", "+left", 0},
{ "Move Right:", "+right", 0 },
{ "Jump:", "+jump", 0 },
{ "Fire:", "+fire", 0 },
{ "Hook:", "+hook", 0 },
{ "Hammer:", "+weapon1", 0 },
{ "Pistol:", "+weapon2", 0 },
{ "Shotgun:", "+weapon3", 0 },
{ "Grenade:", "+weapon4", 0 },
{ "Rifle:", "+weapon5", 0 },
{ "Next Weapon:", "+nextweapon", 0 },
{ "Prev. Weapon:", "+prevweapon", 0 },
{ "Vote Yes:", "vote yes", 0 },
{ "Vote No:", "vote no", 0 },
{ "Chat:", "chat all", 0 },
{ "Team Chat:", "chat team", 0 },
{ "Emoticon:", "+emote", 0 },
{ "Console:", "toggle_local_console", 0 },
{ "Remote Console:", "toggle_remote_console", 0 },
{ "Screenshot:", "screenshot", 0 },
{ "Scoreboard:", "+scoreboard", 0 },
};
const int key_count = sizeof(keys) / sizeof(KEYINFO);
void MENUS::ui_do_getbuttons(int start, int stop, RECT view)
{
for (int i = start; i < stop; i++)
{
KEYINFO key = keys[i];
RECT button, label;
ui_hsplit_t(&view, 20.0f, &button, &view);
ui_vsplit_l(&button, 130.0f, &label, &button);
ui_do_label(&label, key.name, 14.0f, -1);
int oldid = key.keyid;
int newid = ui_do_key_reader((void *)keys[i].name, &button, oldid);
if(newid != oldid)
{
gameclient.binds->bind(oldid, "");
gameclient.binds->bind(newid, keys[i].command);
}
ui_hsplit_t(&view, 5.0f, 0, &view);
}
}
void MENUS::render_settings_controls(RECT main_view)
{
// this is kinda slow, but whatever
for(int i = 0; i < key_count; i++)
keys[i].keyid = 0;
for(int keyid = 0; keyid < KEY_LAST; keyid++)
{
const char *bind = gameclient.binds->get(keyid);
if(!bind[0])
continue;
for(int i = 0; i < key_count; i++)
if(strcmp(bind, keys[i].command) == 0)
{
keys[i].keyid = keyid;
break;
}
}
RECT movement_settings, weapon_settings, voting_settings, chat_settings, misc_settings, reset_button;
ui_vsplit_l(&main_view, main_view.w/2-5.0f, &movement_settings, &voting_settings);
/* movement settings */
{
ui_hsplit_t(&movement_settings, main_view.h/2-5.0f, &movement_settings, &weapon_settings);
ui_draw_rect(&movement_settings, vec4(1,1,1,0.25f), CORNER_ALL, 10.0f);
ui_margin(&movement_settings, 10.0f, &movement_settings);
gfx_text(0, movement_settings.x, movement_settings.y, 14, "Movement", -1);
ui_hsplit_t(&movement_settings, 14.0f+5.0f+10.0f, 0, &movement_settings);
{
RECT button, label;
ui_hsplit_t(&movement_settings, 20.0f, &button, &movement_settings);
ui_vsplit_l(&button, 130.0f, &label, &button);
ui_do_label(&label, "Mouse sens.", 14.0f, -1);
ui_hmargin(&button, 2.0f, &button);
config.inp_mousesens = (int)(ui_do_scrollbar_h(&config.inp_mousesens, &button, (config.inp_mousesens-5)/500.0f)*500.0f)+5;
//*key.key = ui_do_key_reader(key.key, &button, *key.key);
ui_hsplit_t(&movement_settings, 20.0f, 0, &movement_settings);
}
ui_do_getbuttons(0, 5, movement_settings);
}
/* weapon settings */
{
ui_hsplit_t(&weapon_settings, 10.0f, 0, &weapon_settings);
ui_draw_rect(&weapon_settings, vec4(1,1,1,0.25f), CORNER_ALL, 10.0f);
ui_margin(&weapon_settings, 10.0f, &weapon_settings);
gfx_text(0, weapon_settings.x, weapon_settings.y, 14, "Weapon", -1);
ui_hsplit_t(&weapon_settings, 14.0f+5.0f+10.0f, 0, &weapon_settings);
ui_do_getbuttons(5, 12, weapon_settings);
}
/* voting settings */
{
ui_vsplit_l(&voting_settings, 10.0f, 0, &voting_settings);
ui_hsplit_t(&voting_settings, main_view.h/4-5.0f, &voting_settings, &chat_settings);
ui_draw_rect(&voting_settings, vec4(1,1,1,0.25f), CORNER_ALL, 10.0f);
ui_margin(&voting_settings, 10.0f, &voting_settings);
gfx_text(0, voting_settings.x, voting_settings.y, 14, "Voting", -1);
ui_hsplit_t(&voting_settings, 14.0f+5.0f+10.0f, 0, &voting_settings);
ui_do_getbuttons(12, 14, voting_settings);
}
/* chat settings */
{
ui_hsplit_t(&chat_settings, 10.0f, 0, &chat_settings);
ui_hsplit_t(&chat_settings, main_view.h/4-10.0f, &chat_settings, &misc_settings);
ui_draw_rect(&chat_settings, vec4(1,1,1,0.25f), CORNER_ALL, 10.0f);
ui_margin(&chat_settings, 10.0f, &chat_settings);
gfx_text(0, chat_settings.x, chat_settings.y, 14, "Chat", -1);
ui_hsplit_t(&chat_settings, 14.0f+5.0f+10.0f, 0, &chat_settings);
ui_do_getbuttons(14, 16, chat_settings);
}
/* misc settings */
{
ui_hsplit_t(&misc_settings, 10.0f, 0, &misc_settings);
ui_hsplit_t(&misc_settings, main_view.h/2-5.0f-45.0f, &misc_settings, &reset_button);
ui_draw_rect(&misc_settings, vec4(1,1,1,0.25f), CORNER_ALL, 10.0f);
ui_margin(&misc_settings, 10.0f, &misc_settings);
gfx_text(0, misc_settings.x, misc_settings.y, 14, "Miscellaneous", -1);
ui_hsplit_t(&misc_settings, 14.0f+5.0f+10.0f, 0, &misc_settings);
ui_do_getbuttons(16, 21, misc_settings);
}
// defaults
ui_hsplit_t(&reset_button, 10.0f, 0, &reset_button);
static int default_button = 0;
if (ui_do_button((void*)&default_button, "Reset to defaults", 0, &reset_button, ui_draw_menu_button, 0))
gameclient.binds->set_defaults();
}
void MENUS::render_settings_graphics(RECT main_view)
{
RECT button;
char buf[128];
static const int MAX_RESOLUTIONS = 256;
static VIDEO_MODE modes[MAX_RESOLUTIONS];
static int num_modes = -1;
if(num_modes == -1)
num_modes = gfx_get_video_modes(modes, MAX_RESOLUTIONS);
RECT modelist;
ui_vsplit_l(&main_view, 300.0f, &main_view, &modelist);
// draw allmodes switch
RECT header, footer;
ui_hsplit_t(&modelist, 20, &button, &modelist);
if (ui_do_button(&config.gfx_display_all_modes, "Show only supported", config.gfx_display_all_modes^1, &button, ui_draw_checkbox, 0))
{
config.gfx_display_all_modes ^= 1;
num_modes = gfx_get_video_modes(modes, MAX_RESOLUTIONS);
}
// draw header
ui_hsplit_t(&modelist, 20, &header, &modelist);
ui_draw_rect(&header, vec4(1,1,1,0.25f), CORNER_T, 5.0f);
ui_do_label(&header, "Display Modes", 14.0f, 0);
// draw footers
ui_hsplit_b(&modelist, 20, &modelist, &footer);
str_format(buf, sizeof(buf), "Current: %dx%d %d bit", config.gfx_screen_width, config.gfx_screen_height, config.gfx_color_depth);
ui_draw_rect(&footer, vec4(1,1,1,0.25f), CORNER_B, 5.0f);
ui_vsplit_l(&footer, 10.0f, 0, &footer);
ui_do_label(&footer, buf, 14.0f, -1);
// modes
ui_draw_rect(&modelist, vec4(0,0,0,0.15f), 0, 0);
RECT scroll;
ui_vsplit_r(&modelist, 15, &modelist, &scroll);
RECT list = modelist;
ui_hsplit_t(&list, 20, &button, &list);
int num = (int)(modelist.h/button.h);
static float scrollvalue = 0;
static int scrollbar = 0;
ui_hmargin(&scroll, 5.0f, &scroll);
scrollvalue = ui_do_scrollbar_v(&scrollbar, &scroll, scrollvalue);
int start = (int)((num_modes-num)*scrollvalue);
if(start < 0)
start = 0;
for(int i = start; i < start+num && i < num_modes; i++)
{
int depth = modes[i].red+modes[i].green+modes[i].blue;
if(depth < 16)
depth = 16;
else if(depth > 16)
depth = 24;
int selected = 0;
if(config.gfx_color_depth == depth &&
config.gfx_screen_width == modes[i].width &&
config.gfx_screen_height == modes[i].height)
{
selected = 1;
}
str_format(buf, sizeof(buf), " %dx%d %d bit", modes[i].width, modes[i].height, depth);
if(ui_do_button(&modes[i], buf, selected, &button, ui_draw_list_row, 0))
{
config.gfx_color_depth = depth;
config.gfx_screen_width = modes[i].width;
config.gfx_screen_height = modes[i].height;
if(!selected)
need_restart = true;
}
ui_hsplit_t(&list, 20, &button, &list);
}
// switches
ui_hsplit_t(&main_view, 20.0f, &button, &main_view);
if (ui_do_button(&config.gfx_fullscreen, "Fullscreen", config.gfx_fullscreen, &button, ui_draw_checkbox, 0))
{
config.gfx_fullscreen ^= 1;
need_restart = true;
}
ui_hsplit_t(&main_view, 20.0f, &button, &main_view);
if (ui_do_button(&config.gfx_vsync, "V-Sync", config.gfx_vsync, &button, ui_draw_checkbox, 0))
{
config.gfx_vsync ^= 1;
need_restart = true;
}
ui_hsplit_t(&main_view, 20.0f, &button, &main_view);
if (ui_do_button(&config.gfx_fsaa_samples, "FSAA samples", config.gfx_fsaa_samples, &button, ui_draw_checkbox_number, 0))
{
config.gfx_fsaa_samples = (config.gfx_fsaa_samples+1)%17;
need_restart = true;
}
ui_hsplit_t(&main_view, 40.0f, &button, &main_view);
ui_hsplit_t(&main_view, 20.0f, &button, &main_view);
if (ui_do_button(&config.gfx_texture_quality, "Quality Textures", config.gfx_texture_quality, &button, ui_draw_checkbox, 0))
{
config.gfx_texture_quality ^= 1;
need_restart = true;
}
ui_hsplit_t(&main_view, 20.0f, &button, &main_view);
if (ui_do_button(&config.gfx_texture_compression, "Texture Compression", config.gfx_texture_compression, &button, ui_draw_checkbox, 0))
{
config.gfx_texture_compression ^= 1;
need_restart = true;
}
ui_hsplit_t(&main_view, 20.0f, &button, &main_view);
if (ui_do_button(&config.gfx_high_detail, "High Detail", config.gfx_high_detail, &button, ui_draw_checkbox, 0))
config.gfx_high_detail ^= 1;
//
RECT text;
ui_hsplit_t(&main_view, 20.0f, 0, &main_view);
ui_hsplit_t(&main_view, 20.0f, &text, &main_view);
//ui_vsplit_l(&text, 15.0f, 0, &text);
ui_do_label(&text, "UI Color", 14.0f, -1);
const char *labels[] = {"Hue", "Sat.", "Lht.", "Alpha"};
int *color_slider[4] = {&config.ui_color_hue, &config.ui_color_sat, &config.ui_color_lht, &config.ui_color_alpha};
for(int s = 0; s < 4; s++)
{
RECT text;
ui_hsplit_t(&main_view, 19.0f, &button, &main_view);
ui_vmargin(&button, 15.0f, &button);
ui_vsplit_l(&button, 50.0f, &text, &button);
ui_vsplit_r(&button, 5.0f, &button, 0);
ui_hsplit_t(&button, 4.0f, 0, &button);
float k = (*color_slider[s]) / 255.0f;
k = ui_do_scrollbar_h(color_slider[s], &button, k);
*color_slider[s] = (int)(k*255.0f);
ui_do_label(&text, labels[s], 15.0f, -1);
}
}
void MENUS::render_settings_sound(RECT main_view)
{
RECT button;
ui_vsplit_l(&main_view, 300.0f, &main_view, 0);
ui_hsplit_t(&main_view, 20.0f, &button, &main_view);
if (ui_do_button(&config.snd_enable, "Use Sounds", config.snd_enable, &button, ui_draw_checkbox, 0))
{
config.snd_enable ^= 1;
need_restart = true;
}
if(!config.snd_enable)
return;
ui_hsplit_t(&main_view, 20.0f, &button, &main_view);
if (ui_do_button(&config.snd_nonactive_mute, "Mute when not active", config.snd_nonactive_mute, &button, ui_draw_checkbox, 0))
config.snd_nonactive_mute ^= 1;
// sample rate box
{
char buf[64];
str_format(buf, sizeof(buf), "%d", config.snd_rate);
ui_hsplit_t(&main_view, 20.0f, &button, &main_view);
ui_do_label(&button, "Sample Rate", 14.0f, -1);
ui_vsplit_l(&button, 110.0f, 0, &button);
ui_vsplit_l(&button, 180.0f, &button, 0);
ui_do_edit_box(&config.snd_rate, &button, buf, sizeof(buf), 14.0f);
int before = config.snd_rate;
config.snd_rate = atoi(buf);
if(config.snd_rate != before)
need_restart = true;
if(config.snd_rate < 1)
config.snd_rate = 1;
}
// volume slider
{
RECT button, label;
ui_hsplit_t(&main_view, 5.0f, &button, &main_view);
ui_hsplit_t(&main_view, 20.0f, &button, &main_view);
ui_vsplit_l(&button, 110.0f, &label, &button);
ui_hmargin(&button, 2.0f, &button);
ui_do_label(&label, "Sound Volume", 14.0f, -1);
config.snd_volume = (int)(ui_do_scrollbar_h(&config.snd_volume, &button, config.snd_volume/100.0f)*100.0f);
ui_hsplit_t(&main_view, 20.0f, 0, &main_view);
}
}
/*
static void menu2_render_settings_network(RECT main_view)
{
RECT button;
ui_vsplit_l(&main_view, 300.0f, &main_view, 0);
{
ui_hsplit_t(&main_view, 20.0f, &button, &main_view);
ui_do_label(&button, "Rcon Password", 14.0, -1);
ui_vsplit_l(&button, 110.0f, 0, &button);
ui_vsplit_l(&button, 180.0f, &button, 0);
ui_do_edit_box(&config.rcon_password, &button, config.rcon_password, sizeof(config.rcon_password), true);
}
}*/
void MENUS::render_settings(RECT main_view)
{
static int settings_page = 0;
// render background
RECT temp, tabbar;
ui_vsplit_r(&main_view, 120.0f, &main_view, &tabbar);
ui_draw_rect(&main_view, color_tabbar_active, CORNER_B|CORNER_TL, 10.0f);
ui_hsplit_t(&tabbar, 50.0f, &temp, &tabbar);
ui_draw_rect(&temp, color_tabbar_active, CORNER_R, 10.0f);
ui_hsplit_t(&main_view, 10.0f, 0, &main_view);
RECT button;
const char *tabs[] = {"Player", "Controls", "Graphics", "Sound"};
int num_tabs = (int)(sizeof(tabs)/sizeof(*tabs));
for(int i = 0; i < num_tabs; i++)
{
ui_hsplit_t(&tabbar, 10, &button, &tabbar);
ui_hsplit_t(&tabbar, 26, &button, &tabbar);
if(ui_do_button(tabs[i], tabs[i], settings_page == i, &button, ui_draw_settings_tab_button, 0))
settings_page = i;
}
ui_margin(&main_view, 10.0f, &main_view);
if(settings_page == 0)
render_settings_player(main_view);
else if(settings_page == 1)
render_settings_controls(main_view);
else if(settings_page == 2)
render_settings_graphics(main_view);
else if(settings_page == 3)
render_settings_sound(main_view);
if(need_restart)
{
RECT restart_warning;
ui_hsplit_b(&main_view, 40, &main_view, &restart_warning);
ui_do_label(&restart_warning, "You must restart the game for all settings to take effect.", 15.0f, -1, 220);
}
}

View File

@@ -0,0 +1,87 @@
#include <engine/e_client_interface.h>
#include <engine/e_config.h>
#include <game/generated/g_protocol.hpp>
#include <game/generated/gc_data.hpp>
#include <game/client/gameclient.hpp>
#include "motd.hpp"
void MOTD::on_reset()
{
clear();
}
void MOTD::clear()
{
server_motd_time = 0;
}
bool MOTD::is_active()
{
return time_get() < server_motd_time;
}
void MOTD::on_render()
{
if(!is_active())
return;
float width = 400*3.0f*gfx_screenaspect();
float height = 400*3.0f;
gfx_mapscreen(0, 0, width, height);
float h = 800.0f;
float w = 650.0f;
float x = width/2 - w/2;
float y = 150.0f;
gfx_blend_normal();
gfx_texture_set(-1);
gfx_quads_begin();
gfx_setcolor(0,0,0,0.5f);
draw_round_rect(x, y, w, h, 40.0f);
gfx_quads_end();
gfx_text(0, x+40.0f, y+40.0f, 32.0f, server_motd, (int)(w-80.0f));
}
void MOTD::on_message(int msgtype, void *rawmsg)
{
if(msgtype == NETMSGTYPE_SV_MOTD)
{
NETMSG_SV_MOTD *msg = (NETMSG_SV_MOTD *)rawmsg;
// process escaping
str_copy(server_motd, msg->message, sizeof(server_motd));
for(int i = 0; server_motd[i]; i++)
{
if(server_motd[i] == '\\')
{
if(server_motd[i+1] == 'n')
{
server_motd[i] = ' ';
server_motd[i+1] = '\n';
i++;
}
}
}
if(server_motd[0] && config.cl_motd_time)
server_motd_time = time_get()+time_freq()*config.cl_motd_time;
else
server_motd_time = 0;
}
}
bool MOTD::on_input(INPUT_EVENT e)
{
if(is_active() && e.flags&INPFLAG_PRESS && e.key == KEY_ESCAPE)
{
clear();
return true;
}
return false;
}

View File

@@ -0,0 +1,18 @@
#include <game/client/component.hpp>
class MOTD : public COMPONENT
{
// motd
int64 server_motd_time;
public:
char server_motd[900];
void clear();
bool is_active();
virtual void on_reset();
virtual void on_render();
virtual void on_message(int msgtype, void *rawmsg);
virtual bool on_input(INPUT_EVENT e);
};

View File

@@ -0,0 +1,65 @@
#include <engine/e_client_interface.h>
#include <game/generated/g_protocol.hpp>
#include <game/generated/gc_data.hpp>
#include <game/client/gameclient.hpp>
#include <game/client/animstate.hpp>
#include "nameplates.hpp"
#include "controls.hpp"
void NAMEPLATES::render_nameplate(
const NETOBJ_CHARACTER *prev_char,
const NETOBJ_CHARACTER *player_char,
const NETOBJ_PLAYER_INFO *player_info
)
{
float intratick = client_intratick();
vec2 position = mix(vec2(prev_char->x, prev_char->y), vec2(player_char->x, player_char->y), intratick);
// render name plate
if(!player_info->local)
{
//gfx_text_color
float a = 1;
if(config.cl_nameplates_always == 0)
a = clamp(1-powf(distance(gameclient.controls->target_pos, position)/200.0f,16.0f), 0.0f, 1.0f);
const char *name = gameclient.clients[player_info->cid].name;
float tw = gfx_text_width(0, 28.0f, name, -1);
gfx_text_color(1,1,1,a);
gfx_text(0, position.x-tw/2.0f, position.y-60, 28.0f, name, -1);
if(config.debug) // render client id when in debug aswell
{
char buf[128];
str_format(buf, sizeof(buf),"%d", player_info->cid);
gfx_text(0, position.x, position.y-90, 28.0f, buf, -1);
}
gfx_text_color(1,1,1,1);
}
}
void NAMEPLATES::on_render()
{
if (!config.cl_nameplates)
return;
for(int i = 0; i < MAX_CLIENTS; i++)
{
// only render active characters
if(!gameclient.snap.characters[i].active)
continue;
const void *info = snap_find_item(SNAP_CURRENT, NETOBJTYPE_PLAYER_INFO, i);
if(info)
{
render_nameplate(
&gameclient.snap.characters[i].prev,
&gameclient.snap.characters[i].cur,
(const NETOBJ_PLAYER_INFO *)info);
}
}
}

View File

@@ -0,0 +1,14 @@
#include <game/client/component.hpp>
class NAMEPLATES : public COMPONENT
{
void render_nameplate(
const class NETOBJ_CHARACTER *prev_char,
const class NETOBJ_CHARACTER *player_char,
const class NETOBJ_PLAYER_INFO *player_info
);
public:
virtual void on_render();
};

View File

@@ -0,0 +1,154 @@
#include <base/math.hpp>
#include <game/generated/gc_data.hpp>
#include <game/client/render.hpp>
#include <game/gamecore.hpp>
#include "particles.hpp"
PARTICLES::PARTICLES()
{
on_reset();
render_trail.parts = this;
render_explosions.parts = this;
render_general.parts = this;
}
void PARTICLES::on_reset()
{
// reset particles
for(int i = 0; i < MAX_PARTICLES; i++)
{
particles[i].prev_part = i-1;
particles[i].next_part = i+1;
}
particles[0].prev_part = 0;
particles[MAX_PARTICLES-1].next_part = -1;
first_free = 0;
for(int i = 0; i < NUM_GROUPS; i++)
first_part[i] = -1;
}
void PARTICLES::add(int group, PARTICLE *part)
{
if (first_free == -1)
return;
// remove from the free list
int id = first_free;
first_free = particles[id].next_part;
particles[first_free].prev_part = -1;
// copy data
particles[id] = *part;
// insert to the group list
particles[id].prev_part = -1;
particles[id].next_part = first_part[group];
if(first_part[group] != -1)
particles[first_part[group]].prev_part = id;
first_part[group] = id;
// set some parameters
particles[id].life = 0;
}
void PARTICLES::update(float time_passed)
{
static float friction_fraction = 0;
friction_fraction += time_passed;
if(friction_fraction > 2.0f) // safty messure
friction_fraction = 0;
int friction_count = 0;
while(friction_fraction > 0.05f)
{
friction_count++;
friction_fraction -= 0.05f;
}
for(int g = 0; g < NUM_GROUPS; g++)
{
int i = first_part[g];
while(i != -1)
{
int next = particles[i].next_part;
//particles[i].vel += flow_get(particles[i].pos)*time_passed * particles[i].flow_affected;
particles[i].vel.y += particles[i].gravity*time_passed;
for(int f = 0; f < friction_count; f++) // apply friction
particles[i].vel *= particles[i].friction;
// move the point
vec2 vel = particles[i].vel*time_passed;
move_point(&particles[i].pos, &vel, 0.1f+0.9f*frandom(), NULL);
particles[i].vel = vel* (1.0f/time_passed);
particles[i].life += time_passed;
particles[i].rot += time_passed * particles[i].rotspeed;
// check particle death
if(particles[i].life > particles[i].life_span)
{
// remove it from the group list
if(particles[i].prev_part != -1)
particles[particles[i].prev_part].next_part = particles[i].next_part;
else
first_part[g] = particles[i].next_part;
if(particles[i].next_part != -1)
particles[particles[i].next_part].prev_part = particles[i].prev_part;
// insert to the free list
if(first_free != -1)
particles[first_free].prev_part = i;
particles[i].prev_part = -1;
particles[i].next_part = first_free;
first_free = i;
}
i = next;
}
}
}
void PARTICLES::on_render()
{
static int64 lasttime = 0;
int64 t = time_get();
update((float)((t-lasttime)/(double)time_freq()));
lasttime = t;
}
void PARTICLES::render_group(int group)
{
gfx_blend_normal();
//gfx_blend_additive();
gfx_texture_set(data->images[IMAGE_PARTICLES].id);
gfx_quads_begin();
int i = first_part[group];
while(i != -1)
{
select_sprite(particles[i].spr);
float a = particles[i].life / particles[i].life_span;
vec2 p = particles[i].pos;
float size = mix(particles[i].start_size, particles[i].end_size, a);
gfx_quads_setrotation(particles[i].rot);
gfx_setcolor(
particles[i].color.r,
particles[i].color.g,
particles[i].color.b,
particles[i].color.a); // pow(a, 0.75f) *
gfx_quads_draw(p.x, p.y, size, size);
i = particles[i].next_part;
}
gfx_quads_end();
gfx_blend_normal();
}

View File

@@ -0,0 +1,91 @@
#include <base/vmath.hpp>
#include <game/client/component.hpp>
// particles
struct PARTICLE
{
void set_default()
{
vel = vec2(0,0);
life_span = 0;
start_size = 32;
end_size = 32;
rot = 0;
rotspeed = 0;
gravity = 0;
friction = 0;
flow_affected = 1.0f;
color = vec4(1,1,1,1);
}
vec2 pos;
vec2 vel;
int spr;
float flow_affected;
float life_span;
float start_size;
float end_size;
float rot;
float rotspeed;
float gravity;
float friction;
vec4 color;
// set by the particle system
float life;
int prev_part;
int next_part;
};
class PARTICLES : public COMPONENT
{
friend class GAMECLIENT;
public:
enum
{
GROUP_PROJECTILE_TRAIL=0,
GROUP_EXPLOSIONS,
GROUP_GENERAL,
NUM_GROUPS
};
PARTICLES();
void add(int group, PARTICLE *part);
virtual void on_reset();
virtual void on_render();
private:
enum
{
MAX_PARTICLES=1024*8,
};
PARTICLE particles[MAX_PARTICLES];
int first_free;
int first_part[NUM_GROUPS];
void render_group(int group);
void update(float time_passed);
template<int TGROUP>
class RENDER_GROUP : public COMPONENT
{
public:
PARTICLES *parts;
virtual void on_render() { parts->render_group(TGROUP); }
};
RENDER_GROUP<GROUP_PROJECTILE_TRAIL> render_trail;
RENDER_GROUP<GROUP_EXPLOSIONS> render_explosions;
RENDER_GROUP<GROUP_GENERAL> render_general;
};

View File

@@ -0,0 +1,494 @@
#include <engine/e_client_interface.h>
#include <game/generated/g_protocol.hpp>
#include <game/generated/gc_data.hpp>
#include <game/gamecore.hpp> // get_angle
#include <game/client/animstate.hpp>
#include <game/client/gameclient.hpp>
#include <game/client/ui.hpp>
#include <game/client/render.hpp>
#include <game/client/components/flow.hpp>
#include <game/client/components/skins.hpp>
#include <game/client/components/effects.hpp>
#include <game/client/components/sounds.hpp>
#include <game/client/components/controls.hpp>
#include "players.hpp"
void PLAYERS::render_hand(TEE_RENDER_INFO *info, vec2 center_pos, vec2 dir, float angle_offset, vec2 post_rot_offset)
{
// for drawing hand
//const skin *s = skin_get(skin_id);
float basesize = 10.0f;
//dir = normalize(hook_pos-pos);
vec2 hand_pos = center_pos + dir;
float angle = get_angle(dir);
if (dir.x < 0)
angle -= angle_offset;
else
angle += angle_offset;
vec2 dirx = dir;
vec2 diry(-dir.y,dir.x);
if (dir.x < 0)
diry = -diry;
hand_pos += dirx * post_rot_offset.x;
hand_pos += diry * post_rot_offset.y;
//gfx_texture_set(data->images[IMAGE_CHAR_DEFAULT].id);
gfx_texture_set(info->texture);
gfx_quads_begin();
gfx_setcolor(info->color_body.r, info->color_body.g, info->color_body.b, info->color_body.a);
// two passes
for (int i = 0; i < 2; i++)
{
bool outline = i == 0;
select_sprite(outline?SPRITE_TEE_HAND_OUTLINE:SPRITE_TEE_HAND, 0, 0, 0);
gfx_quads_setrotation(angle);
gfx_quads_draw(hand_pos.x, hand_pos.y, 2*basesize, 2*basesize);
}
gfx_quads_setrotation(0);
gfx_quads_end();
}
inline float normalize_angular(float f)
{
return fmod(f+pi*2, pi*2);
}
inline float angular_mix_direction(float src, float dst) { return sinf(dst-src) >0?1:-1; }
inline float angular_distance(float src, float dst) { return asinf(sinf(dst-src)); }
inline float angular_approach(float src, float dst, float amount)
{
float d = angular_mix_direction(src, dst);
float n = src + amount*d;
if(angular_mix_direction(n, dst) != d)
return dst;
return n;
}
void PLAYERS::render_player(
const NETOBJ_CHARACTER *prev_char,
const NETOBJ_CHARACTER *player_char,
const NETOBJ_PLAYER_INFO *prev_info,
const NETOBJ_PLAYER_INFO *player_info
)
{
NETOBJ_CHARACTER prev;
NETOBJ_CHARACTER player;
prev = *prev_char;
player = *player_char;
NETOBJ_PLAYER_INFO info = *player_info;
TEE_RENDER_INFO render_info = gameclient.clients[info.cid].render_info;
// check for teamplay modes
bool is_teamplay = false;
bool new_tick = gameclient.new_tick;
if(gameclient.snap.gameobj)
is_teamplay = (gameclient.snap.gameobj->flags&GAMEFLAG_TEAMS) != 0;
// check for ninja
if (player.weapon == WEAPON_NINJA)
{
// change the skin for the player to the ninja
int skin = gameclient.skins->find("x_ninja");
if(skin != -1)
{
if(is_teamplay)
render_info.texture = gameclient.skins->get(skin)->color_texture;
else
{
render_info.texture = gameclient.skins->get(skin)->org_texture;
render_info.color_body = vec4(1,1,1,1);
render_info.color_feet = vec4(1,1,1,1);
}
}
}
// set size
render_info.size = 64.0f;
float intratick = client_intratick();
if(player.health < 0) // dont render dead players
return;
float angle = mix((float)prev.angle, (float)player.angle, intratick)/256.0f;
//float angle = 0;
if(info.local && client_state() != CLIENTSTATE_DEMOPLAYBACK)
{
// just use the direct input if it's local player we are rendering
angle = get_angle(gameclient.controls->mouse_pos);
}
else
{
/*
float mixspeed = client_frametime()*2.5f;
if(player.attacktick != prev.attacktick) // shooting boosts the mixing speed
mixspeed *= 15.0f;
// move the delta on a constant speed on a x^2 curve
float current = gameclient.clients[info.cid].angle;
float target = player.angle/256.0f;
float delta = angular_distance(current, target);
float sign = delta < 0 ? -1 : 1;
float new_delta = delta - 2*mixspeed*sqrt(delta*sign)*sign + mixspeed*mixspeed;
// make sure that it doesn't vibrate when it's still
if(fabs(delta) < 2/256.0f)
angle = target;
else
angle = angular_approach(current, target, fabs(delta-new_delta));
gameclient.clients[info.cid].angle = angle;*/
}
// use preditect players if needed
if(info.local && config.cl_predict && client_state() != CLIENTSTATE_DEMOPLAYBACK)
{
if(!gameclient.snap.local_character || (gameclient.snap.local_character->health < 0) || (gameclient.snap.gameobj && gameclient.snap.gameobj->game_over))
{
}
else
{
// apply predicted results
gameclient.predicted_char.write(&player);
gameclient.predicted_prev_char.write(&prev);
intratick = client_predintratick();
new_tick = gameclient.new_predicted_tick;
}
}
vec2 direction = get_direction((int)(angle*256.0f));
vec2 position = mix(vec2(prev.x, prev.y), vec2(player.x, player.y), intratick);
vec2 vel = mix(vec2(prev.vx/256.0f, prev.vy/256.0f), vec2(player.vx/256.0f, player.vy/256.0f), intratick);
gameclient.flow->add(position, vel*100.0f, 10.0f);
render_info.got_airjump = player.jumped&2?0:1;
// detect events
if(new_tick)
{
// detect air jump
if(!render_info.got_airjump && !(prev.jumped&2))
gameclient.effects->air_jump(position);
}
if(prev.health < 0) // Don't flicker from previous position
position = vec2(player.x, player.y);
bool stationary = player.vx <= 1 && player.vx >= -1;
bool inair = col_check_point(player.x, player.y+16) == 0;
bool want_other_dir = (player.direction == -1 && vel.x > 0) || (player.direction == 1 && vel.x < 0);
// evaluate animation
float walk_time = fmod(position.x, 100.0f)/100.0f;
ANIMSTATE state;
state.set(&data->animations[ANIM_BASE], 0);
if(inair)
state.add(&data->animations[ANIM_INAIR], 0, 1.0f); // TODO: some sort of time here
else if(stationary)
state.add(&data->animations[ANIM_IDLE], 0, 1.0f); // TODO: some sort of time here
else if(!want_other_dir)
state.add(&data->animations[ANIM_WALK], walk_time, 1.0f);
if (player.weapon == WEAPON_HAMMER)
{
float ct = (client_prevtick()-player.attacktick)/(float)SERVER_TICK_SPEED + client_ticktime();
state.add(&data->animations[ANIM_HAMMER_SWING], clamp(ct*5.0f,0.0f,1.0f), 1.0f);
}
if (player.weapon == WEAPON_NINJA)
{
float ct = (client_prevtick()-player.attacktick)/(float)SERVER_TICK_SPEED + client_ticktime();
state.add(&data->animations[ANIM_NINJA_SWING], clamp(ct*2.0f,0.0f,1.0f), 1.0f);
}
// do skidding
if(!inair && want_other_dir && length(vel*50) > 500.0f)
{
static int64 skid_sound_time = 0;
if(time_get()-skid_sound_time > time_freq()/10)
{
gameclient.sounds->play(SOUNDS::CHN_WORLD, SOUND_PLAYER_SKID, 0.25f, position);
skid_sound_time = time_get();
}
gameclient.effects->skidtrail(
position+vec2(-player.direction*6,12),
vec2(-player.direction*100*length(vel),-50)
);
}
// draw hook
if (prev.hook_state>0 && player.hook_state>0)
{
gfx_texture_set(data->images[IMAGE_GAME].id);
gfx_quads_begin();
//gfx_quads_begin();
vec2 pos = position;
vec2 hook_pos;
if(player_char->hooked_player != -1)
{
if(gameclient.snap.local_info && player_char->hooked_player == gameclient.snap.local_info->cid)
{
hook_pos = mix(vec2(gameclient.predicted_prev_char.pos.x, gameclient.predicted_prev_char.pos.y),
vec2(gameclient.predicted_char.pos.x, gameclient.predicted_char.pos.y), client_predintratick());
}
else
hook_pos = mix(vec2(prev_char->hook_x, prev_char->hook_y), vec2(player_char->hook_x, player_char->hook_y), client_intratick());
}
else
hook_pos = mix(vec2(prev.hook_x, prev.hook_y), vec2(player.hook_x, player.hook_y), intratick);
float d = distance(pos, hook_pos);
vec2 dir = normalize(pos-hook_pos);
gfx_quads_setrotation(get_angle(dir)+pi);
// render head
select_sprite(SPRITE_HOOK_HEAD);
gfx_quads_draw(hook_pos.x, hook_pos.y, 24,16);
// render chain
select_sprite(SPRITE_HOOK_CHAIN);
int i = 0;
for(float f = 24; f < d && i < 1024; f += 24, i++)
{
vec2 p = hook_pos + dir*f;
gfx_quads_draw(p.x, p.y,24,16);
}
gfx_quads_setrotation(0);
gfx_quads_end();
render_hand(&render_info, position, normalize(hook_pos-pos), -pi/2, vec2(20, 0));
}
// draw gun
{
gfx_texture_set(data->images[IMAGE_GAME].id);
gfx_quads_begin();
gfx_quads_setrotation(state.attach.angle*pi*2+angle);
// normal weapons
int iw = clamp(player.weapon, 0, NUM_WEAPONS-1);
select_sprite(data->weapons.id[iw].sprite_body, direction.x < 0 ? SPRITE_FLAG_FLIP_Y : 0);
vec2 dir = direction;
float recoil = 0.0f;
vec2 p;
if (player.weapon == WEAPON_HAMMER)
{
// Static position for hammer
p = position + vec2(state.attach.x, state.attach.y);
p.y += data->weapons.id[iw].offsety;
// if attack is under way, bash stuffs
if(direction.x < 0)
{
gfx_quads_setrotation(-pi/2-state.attach.angle*pi*2);
p.x -= data->weapons.id[iw].offsetx;
}
else
{
gfx_quads_setrotation(-pi/2+state.attach.angle*pi*2);
}
draw_sprite(p.x, p.y, data->weapons.id[iw].visual_size);
}
else if (player.weapon == WEAPON_NINJA)
{
p = position;
p.y += data->weapons.id[iw].offsety;
if(direction.x < 0)
{
gfx_quads_setrotation(-pi/2-state.attach.angle*pi*2);
p.x -= data->weapons.id[iw].offsetx;
gameclient.effects->powerupshine(p+vec2(32,0), vec2(32,12));
}
else
{
gfx_quads_setrotation(-pi/2+state.attach.angle*pi*2);
gameclient.effects->powerupshine(p-vec2(32,0), vec2(32,12));
}
draw_sprite(p.x, p.y, data->weapons.id[iw].visual_size);
// HADOKEN
if ((client_tick()-player.attacktick) <= (SERVER_TICK_SPEED / 6) && data->weapons.id[iw].num_sprite_muzzles)
{
int itex = rand() % data->weapons.id[iw].num_sprite_muzzles;
float alpha = 1.0f;
if (alpha > 0.0f && data->weapons.id[iw].sprite_muzzles[itex])
{
vec2 dir = vec2(player_char->x,player_char->y) - vec2(prev_char->x, prev_char->y);
dir = normalize(dir);
float hadokenangle = get_angle(dir);
gfx_quads_setrotation(hadokenangle);
//float offsety = -data->weapons[iw].muzzleoffsety;
select_sprite(data->weapons.id[iw].sprite_muzzles[itex], 0);
vec2 diry(-dir.y,dir.x);
p = position;
float offsetx = data->weapons.id[iw].muzzleoffsetx;
p -= dir * offsetx;
draw_sprite(p.x, p.y, 160.0f);
}
}
}
else
{
// TODO: should be an animation
recoil = 0;
float a = (client_tick()-player.attacktick+intratick)/5.0f;
if(a < 1)
recoil = sinf(a*pi);
p = position + dir * data->weapons.id[iw].offsetx - dir*recoil*10.0f;
p.y += data->weapons.id[iw].offsety;
draw_sprite(p.x, p.y, data->weapons.id[iw].visual_size);
}
if (player.weapon == WEAPON_GUN || player.weapon == WEAPON_SHOTGUN)
{
// check if we're firing stuff
if(data->weapons.id[iw].num_sprite_muzzles)//prev.attackticks)
{
float alpha = 0.0f;
int phase1tick = (client_tick() - player.attacktick);
if (phase1tick < (data->weapons.id[iw].muzzleduration + 3))
{
float t = ((((float)phase1tick) + intratick)/(float)data->weapons.id[iw].muzzleduration);
alpha = LERP(2.0, 0.0f, min(1.0f,max(0.0f,t)));
}
int itex = rand() % data->weapons.id[iw].num_sprite_muzzles;
if (alpha > 0.0f && data->weapons.id[iw].sprite_muzzles[itex])
{
float offsety = -data->weapons.id[iw].muzzleoffsety;
select_sprite(data->weapons.id[iw].sprite_muzzles[itex], direction.x < 0 ? SPRITE_FLAG_FLIP_Y : 0);
if(direction.x < 0)
offsety = -offsety;
vec2 diry(-dir.y,dir.x);
vec2 muzzlepos = p + dir * data->weapons.id[iw].muzzleoffsetx + diry * offsety;
draw_sprite(muzzlepos.x, muzzlepos.y, data->weapons.id[iw].visual_size);
}
}
}
gfx_quads_end();
switch (player.weapon)
{
case WEAPON_GUN: render_hand(&render_info, p, direction, -3*pi/4, vec2(-15, 4)); break;
case WEAPON_SHOTGUN: render_hand(&render_info, p, direction, -pi/2, vec2(-5, 4)); break;
case WEAPON_GRENADE: render_hand(&render_info, p, direction, -pi/2, vec2(-4, 7)); break;
}
}
// render the "shadow" tee
if(info.local && config.debug)
{
vec2 ghost_position = mix(vec2(prev_char->x, prev_char->y), vec2(player_char->x, player_char->y), client_intratick());
TEE_RENDER_INFO ghost = render_info;
ghost.color_body.a = 0.5f;
ghost.color_feet.a = 0.5f;
render_tee(&state, &ghost, player.emote, direction, ghost_position); // render ghost
}
render_info.size = 64.0f; // force some settings
render_info.color_body.a = 1.0f;
render_info.color_feet.a = 1.0f;
render_tee(&state, &render_info, player.emote, direction, position);
if(player.player_state == PLAYERSTATE_CHATTING)
{
gfx_texture_set(data->images[IMAGE_EMOTICONS].id);
gfx_quads_begin();
select_sprite(SPRITE_DOTDOT);
gfx_quads_draw(position.x + 24, position.y - 40, 64,64);
gfx_quads_end();
}
if (gameclient.clients[info.cid].emoticon_start != -1 && gameclient.clients[info.cid].emoticon_start + 2 * client_tickspeed() > client_tick())
{
gfx_texture_set(data->images[IMAGE_EMOTICONS].id);
gfx_quads_begin();
int since_start = client_tick() - gameclient.clients[info.cid].emoticon_start;
int from_end = gameclient.clients[info.cid].emoticon_start + 2 * client_tickspeed() - client_tick();
float a = 1;
if (from_end < client_tickspeed() / 5)
a = from_end / (client_tickspeed() / 5.0);
float h = 1;
if (since_start < client_tickspeed() / 10)
h = since_start / (client_tickspeed() / 10.0);
float wiggle = 0;
if (since_start < client_tickspeed() / 5)
wiggle = since_start / (client_tickspeed() / 5.0);
float wiggle_angle = sin(5*wiggle);
gfx_quads_setrotation(pi/6*wiggle_angle);
gfx_setcolor(1.0f,1.0f,1.0f,a);
// client_datas::emoticon is an offset from the first emoticon
select_sprite(SPRITE_OOP + gameclient.clients[info.cid].emoticon);
gfx_quads_draw(position.x, position.y - 23 - 32*h, 64, 64*h);
gfx_quads_end();
}
}
void PLAYERS::on_render()
{
// render other players in two passes, first pass we render the other, second pass we render our self
for(int p = 0; p < 2; p++)
{
for(int i = 0; i < MAX_CLIENTS; i++)
{
// only render active characters
if(!gameclient.snap.characters[i].active)
continue;
const void *prev_info = snap_find_item(SNAP_PREV, NETOBJTYPE_PLAYER_INFO, i);
const void *info = snap_find_item(SNAP_CURRENT, NETOBJTYPE_PLAYER_INFO, i);
if(prev_info && info)
{
//
bool local = ((const NETOBJ_PLAYER_INFO *)info)->local !=0;
if(p == 0 && local) continue;
if(p == 1 && !local) continue;
NETOBJ_CHARACTER prev_char = gameclient.snap.characters[i].prev;
NETOBJ_CHARACTER cur_char = gameclient.snap.characters[i].cur;
render_player(
&prev_char,
&cur_char,
(const NETOBJ_PLAYER_INFO *)prev_info,
(const NETOBJ_PLAYER_INFO *)info
);
}
}
}
}

View File

@@ -0,0 +1,16 @@
#include <game/client/component.hpp>
class PLAYERS : public COMPONENT
{
void render_hand(class TEE_RENDER_INFO *info, vec2 center_pos, vec2 dir, float angle_offset, vec2 post_rot_offset);
void render_player(
const class NETOBJ_CHARACTER *prev_char,
const class NETOBJ_CHARACTER *player_char,
const class NETOBJ_PLAYER_INFO *prev_info,
const class NETOBJ_PLAYER_INFO *player_info
);
public:
virtual void on_render();
};

View File

@@ -0,0 +1,314 @@
#include <string.h>
#include <engine/e_client_interface.h>
#include <game/generated/g_protocol.hpp>
#include <game/generated/gc_data.hpp>
#include <game/client/gameclient.hpp>
#include <game/client/animstate.hpp>
#include <game/client/render.hpp>
#include <game/client/components/motd.hpp>
#include "scoreboard.hpp"
SCOREBOARD::SCOREBOARD()
{
on_reset();
}
void SCOREBOARD::con_key_scoreboard(void *result, void *user_data)
{
((SCOREBOARD *)user_data)->active = console_arg_int(result, 0) != 0;
}
void SCOREBOARD::on_reset()
{
active = false;
}
void SCOREBOARD::on_console_init()
{
MACRO_REGISTER_COMMAND("+scoreboard", "", CFGFLAG_CLIENT, con_key_scoreboard, this, "Show scoreboard");
}
void SCOREBOARD::render_goals(float x, float y, float w)
{
float h = 50.0f;
gfx_blend_normal();
gfx_texture_set(-1);
gfx_quads_begin();
gfx_setcolor(0,0,0,0.5f);
draw_round_rect(x-10.f, y-10.f, w, h, 10.0f);
gfx_quads_end();
// render goals
//y = ystart+h-54;
float tw = 0.0f;
if(gameclient.snap.gameobj && gameclient.snap.gameobj->score_limit)
{
char buf[64];
str_format(buf, sizeof(buf), "Score Limit: %d", gameclient.snap.gameobj->score_limit);
gfx_text(0, x+20.0f, y, 22.0f, buf, -1);
tw += gfx_text_width(0, 22.0f, buf, -1);
}
if(gameclient.snap.gameobj && gameclient.snap.gameobj->time_limit)
{
char buf[64];
str_format(buf, sizeof(buf), "Time Limit: %d min", gameclient.snap.gameobj->time_limit);
gfx_text(0, x+220.0f, y, 22.0f, buf, -1);
tw += gfx_text_width(0, 22.0f, buf, -1);
}
if(gameclient.snap.gameobj && gameclient.snap.gameobj->round_num && gameclient.snap.gameobj->round_current)
{
char buf[64];
str_format(buf, sizeof(buf), "Round %d/%d", gameclient.snap.gameobj->round_current, gameclient.snap.gameobj->round_num);
gfx_text(0, x+450.0f, y, 22.0f, buf, -1);
/*[48c3fd4c][game/scoreboard]: timelimit x:219.428558
[48c3fd4c][game/scoreboard]: round x:453.142822*/
}
}
void SCOREBOARD::render_spectators(float x, float y, float w)
{
char buffer[1024*4];
int count = 0;
float h = 120.0f;
str_copy(buffer, "Spectators: ", sizeof(buffer));
gfx_blend_normal();
gfx_texture_set(-1);
gfx_quads_begin();
gfx_setcolor(0,0,0,0.5f);
draw_round_rect(x-10.f, y-10.f, w, h, 10.0f);
gfx_quads_end();
for(int i = 0; i < snap_num_items(SNAP_CURRENT); i++)
{
SNAP_ITEM item;
const void *data = snap_get_item(SNAP_CURRENT, i, &item);
if(item.type == NETOBJTYPE_PLAYER_INFO)
{
const NETOBJ_PLAYER_INFO *info = (const NETOBJ_PLAYER_INFO *)data;
if(info->team == -1)
{
if(count)
strcat(buffer, ", ");
strcat(buffer, gameclient.clients[info->cid].name);
count++;
}
}
}
gfx_text(0, x+10, y, 32, buffer, (int)w-20);
}
void SCOREBOARD::render_scoreboard(float x, float y, float w, int team, const char *title)
{
//float ystart = y;
float h = 750.0f;
gfx_blend_normal();
gfx_texture_set(-1);
gfx_quads_begin();
gfx_setcolor(0,0,0,0.5f);
draw_round_rect(x-10.f, y-10.f, w, h, 17.0f);
gfx_quads_end();
// render title
if(!title)
{
if(gameclient.snap.gameobj->game_over)
title = "Game Over";
else
title = "Score Board";
}
float tw = gfx_text_width(0, 48, title, -1);
if(team == -1)
{
gfx_text(0, x+w/2-tw/2, y, 48, title, -1);
}
else
{
gfx_text(0, x+10, y, 48, title, -1);
if(gameclient.snap.gameobj)
{
char buf[128];
int score = team ? gameclient.snap.gameobj->teamscore_blue : gameclient.snap.gameobj->teamscore_red;
str_format(buf, sizeof(buf), "%d", score);
tw = gfx_text_width(0, 48, buf, -1);
gfx_text(0, x+w-tw-30, y, 48, buf, -1);
}
}
y += 54.0f;
// find players
const NETOBJ_PLAYER_INFO *players[MAX_CLIENTS] = {0};
int num_players = 0;
for(int i = 0; i < snap_num_items(SNAP_CURRENT); i++)
{
SNAP_ITEM item;
const void *data = snap_get_item(SNAP_CURRENT, i, &item);
if(item.type == NETOBJTYPE_PLAYER_INFO)
{
const NETOBJ_PLAYER_INFO *info = (const NETOBJ_PLAYER_INFO *)data;
if(info->team == team)
{
players[num_players] = info;
num_players++;
}
}
}
// sort players
for(int k = 0; k < num_players; k++) // ffs, bubblesort
{
for(int i = 0; i < num_players-k-1; i++)
{
if(players[i]->score < players[i+1]->score)
{
const NETOBJ_PLAYER_INFO *tmp = players[i];
players[i] = players[i+1];
players[i+1] = tmp;
}
}
}
// render headlines
gfx_text(0, x+10, y, 24.0f, "Score", -1);
gfx_text(0, x+125, y, 24.0f, "Name", -1);
gfx_text(0, x+w-70, y, 24.0f, "Ping", -1);
y += 29.0f;
float font_size = 35.0f;
float line_height = 50.0f;
float tee_sizemod = 1.0f;
float tee_offset = 0.0f;
if(num_players > 13)
{
font_size = 30.0f;
line_height = 40.0f;
tee_sizemod = 0.8f;
tee_offset = -5.0f;
}
// render player scores
for(int i = 0; i < num_players; i++)
{
const NETOBJ_PLAYER_INFO *info = players[i];
// make sure that we render the correct team
char buf[128];
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, line_height*0.95f, 17.0f);
gfx_quads_end();
}
str_format(buf, sizeof(buf), "%4d", info->score);
gfx_text(0, x+60-gfx_text_width(0, font_size,buf,-1), y, font_size, buf, -1);
gfx_text(0, x+128, y, font_size, gameclient.clients[info->cid].name, -1);
str_format(buf, sizeof(buf), "%4d", info->latency);
float tw = gfx_text_width(0, font_size, buf, -1);
gfx_text(0, x+w-tw-35, y, font_size, buf, -1);
// render avatar
if((gameclient.snap.flags[0] && gameclient.snap.flags[0]->carried_by == info->cid) ||
(gameclient.snap.flags[1] && gameclient.snap.flags[1]->carried_by == info->cid))
{
gfx_blend_normal();
gfx_texture_set(data->images[IMAGE_GAME].id);
gfx_quads_begin();
if(info->team == 0) select_sprite(SPRITE_FLAG_BLUE, SPRITE_FLAG_FLIP_X);
else select_sprite(SPRITE_FLAG_RED, SPRITE_FLAG_FLIP_X);
float size = 64.0f;
gfx_quads_drawTL(x+55, y-15, size/2, size);
gfx_quads_end();
}
TEE_RENDER_INFO teeinfo = gameclient.clients[info->cid].render_info;
teeinfo.size *= tee_sizemod;
render_tee(ANIMSTATE::get_idle(), &teeinfo, EMOTE_NORMAL, vec2(1,0), vec2(x+90, y+28+tee_offset));
y += line_height;
}
}
void SCOREBOARD::on_render()
{
bool do_scoreboard = false;
// if we activly wanna look on the scoreboard
if(active)
do_scoreboard = true;
if(gameclient.snap.local_info && gameclient.snap.local_info->team != -1)
{
// we are not a spectator, check if we are ead
if(!gameclient.snap.local_character || gameclient.snap.local_character->health < 0)
do_scoreboard = true;
}
// if we the game is over
if(gameclient.snap.gameobj && gameclient.snap.gameobj->game_over)
do_scoreboard = true;
if(!do_scoreboard)
return;
// if the score board is active, then we should clear the motd message aswell
if(active)
gameclient.motd->clear();
float width = 400*3.0f*gfx_screenaspect();
float height = 400*3.0f;
gfx_mapscreen(0, 0, width, height);
float w = 650.0f;
if(gameclient.snap.gameobj && !(gameclient.snap.gameobj->flags&GAMEFLAG_TEAMS))
{
render_scoreboard(width/2-w/2, 150.0f, w, 0, 0);
//render_scoreboard(gameobj, 0, 0, -1, 0);
}
else
{
if(gameclient.snap.gameobj && gameclient.snap.gameobj->game_over)
{
const char *text = "DRAW!";
if(gameclient.snap.gameobj->teamscore_red > gameclient.snap.gameobj->teamscore_blue)
text = "Red Team Wins!";
else if(gameclient.snap.gameobj->teamscore_blue > gameclient.snap.gameobj->teamscore_red)
text = "Blue Team Wins!";
float w = gfx_text_width(0, 92.0f, text, -1);
gfx_text(0, width/2-w/2, 45, 92.0f, text, -1);
}
render_scoreboard(width/2-w-20, 150.0f, w, 0, "Red Team");
render_scoreboard(width/2 + 20, 150.0f, w, 1, "Blue Team");
}
render_goals(width/2-w/2, 150+750+25, w);
render_spectators(width/2-w/2, 150+750+25+50+25, w);
}

View File

@@ -0,0 +1,19 @@
#include <game/client/component.hpp>
class SCOREBOARD : public COMPONENT
{
void render_goals(float x, float y, float w);
void render_spectators(float x, float y, float w);
void render_scoreboard(float x, float y, float w, int team, const char *title);
static void con_key_scoreboard(void *result, void *user_data);
bool active;
public:
SCOREBOARD();
virtual void on_reset();
virtual void on_console_init();
virtual void on_render();
};

View File

@@ -0,0 +1,194 @@
/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <base/system.h>
#include <base/math.hpp>
#include <engine/e_client_interface.h>
extern "C"
{
#include <engine/e_engine.h>
}
#include "skins.hpp"
SKINS::SKINS()
{
num_skins = 0;
}
void SKINS::skinscan(const char *name, int is_dir, void *user)
{
SKINS *self = (SKINS *)user;
int l = strlen(name);
if(l < 4 || is_dir || self->num_skins == MAX_SKINS)
return;
if(strcmp(name+l-4, ".png") != 0)
return;
char buf[512];
str_format(buf, sizeof(buf), "skins/%s", name);
IMAGE_INFO info;
if(!gfx_load_png(&info, buf))
{
dbg_msg("game", "failed to load skin from %s", name);
return;
}
self->skins[self->num_skins].org_texture = gfx_load_texture_raw(info.width, info.height, info.format, info.data, info.format, 0);
int body_size = 96; // body size
unsigned char *d = (unsigned char *)info.data;
int pitch = info.width*4;
// dig out blood color
{
int colors[3] = {0};
for(int y = 0; y < body_size; y++)
for(int x = 0; x < body_size; x++)
{
if(d[y*pitch+x*4+3] > 128)
{
colors[0] += d[y*pitch+x*4+0];
colors[1] += d[y*pitch+x*4+1];
colors[2] += d[y*pitch+x*4+2];
}
}
self->skins[self->num_skins].blood_color = normalize(vec3(colors[0], colors[1], colors[2]));
}
// create colorless version
int step = info.format == IMG_RGBA ? 4 : 3;
// make the texture gray scale
for(int i = 0; i < info.width*info.height; i++)
{
int v = (d[i*step]+d[i*step+1]+d[i*step+2])/3;
d[i*step] = v;
d[i*step+1] = v;
d[i*step+2] = v;
}
if(1)
{
int freq[256] = {0};
int org_weight = 0;
int new_weight = 192;
// find most common frequence
for(int y = 0; y < body_size; y++)
for(int x = 0; x < body_size; x++)
{
if(d[y*pitch+x*4+3] > 128)
freq[d[y*pitch+x*4]]++;
}
for(int i = 1; i < 256; i++)
{
if(freq[org_weight] < freq[i])
org_weight = i;
}
// reorder
int inv_org_weight = 255-org_weight;
int inv_new_weight = 255-new_weight;
for(int y = 0; y < body_size; y++)
for(int x = 0; x < body_size; x++)
{
int v = d[y*pitch+x*4];
if(v <= org_weight)
v = (int)(((v/(float)org_weight) * new_weight));
else
v = (int)(((v-org_weight)/(float)inv_org_weight)*inv_new_weight + new_weight);
d[y*pitch+x*4] = v;
d[y*pitch+x*4+1] = v;
d[y*pitch+x*4+2] = v;
}
}
self->skins[self->num_skins].color_texture = gfx_load_texture_raw(info.width, info.height, info.format, info.data, info.format, 0);
mem_free(info.data);
// set skin data
strncpy(self->skins[self->num_skins].name, name, min((int)sizeof(self->skins[self->num_skins].name),l-4));
dbg_msg("game", "load skin %s", self->skins[self->num_skins].name);
self->num_skins++;
}
void SKINS::init()
{
// load skins
num_skins = 0;
engine_listdir(LISTDIRTYPE_ALL, "skins", skinscan, this);
}
int SKINS::num()
{
return num_skins;
}
const SKINS::SKIN *SKINS::get(int index)
{
return &skins[index%num_skins];
}
int SKINS::find(const char *name)
{
for(int i = 0; i < num_skins; i++)
{
if(strcmp(skins[i].name, name) == 0)
return i;
}
return -1;
}
// these converter functions were nicked from some random internet pages
static float hue_to_rgb(float v1, float v2, float h)
{
if(h < 0) h += 1;
if(h > 1) h -= 1;
if((6 * h) < 1) return v1 + ( v2 - v1 ) * 6 * h;
if((2 * h) < 1) return v2;
if((3 * h) < 2) return v1 + ( v2 - v1 ) * ((2.0f/3.0f) - h) * 6;
return v1;
}
static vec3 hsl_to_rgb(vec3 in)
{
float v1, v2;
vec3 out;
if(in.s == 0)
{
out.r = in.l;
out.g = in.l;
out.b = in.l;
}
else
{
if(in.l < 0.5f)
v2 = in.l * (1 + in.s);
else
v2 = (in.l+in.s) - (in.s*in.l);
v1 = 2 * in.l - v2;
out.r = hue_to_rgb(v1, v2, in.h + (1.0f/3.0f));
out.g = hue_to_rgb(v1, v2, in.h);
out.b = hue_to_rgb(v1, v2, in.h - (1.0f/3.0f));
}
return out;
}
vec4 SKINS::get_color(int v)
{
vec3 r = hsl_to_rgb(vec3((v>>16)/255.0f, ((v>>8)&0xff)/255.0f, 0.5f+(v&0xff)/255.0f*0.5f));
return vec4(r.r, r.g, r.b, 1.0f);
}

View File

@@ -0,0 +1,36 @@
#include <base/vmath.hpp>
#include <game/client/component.hpp>
class SKINS : public COMPONENT
{
public:
// do this better and nicer
typedef struct
{
int org_texture;
int color_texture;
char name[31];
char term[1];
vec3 blood_color;
} SKIN;
SKINS();
void init();
vec4 get_color(int v);
int num();
const SKIN *get(int index);
int find(const char *name);
private:
enum
{
MAX_SKINS=256,
};
SKIN skins[MAX_SKINS];
int num_skins;
static void skinscan(const char *name, int is_dir, void *user);
};

View File

@@ -0,0 +1,54 @@
#include <engine/e_client_interface.h>
#include <game/generated/gc_data.hpp>
#include <game/client/gameclient.hpp>
#include <game/client/components/camera.hpp>
#include "sounds.hpp"
void SOUNDS::on_init()
{
// setup sound channels
snd_set_channel(SOUNDS::CHN_GUI, 1.0f, 0.0f);
snd_set_channel(SOUNDS::CHN_MUSIC, 1.0f, 0.0f);
snd_set_channel(SOUNDS::CHN_WORLD, 0.9f, 1.0f);
snd_set_channel(SOUNDS::CHN_GLOBAL, 1.0f, 0.0f);
snd_set_listener_pos(0.0f, 0.0f);
}
void SOUNDS::on_render()
{
// set listner pos
snd_set_listener_pos(gameclient.camera->center.x, gameclient.camera->center.y);
}
void SOUNDS::play_and_record(int chn, int setid, float vol, vec2 pos)
{
NETMSG_SV_SOUNDGLOBAL msg;
msg.soundid = setid;
msg.pack(MSGFLAG_NOSEND|MSGFLAG_RECORD);
client_send_msg();
play(chn, setid, vol, pos);
}
void SOUNDS::play(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;
}

View File

@@ -0,0 +1,22 @@
#include <game/client/component.hpp>
class SOUNDS : public COMPONENT
{
public:
// sound channels
enum
{
CHN_GUI=0,
CHN_MUSIC,
CHN_WORLD,
CHN_GLOBAL,
};
virtual void on_init();
virtual void on_render();
void play(int chn, int setid, float vol, vec2 pos);
void play_and_record(int chn, int setid, float vol, vec2 pos);
};

View File

@@ -0,0 +1,202 @@
#include <engine/e_client_interface.h>
#include <game/generated/g_protocol.hpp>
#include <base/vmath.hpp>
#include <game/client/render.hpp>
//#include <game/client/gameclient.hpp>
#include "voting.hpp"
void VOTING::con_callvote(void *result, void *user_data)
{
VOTING *self = (VOTING*)user_data;
self->callvote(console_arg_string(result, 0), console_arg_string(result, 1));
}
void VOTING::con_vote(void *result, void *user_data)
{
VOTING *self = (VOTING *)user_data;
if(str_comp_nocase(console_arg_string(result, 0), "yes") == 0)
self->vote(1);
else if(str_comp_nocase(console_arg_string(result, 0), "no") == 0)
self->vote(-1);
}
void VOTING::callvote(const char *type, const char *value)
{
NETMSG_CL_CALLVOTE msg = {0};
msg.type = type;
msg.value = value;
msg.pack(MSGFLAG_VITAL);
client_send_msg();
}
void VOTING::callvote_kick(int client_id)
{
char buf[32];
str_format(buf, sizeof(buf), "%d", client_id);
callvote("kick", buf);
}
void VOTING::callvote_option(int option_id)
{
VOTEOPTION *option = this->first;
while(option && option_id >= 0)
{
if(option_id == 0)
{
callvote("option", option->command);
break;
}
option_id--;
option = option->next;
}
}
void VOTING::vote(int v)
{
NETMSG_CL_VOTE msg = {v};
msg.pack(MSGFLAG_VITAL);
client_send_msg();
}
VOTING::VOTING()
{
heap = 0;
clearoptions();
on_reset();
}
void VOTING::clearoptions()
{
if(heap)
memheap_destroy(heap);
heap = memheap_create();
first = 0;
last = 0;
}
void VOTING::on_reset()
{
closetime = 0;
description[0] = 0;
command[0] = 0;
yes = no = pass = total = 0;
voted = 0;
}
void VOTING::on_console_init()
{
MACRO_REGISTER_COMMAND("callvote", "sr", CFGFLAG_CLIENT, con_callvote, this, "Call vote");
MACRO_REGISTER_COMMAND("vote", "r", CFGFLAG_CLIENT, con_vote, this, "Vote yes/no");
}
void VOTING::on_message(int msgtype, void *rawmsg)
{
if(msgtype == NETMSGTYPE_SV_VOTE_SET)
{
NETMSG_SV_VOTE_SET *msg = (NETMSG_SV_VOTE_SET *)rawmsg;
if(msg->timeout)
{
on_reset();
str_copy(description, msg->description, sizeof(description));
str_copy(command, msg->command, sizeof(description));
closetime = time_get() + time_freq() * msg->timeout;
}
else
on_reset();
}
else if(msgtype == NETMSGTYPE_SV_VOTE_STATUS)
{
NETMSG_SV_VOTE_STATUS *msg = (NETMSG_SV_VOTE_STATUS *)rawmsg;
yes = msg->yes;
no = msg->no;
pass = msg->pass;
total = msg->total;
}
else if(msgtype == NETMSGTYPE_SV_VOTE_CLEAROPTIONS)
{
clearoptions();
}
else if(msgtype == NETMSGTYPE_SV_VOTE_OPTION)
{
NETMSG_SV_VOTE_OPTION *msg = (NETMSG_SV_VOTE_OPTION *)rawmsg;
int len = str_length(msg->command);
VOTEOPTION *option = (VOTEOPTION *)memheap_allocate(heap, sizeof(VOTEOPTION) + len);
option->next = 0;
option->prev = last;
if(option->prev)
option->prev->next = option;
last = option;
if(!first)
first = option;
mem_copy(option->command, msg->command, len+1);
}
}
void VOTING::on_render()
{
}
void VOTING::render_bars(RECT bars, bool text)
{
ui_draw_rect(&bars, vec4(0.8f,0.8f,0.8f,0.5f), CORNER_ALL, bars.h/3);
RECT splitter = bars;
splitter.x = splitter.x+splitter.w/2;
splitter.w = splitter.h/2.0f;
splitter.x -= splitter.w/2;
ui_draw_rect(&splitter, vec4(0.4f,0.4f,0.4f,0.5f), CORNER_ALL, splitter.h/4);
if(total)
{
RECT pass_area = bars;
if(yes)
{
RECT yes_area = bars;
yes_area.w *= yes/(float)total;
ui_draw_rect(&yes_area, vec4(0.2f,0.9f,0.2f,0.85f), CORNER_ALL, bars.h/3);
if(text)
{
char buf[256];
str_format(buf, sizeof(buf), "%d", yes);
ui_do_label(&yes_area, buf, bars.h*0.75f, 0);
}
pass_area.x += yes_area.w;
pass_area.w -= yes_area.w;
}
if(no)
{
RECT no_area = bars;
no_area.w *= no/(float)total;
no_area.x = (bars.x + bars.w)-no_area.w;
ui_draw_rect(&no_area, vec4(0.9f,0.2f,0.2f,0.85f), CORNER_ALL, bars.h/3);
if(text)
{
char buf[256];
str_format(buf, sizeof(buf), "%d", no);
ui_do_label(&no_area, buf, bars.h*0.75f, 0);
}
pass_area.w -= no_area.w;
}
if(text && pass)
{
char buf[256];
str_format(buf, sizeof(buf), "%d", pass);
ui_do_label(&pass_area, buf, bars.h*0.75f, 0);
}
}
}

View File

@@ -0,0 +1,56 @@
#include <game/client/component.hpp>
#include <game/client/ui.hpp>
extern "C"
{
#include <engine/e_memheap.h>
}
class VOTING : public COMPONENT
{
HEAP *heap;
static void con_callvote(void *result, void *user_data);
static void con_vote(void *result, void *user_data);
int64 closetime;
char description[512];
char command[512];
int voted;
void clearoptions();
void callvote(const char *type, const char *value);
public:
struct VOTEOPTION
{
VOTEOPTION *next;
VOTEOPTION *prev;
char command[1];
};
VOTEOPTION *first;
VOTEOPTION *last;
VOTING();
virtual void on_reset();
virtual void on_console_init();
virtual void on_message(int msgtype, void *rawmsg);
virtual void on_render();
void render_bars(RECT bars, bool text);
void callvote_kick(int client_id);
void callvote_option(int option);
void vote(int v); // -1 = no, 1 = yes
int seconds_left() { return (closetime - time_get())/time_freq(); }
bool is_voting() { return closetime != 0; }
int taken_choice() const { return voted; }
const char *vote_description() const { return description; }
const char *vote_command() const { return command; }
int yes, no, pass, total;
};

View File

@@ -0,0 +1,885 @@
#include <string.h>
#include <engine/e_client_interface.h>
#include <engine/e_demorec.h>
#include <game/generated/g_protocol.hpp>
#include <game/generated/gc_data.hpp>
#include <game/layers.hpp>
#include "render.hpp"
#include "gameclient.hpp"
#include "components/binds.hpp"
#include "components/broadcast.hpp"
#include "components/camera.hpp"
#include "components/chat.hpp"
#include "components/console.hpp"
#include "components/controls.hpp"
#include "components/damageind.hpp"
#include "components/debughud.hpp"
#include "components/effects.hpp"
#include "components/emoticon.hpp"
#include "components/flow.hpp"
#include "components/hud.hpp"
#include "components/items.hpp"
#include "components/killmessages.hpp"
#include "components/mapimages.hpp"
#include "components/maplayers.hpp"
#include "components/menus.hpp"
#include "components/motd.hpp"
#include "components/particles.hpp"
#include "components/players.hpp"
#include "components/nameplates.hpp"
#include "components/scoreboard.hpp"
#include "components/skins.hpp"
#include "components/sounds.hpp"
#include "components/voting.hpp"
GAMECLIENT gameclient;
// instanciate all systems
static KILLMESSAGES killmessages;
static CAMERA camera;
static CHAT chat;
static MOTD motd;
static BROADCAST broadcast;
static CONSOLE console;
static BINDS binds;
static PARTICLES particles;
static MENUS menus;
static SKINS skins;
static FLOW flow;
static HUD hud;
static DEBUGHUD debughud;
static CONTROLS controls;
static EFFECTS effects;
static SCOREBOARD scoreboard;
static SOUNDS sounds;
static EMOTICON emoticon;
static DAMAGEIND damageind;
static VOTING voting;
static PLAYERS players;
static NAMEPLATES nameplates;
static ITEMS items;
static MAPIMAGES mapimages;
static MAPLAYERS maplayers_background(MAPLAYERS::TYPE_BACKGROUND);
static MAPLAYERS maplayers_foreground(MAPLAYERS::TYPE_FOREGROUND);
GAMECLIENT::STACK::STACK() { num = 0; }
void GAMECLIENT::STACK::add(class COMPONENT *component) { components[num++] = component; }
static int load_current;
static int load_total;
static void load_sounds_thread(void *do_render)
{
// load sounds
for(int s = 0; s < data->num_sounds; s++)
{
if(do_render)
gameclient.menus->render_loading(load_current/(float)load_total);
for(int i = 0; i < data->sounds[s].num_sounds; i++)
{
int id = snd_load_wv(data->sounds[s].sounds[i].filename);
data->sounds[s].sounds[i].id = id;
}
if(do_render)
load_current++;
}
}
static void con_serverdummy(void *result, void *user_data)
{
dbg_msg("client", "this command is not available on the client");
}
void GAMECLIENT::on_console_init()
{
// setup pointers
binds = &::binds;
console = &::console;
particles = &::particles;
menus = &::menus;
skins = &::skins;
chat = &::chat;
flow = &::flow;
camera = &::camera;
controls = &::controls;
effects = &::effects;
sounds = &::sounds;
motd = &::motd;
damageind = &::damageind;
mapimages = &::mapimages;
voting = &::voting;
// make a list of all the systems, make sure to add them in the corrent render order
all.add(skins);
all.add(mapimages);
all.add(effects); // doesn't render anything, just updates effects
all.add(particles);
all.add(binds);
all.add(controls);
all.add(camera);
all.add(sounds);
all.add(voting);
all.add(particles); // doesn't render anything, just updates all the particles
all.add(&maplayers_background); // first to render
all.add(&particles->render_trail);
all.add(&particles->render_explosions);
all.add(&items);
all.add(&players);
all.add(&maplayers_foreground);
all.add(&nameplates);
all.add(&particles->render_general);
all.add(damageind);
all.add(&hud);
all.add(&emoticon);
all.add(&killmessages);
all.add(chat);
all.add(&broadcast);
all.add(&debughud);
all.add(&scoreboard);
all.add(motd);
all.add(menus);
all.add(console);
// build the input stack
input.add(&menus->binder); // this will take over all input when we want to bind a key
input.add(&binds->special_binds);
input.add(console);
input.add(chat); // chat has higher prio due to tha you can quit it by pressing esc
input.add(motd); // for pressing esc to remove it
input.add(menus);
input.add(&emoticon);
input.add(controls);
input.add(binds);
// add the some console commands
MACRO_REGISTER_COMMAND("team", "i", CFGFLAG_CLIENT, con_team, this, "Switch team");
MACRO_REGISTER_COMMAND("kill", "", CFGFLAG_CLIENT, con_kill, this, "Kill yourself");
// register server dummy commands for tab completion
MACRO_REGISTER_COMMAND("tune", "si", CFGFLAG_SERVER, con_serverdummy, 0, "Tune variable to value");
MACRO_REGISTER_COMMAND("tune_reset", "", CFGFLAG_SERVER, con_serverdummy, 0, "Reset tuning");
MACRO_REGISTER_COMMAND("tune_dump", "", CFGFLAG_SERVER, con_serverdummy, 0, "Dump tuning");
MACRO_REGISTER_COMMAND("change_map", "r", CFGFLAG_SERVER, con_serverdummy, 0, "Change map");
MACRO_REGISTER_COMMAND("restart", "?i", CFGFLAG_SERVER, con_serverdummy, 0, "Restart in x seconds");
MACRO_REGISTER_COMMAND("broadcast", "r", CFGFLAG_SERVER, con_serverdummy, 0, "Broadcast message");
/*MACRO_REGISTER_COMMAND("say", "r", CFGFLAG_SERVER, con_serverdummy, 0);*/
MACRO_REGISTER_COMMAND("set_team", "ii", CFGFLAG_SERVER, con_serverdummy, 0, "Set team of player to team");
MACRO_REGISTER_COMMAND("addvote", "r", CFGFLAG_SERVER, con_serverdummy, 0, "Add a voting option");
/*MACRO_REGISTER_COMMAND("vote", "", CFGFLAG_SERVER, con_serverdummy, 0);*/
// let all the other components register their console commands
for(int i = 0; i < all.num; i++)
all.components[i]->on_console_init();
//
suppress_events = false;
}
void GAMECLIENT::on_init()
{
// init all components
for(int i = 0; i < all.num; i++)
all.components[i]->on_init();
// setup item sizes
for(int i = 0; i < NUM_NETOBJTYPES; i++)
snap_set_staticsize(i, netobj_get_size(i));
// load default font
static FONT_SET default_font;
int64 start = time_get();
int before = gfx_memory_usage();
font_set_load(&default_font, "fonts/default_font%d.tfnt", "fonts/default_font%d.png", "fonts/default_font%d_b.png", 14, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 36);
dbg_msg("font", "gfx memory used for font textures: %d", gfx_memory_usage()-before);
gfx_text_set_default_font(&default_font);
config.cl_threadsoundloading = 0;
// setup load amount
load_total = data->num_images;
load_current = 0;
if(!config.cl_threadsoundloading)
load_total += data->num_sounds;
// load textures
for(int i = 0; i < data->num_images; i++)
{
gameclient.menus->render_loading(load_current/load_total);
data->images[i].id = gfx_load_texture(data->images[i].filename, IMG_AUTO, 0);
load_current++;
}
::skins.init();
if(config.cl_threadsoundloading)
thread_create(load_sounds_thread, 0);
else
load_sounds_thread((void*)1);
for(int i = 0; i < all.num; i++)
all.components[i]->on_reset();
int64 end = time_get();
dbg_msg("", "%f.2ms", ((end-start)*1000)/(float)time_freq());
servermode = SERVERMODE_PURE;
}
void GAMECLIENT::on_save()
{
for(int i = 0; i < all.num; i++)
all.components[i]->on_save();
}
void GAMECLIENT::dispatch_input()
{
// handle mouse movement
int x=0, y=0;
inp_mouse_relative(&x, &y);
if(x || y)
{
for(int h = 0; h < input.num; h++)
{
if(input.components[h]->on_mousemove(x, y))
break;
}
}
// handle key presses
for(int i = 0; i < inp_num_events(); i++)
{
INPUT_EVENT e = inp_get_event(i);
for(int h = 0; h < input.num; h++)
{
if(input.components[h]->on_input(e))
{
//dbg_msg("", "%d char=%d key=%d flags=%d", h, e.ch, e.key, e.flags);
break;
}
}
}
// clear all events for this frame
inp_clear_events();
}
int GAMECLIENT::on_snapinput(int *data)
{
return controls->snapinput(data);
}
void GAMECLIENT::on_connected()
{
layers_init();
col_init();
render_tilemap_generate_skip();
for(int i = 0; i < all.num; i++)
{
all.components[i]->on_mapload();
all.components[i]->on_reset();
}
SERVER_INFO current_server_info;
client_serverinfo(&current_server_info);
servermode = SERVERMODE_PURE;
// send the inital info
send_info(true);
}
void GAMECLIENT::on_reset()
{
// clear out the invalid pointers
last_new_predicted_tick = -1;
mem_zero(&gameclient.snap, sizeof(gameclient.snap));
for(int i = 0; i < MAX_CLIENTS; i++)
{
clients[i].name[0] = 0;
clients[i].skin_id = 0;
clients[i].team = 0;
clients[i].angle = 0;
clients[i].emoticon = 0;
clients[i].emoticon_start = -1;
clients[i].skin_info.texture = gameclient.skins->get(0)->color_texture;
clients[i].skin_info.color_body = vec4(1,1,1,1);
clients[i].skin_info.color_feet = vec4(1,1,1,1);
clients[i].update_render_info();
}
for(int i = 0; i < all.num; i++)
all.components[i]->on_reset();
}
void GAMECLIENT::update_local_character_pos()
{
if(config.cl_predict && client_state() != CLIENTSTATE_DEMOPLAYBACK)
{
if(!snap.local_character || (snap.local_character->health < 0) || (snap.gameobj && snap.gameobj->game_over))
{
// don't use predicted
}
else
local_character_pos = mix(predicted_prev_char.pos, predicted_char.pos, client_predintratick());
}
else if(snap.local_character && snap.local_prev_character)
{
local_character_pos = mix(
vec2(snap.local_prev_character->x, snap.local_prev_character->y),
vec2(snap.local_character->x, snap.local_character->y), client_intratick());
}
}
static void evolve(NETOBJ_CHARACTER *character, int tick)
{
WORLD_CORE tempworld;
CHARACTER_CORE tempcore;
mem_zero(&tempcore, sizeof(tempcore));
tempcore.world = &tempworld;
tempcore.read(character);
while(character->tick < tick)
{
character->tick++;
tempcore.tick(false);
tempcore.move();
tempcore.quantize();
}
tempcore.write(character);
}
void GAMECLIENT::on_render()
{
// update the local character position
update_local_character_pos();
// dispatch all input to systems
dispatch_input();
// render all systems
for(int i = 0; i < all.num; i++)
all.components[i]->on_render();
// clear new tick flags
new_tick = false;
new_predicted_tick = false;
}
void GAMECLIENT::on_message(int msgtype)
{
// special messages
if(msgtype == NETMSGTYPE_SV_EXTRAPROJECTILE)
{
/*
int num = msg_unpack_int();
for(int k = 0; k < num; k++)
{
NETOBJ_PROJECTILE proj;
for(unsigned i = 0; i < sizeof(NETOBJ_PROJECTILE)/sizeof(int); i++)
((int *)&proj)[i] = msg_unpack_int();
if(msg_unpack_error())
return;
if(extraproj_num != MAX_EXTRA_PROJECTILES)
{
extraproj_projectiles[extraproj_num] = proj;
extraproj_num++;
}
}
return;*/
}
else if(msgtype == NETMSGTYPE_SV_TUNEPARAMS)
{
// unpack the new tuning
TUNING_PARAMS new_tuning;
int *params = (int *)&new_tuning;
for(unsigned i = 0; i < sizeof(TUNING_PARAMS)/sizeof(int); i++)
params[i] = msg_unpack_int();
// check for unpacking errors
if(msg_unpack_error())
return;
servermode = SERVERMODE_PURE;
// apply new tuning
tuning = new_tuning;
return;
}
void *rawmsg = netmsg_secure_unpack(msgtype);
if(!rawmsg)
{
dbg_msg("client", "dropped weird message '%s' (%d), failed on '%s'", netmsg_get_name(msgtype), msgtype, netmsg_failed_on());
return;
}
// TODO: this should be done smarter
for(int i = 0; i < all.num; i++)
all.components[i]->on_message(msgtype, rawmsg);
if(msgtype == NETMSGTYPE_SV_READYTOENTER)
{
client_entergame();
}
else if (msgtype == NETMSGTYPE_SV_EMOTICON)
{
NETMSG_SV_EMOTICON *msg = (NETMSG_SV_EMOTICON *)rawmsg;
// apply
clients[msg->cid].emoticon = msg->emoticon;
clients[msg->cid].emoticon_start = client_tick();
}
else if(msgtype == NETMSGTYPE_SV_SOUNDGLOBAL)
{
if(suppress_events)
return;
NETMSG_SV_SOUNDGLOBAL *msg = (NETMSG_SV_SOUNDGLOBAL *)rawmsg;
gameclient.sounds->play(SOUNDS::CHN_GLOBAL, msg->soundid, 1.0f, vec2(0,0));
}
}
void GAMECLIENT::on_statechange(int new_state, int old_state)
{
if(demorec_isrecording())
demorec_record_stop();
// reset everything
on_reset();
// then change the state
for(int i = 0; i < all.num; i++)
all.components[i]->on_statechange(new_state, old_state);
}
void GAMECLIENT::process_events()
{
if(suppress_events)
return;
int snaptype = SNAP_CURRENT;
int num = snap_num_items(snaptype);
for(int index = 0; index < num; index++)
{
SNAP_ITEM item;
const void *data = snap_get_item(snaptype, index, &item);
if(item.type == NETEVENTTYPE_DAMAGEIND)
{
NETEVENT_DAMAGEIND *ev = (NETEVENT_DAMAGEIND *)data;
gameclient.effects->damage_indicator(vec2(ev->x, ev->y), get_direction(ev->angle));
}
else if(item.type == NETEVENTTYPE_EXPLOSION)
{
NETEVENT_EXPLOSION *ev = (NETEVENT_EXPLOSION *)data;
gameclient.effects->explosion(vec2(ev->x, ev->y));
}
else if(item.type == NETEVENTTYPE_HAMMERHIT)
{
NETEVENT_HAMMERHIT *ev = (NETEVENT_HAMMERHIT *)data;
gameclient.effects->hammerhit(vec2(ev->x, ev->y));
}
else if(item.type == NETEVENTTYPE_SPAWN)
{
NETEVENT_SPAWN *ev = (NETEVENT_SPAWN *)data;
gameclient.effects->playerspawn(vec2(ev->x, ev->y));
}
else if(item.type == NETEVENTTYPE_DEATH)
{
NETEVENT_DEATH *ev = (NETEVENT_DEATH *)data;
gameclient.effects->playerdeath(vec2(ev->x, ev->y), ev->cid);
}
else if(item.type == NETEVENTTYPE_SOUNDWORLD)
{
NETEVENT_SOUNDWORLD *ev = (NETEVENT_SOUNDWORLD *)data;
gameclient.sounds->play(SOUNDS::CHN_WORLD, ev->soundid, 1.0f, vec2(ev->x, ev->y));
}
}
}
void GAMECLIENT::on_snapshot()
{
new_tick = true;
// clear out the invalid pointers
mem_zero(&gameclient.snap, sizeof(gameclient.snap));
snap.local_cid = -1;
// secure snapshot
{
int num = snap_num_items(SNAP_CURRENT);
for(int index = 0; index < num; index++)
{
SNAP_ITEM item;
void *data = snap_get_item(SNAP_CURRENT, index, &item);
if(netobj_validate(item.type, data, item.datasize) != 0)
{
if(config.debug)
dbg_msg("game", "invalidated index=%d type=%d (%s) size=%d id=%d", index, item.type, netobj_get_name(item.type), item.datasize, item.id);
snap_invalidate_item(SNAP_CURRENT, index);
}
}
}
process_events();
if(config.dbg_stress)
{
if((client_tick()%100) == 0)
{
char message[64];
int msglen = rand()%(sizeof(message)-1);
for(int i = 0; i < msglen; i++)
message[i] = 'a'+(rand()%('z'-'a'));
message[msglen] = 0;
NETMSG_CL_SAY msg;
msg.team = rand()&1;
msg.message = message;
msg.pack(MSGFLAG_VITAL);
client_send_msg();
}
}
// go trough all the items in the snapshot and gather the info we want
{
snap.team_size[0] = snap.team_size[1] = 0;
int num = snap_num_items(SNAP_CURRENT);
for(int i = 0; i < num; i++)
{
SNAP_ITEM item;
const void *data = snap_get_item(SNAP_CURRENT, i, &item);
if(item.type == NETOBJTYPE_CLIENT_INFO)
{
const NETOBJ_CLIENT_INFO *info = (const NETOBJ_CLIENT_INFO *)data;
int cid = item.id;
ints_to_str(&info->name0, 6, clients[cid].name);
ints_to_str(&info->skin0, 6, clients[cid].skin_name);
clients[cid].use_custom_color = info->use_custom_color;
clients[cid].color_body = info->color_body;
clients[cid].color_feet = info->color_feet;
// prepare the info
if(clients[cid].skin_name[0] == 'x' || clients[cid].skin_name[1] == '_')
str_copy(clients[cid].skin_name, "default", 64);
clients[cid].skin_info.color_body = skins->get_color(clients[cid].color_body);
clients[cid].skin_info.color_feet = skins->get_color(clients[cid].color_feet);
clients[cid].skin_info.size = 64;
// find new skin
clients[cid].skin_id = gameclient.skins->find(clients[cid].skin_name);
if(clients[cid].skin_id < 0)
{
clients[cid].skin_id = gameclient.skins->find("default");
if(clients[cid].skin_id < 0)
clients[cid].skin_id = 0;
}
if(clients[cid].use_custom_color)
clients[cid].skin_info.texture = gameclient.skins->get(clients[cid].skin_id)->color_texture;
else
{
clients[cid].skin_info.texture = gameclient.skins->get(clients[cid].skin_id)->org_texture;
clients[cid].skin_info.color_body = vec4(1,1,1,1);
clients[cid].skin_info.color_feet = vec4(1,1,1,1);
}
clients[cid].update_render_info();
gameclient.snap.num_players++;
}
else if(item.type == NETOBJTYPE_PLAYER_INFO)
{
const NETOBJ_PLAYER_INFO *info = (const NETOBJ_PLAYER_INFO *)data;
clients[info->cid].team = info->team;
snap.player_infos[info->cid] = info;
if(info->local)
{
snap.local_cid = item.id;
snap.local_info = info;
if (info->team == -1)
snap.spectate = true;
}
// calculate team-balance
if(info->team != -1)
snap.team_size[info->team]++;
}
else if(item.type == NETOBJTYPE_CHARACTER)
{
const void *old = snap_find_item(SNAP_PREV, NETOBJTYPE_CHARACTER, item.id);
if(old)
{
snap.characters[item.id].active = true;
snap.characters[item.id].prev = *((const NETOBJ_CHARACTER *)old);
snap.characters[item.id].cur = *((const NETOBJ_CHARACTER *)data);
if(snap.characters[item.id].prev.tick)
evolve(&snap.characters[item.id].prev, client_prevtick());
if(snap.characters[item.id].cur.tick)
evolve(&snap.characters[item.id].cur, client_tick());
}
}
else if(item.type == NETOBJTYPE_GAME)
snap.gameobj = (NETOBJ_GAME *)data;
else if(item.type == NETOBJTYPE_FLAG)
snap.flags[item.id%2] = (const NETOBJ_FLAG *)data;
}
}
// setup local pointers
if(snap.local_cid >= 0)
{
SNAPSTATE::CHARACTERINFO *c = &snap.characters[snap.local_cid];
if(c->active)
{
snap.local_character = &c->cur;
snap.local_prev_character = &c->prev;
local_character_pos = vec2(snap.local_character->x, snap.local_character->y);
}
}
else
snap.spectate = true;
TUNING_PARAMS standard_tuning;
SERVER_INFO current_server_info;
client_serverinfo(&current_server_info);
if(current_server_info.gametype[0] != '0')
{
if(strcmp(current_server_info.gametype, "DM") != 0 && strcmp(current_server_info.gametype, "TDM") != 0 && strcmp(current_server_info.gametype, "CTF") != 0)
servermode = SERVERMODE_MOD;
else if(memcmp(&standard_tuning, &tuning, sizeof(TUNING_PARAMS)) == 0)
servermode = SERVERMODE_PURE;
else
servermode = SERVERMODE_PUREMOD;
}
// update render info
for(int i = 0; i < MAX_CLIENTS; i++)
clients[i].update_render_info();
}
void GAMECLIENT::on_predict()
{
// store the previous values so we can detect prediction errors
CHARACTER_CORE before_prev_char = predicted_prev_char;
CHARACTER_CORE before_char = predicted_char;
// we can't predict without our own id or own character
if(snap.local_cid == -1 || !snap.characters[snap.local_cid].active)
return;
// don't predict anything if we are paused
if(snap.gameobj && snap.gameobj->paused)
{
if(snap.local_character)
predicted_char.read(snap.local_character);
if(snap.local_prev_character)
predicted_prev_char.read(snap.local_prev_character);
return;
}
// repredict character
WORLD_CORE world;
world.tuning = tuning;
// search for players
for(int i = 0; i < MAX_CLIENTS; i++)
{
if(!snap.characters[i].active)
continue;
gameclient.clients[i].predicted.world = &world;
world.characters[i] = &gameclient.clients[i].predicted;
gameclient.clients[i].predicted.read(&snap.characters[i].cur);
}
// predict
for(int tick = client_tick()+1; tick <= client_predtick(); tick++)
{
// fetch the local
if(tick == client_predtick() && world.characters[snap.local_cid])
predicted_prev_char = *world.characters[snap.local_cid];
// first calculate where everyone should move
for(int c = 0; c < MAX_CLIENTS; c++)
{
if(!world.characters[c])
continue;
mem_zero(&world.characters[c]->input, sizeof(world.characters[c]->input));
if(snap.local_cid == c)
{
// apply player input
int *input = client_get_input(tick);
if(input)
world.characters[c]->input = *((NETOBJ_PLAYER_INPUT*)input);
world.characters[c]->tick(true);
}
else
world.characters[c]->tick(false);
}
// move all players and quantize their data
for(int c = 0; c < MAX_CLIENTS; c++)
{
if(!world.characters[c])
continue;
world.characters[c]->move();
world.characters[c]->quantize();
}
// check if we want to trigger effects
if(tick > last_new_predicted_tick)
{
last_new_predicted_tick = tick;
new_predicted_tick = true;
if(snap.local_cid != -1 && world.characters[snap.local_cid])
{
vec2 pos = world.characters[snap.local_cid]->pos;
int events = world.characters[snap.local_cid]->triggered_events;
if(events&COREEVENT_GROUND_JUMP) gameclient.sounds->play_and_record(SOUNDS::CHN_WORLD, SOUND_PLAYER_JUMP, 1.0f, pos);
/*if(events&COREEVENT_AIR_JUMP)
{
gameclient.effects->air_jump(pos);
gameclient.sounds->play_and_record(SOUNDS::CHN_WORLD, SOUND_PLAYER_AIRJUMP, 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) gameclient.sounds->play_and_record(SOUNDS::CHN_WORLD, SOUND_HOOK_ATTACH_GROUND, 1.0f, pos);
if(events&COREEVENT_HOOK_HIT_NOHOOK) gameclient.sounds->play_and_record(SOUNDS::CHN_WORLD, SOUND_HOOK_NOATTACH, 1.0f, pos);
//if(events&COREEVENT_HOOK_RETRACT) snd_play_random(CHN_WORLD, SOUND_PLAYER_JUMP, 1.0f, pos);
}
}
if(tick == client_predtick() && world.characters[snap.local_cid])
predicted_char = *world.characters[snap.local_cid];
}
if(config.debug && config.cl_predict && predicted_tick == client_predtick())
{
NETOBJ_CHARACTER_CORE before = {0}, now = {0}, before_prev = {0}, now_prev = {0};
before_char.write(&before);
before_prev_char.write(&before_prev);
predicted_char.write(&now);
predicted_prev_char.write(&now_prev);
if(mem_comp(&before, &now, sizeof(NETOBJ_CHARACTER_CORE)) != 0)
{
dbg_msg("client", "prediction error");
for(unsigned i = 0; i < sizeof(NETOBJ_CHARACTER_CORE)/sizeof(int); i++)
if(((int *)&before)[i] != ((int *)&now)[i])
{
dbg_msg("", "\t%d %d %d (%d %d)", i, ((int *)&before)[i], ((int *)&now)[i], ((int *)&before_prev)[i], ((int *)&now_prev)[i]);
}
}
}
predicted_tick = client_predtick();
}
void GAMECLIENT::CLIENT_DATA::update_render_info()
{
render_info = skin_info;
// force team colors
if(gameclient.snap.gameobj && gameclient.snap.gameobj->flags&GAMEFLAG_TEAMS)
{
const int team_colors[2] = {65387, 10223467};
if(team >= 0 || team <= 1)
{
render_info.texture = gameclient.skins->get(skin_id)->color_texture;
render_info.color_body = gameclient.skins->get_color(team_colors[team]);
render_info.color_feet = gameclient.skins->get_color(team_colors[team]);
}
}
}
void GAMECLIENT::send_switch_team(int team)
{
NETMSG_CL_SETTEAM msg;
msg.team = team;
msg.pack(MSGFLAG_VITAL);
client_send_msg();
}
void GAMECLIENT::send_info(bool start)
{
if(start)
{
NETMSG_CL_STARTINFO msg;
msg.name = config.player_name;
msg.skin = config.player_skin;
msg.use_custom_color = config.player_use_custom_color;
msg.color_body = config.player_color_body;
msg.color_feet = config.player_color_feet;
msg.pack(MSGFLAG_VITAL);
}
else
{
NETMSG_CL_CHANGEINFO msg;
msg.name = config.player_name;
msg.skin = config.player_skin;
msg.use_custom_color = config.player_use_custom_color;
msg.color_body = config.player_color_body;
msg.color_feet = config.player_color_feet;
msg.pack(MSGFLAG_VITAL);
}
client_send_msg();
}
void GAMECLIENT::send_kill(int client_id)
{
NETMSG_CL_KILL msg;
msg.pack(MSGFLAG_VITAL);
client_send_msg();
}
void GAMECLIENT::con_team(void *result, void *user_data)
{
((GAMECLIENT*)user_data)->send_switch_team(console_arg_int(result, 0));
}
void GAMECLIENT::con_kill(void *result, void *user_data)
{
((GAMECLIENT*)user_data)->send_kill(-1);
}

View File

@@ -0,0 +1,159 @@
#include <base/vmath.hpp>
#include <game/gamecore.hpp>
#include "render.hpp"
class GAMECLIENT
{
class STACK
{
public:
enum
{
MAX_COMPONENTS = 64,
};
STACK();
void add(class COMPONENT *component);
class COMPONENT *components[MAX_COMPONENTS];
int num;
};
STACK all;
STACK input;
void dispatch_input();
void process_events();
void update_local_character_pos();
int predicted_tick;
int last_new_predicted_tick;
static void con_team(void *result, void *user_data);
static void con_kill(void *result, void *user_data);
public:
bool suppress_events;
bool new_tick;
bool new_predicted_tick;
// TODO: move this
TUNING_PARAMS tuning;
enum
{
SERVERMODE_PURE=0,
SERVERMODE_MOD,
SERVERMODE_PUREMOD,
};
int servermode;
vec2 local_character_pos;
// predicted players
CHARACTER_CORE predicted_prev_char;
CHARACTER_CORE predicted_char;
// snap pointers
struct SNAPSTATE
{
const NETOBJ_CHARACTER *local_character;
const NETOBJ_CHARACTER *local_prev_character;
const NETOBJ_PLAYER_INFO *local_info;
const NETOBJ_FLAG *flags[2];
const NETOBJ_GAME *gameobj;
const NETOBJ_PLAYER_INFO *player_infos[MAX_CLIENTS];
const NETOBJ_PLAYER_INFO *info_by_score[MAX_CLIENTS];
int local_cid;
int num_players;
int team_size[2];
bool spectate;
//
struct CHARACTERINFO
{
bool active;
// snapshots
NETOBJ_CHARACTER prev;
NETOBJ_CHARACTER cur;
// interpolated position
vec2 position;
};
CHARACTERINFO characters[MAX_CLIENTS];
};
SNAPSTATE snap;
// client data
struct CLIENT_DATA
{
int use_custom_color;
int color_body;
int color_feet;
char name[64];
char skin_name[64];
int skin_id;
int skin_color;
int team;
int emoticon;
int emoticon_start;
CHARACTER_CORE predicted;
TEE_RENDER_INFO skin_info; // this is what the server reports
TEE_RENDER_INFO render_info; // this is what we use
float angle;
void update_render_info();
};
CLIENT_DATA clients[MAX_CLIENTS];
void on_reset();
// hooks
void on_connected();
void on_render();
void on_init();
void on_save();
void on_console_init();
void on_statechange(int new_state, int old_state);
void on_message(int msgtype);
void on_snapshot();
void on_predict();
int on_snapinput(int *data);
// actions
// TODO: move these
void send_switch_team(int team);
void send_info(bool start);
void send_kill(int client_id);
// pointers to all systems
class CONSOLE *console;
class BINDS *binds;
class PARTICLES *particles;
class MENUS *menus;
class SKINS *skins;
class FLOW *flow;
class CHAT *chat;
class DAMAGEIND *damageind;
class CAMERA *camera;
class CONTROLS *controls;
class EFFECTS *effects;
class SOUNDS *sounds;
class MOTD *motd;
class MAPIMAGES *mapimages;
class VOTING *voting;
};
extern GAMECLIENT gameclient;

View File

@@ -0,0 +1,66 @@
#include <engine/e_client_interface.h>
#include <string.h> // strlen
#include "lineinput.hpp"
LINEINPUT::LINEINPUT()
{
clear();
}
void LINEINPUT::clear()
{
mem_zero(str, sizeof(str));
len = 0;
cursor_pos = 0;
}
void LINEINPUT::set(const char *string)
{
str_copy(str, string, sizeof(str));
len = strlen(str);
cursor_pos = len;
}
void LINEINPUT::process_input(INPUT_EVENT e)
{
if(cursor_pos > len)
cursor_pos = len;
char c = e.ch;
int k = e.key;
// 127 is produced on Mac OS X and corresponds to the delete key
if (!(c >= 0 && c < 32) && c != 127)
{
if (len < sizeof(str) - 1 && cursor_pos < sizeof(str) - 1)
{
memmove(str + cursor_pos + 1, str + cursor_pos, len - cursor_pos + 1);
str[cursor_pos] = c;
cursor_pos++;
len++;
}
}
if(e.flags&INPFLAG_PRESS)
{
if (k == KEY_BACKSPACE && cursor_pos > 0)
{
memmove(str + cursor_pos - 1, str + cursor_pos, len - cursor_pos + 1);
cursor_pos--;
len--;
}
else if (k == KEY_DELETE && cursor_pos < len)
{
memmove(str + cursor_pos, str + cursor_pos + 1, len - cursor_pos);
len--;
}
else if (k == KEY_LEFT && cursor_pos > 0)
cursor_pos--;
else if (k == KEY_RIGHT && cursor_pos < len)
cursor_pos++;
else if (k == KEY_HOME)
cursor_pos = 0;
else if (k == KEY_END)
cursor_pos = len;
}
}

View File

@@ -0,0 +1,27 @@
#ifndef GAME_CLIENT_LINEINPUT_H
#define GAME_CLIENT_LINEINPUT_H
// line input helter
class LINEINPUT
{
char str[256];
unsigned len;
unsigned cursor_pos;
public:
class CALLBACK
{
public:
virtual ~CALLBACK() {}
virtual bool event(INPUT_EVENT e) = 0;
};
LINEINPUT();
void clear();
void process_input(INPUT_EVENT e);
void set(const char *string);
const char *get_string() const { return str; }
int get_length() const { return len; }
unsigned cursor_offset() const { return cursor_pos; }
};
#endif

View File

@@ -0,0 +1,311 @@
/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */
#include <math.h>
#include <base/math.hpp>
#include <engine/e_client_interface.h>
#include <engine/e_config.h>
#include <game/generated/gc_data.hpp>
#include <game/generated/g_protocol.hpp>
#include <game/layers.hpp>
#include "animstate.hpp"
#include "render.hpp"
static float sprite_w_scale;
static float sprite_h_scale;
/*
static void layershot_begin()
{
if(!config.cl_layershot)
return;
gfx_clear(0,0,0);
}
static void layershot_end()
{
if(!config.cl_layershot)
return;
char buf[256];
str_format(buf, sizeof(buf), "screenshots/layers_%04d.png", config.cl_layershot);
gfx_screenshot_direct(buf);
config.cl_layershot++;
}*/
void select_sprite(SPRITE *spr, int flags, int sx, int sy)
{
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, int sx, int sy)
{
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);
}
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);
}
void ui_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*ui_scale(), corners);
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, 1.0f);
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
ANIM_KEYFRAME *foot = f ? &anim->front_foot : &anim->back_foot;
float w = basesize;
float h = basesize/2;
gfx_quads_setrotation(foot->angle*pi*2);
bool indicate = !info->got_airjump && config.cl_airjumpindicator;
float cs = 1.0f; // color scale
if(outline)
select_sprite(SPRITE_TEE_FOOT_OUTLINE, 0, 0, 0);
else
{
select_sprite(SPRITE_TEE_FOOT, 0, 0, 0);
if(indicate)
cs = 0.5f;
}
gfx_setcolor(info->color_feet.r*cs, info->color_feet.g*cs, info->color_feet.b*cs, 1.0f);
gfx_quads_draw(position.x+foot->x*animscale, position.y+foot->y*animscale, w, h);
}
}
gfx_quads_end();
}
static void calc_screen_params(float amount, float wmax, float hmax, float aspect, float *w, float *h)
{
float f = sqrt(amount) / sqrt(aspect);
*w = f*aspect;
*h = f;
// limit the view
if(*w > wmax)
{
*w = wmax;
*h = *w/aspect;
}
if(*h > hmax)
{
*h = hmax;
*w = *h*aspect;
}
}
void mapscreen_to_world(float center_x, float center_y, float parallax_x, float parallax_y,
float offset_x, float offset_y, float aspect, float zoom, float *points)
{
float width, height;
calc_screen_params(1150*1000, 1500, 1050, aspect, &width, &height);
center_x *= parallax_x;
center_y *= parallax_y;
width *= zoom;
height *= zoom;
points[0] = offset_x+center_x-width/2;
points[1] = offset_y+center_y-height/2;
points[2] = offset_x+center_x+width/2;
points[3] = offset_y+center_y+height/2;
}
void render_tilemap_generate_skip()
{
for(int g = 0; g < layers_num_groups(); g++)
{
MAPITEM_GROUP *group = layers_get_group(g);
for(int l = 0; l < group->num_layers; l++)
{
MAPITEM_LAYER *layer = layers_get_layer(group->start_layer+l);
if(layer->type == LAYERTYPE_TILES)
{
MAPITEM_LAYER_TILEMAP *tmap = (MAPITEM_LAYER_TILEMAP *)layer;
TILE *tiles = (TILE *)map_get_data(tmap->data);
for(int y = 0; y < tmap->height; y++)
{
for(int x = 1; x < tmap->width; x++)
{
int sx;
for(sx = 1; x+sx < tmap->width && sx < 255; sx++)
{
if(tiles[y*tmap->width+x+sx].index)
break;
}
tiles[y*tmap->width+x].skip = sx-1;
}
}
}
}
}
}

View File

@@ -0,0 +1,68 @@
/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */
#ifndef GAME_CLIENT_RENDER_H
#define GAME_CLIENT_RENDER_H
#include <base/vmath.hpp>
#include "../mapitems.hpp"
#include "ui.hpp"
struct TEE_RENDER_INFO
{
TEE_RENDER_INFO()
{
texture = -1;
color_body = vec4(1,1,1,1);
color_feet = vec4(1,1,1,1);
size = 1.0f;
got_airjump = 1;
};
int texture;
vec4 color_body;
vec4 color_feet;
float size;
int got_airjump;
};
// sprite renderings
enum
{
SPRITE_FLAG_FLIP_Y=1,
SPRITE_FLAG_FLIP_X=2,
LAYERRENDERFLAG_OPAQUE=1,
LAYERRENDERFLAG_TRANSPARENT=2,
TILERENDERFLAG_EXTEND=4,
};
struct SPRITE;
void select_sprite(SPRITE *spr, int flags=0, int sx=0, int sy=0);
void select_sprite(int id, int flags=0, int sx=0, int sy=0);
void draw_sprite(float x, float y, float size);
// rects
void draw_round_rect(float x, float y, float w, float h, float r);
void draw_round_rect_ext(float x, float y, float w, float h, float r, int corners);
void ui_draw_rect(const RECT *r, vec4 color, int corners, float rounding);
// larger rendering methods
void render_tilemap_generate_skip();
// object render methods (gc_render_obj.cpp)
void render_tee(class ANIMSTATE *anim, TEE_RENDER_INFO *info, int emote, vec2 dir, vec2 pos);
// map render methods (gc_render_map.cpp)
void render_eval_envelope(ENVPOINT *points, int num_points, int channels, float time, float *result);
void render_quads(QUAD *quads, int num_quads, void (*eval)(float time_offset, int env, float *channels), int flags);
void render_tilemap(TILE *tiles, int w, int h, float scale, vec4 color, int flags);
// helpers
void mapscreen_to_world(float center_x, float center_y, float parallax_x, float parallax_y,
float offset_x, float offset_y, float aspect, float zoom, float *points);
#endif

View File

@@ -0,0 +1,274 @@
/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */
#include <math.h>
#include <base/math.hpp>
#include <engine/e_client_interface.h>
#include "render.hpp"
void render_eval_envelope(ENVPOINT *points, int num_points, int channels, float time, float *result)
{
if(num_points == 0)
{
result[0] = 0;
result[1] = 0;
result[2] = 0;
result[3] = 0;
return;
}
if(num_points == 1)
{
result[0] = fx2f(points[0].values[0]);
result[1] = fx2f(points[0].values[1]);
result[2] = fx2f(points[0].values[2]);
result[3] = fx2f(points[0].values[3]);
return;
}
time = fmod(time, points[num_points-1].time/1000.0f)*1000.0f;
for(int i = 0; i < num_points-1; i++)
{
if(time >= points[i].time && time <= points[i+1].time)
{
float delta = points[i+1].time-points[i].time;
float a = (time-points[i].time)/delta;
if(points[i].curvetype == CURVETYPE_SMOOTH)
a = -2*a*a*a + 3*a*a; // second hermite basis
else if(points[i].curvetype == CURVETYPE_SLOW)
a = a*a*a;
else if(points[i].curvetype == CURVETYPE_FAST)
{
a = 1-a;
a = 1-a*a*a;
}
else if (points[i].curvetype == CURVETYPE_STEP)
a = 0;
else
{
// linear
}
for(int c = 0; c < channels; c++)
{
float v0 = fx2f(points[i].values[c]);
float v1 = fx2f(points[i+1].values[c]);
result[c] = v0 + (v1-v0) * a;
}
return;
}
}
result[0] = fx2f(points[num_points-1].values[0]);
result[1] = fx2f(points[num_points-1].values[1]);
result[2] = fx2f(points[num_points-1].values[2]);
result[3] = fx2f(points[num_points-1].values[3]);
return;
}
static void rotate(POINT *center, POINT *point, float rotation)
{
int x = point->x - center->x;
int y = point->y - center->y;
point->x = (int)(x * cosf(rotation) - y * sinf(rotation) + center->x);
point->y = (int)(x * sinf(rotation) + y * cosf(rotation) + center->y);
}
void render_quads(QUAD *quads, int num_quads, void (*eval)(float time_offset, int env, float *channels), int renderflags)
{
gfx_quads_begin();
float conv = 1/255.0f;
for(int i = 0; i < num_quads; i++)
{
QUAD *q = &quads[i];
float r=1, g=1, b=1, a=1;
if(q->color_env >= 0)
{
float channels[4];
eval(q->color_env_offset/1000.0f, q->color_env, channels);
r = channels[0];
g = channels[1];
b = channels[2];
a = channels[3];
}
bool opaque = false;
if(a < 0.01f || (q->colors[0].a < 0.01f && q->colors[1].a < 0.01f && q->colors[2].a < 0.01f && q->colors[3].a < 0.01f))
opaque = true;
if(opaque && !(renderflags&LAYERRENDERFLAG_OPAQUE))
continue;
if(!opaque && !(renderflags&LAYERRENDERFLAG_TRANSPARENT))
continue;
gfx_quads_setsubset_free(
fx2f(q->texcoords[0].x), fx2f(q->texcoords[0].y),
fx2f(q->texcoords[1].x), fx2f(q->texcoords[1].y),
fx2f(q->texcoords[2].x), fx2f(q->texcoords[2].y),
fx2f(q->texcoords[3].x), fx2f(q->texcoords[3].y)
);
float offset_x = 0;
float offset_y = 0;
float rot = 0;
// TODO: fix this
if(q->pos_env >= 0)
{
float channels[4];
eval(q->pos_env_offset/1000.0f, q->pos_env, channels);
offset_x = channels[0];
offset_y = channels[1];
rot = channels[2]/360.0f*pi*2;
}
gfx_setcolorvertex(0, q->colors[0].r*conv*r, q->colors[0].g*conv*g, q->colors[0].b*conv*b, q->colors[0].a*conv*a);
gfx_setcolorvertex(1, q->colors[1].r*conv*r, q->colors[1].g*conv*g, q->colors[1].b*conv*b, q->colors[1].a*conv*a);
gfx_setcolorvertex(2, q->colors[2].r*conv*r, q->colors[2].g*conv*g, q->colors[2].b*conv*b, q->colors[2].a*conv*a);
gfx_setcolorvertex(3, q->colors[3].r*conv*r, q->colors[3].g*conv*g, q->colors[3].b*conv*b, q->colors[3].a*conv*a);
POINT *points = q->points;
if(rot != 0)
{
static POINT rotated[4];
rotated[0] = q->points[0];
rotated[1] = q->points[1];
rotated[2] = q->points[2];
rotated[3] = q->points[3];
points = rotated;
rotate(&q->points[4], &rotated[0], rot);
rotate(&q->points[4], &rotated[1], rot);
rotate(&q->points[4], &rotated[2], rot);
rotate(&q->points[4], &rotated[3], rot);
}
gfx_quads_draw_freeform(
fx2f(points[0].x)+offset_x, fx2f(points[0].y)+offset_y,
fx2f(points[1].x)+offset_x, fx2f(points[1].y)+offset_y,
fx2f(points[2].x)+offset_x, fx2f(points[2].y)+offset_y,
fx2f(points[3].x)+offset_x, fx2f(points[3].y)+offset_y
);
}
gfx_quads_end();
}
void render_tilemap(TILE *tiles, int w, int h, float scale, vec4 color, int renderflags)
{
//gfx_texture_set(img_get(tmap->image));
float screen_x0, screen_y0, screen_x1, screen_y1;
gfx_getscreen(&screen_x0, &screen_y0, &screen_x1, &screen_y1);
//gfx_mapscreen(screen_x0-50, screen_y0-50, screen_x1+50, screen_y1+50);
// calculate the final pixelsize for the tiles
float tile_pixelsize = 1024/32.0f;
float final_tilesize = scale/(screen_x1-screen_x0) * gfx_screenwidth();
float final_tilesize_scale = final_tilesize/tile_pixelsize;
gfx_quads_begin();
gfx_setcolor(color.r, color.g, color.b, color.a);
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;
// adjust the texture shift according to mipmap level
float texsize = 1024.0f;
float frac = (1.25f/texsize) * (1/final_tilesize_scale);
float nudge = (0.5f/texsize) * (1/final_tilesize_scale);
for(int y = starty; y < endy; y++)
for(int x = startx; x < endx; x++)
{
int mx = x;
int my = y;
if(renderflags&TILERENDERFLAG_EXTEND)
{
if(mx<0)
mx = 0;
if(mx>=w)
mx = w-1;
if(my<0)
my = 0;
if(my>=h)
my = h-1;
}
else
{
if(mx<0)
continue; // mx = 0;
if(mx>=w)
continue; // mx = w-1;
if(my<0)
continue; // my = 0;
if(my>=h)
continue; // my = h-1;
}
int c = mx + my*w;
unsigned char index = tiles[c].index;
if(index)
{
unsigned char flags = tiles[c].flags;
bool render = false;
if(flags&TILEFLAG_OPAQUE)
{
if(renderflags&LAYERRENDERFLAG_OPAQUE)
render = true;
}
else
{
if(renderflags&LAYERRENDERFLAG_TRANSPARENT)
render = true;
}
if(render)
{
int tx = index%16;
int ty = index/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(flags&TILEFLAG_VFLIP)
{
float tmp = u0;
u0 = u1;
u1 = tmp;
}
if(flags&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);
}
}
x += tiles[c].skip;
}
gfx_quads_end();
gfx_mapscreen(screen_x0, screen_y0, screen_x1, screen_y1);
}

View File

@@ -0,0 +1,297 @@
/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */
#include <base/system.h>
#include <engine/e_client_interface.h>
#include <engine/e_config.h>
#include "ui.hpp"
/********************************************************
UI
*********************************************************/
static const void *hot_item = 0;
static const void *active_item = 0;
static const void *last_active_item = 0;
static const void *becomming_hot_item = 0;
static float mouse_x, mouse_y; /* in gui space */
static float mouse_wx, mouse_wy; /* in world space */
static unsigned mouse_buttons = 0;
static unsigned last_mouse_buttons = 0;
float ui_mouse_x() { return mouse_x; }
float ui_mouse_y() { return mouse_y; }
float ui_mouse_world_x() { return mouse_wx; }
float ui_mouse_world_y() { return mouse_wy; }
int ui_mouse_button(int index) { return (mouse_buttons>>index)&1; }
int ui_mouse_button_clicked(int index) { return ui_mouse_button(index) && !((last_mouse_buttons>>index)&1) ; }
void ui_set_hot_item(const void *id) { becomming_hot_item = id; }
void ui_set_active_item(const void *id) { active_item = id; if (id) last_active_item = id; }
void ui_clear_last_active_item() { last_active_item = 0; }
const void *ui_hot_item() { return hot_item; }
const void *ui_next_hot_item() { return becomming_hot_item; }
const void *ui_active_item() { return active_item; }
const void *ui_last_active_item() { return last_active_item; }
int ui_update(float mx, float my, float mwx, float mwy, int buttons)
{
mouse_x = mx;
mouse_y = my;
mouse_wx = mwx;
mouse_wy = mwy;
last_mouse_buttons = mouse_buttons;
mouse_buttons = buttons;
hot_item = becomming_hot_item;
if(active_item)
hot_item = active_item;
becomming_hot_item = 0;
return 0;
}
/*
bool ui_
*/
int ui_mouse_inside(const RECT *r)
{
if(mouse_x >= r->x && mouse_x <= r->x+r->w && mouse_y >= r->y && mouse_y <= r->y+r->h)
return 1;
return 0;
}
static RECT screen = { 0.0f, 0.0f, 848.0f, 480.0f };
RECT *ui_screen()
{
float aspect = gfx_screenaspect();
float w, h;
h = 600;
w = aspect*h;
screen.w = w;
screen.h = h;
return &screen;
}
void ui_set_scale(float s)
{
config.ui_scale = (int)(s*100.0f);
}
float ui_scale()
{
return config.ui_scale/100.0f;
}
void ui_clip_enable(const RECT *r)
{
float xscale = gfx_screenwidth()/ui_screen()->w;
float yscale = gfx_screenheight()/ui_screen()->h;
gfx_clip_enable((int)(r->x*xscale), (int)(r->y*yscale), (int)(r->w*xscale), (int)(r->h*yscale));
}
void ui_clip_disable()
{
gfx_clip_disable();
}
void ui_hsplit_t(const RECT *original, float cut, RECT *top, RECT *bottom)
{
RECT r = *original;
cut *= ui_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 ui_hsplit_b(const RECT *original, float cut, RECT *top, RECT *bottom)
{
RECT r = *original;
cut *= ui_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 ui_vsplit_mid(const RECT *original, RECT *left, RECT *right)
{
RECT r = *original;
float cut = r.w/2;
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 ui_vsplit_l(const RECT *original, float cut, RECT *left, RECT *right)
{
RECT r = *original;
cut *= ui_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 ui_vsplit_r(const RECT *original, float cut, RECT *left, RECT *right)
{
RECT r = *original;
cut *= ui_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 ui_margin(const RECT *original, float cut, RECT *other_rect)
{
RECT r = *original;
cut *= ui_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 ui_vmargin(const RECT *original, float cut, RECT *other_rect)
{
RECT r = *original;
cut *= ui_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 ui_hmargin(const RECT *original, float cut, RECT *other_rect)
{
RECT r = *original;
cut *= ui_scale();
other_rect->x = r.x;
other_rect->y = r.y + cut;
other_rect->w = r.w;
other_rect->h = r.h - 2*cut;
}
int ui_do_button(const void *id, const char *text, int checked, const RECT *r, ui_draw_button_func draw_func, const void *extra)
{
/* logic */
int ret = 0;
int inside = ui_mouse_inside(r);
static int button_used = 0;
if(ui_active_item() == id)
{
if(!ui_mouse_button(button_used))
{
if(inside && checked >= 0)
ret = 1+button_used;
ui_set_active_item(0);
}
}
else if(ui_hot_item() == id)
{
if(ui_mouse_button(0))
{
ui_set_active_item(id);
button_used = 0;
}
if(ui_mouse_button(1))
{
ui_set_active_item(id);
button_used = 1;
}
}
if(inside)
ui_set_hot_item(id);
if(draw_func)
draw_func(id, text, checked, r, extra);
return ret;
}
void ui_do_label(const RECT *r, const char *text, float size, int align, int max_width)
{
gfx_blend_normal();
size *= ui_scale();
if(align == 0)
{
float tw = gfx_text_width(0, size, text, max_width);
gfx_text(0, r->x + r->w/2-tw/2, r->y, size, text, max_width);
}
else if(align < 0)
gfx_text(0, r->x, r->y, size, text, max_width);
else if(align > 0)
{
float tw = gfx_text_width(0, size, text, max_width);
gfx_text(0, r->x + r->w-tw, r->y, size, text, max_width);
}
}

View File

@@ -0,0 +1,65 @@
/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */
#ifndef FILE_GAME_CLIENT_UI_H
#define FILE_GAME_CLIENT_UI_H
typedef struct
{
float x, y, w, h;
} RECT;
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
};
int ui_update(float mx, float my, float mwx, float mwy, int buttons);
float ui_mouse_x();
float ui_mouse_y();
float ui_mouse_world_x();
float ui_mouse_world_y();
int ui_mouse_button(int index);
int ui_mouse_button_clicked(int index);
void ui_set_hot_item(const void *id);
void ui_set_active_item(const void *id);
void ui_clear_last_active_item();
const void *ui_hot_item();
const void *ui_next_hot_item();
const void *ui_active_item();
const void *ui_last_active_item();
int ui_mouse_inside(const RECT *r);
RECT *ui_screen();
void ui_set_scale(float s);
float ui_scale();
void ui_clip_enable(const RECT *r);
void ui_clip_disable();
void ui_hsplit_t(const RECT *original, float cut, RECT *top, RECT *bottom);
void ui_hsplit_b(const RECT *original, float cut, RECT *top, RECT *bottom);
void ui_vsplit_mid(const RECT *original, RECT *left, RECT *right);
void ui_vsplit_l(const RECT *original, float cut, RECT *left, RECT *right);
void ui_vsplit_r(const RECT *original, float cut, RECT *left, RECT *right);
void ui_margin(const RECT *original, float cut, RECT *other_rect);
void ui_vmargin(const RECT *original, float cut, RECT *other_rect);
void ui_hmargin(const RECT *original, float cut, RECT *other_rect);
typedef void (*ui_draw_button_func)(const void *id, const char *text, int checked, const RECT *r, const void *extra);
int ui_do_button(const void *id, const char *text, int checked, const RECT *r, ui_draw_button_func draw_func, const void *extra);
void ui_do_label(const RECT *r, const char *text, float size, int align, int max_width = -1);
#endif

View File

@@ -0,0 +1,87 @@
/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */
#include <base/system.h>
#include <base/math.hpp>
#include <base/vmath.hpp>
#include <math.h>
#include <engine/e_common_interface.h>
#include <game/mapitems.hpp>
#include <game/layers.hpp>
#include <game/collision.hpp>
static TILE *tiles;
static int width = 0;
static int height = 0;
int col_width() { return width; }
int col_height() { return height; }
int col_init()
{
width = layers_game_layer()->width;
height = layers_game_layer()->height;
tiles = (TILE *)map_get_data(layers_game_layer()->data);
for(int i = 0; i < width*height; i++)
{
int index = tiles[i].index;
if(index > 128)
continue;
if(index == TILE_DEATH)
tiles[i].index = COLFLAG_DEATH;
else if(index == TILE_SOLID)
tiles[i].index = COLFLAG_SOLID;
else if(index == TILE_NOHOOK)
tiles[i].index = COLFLAG_SOLID|COLFLAG_NOHOOK;
else
tiles[i].index = 0;
}
return 1;
}
int col_get(int x, int y)
{
int nx = clamp(x/32, 0, width-1);
int ny = clamp(y/32, 0, height-1);
if(tiles[ny*width+nx].index > 128)
return 0;
return tiles[ny*width+nx].index;
}
int col_is_solid(int x, int y)
{
return col_get(x,y)&COLFLAG_SOLID;
}
// TODO: rewrite this smarter!
int col_intersect_line(vec2 pos0, vec2 pos1, vec2 *out_collision, vec2 *out_before_collision)
{
float d = distance(pos0, pos1);
vec2 last = pos0;
for(float f = 0; f < d; f++)
{
float a = f/d;
vec2 pos = mix(pos0, pos1, a);
if(col_is_solid(round(pos.x), round(pos.y)))
{
if(out_collision)
*out_collision = pos;
if(out_before_collision)
*out_before_collision = last;
return col_get(round(pos.x), round(pos.y));
}
last = pos;
}
if(out_collision)
*out_collision = pos1;
if(out_before_collision)
*out_before_collision = pos1;
return 0;
}

View File

@@ -0,0 +1,21 @@
/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */
#ifndef GAME_MAPRES_COL_H
#define GAME_MAPRES_COL_H
#include <base/vmath.hpp>
enum
{
COLFLAG_SOLID=1,
COLFLAG_DEATH=2,
COLFLAG_NOHOOK=4,
};
int col_init();
int col_is_solid(int x, int y);
int col_get(int x, int y);
int col_width();
int col_height();
int col_intersect_line(vec2 pos0, vec2 pos1, vec2 *out_collision, vec2 *out_before_collision);
#endif

View File

@@ -0,0 +1,238 @@
template <class T>
class array
{
//
//
void init()
{
list = 0;
clear();
}
public:
array()
{
init();
}
//
array(const array &other)
{
init();
setsize(other.len());
for(int i = 0; i < len(); i++)
(*this)[i] = other[i];
}
//
//
virtual ~array()
{
delete [] list;
list = 0;
}
//
//
void deleteall()
{
for(int i = 0; i < len(); i++)
delete list[i];
clear();
}
//
//
void clear()
{
delete [] list;
list_size = 1;
list = new T[1];
num_elements = 0;
}
int find(T val)
{
for(int i = 0; i < len(); i++)
if((*this)[i] == val)
return i;
return -1;
}
bool exist(T val)
{
return find(val) != -1;
}
//
// returns the number of elements in the list
//
int len() const
{
return num_elements;
}
//
// This doesn't conserve the order in the list. Be careful
//
void removebyindexfast(int index)
{
//ASSUME(_Pos >= 0 && _Pos < num_elements);
list[index] = list[num_elements-1];
setsize(len()-1);
}
void removefast(const T& _Elem)
{
for(int i = 0; i < len(); i++)
if(list[i] == _Elem)
{
removebyindexfast(i);
return;
}
}
//
//
void removebyindex(int index)
{
//ASSUME(_Pos >= 0 && _Pos < num_elements);
for(int i = index+1; i < num_elements; i++)
list[i-1] = list[i];
setsize(len()-1);
}
void insert(int index, const T& element)
{
int some_len = len();
if (index < some_len)
setsize(some_len+1);
else
setsize(index + 1);
for(int i = num_elements-2; i >= index; i--)
list[i+1] = list[i];
list[index] = element;
}
bool remove(const T& element)
{
for(int i = 0; i < len(); i++)
if(list[i] == element)
{
removebyindex(i);
return true;
}
return false;
}
//
//
int add(const T& element)
{
//if(num_elements == list_size)
setsize(len()+1);
list[num_elements-1] = element;
return num_elements-1;
}
//
//
int add(const T& elem, int index)
{
setsize(len()+1);
for(int i = num_elements-1; i > index; i--)
list[i] = list[i-1];
list[index] = elem;
//num_elements++;
return num_elements-1;
}
//
//
T& operator[] (int index)
{
return list[index];
}
const T& operator[] (int index) const
{
return list[index];
}
//
//
T *getptr()
{
return list;
}
const T *getptr() const
{
return list;
}
//
//
//
void setsize(int new_len)
{
if (list_size < new_len)
allocsize(new_len);
num_elements = new_len;
}
// removes unnessasary data, returns how many bytes was earned
int optimize()
{
int Before = memoryusage();
setsize(num_elements);
return Before - memoryusage();
}
// returns how much memory this dynamic array is using
int memoryusage()
{
return sizeof(array) + sizeof(T)*list_size;
}
//
array &operator = (const array &other)
{
setsize(other.len());
for(int i = 0; i < len(); i++)
(*this)[i] = other[i];
return *this;
}
private:
void allocsize(int new_len)
{
list_size = new_len;
T *new_list = new T[list_size];
long end = num_elements < list_size ? num_elements : list_size;
for(int i = 0; i < end; i++)
new_list[i] = list[i];
delete [] list;
list = 0;
num_elements = num_elements < list_size ? num_elements : list_size;
list = new_list;
}
T *list;
long list_size;
long num_elements;
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,522 @@
/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */
#include <base/system.h>
#include <base/math.hpp>
#include <stdlib.h>
#include <math.h>
#include "array.hpp"
#include "../mapitems.hpp"
#include "../client/render.hpp"
extern "C" {
#include <engine/e_client_interface.h>
#include <engine/e_datafile.h>
#include <engine/e_config.h>
}
#include <game/client/ui.hpp>
typedef void (*INDEX_MODIFY_FUNC)(int *index);
// EDITOR SPECIFIC
template<typename T>
void swap(T &a, T &b)
{
T tmp = a;
a = b;
b = tmp;
}
enum
{
MODE_LAYERS=0,
MODE_IMAGES,
DIALOG_NONE=0,
DIALOG_FILE,
};
typedef struct
{
POINT position;
int type;
} ENTITY;
class ENVELOPE
{
public:
int channels;
array<ENVPOINT> points;
char name[32];
float bottom, top;
ENVELOPE(int chan)
{
channels = chan;
name[0] = 0;
bottom = 0;
top = 0;
}
static int sort_comp(const void *v0, const void *v1)
{
const ENVPOINT *p0 = (const ENVPOINT *)v0;
const ENVPOINT *p1 = (const ENVPOINT *)v1;
if(p0->time < p1->time)
return -1;
if(p0->time > p1->time)
return 1;
return 0;
}
void resort()
{
qsort(points.getptr(), points.len(), sizeof(ENVPOINT), sort_comp);
find_top_bottom(0xf);
}
void find_top_bottom(int channelmask)
{
top = -1000000000.0f;
bottom = 1000000000.0f;
for(int i = 0; i < points.len(); i++)
{
for(int c = 0; c < channels; c++)
{
if(channelmask&(1<<c))
{
float v = fx2f(points[i].values[c]);
if(v > top) top = v;
if(v < bottom) bottom = v;
}
}
}
}
int eval(float time, float *result)
{
render_eval_envelope(points.getptr(), points.len(), channels, time, result);
return channels;
}
void add_point(int time, int v0, int v1=0, int v2=0, int v3=0)
{
ENVPOINT p;
p.time = time;
p.values[0] = v0;
p.values[1] = v1;
p.values[2] = v2;
p.values[3] = v3;
p.curvetype = CURVETYPE_LINEAR;
points.add(p);
resort();
}
float end_time()
{
if(points.len())
return points[points.len()-1].time*(1.0f/1000.0f);
return 0;
}
};
class LAYER;
class LAYERGROUP;
class MAP;
class LAYER
{
public:
LAYER()
{
type = LAYERTYPE_INVALID;
type_name = "(invalid)";
visible = true;
readonly = false;
flags = 0;
}
virtual ~LAYER()
{
}
virtual void brush_selecting(RECT rect) {}
virtual int brush_grab(LAYERGROUP *brush, RECT rect) { return 0; }
virtual void brush_draw(LAYER *brush, float x, float y) {}
virtual void brush_place(LAYER *brush, float x, float y) {}
virtual void brush_flip_x() {}
virtual void brush_flip_y() {}
virtual void brush_rotate(float amount) {}
virtual void render() {}
virtual int render_properties(RECT *toolbox) { return 0; }
virtual void modify_image_index(INDEX_MODIFY_FUNC func) {}
virtual void modify_envelope_index(INDEX_MODIFY_FUNC func) {}
virtual void get_size(float *w, float *h) { *w = 0; *h = 0;}
const char *type_name;
int type;
int flags;
bool readonly;
bool visible;
};
class LAYERGROUP
{
public:
array<LAYER*> layers;
int offset_x;
int offset_y;
int parallax_x;
int parallax_y;
int use_clipping;
int clip_x;
int clip_y;
int clip_w;
int clip_h;
const char *name;
bool game_group;
bool visible;
LAYERGROUP();
~LAYERGROUP();
void convert(RECT *rect);
void render();
void mapscreen();
void mapping(float *points);
bool is_empty() const;
void clear();
void add_layer(LAYER *l);
void get_size(float *w, float *h);
void delete_layer(int index);
int swap_layers(int index0, int index1);
void modify_image_index(INDEX_MODIFY_FUNC func)
{
for(int i = 0; i < layers.len(); i++)
layers[i]->modify_image_index(func);
}
void modify_envelope_index(INDEX_MODIFY_FUNC func)
{
for(int i = 0; i < layers.len(); i++)
layers[i]->modify_envelope_index(func);
}
};
class EDITOR_IMAGE : public IMAGE_INFO
{
public:
EDITOR_IMAGE()
{
tex_id = -1;
name[0] = 0;
external = 0;
width = 0;
height = 0;
data = 0;
format = 0;
}
~EDITOR_IMAGE()
{
gfx_unload_texture(tex_id);
}
void analyse_tileflags();
int tex_id;
int external;
char name[128];
unsigned char tileflags[256];
};
class MAP
{
void make_game_group(LAYERGROUP *group);
void make_game_layer(LAYER *layer);
public:
MAP()
{
clean();
}
array<LAYERGROUP*> groups;
array<EDITOR_IMAGE*> images;
array<ENVELOPE*> envelopes;
class LAYER_GAME *game_layer;
LAYERGROUP *game_group;
ENVELOPE *new_envelope(int channels)
{
ENVELOPE *e = new ENVELOPE(channels);
envelopes.add(e);
return e;
}
LAYERGROUP *new_group()
{
LAYERGROUP *g = new LAYERGROUP;
groups.add(g);
return g;
}
int swap_groups(int index0, int index1)
{
if(index0 < 0 || index0 >= groups.len()) return index0;
if(index1 < 0 || index1 >= groups.len()) return index0;
if(index0 == index1) return index0;
swap(groups[index0], groups[index1]);
return index1;
}
void delete_group(int index)
{
if(index < 0 || index >= groups.len()) return;
delete groups[index];
groups.removebyindex(index);
}
void modify_image_index(INDEX_MODIFY_FUNC func)
{
for(int i = 0; i < groups.len(); i++)
groups[i]->modify_image_index(func);
}
void modify_envelope_index(INDEX_MODIFY_FUNC func)
{
for(int i = 0; i < groups.len(); i++)
groups[i]->modify_envelope_index(func);
}
void clean();
void create_default(int entities_texture);
// io
int save(const char *filename);
int load(const char *filename);
};
struct PROPERTY
{
const char *name;
int value;
int type;
int min;
int max;
};
enum
{
PROPTYPE_NULL=0,
PROPTYPE_BOOL,
PROPTYPE_INT_STEP,
PROPTYPE_INT_SCROLL,
PROPTYPE_COLOR,
PROPTYPE_IMAGE,
PROPTYPE_ENVELOPE,
};
class EDITOR
{
public:
EDITOR()
{
mode = MODE_LAYERS;
dialog = 0;
tooltip = 0;
world_offset_x = 0;
world_offset_y = 0;
editor_offset_x = 0.0f;
editor_offset_y = 0.0f;
world_zoom = 1.0f;
zoom_level = 100;
lock_mouse = false;
show_mouse_pointer = true;
mouse_delta_x = 0;
mouse_delta_y = 0;
mouse_delta_wx = 0;
mouse_delta_wy = 0;
gui_active = true;
proof_borders = false;
show_detail = true;
animate = false;
animate_start = 0;
animate_time = 0;
animate_speed = 1;
show_envelope_editor = 0;
}
void invoke_file_dialog(int listdir_type, const char *title, const char *button_text,
const char *basepath, const char *default_name,
void (*func)(const char *filename));
void reset(bool create_default=true);
int save(const char *filename);
int load(const char *filename);
int append(const char *filename);
void render();
QUAD *get_selected_quad();
LAYER *get_selected_layer_type(int index, int type);
LAYER *get_selected_layer(int index);
LAYERGROUP *get_selected_group();
int do_properties(RECT *toolbox, PROPERTY *props, int *ids, int *new_val);
int mode;
int dialog;
const char *tooltip;
float world_offset_x;
float world_offset_y;
float editor_offset_x;
float editor_offset_y;
float world_zoom;
int zoom_level;
bool lock_mouse;
bool show_mouse_pointer;
bool gui_active;
bool proof_borders;
float mouse_delta_x;
float mouse_delta_y;
float mouse_delta_wx;
float mouse_delta_wy;
bool show_detail;
bool animate;
int64 animate_start;
float animate_time;
float animate_speed;
int show_envelope_editor;
int selected_layer;
int selected_group;
int selected_quad;
int selected_points;
int selected_envelope;
int selected_image;
MAP map;
};
extern EDITOR editor;
typedef struct
{
int x, y;
int w, h;
} RECTi;
class LAYER_TILES : public LAYER
{
public:
LAYER_TILES(int w, int h);
~LAYER_TILES();
void resize(int new_w, int new_h);
void make_palette();
virtual void render();
int convert_x(float x) const;
int convert_y(float y) const;
void convert(RECT rect, RECTi *out);
void snap(RECT *rect);
void clamp(RECTi *rect);
virtual void brush_selecting(RECT rect);
virtual int brush_grab(LAYERGROUP *brush, RECT rect);
virtual void brush_draw(LAYER *brush, float wx, float wy);
virtual void brush_flip_x();
virtual void brush_flip_y();
virtual int render_properties(RECT *toolbox);
virtual void modify_image_index(INDEX_MODIFY_FUNC func);
virtual void modify_envelope_index(INDEX_MODIFY_FUNC func);
void prepare_for_save();
void get_size(float *w, float *h) { *w = width*32.0f; *h = height*32.0f; }
int tex_id;
int game;
int image;
int width;
int height;
TILE *tiles;
};
class LAYER_QUADS : public LAYER
{
public:
LAYER_QUADS();
~LAYER_QUADS();
virtual void render();
QUAD *new_quad();
virtual void brush_selecting(RECT rect);
virtual int brush_grab(LAYERGROUP *brush, RECT rect);
virtual void brush_place(LAYER *brush, float wx, float wy);
virtual void brush_flip_x();
virtual void brush_flip_y();
virtual void brush_rotate(float amount);
virtual int render_properties(RECT *toolbox);
virtual void modify_image_index(INDEX_MODIFY_FUNC func);
virtual void modify_envelope_index(INDEX_MODIFY_FUNC func);
void get_size(float *w, float *h);
int image;
array<QUAD> quads;
};
class LAYER_GAME : public LAYER_TILES
{
public:
LAYER_GAME(int w, int h);
~LAYER_GAME();
virtual int render_properties(RECT *toolbox);
};
int do_editor_button(const void *id, const char *text, int checked, const RECT *r, ui_draw_button_func draw_func, int flags, const char *tooltip);
void draw_editor_button(const void *id, const char *text, int checked, const RECT *r, const void *extra);
void draw_editor_button_menuitem(const void *id, const char *text, int checked, const RECT *r, const void *extra);
void ui_invoke_popup_menu(void *id, int flags, float x, float y, float w, float h, int (*func)(RECT rect), void *extra=0);
void ui_do_popup_menu();
int popup_group(RECT view);
int popup_layer(RECT view);
int popup_quad(RECT view);
int popup_point(RECT view);
void popup_select_image_invoke(int current, float x, float y);
int popup_select_image_result();

View File

@@ -0,0 +1,602 @@
#include <string.h>
#include <stdio.h>
#include "ed_editor.hpp"
template<typename T>
static int make_version(int i, const T &v)
{ return (i<<16)+sizeof(T); }
// backwards compatiblity
void editor_load_old(DATAFILE *df, MAP *map)
{
class mapres_image
{
public:
int width;
int height;
int image_data;
};
struct mapres_tilemap
{
int image;
int width;
int height;
int x, y;
int scale;
int data;
int main;
};
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,
};
enum
{
MAPRES_REGISTERED=0x8000,
MAPRES_IMAGE=0x8001,
MAPRES_TILEMAP=0x8002,
MAPRES_COLLISIONMAP=0x8003,
MAPRES_TEMP_THEME=0x8fff,
};
// load tilemaps
int game_width = 0;
int game_height = 0;
{
int start, num;
datafile_get_type(df, MAPRES_TILEMAP, &start, &num);
for(int t = 0; t < num; t++)
{
mapres_tilemap *tmap = (mapres_tilemap *)datafile_get_item(df, start+t,0,0);
LAYER_TILES *l = new LAYER_TILES(tmap->width, tmap->height);
if(tmap->main)
{
// move game layer to correct position
for(int i = 0; i < map->groups[0]->layers.len()-1; i++)
{
if(map->groups[0]->layers[i] == editor.map.game_layer)
map->groups[0]->swap_layers(i, i+1);
}
game_width = tmap->width;
game_height = tmap->height;
}
// add new layer
map->groups[0]->add_layer(l);
// process the data
unsigned char *src_data = (unsigned char *)datafile_get_data(df, tmap->data);
TILE *dst_data = l->tiles;
for(int y = 0; y < tmap->height; y++)
for(int x = 0; x < tmap->width; x++, dst_data++, src_data+=2)
{
dst_data->index = src_data[0];
dst_data->flags = src_data[1];
}
l->image = tmap->image;
}
}
// load images
{
int start, count;
datafile_get_type(df, MAPRES_IMAGE, &start, &count);
for(int i = 0; i < count; i++)
{
mapres_image *imgres = (mapres_image *)datafile_get_item(df, start+i, 0, 0);
void *data = datafile_get_data(df, imgres->image_data);
EDITOR_IMAGE *img = new EDITOR_IMAGE;
img->width = imgres->width;
img->height = imgres->height;
img->format = IMG_RGBA;
// copy image data
img->data = mem_alloc(img->width*img->height*4, 1);
mem_copy(img->data, data, img->width*img->height*4);
img->tex_id = gfx_load_texture_raw(img->width, img->height, img->format, img->data, IMG_AUTO, 0);
map->images.add(img);
// unload image
datafile_unload_data(df, imgres->image_data);
}
}
// load entities
{
LAYER_GAME *g = map->game_layer;
g->resize(game_width, game_height);
for(int t = MAPRES_ENTS_START; t < MAPRES_ENTS_END; t++)
{
// fetch entities of this class
int start, num;
datafile_get_type(df, t, &start, &num);
for(int i = 0; i < num; i++)
{
mapres_entity *e = (mapres_entity *)datafile_get_item(df, start+i,0,0);
int x = e->x/32;
int y = e->y/32;
int id = -1;
if(t == MAPRES_SPAWNPOINT) id = ENTITY_SPAWN;
else if(t == MAPRES_SPAWNPOINT_RED) id = ENTITY_SPAWN_RED;
else if(t == MAPRES_SPAWNPOINT_BLUE) id = ENTITY_SPAWN_BLUE;
else if(t == MAPRES_FLAGSTAND_RED) id = ENTITY_FLAGSTAND_RED;
else if(t == MAPRES_FLAGSTAND_BLUE) id = ENTITY_FLAGSTAND_BLUE;
else if(t == MAPRES_ITEM)
{
if(e->data[0] == ITEM_WEAPON_SHOTGUN) id = ENTITY_WEAPON_SHOTGUN;
else if(e->data[0] == ITEM_WEAPON_ROCKET) id = ENTITY_WEAPON_GRENADE;
else if(e->data[0] == ITEM_NINJA) id = ENTITY_POWERUP_NINJA;
else if(e->data[0] == ITEM_ARMOR) id = ENTITY_ARMOR_1;
else if(e->data[0] == ITEM_HEALTH) id = ENTITY_HEALTH_1;
}
if(id > 0 && x >= 0 && x < g->width && y >= 0 && y < g->height)
g->tiles[y*g->width+x].index = id+ENTITY_OFFSET;
}
}
}
}
int EDITOR::save(const char *filename)
{
return map.save(filename);
}
int MAP::save(const char *filename)
{
dbg_msg("editor", "saving to '%s'...", filename);
DATAFILE_OUT *df = datafile_create(filename);
if(!df)
{
dbg_msg("editor", "failed to open file '%s'...", filename);
return 0;
}
// save version
{
MAPITEM_VERSION item;
item.version = 1;
datafile_add_item(df, MAPITEMTYPE_VERSION, 0, sizeof(item), &item);
}
// save images
for(int i = 0; i < images.len(); i++)
{
EDITOR_IMAGE *img = images[i];
// analyse the image for when saving (should be done when we load the image)
// TODO!
img->analyse_tileflags();
MAPITEM_IMAGE item;
item.version = 1;
item.width = img->width;
item.height = img->height;
item.external = img->external;
item.image_name = datafile_add_data(df, strlen(img->name)+1, img->name);
if(img->external)
item.image_data = -1;
else
item.image_data = datafile_add_data(df, item.width*item.height*4, img->data);
datafile_add_item(df, MAPITEMTYPE_IMAGE, i, sizeof(item), &item);
}
// save layers
int layer_count = 0;
for(int g = 0; g < groups.len(); g++)
{
LAYERGROUP *group = groups[g];
MAPITEM_GROUP gitem;
gitem.version = MAPITEM_GROUP::CURRENT_VERSION;
gitem.parallax_x = group->parallax_x;
gitem.parallax_y = group->parallax_y;
gitem.offset_x = group->offset_x;
gitem.offset_y = group->offset_y;
gitem.use_clipping = group->use_clipping;
gitem.clip_x = group->clip_x;
gitem.clip_y = group->clip_y;
gitem.clip_w = group->clip_w;
gitem.clip_h = group->clip_h;
gitem.start_layer = layer_count;
gitem.num_layers = 0;
for(int l = 0; l < group->layers.len(); l++)
{
if(group->layers[l]->type == LAYERTYPE_TILES)
{
dbg_msg("editor", "saving tiles layer");
LAYER_TILES *layer = (LAYER_TILES *)group->layers[l];
layer->prepare_for_save();
MAPITEM_LAYER_TILEMAP item;
item.version = 2;
item.layer.flags = layer->flags;
item.layer.type = layer->type;
item.color.r = 255; // not in use right now
item.color.g = 255;
item.color.b = 255;
item.color.a = 255;
item.color_env = -1;
item.color_env_offset = 0;
item.width = layer->width;
item.height = layer->height;
item.flags = layer->game;
item.image = layer->image;
item.data = datafile_add_data(df, layer->width*layer->height*sizeof(TILE), layer->tiles);
datafile_add_item(df, MAPITEMTYPE_LAYER, layer_count, sizeof(item), &item);
gitem.num_layers++;
layer_count++;
}
else if(group->layers[l]->type == LAYERTYPE_QUADS)
{
dbg_msg("editor", "saving quads layer");
LAYER_QUADS *layer = (LAYER_QUADS *)group->layers[l];
if(layer->quads.len())
{
MAPITEM_LAYER_QUADS item;
item.version = 1;
item.layer.flags = layer->flags;
item.layer.type = layer->type;
item.image = layer->image;
// add the data
item.num_quads = layer->quads.len();
item.data = datafile_add_data_swapped(df, layer->quads.len()*sizeof(QUAD), layer->quads.getptr());
datafile_add_item(df, MAPITEMTYPE_LAYER, layer_count, sizeof(item), &item);
// clean up
//mem_free(quads);
gitem.num_layers++;
layer_count++;
}
}
}
datafile_add_item(df, MAPITEMTYPE_GROUP, g, sizeof(gitem), &gitem);
}
// save envelopes
int point_count = 0;
for(int e = 0; e < envelopes.len(); e++)
{
MAPITEM_ENVELOPE item;
item.version = 1;
item.channels = envelopes[e]->channels;
item.start_point = point_count;
item.num_points = envelopes[e]->points.len();
item.name = -1;
datafile_add_item(df, MAPITEMTYPE_ENVELOPE, e, sizeof(item), &item);
point_count += item.num_points;
}
// save points
int totalsize = sizeof(ENVPOINT) * point_count;
ENVPOINT *points = (ENVPOINT *)mem_alloc(totalsize, 1);
point_count = 0;
for(int e = 0; e < envelopes.len(); e++)
{
int count = envelopes[e]->points.len();
mem_copy(&points[point_count], envelopes[e]->points.getptr(), sizeof(ENVPOINT)*count);
point_count += count;
}
datafile_add_item(df, MAPITEMTYPE_ENVPOINTS, 0, totalsize, points);
// finish the data file
datafile_finish(df);
dbg_msg("editor", "done");
// send rcon.. if we can
if(client_rcon_authed())
{
client_rcon("sv_map_reload 1");
}
return 1;
}
int EDITOR::load(const char *filename)
{
reset();
return map.load(filename);
}
int MAP::load(const char *filename)
{
DATAFILE *df = datafile_load(filename);
if(!df)
return 0;
clean();
// check version
MAPITEM_VERSION *item = (MAPITEM_VERSION *)datafile_find_item(df, MAPITEMTYPE_VERSION, 0);
if(!item)
{
// import old map
MAP old_mapstuff;
editor.reset();
editor_load_old(df, this);
}
else if(item->version == 1)
{
//editor.reset(false);
// load images
{
int start, num;
datafile_get_type(df, MAPITEMTYPE_IMAGE, &start, &num);
for(int i = 0; i < num; i++)
{
MAPITEM_IMAGE *item = (MAPITEM_IMAGE *)datafile_get_item(df, start+i, 0, 0);
char *name = (char *)datafile_get_data(df, item->image_name);
// copy base info
EDITOR_IMAGE *img = new EDITOR_IMAGE;
img->external = item->external;
if(item->external)
{
char buf[256];
sprintf(buf, "mapres/%s.png", name);
// load external
EDITOR_IMAGE imginfo;
if(gfx_load_png(&imginfo, buf))
{
*img = imginfo;
img->tex_id = gfx_load_texture_raw(imginfo.width, imginfo.height, imginfo.format, imginfo.data, IMG_AUTO, 0);
img->external = 1;
}
}
else
{
img->width = item->width;
img->height = item->height;
img->format = IMG_RGBA;
// copy image data
void *data = datafile_get_data(df, item->image_data);
img->data = mem_alloc(img->width*img->height*4, 1);
mem_copy(img->data, data, img->width*img->height*4);
img->tex_id = gfx_load_texture_raw(img->width, img->height, img->format, img->data, IMG_AUTO, 0);
}
// copy image name
if(name)
strncpy(img->name, name, 128);
images.add(img);
// unload image
datafile_unload_data(df, item->image_data);
datafile_unload_data(df, item->image_name);
}
}
// load groups
{
int layers_start, layers_num;
datafile_get_type(df, MAPITEMTYPE_LAYER, &layers_start, &layers_num);
int start, num;
datafile_get_type(df, MAPITEMTYPE_GROUP, &start, &num);
for(int g = 0; g < num; g++)
{
MAPITEM_GROUP *gitem = (MAPITEM_GROUP *)datafile_get_item(df, start+g, 0, 0);
if(gitem->version < 1 || gitem->version > MAPITEM_GROUP::CURRENT_VERSION)
continue;
LAYERGROUP *group = new_group();
group->parallax_x = gitem->parallax_x;
group->parallax_y = gitem->parallax_y;
group->offset_x = gitem->offset_x;
group->offset_y = gitem->offset_y;
if(gitem->version >= 2)
{
group->use_clipping = gitem->use_clipping;
group->clip_x = gitem->clip_x;
group->clip_y = gitem->clip_y;
group->clip_w = gitem->clip_w;
group->clip_h = gitem->clip_h;
}
for(int l = 0; l < gitem->num_layers; l++)
{
LAYER *layer = 0;
MAPITEM_LAYER *layer_item = (MAPITEM_LAYER *)datafile_get_item(df, layers_start+gitem->start_layer+l, 0, 0);
if(!layer_item)
continue;
if(layer_item->type == LAYERTYPE_TILES)
{
MAPITEM_LAYER_TILEMAP *tilemap_item = (MAPITEM_LAYER_TILEMAP *)layer_item;
LAYER_TILES *tiles = 0;
if(tilemap_item->flags&1)
{
tiles = new LAYER_GAME(tilemap_item->width, tilemap_item->height);
make_game_layer(tiles);
make_game_group(group);
}
else
tiles = new LAYER_TILES(tilemap_item->width, tilemap_item->height);
layer = tiles;
group->add_layer(tiles);
void *data = datafile_get_data(df, tilemap_item->data);
tiles->image = tilemap_item->image;
tiles->game = tilemap_item->flags&1;
mem_copy(tiles->tiles, data, tiles->width*tiles->height*sizeof(TILE));
if(tiles->game && tilemap_item->version == make_version(1, *tilemap_item))
{
for(int i = 0; i < tiles->width*tiles->height; i++)
{
if(tiles->tiles[i].index)
tiles->tiles[i].index += ENTITY_OFFSET;
}
}
datafile_unload_data(df, tilemap_item->data);
}
else if(layer_item->type == LAYERTYPE_QUADS)
{
MAPITEM_LAYER_QUADS *quads_item = (MAPITEM_LAYER_QUADS *)layer_item;
LAYER_QUADS *quads = new LAYER_QUADS;
layer = quads;
quads->image = quads_item->image;
if(quads->image < -1 || quads->image >= images.len())
quads->image = -1;
void *data = datafile_get_data_swapped(df, quads_item->data);
group->add_layer(quads);
quads->quads.setsize(quads_item->num_quads);
mem_copy(quads->quads.getptr(), data, sizeof(QUAD)*quads_item->num_quads);
datafile_unload_data(df, quads_item->data);
}
if(layer)
layer->flags = layer_item->flags;
}
}
}
// load envelopes
{
ENVPOINT *points = 0;
{
int start, num;
datafile_get_type(df, MAPITEMTYPE_ENVPOINTS, &start, &num);
if(num)
points = (ENVPOINT *)datafile_get_item(df, start, 0, 0);
}
int start, num;
datafile_get_type(df, MAPITEMTYPE_ENVELOPE, &start, &num);
for(int e = 0; e < num; e++)
{
MAPITEM_ENVELOPE *item = (MAPITEM_ENVELOPE *)datafile_get_item(df, start+e, 0, 0);
ENVELOPE *env = new ENVELOPE(item->channels);
env->points.setsize(item->num_points);
mem_copy(env->points.getptr(), &points[item->start_point], sizeof(ENVPOINT)*item->num_points);
envelopes.add(env);
}
}
}
datafile_unload(df);
return 0;
}
static int modify_add_amount = 0;
static void modify_add(int *index)
{
if(*index >= 0)
*index += modify_add_amount;
}
int EDITOR::append(const char *filename)
{
MAP new_map;
int err;
err = new_map.load(filename);
if(err)
return err;
// modify indecies
modify_add_amount = map.images.len();
new_map.modify_image_index(modify_add);
modify_add_amount = map.envelopes.len();
new_map.modify_envelope_index(modify_add);
// transfer images
for(int i = 0; i < new_map.images.len(); i++)
map.images.add(new_map.images[i]);
new_map.images.clear();
// transfer envelopes
for(int i = 0; i < new_map.envelopes.len(); i++)
map.envelopes.add(new_map.envelopes[i]);
new_map.envelopes.clear();
// transfer groups
for(int i = 0; i < new_map.groups.len(); i++)
{
if(new_map.groups[i] == new_map.game_group)
delete new_map.groups[i];
else
map.groups.add(new_map.groups[i]);
}
new_map.groups.clear();
// all done \o/
return 0;
}

View File

@@ -0,0 +1,20 @@
#include "ed_editor.hpp"
LAYER_GAME::LAYER_GAME(int w, int h)
: LAYER_TILES(w, h)
{
type_name = "Game";
game = 1;
}
LAYER_GAME::~LAYER_GAME()
{
}
int LAYER_GAME::render_properties(RECT *toolbox)
{
int r = LAYER_TILES::render_properties(toolbox);
image = -1;
return r;
}

View File

@@ -0,0 +1,249 @@
#include <base/math.hpp>
#include "ed_editor.hpp"
#include <game/generated/gc_data.hpp>
#include <game/client/render.hpp>
LAYER_QUADS::LAYER_QUADS()
{
type = LAYERTYPE_QUADS;
type_name = "Quads";
image = -1;
}
LAYER_QUADS::~LAYER_QUADS()
{
}
static void envelope_eval(float time_offset, int env, float *channels)
{
if(env < 0 || env > editor.map.envelopes.len())
{
channels[0] = 0;
channels[1] = 0;
channels[2] = 0;
channels[3] = 0;
return;
}
ENVELOPE *e = editor.map.envelopes[env];
float t = editor.animate_time+time_offset;
t *= editor.animate_speed;
e->eval(t, channels);
}
void LAYER_QUADS::render()
{
gfx_texture_set(-1);
if(image >= 0 && image < editor.map.images.len())
gfx_texture_set(editor.map.images[image]->tex_id);
render_quads(quads.getptr(), quads.len(), envelope_eval, LAYERRENDERFLAG_OPAQUE|LAYERRENDERFLAG_TRANSPARENT);
}
QUAD *LAYER_QUADS::new_quad()
{
QUAD *q = &quads[quads.add(QUAD())];
q->pos_env = -1;
q->color_env = -1;
q->pos_env_offset = 0;
q->color_env_offset = 0;
int x = 0, y = 0;
q->points[0].x = x;
q->points[0].y = y;
q->points[1].x = x+64;
q->points[1].y = y;
q->points[2].x = x;
q->points[2].y = y+64;
q->points[3].x = x+64;
q->points[3].y = y+64;
q->points[4].x = x+32; // pivot
q->points[4].y = y+32;
for(int i = 0; i < 5; i++)
{
q->points[i].x <<= 10;
q->points[i].y <<= 10;
}
q->texcoords[0].x = 0;
q->texcoords[0].y = 0;
q->texcoords[1].x = 1<<10;
q->texcoords[1].y = 0;
q->texcoords[2].x = 0;
q->texcoords[2].y = 1<<10;
q->texcoords[3].x = 1<<10;
q->texcoords[3].y = 1<<10;
q->colors[0].r = 255; q->colors[0].g = 255; q->colors[0].b = 255; q->colors[0].a = 255;
q->colors[1].r = 255; q->colors[1].g = 255; q->colors[1].b = 255; q->colors[1].a = 255;
q->colors[2].r = 255; q->colors[2].g = 255; q->colors[2].b = 255; q->colors[2].a = 255;
q->colors[3].r = 255; q->colors[3].g = 255; q->colors[3].b = 255; q->colors[3].a = 255;
return q;
}
void LAYER_QUADS::brush_selecting(RECT rect)
{
// draw selection rectangle
gfx_texture_set(-1);
gfx_lines_begin();
gfx_lines_draw(rect.x, rect.y, rect.x+rect.w, rect.y);
gfx_lines_draw(rect.x+rect.w, rect.y, rect.x+rect.w, rect.y+rect.h);
gfx_lines_draw(rect.x+rect.w, rect.y+rect.h, rect.x, rect.y+rect.h);
gfx_lines_draw(rect.x, rect.y+rect.h, rect.x, rect.y);
gfx_lines_end();
}
int LAYER_QUADS::brush_grab(LAYERGROUP *brush, RECT rect)
{
// create new layers
LAYER_QUADS *grabbed = new LAYER_QUADS();
grabbed->image = image;
brush->add_layer(grabbed);
//dbg_msg("", "%f %f %f %f", rect.x, rect.y, rect.w, rect.h);
for(int i = 0; i < quads.len(); i++)
{
QUAD *q = &quads[i];
float px = fx2f(q->points[4].x);
float py = fx2f(q->points[4].y);
if(px > rect.x && px < rect.x+rect.w && py > rect.y && py < rect.y+rect.h)
{
dbg_msg("", "grabbed one");
QUAD n;
n = *q;
for(int p = 0; p < 5; p++)
{
n.points[p].x -= f2fx(rect.x);
n.points[p].y -= f2fx(rect.y);
}
grabbed->quads.add(n);
}
}
return grabbed->quads.len()?1:0;
}
void LAYER_QUADS::brush_place(LAYER *brush, float wx, float wy)
{
LAYER_QUADS *l = (LAYER_QUADS *)brush;
for(int i = 0; i < l->quads.len(); i++)
{
QUAD n = l->quads[i];
for(int p = 0; p < 5; p++)
{
n.points[p].x += f2fx(wx);
n.points[p].y += f2fx(wy);
}
quads.add(n);
}
}
void LAYER_QUADS::brush_flip_x()
{
}
void LAYER_QUADS::brush_flip_y()
{
}
void rotate(vec2 *center, vec2 *point, float rotation)
{
float x = point->x - center->x;
float y = point->y - center->y;
point->x = x * cosf(rotation) - y * sinf(rotation) + center->x;
point->y = x * sinf(rotation) + y * cosf(rotation) + center->y;
}
void LAYER_QUADS::brush_rotate(float amount)
{
vec2 center;
get_size(&center.x, &center.y);
center.x /= 2;
center.y /= 2;
for(int i = 0; i < quads.len(); i++)
{
QUAD *q = &quads[i];
for(int p = 0; p < 5; p++)
{
vec2 pos(fx2f(q->points[p].x), fx2f(q->points[p].y));
rotate(&center, &pos, amount);
q->points[p].x = f2fx(pos.x);
q->points[p].y = f2fx(pos.y);
}
}
}
void LAYER_QUADS::get_size(float *w, float *h)
{
*w = 0; *h = 0;
for(int i = 0; i < quads.len(); i++)
{
for(int p = 0; p < 5; p++)
{
*w = max(*w, fx2f(quads[i].points[p].x));
*h = max(*h, fx2f(quads[i].points[p].y));
}
}
}
extern int selected_points;
int LAYER_QUADS::render_properties(RECT *toolbox)
{
// layer props
enum
{
PROP_IMAGE=0,
NUM_PROPS,
};
PROPERTY props[] = {
{"Image", image, PROPTYPE_IMAGE, -1, 0},
{0},
};
static int ids[NUM_PROPS] = {0};
int new_val = 0;
int prop = editor.do_properties(toolbox, props, ids, &new_val);
if(prop == PROP_IMAGE)
{
if(new_val >= 0)
image = new_val%editor.map.images.len();
else
image = -1;
}
return 0;
}
void LAYER_QUADS::modify_image_index(INDEX_MODIFY_FUNC func)
{
func(&image);
}
void LAYER_QUADS::modify_envelope_index(INDEX_MODIFY_FUNC func)
{
for(int i = 0; i < quads.len(); i++)
{
func(&quads[i].pos_env);
func(&quads[i].color_env);
}
}

View File

@@ -0,0 +1,296 @@
#include <base/math.hpp>
#include <game/generated/gc_data.hpp>
#include <game/client/render.hpp>
#include "ed_editor.hpp"
LAYER_TILES::LAYER_TILES(int w, int h)
{
type = LAYERTYPE_TILES;
type_name = "Tiles";
width = w;
height = h;
image = -1;
tex_id = -1;
game = 0;
tiles = new TILE[width*height];
mem_zero(tiles, width*height*sizeof(TILE));
}
LAYER_TILES::~LAYER_TILES()
{
delete [] tiles;
}
void LAYER_TILES::prepare_for_save()
{
for(int y = 0; y < height; y++)
for(int x = 0; x < width; x++)
tiles[y*width+x].flags &= TILEFLAG_VFLIP|TILEFLAG_HFLIP;
if(image != -1)
{
for(int y = 0; y < height; y++)
for(int x = 0; x < width; x++)
tiles[y*width+x].flags |= editor.map.images[image]->tileflags[tiles[y*width+x].index];
}
}
void LAYER_TILES::make_palette()
{
for(int y = 0; y < height; y++)
for(int x = 0; x < width; x++)
tiles[y*width+x].index = y*16+x;
}
void LAYER_TILES::render()
{
if(image >= 0 && image < editor.map.images.len())
tex_id = editor.map.images[image]->tex_id;
gfx_texture_set(tex_id);
render_tilemap(tiles, width, height, 32.0f, vec4(1,1,1,1), LAYERRENDERFLAG_OPAQUE|LAYERRENDERFLAG_TRANSPARENT);
}
int LAYER_TILES::convert_x(float x) const { return (int)(x/32.0f); }
int LAYER_TILES::convert_y(float y) const { return (int)(y/32.0f); }
void LAYER_TILES::convert(RECT rect, RECTi *out)
{
out->x = convert_x(rect.x);
out->y = convert_y(rect.y);
out->w = convert_x(rect.x+rect.w+31) - out->x;
out->h = convert_y(rect.y+rect.h+31) - out->y;
}
void LAYER_TILES::snap(RECT *rect)
{
RECTi out;
convert(*rect, &out);
rect->x = out.x*32.0f;
rect->y = out.y*32.0f;
rect->w = out.w*32.0f;
rect->h = out.h*32.0f;
}
void LAYER_TILES::clamp(RECTi *rect)
{
if(rect->x < 0)
{
rect->w += rect->x;
rect->x = 0;
}
if(rect->y < 0)
{
rect->h += rect->y;
rect->y = 0;
}
if(rect->x+rect->w > width)
rect->w = width-rect->x;
if(rect->y+rect->h > height)
rect->h = height-rect->y;
if(rect->h < 0)
rect->h = 0;
if(rect->w < 0)
rect->w = 0;
}
void LAYER_TILES::brush_selecting(RECT rect)
{
gfx_texture_set(-1);
gfx_quads_begin();
gfx_setcolor(1,1,1,0.4f);
snap(&rect);
gfx_quads_drawTL(rect.x, rect.y, rect.w, rect.h);
gfx_quads_end();
char buf[16];
str_format(buf, sizeof(buf), "%d,%d", convert_x(rect.w), convert_y(rect.h));
gfx_text(0, rect.x+3.0f, rect.y+3.0f, 15.0f*editor.world_zoom, buf, -1);
}
int LAYER_TILES::brush_grab(LAYERGROUP *brush, RECT rect)
{
RECTi r;
convert(rect, &r);
clamp(&r);
if(!r.w || !r.h)
return 0;
// create new layers
LAYER_TILES *grabbed = new LAYER_TILES(r.w, r.h);
grabbed->tex_id = tex_id;
grabbed->image = image;
brush->add_layer(grabbed);
// copy the tiles
for(int y = 0; y < r.h; y++)
for(int x = 0; x < r.w; x++)
grabbed->tiles[y*grabbed->width+x] = tiles[(r.y+y)*width+(r.x+x)];
return 1;
}
void LAYER_TILES::brush_draw(LAYER *brush, float wx, float wy)
{
if(readonly)
return;
//
LAYER_TILES *l = (LAYER_TILES *)brush;
int sx = convert_x(wx);
int sy = convert_y(wy);
for(int y = 0; y < l->height; y++)
for(int x = 0; x < l->width; x++)
{
int fx = x+sx;
int fy = y+sy;
if(fx<0 || fx >= width || fy < 0 || fy >= height)
continue;
tiles[fy*width+fx] = l->tiles[y*l->width+x];
}
}
void LAYER_TILES::brush_flip_x()
{
for(int y = 0; y < height; y++)
for(int x = 0; x < width/2; x++)
{
TILE tmp = tiles[y*width+x];
tiles[y*width+x] = tiles[y*width+width-1-x];
tiles[y*width+width-1-x] = tmp;
}
for(int y = 0; y < height; y++)
for(int x = 0; x < width; x++)
tiles[y*width+x].flags ^= TILEFLAG_VFLIP;
}
void LAYER_TILES::brush_flip_y()
{
for(int y = 0; y < height/2; y++)
for(int x = 0; x < width; x++)
{
TILE tmp = tiles[y*width+x];
tiles[y*width+x] = tiles[(height-1-y)*width+x];
tiles[(height-1-y)*width+x] = tmp;
}
for(int y = 0; y < height; y++)
for(int x = 0; x < width; x++)
tiles[y*width+x].flags ^= TILEFLAG_HFLIP;
}
void LAYER_TILES::resize(int new_w, int new_h)
{
TILE *new_data = new TILE[new_w*new_h];
mem_zero(new_data, new_w*new_h*sizeof(TILE));
// copy old data
for(int y = 0; y < min(new_h, height); y++)
mem_copy(&new_data[y*new_w], &tiles[y*width], min(width, new_w)*sizeof(TILE));
// replace old
delete [] tiles;
tiles = new_data;
width = new_w;
height = new_h;
}
int LAYER_TILES::render_properties(RECT *toolbox)
{
RECT button;
ui_hsplit_b(toolbox, 12.0f, toolbox, &button);
bool in_gamegroup = editor.map.game_group->layers.find(this) != -1;
if(editor.map.game_layer == this)
in_gamegroup = false;
static int colcl_button = 0;
if(do_editor_button(&colcl_button, "Clear Collision", in_gamegroup?0:-1, &button, draw_editor_button, 0, "Removes collision from this layer"))
{
LAYER_TILES *gl = editor.map.game_layer;
int w = min(gl->width, width);
int h = min(gl->height, height);
for(int y = 0; y < h; y++)
for(int x = 0; x < w; x++)
{
if(gl->tiles[y*gl->width+x].index <= TILE_SOLID)
if(tiles[y*width+x].index)
gl->tiles[y*gl->width+x].index = TILE_AIR;
}
return 1;
}
static int col_button = 0;
ui_hsplit_b(toolbox, 5.0f, toolbox, &button);
ui_hsplit_b(toolbox, 12.0f, toolbox, &button);
if(do_editor_button(&col_button, "Make Collision", in_gamegroup?0:-1, &button, draw_editor_button, 0, "Constructs collision from this layer"))
{
LAYER_TILES *gl = editor.map.game_layer;
int w = min(gl->width, width);
int h = min(gl->height, height);
for(int y = 0; y < h; y++)
for(int x = 0; x < w; x++)
{
if(gl->tiles[y*gl->width+x].index <= TILE_SOLID)
gl->tiles[y*gl->width+x].index = tiles[y*width+x].index?TILE_SOLID:TILE_AIR;
}
return 1;
}
enum
{
PROP_WIDTH=0,
PROP_HEIGHT,
PROP_IMAGE,
NUM_PROPS,
};
PROPERTY props[] = {
{"Width", width, PROPTYPE_INT_STEP, 1, 1000000000},
{"Height", height, PROPTYPE_INT_STEP, 1, 1000000000},
{"Image", image, PROPTYPE_IMAGE, 0, 0},
{0},
};
if(editor.map.game_layer == this) // remove the image from the selection if this is the game layer
props[2].name = 0;
static int ids[NUM_PROPS] = {0};
int new_val = 0;
int prop = editor.do_properties(toolbox, props, ids, &new_val);
if(prop == PROP_WIDTH && new_val > 1)
resize(new_val, height);
else if(prop == PROP_HEIGHT && new_val > 1)
resize(width, new_val);
else if(prop == PROP_IMAGE)
{
if (new_val == -1)
{
tex_id = -1;
image = -1;
}
else
image = new_val%editor.map.images.len();
}
return 0;
}
void LAYER_TILES::modify_image_index(INDEX_MODIFY_FUNC func)
{
func(&image);
}
void LAYER_TILES::modify_envelope_index(INDEX_MODIFY_FUNC func)
{
}

View File

@@ -0,0 +1,424 @@
#include <stdio.h>
#include "ed_editor.hpp"
// popup menu handling
static struct
{
RECT rect;
void *id;
int (*func)(RECT rect);
int is_menu;
void *extra;
} ui_popups[8];
static int ui_num_popups = 0;
void ui_invoke_popup_menu(void *id, int flags, float x, float y, float w, float h, int (*func)(RECT rect), void *extra)
{
dbg_msg("", "invoked");
ui_popups[ui_num_popups].id = id;
ui_popups[ui_num_popups].is_menu = flags;
ui_popups[ui_num_popups].rect.x = x;
ui_popups[ui_num_popups].rect.y = y;
ui_popups[ui_num_popups].rect.w = w;
ui_popups[ui_num_popups].rect.h = h;
ui_popups[ui_num_popups].func = func;
ui_popups[ui_num_popups].extra = extra;
ui_num_popups++;
}
void ui_do_popup_menu()
{
for(int i = 0; i < ui_num_popups; i++)
{
bool inside = ui_mouse_inside(&ui_popups[i].rect);
ui_set_hot_item(&ui_popups[i].id);
if(ui_active_item() == &ui_popups[i].id)
{
if(!ui_mouse_button(0))
{
if(!inside)
ui_num_popups--;
ui_set_active_item(0);
}
}
else if(ui_hot_item() == &ui_popups[i].id)
{
if(ui_mouse_button(0))
ui_set_active_item(&ui_popups[i].id);
}
int corners = CORNER_ALL;
if(ui_popups[i].is_menu)
corners = CORNER_R|CORNER_B;
RECT r = ui_popups[i].rect;
ui_draw_rect(&r, vec4(0.5f,0.5f,0.5f,0.75f), corners, 3.0f);
ui_margin(&r, 1.0f, &r);
ui_draw_rect(&r, vec4(0,0,0,0.75f), corners, 3.0f);
ui_margin(&r, 4.0f, &r);
if(ui_popups[i].func(r))
ui_num_popups--;
if(inp_key_down(KEY_ESCAPE))
ui_num_popups--;
}
}
int popup_group(RECT view)
{
// remove group button
RECT button;
ui_hsplit_b(&view, 12.0f, &view, &button);
static int delete_button = 0;
// don't allow deletion of game group
if(editor.map.game_group != editor.get_selected_group() &&
do_editor_button(&delete_button, "Delete Group", 0, &button, draw_editor_button, 0, "Delete group"))
{
editor.map.delete_group(editor.selected_group);
return 1;
}
// new tile layer
ui_hsplit_b(&view, 10.0f, &view, &button);
ui_hsplit_b(&view, 12.0f, &view, &button);
static int new_quad_layer_button = 0;
if(do_editor_button(&new_quad_layer_button, "Add Quads Layer", 0, &button, draw_editor_button, 0, "Creates a new quad layer"))
{
LAYER *l = new LAYER_QUADS;
editor.map.groups[editor.selected_group]->add_layer(l);
editor.selected_layer = editor.map.groups[editor.selected_group]->layers.len()-1;
return 1;
}
// new quad layer
ui_hsplit_b(&view, 5.0f, &view, &button);
ui_hsplit_b(&view, 12.0f, &view, &button);
static int new_tile_layer_button = 0;
if(do_editor_button(&new_tile_layer_button, "Add Tile Layer", 0, &button, draw_editor_button, 0, "Creates a new tile layer"))
{
LAYER *l = new LAYER_TILES(50, 50);
editor.map.groups[editor.selected_group]->add_layer(l);
editor.selected_layer = editor.map.groups[editor.selected_group]->layers.len()-1;
return 1;
}
enum
{
PROP_ORDER=0,
PROP_POS_X,
PROP_POS_Y,
PROP_PARA_X,
PROP_PARA_Y,
PROP_USE_CLIPPING,
PROP_CLIP_X,
PROP_CLIP_Y,
PROP_CLIP_W,
PROP_CLIP_H,
NUM_PROPS,
};
PROPERTY props[] = {
{"Order", editor.selected_group, PROPTYPE_INT_STEP, 0, editor.map.groups.len()-1},
{"Pos X", -editor.map.groups[editor.selected_group]->offset_x, PROPTYPE_INT_SCROLL, -1000000, 1000000},
{"Pos Y", -editor.map.groups[editor.selected_group]->offset_y, PROPTYPE_INT_SCROLL, -1000000, 1000000},
{"Para X", editor.map.groups[editor.selected_group]->parallax_x, PROPTYPE_INT_SCROLL, -1000000, 1000000},
{"Para Y", editor.map.groups[editor.selected_group]->parallax_y, PROPTYPE_INT_SCROLL, -1000000, 1000000},
{"Use Clipping", editor.map.groups[editor.selected_group]->use_clipping, PROPTYPE_BOOL, 0, 1},
{"Clip X", editor.map.groups[editor.selected_group]->clip_x, PROPTYPE_INT_SCROLL, -1000000, 1000000},
{"Clip Y", editor.map.groups[editor.selected_group]->clip_y, PROPTYPE_INT_SCROLL, -1000000, 1000000},
{"Clip W", editor.map.groups[editor.selected_group]->clip_w, PROPTYPE_INT_SCROLL, -1000000, 1000000},
{"Clip H", editor.map.groups[editor.selected_group]->clip_h, PROPTYPE_INT_SCROLL, -1000000, 1000000},
{0},
};
static int ids[NUM_PROPS] = {0};
int new_val = 0;
// cut the properties that isn't needed
if(editor.get_selected_group()->game_group)
props[PROP_POS_X].name = 0;
int prop = editor.do_properties(&view, props, ids, &new_val);
if(prop == PROP_ORDER)
editor.selected_group = editor.map.swap_groups(editor.selected_group, new_val);
// these can not be changed on the game group
if(!editor.get_selected_group()->game_group)
{
if(prop == PROP_PARA_X) editor.map.groups[editor.selected_group]->parallax_x = new_val;
else if(prop == PROP_PARA_Y) editor.map.groups[editor.selected_group]->parallax_y = new_val;
else if(prop == PROP_POS_X) editor.map.groups[editor.selected_group]->offset_x = -new_val;
else if(prop == PROP_POS_Y) editor.map.groups[editor.selected_group]->offset_y = -new_val;
else if(prop == PROP_USE_CLIPPING) editor.map.groups[editor.selected_group]->use_clipping = new_val;
else if(prop == PROP_CLIP_X) editor.map.groups[editor.selected_group]->clip_x = new_val;
else if(prop == PROP_CLIP_Y) editor.map.groups[editor.selected_group]->clip_y = new_val;
else if(prop == PROP_CLIP_W) editor.map.groups[editor.selected_group]->clip_w = new_val;
else if(prop == PROP_CLIP_H) editor.map.groups[editor.selected_group]->clip_h = new_val;
}
return 0;
}
int popup_layer(RECT view)
{
// remove layer button
RECT button;
ui_hsplit_b(&view, 12.0f, &view, &button);
static int delete_button = 0;
// don't allow deletion of game layer
if(editor.map.game_layer != editor.get_selected_layer(0) &&
do_editor_button(&delete_button, "Delete Layer", 0, &button, draw_editor_button, 0, "Deletes the layer"))
{
editor.map.groups[editor.selected_group]->delete_layer(editor.selected_layer);
return 1;
}
ui_hsplit_b(&view, 10.0f, &view, 0);
LAYERGROUP *current_group = editor.map.groups[editor.selected_group];
LAYER *current_layer = editor.get_selected_layer(0);
enum
{
PROP_GROUP=0,
PROP_ORDER,
PROP_HQ,
NUM_PROPS,
};
PROPERTY props[] = {
{"Group", editor.selected_group, PROPTYPE_INT_STEP, 0, editor.map.groups.len()-1},
{"Order", editor.selected_layer, PROPTYPE_INT_STEP, 0, current_group->layers.len()},
{"Detail", current_layer->flags&LAYERFLAG_DETAIL, PROPTYPE_BOOL, 0, 1},
{0},
};
static int ids[NUM_PROPS] = {0};
int new_val = 0;
int prop = editor.do_properties(&view, props, ids, &new_val);
if(prop == PROP_ORDER)
editor.selected_layer = current_group->swap_layers(editor.selected_layer, new_val);
else if(prop == PROP_GROUP && current_layer->type != LAYERTYPE_GAME)
{
if(new_val >= 0 && new_val < editor.map.groups.len())
{
current_group->layers.remove(current_layer);
editor.map.groups[new_val]->layers.add(current_layer);
editor.selected_group = new_val;
editor.selected_layer = editor.map.groups[new_val]->layers.len()-1;
}
}
else if(prop == PROP_HQ)
{
current_layer->flags &= ~LAYERFLAG_DETAIL;
if(new_val)
current_layer->flags |= LAYERFLAG_DETAIL;
}
return current_layer->render_properties(&view);
}
int popup_quad(RECT view)
{
QUAD *quad = editor.get_selected_quad();
RECT button;
// delete button
ui_hsplit_b(&view, 12.0f, &view, &button);
static int delete_button = 0;
if(do_editor_button(&delete_button, "Delete", 0, &button, draw_editor_button, 0, "Deletes the current quad"))
{
LAYER_QUADS *layer = (LAYER_QUADS *)editor.get_selected_layer_type(0, LAYERTYPE_QUADS);
if(layer)
{
layer->quads.removebyindex(editor.selected_quad);
editor.selected_quad--;
}
return 1;
}
// square button
ui_hsplit_b(&view, 10.0f, &view, &button);
ui_hsplit_b(&view, 12.0f, &view, &button);
static int sq_button = 0;
if(do_editor_button(&sq_button, "Square", 0, &button, draw_editor_button, 0, "Squares the current quad"))
{
int top = quad->points[0].y;
int left = quad->points[0].x;
int bottom = quad->points[0].y;
int right = quad->points[0].x;
for(int k = 1; k < 4; k++)
{
if(quad->points[k].y < top) top = quad->points[k].y;
if(quad->points[k].x < left) left = quad->points[k].x;
if(quad->points[k].y > bottom) bottom = quad->points[k].y;
if(quad->points[k].x > right) right = quad->points[k].x;
}
quad->points[0].x = left; quad->points[0].y = top;
quad->points[1].x = right; quad->points[1].y = top;
quad->points[2].x = left; quad->points[2].y = bottom;
quad->points[3].x = right; quad->points[3].y = bottom;
return 1;
}
enum
{
PROP_POS_ENV=0,
PROP_POS_ENV_OFFSET,
PROP_COLOR_ENV,
PROP_COLOR_ENV_OFFSET,
NUM_PROPS,
};
PROPERTY props[] = {
{"Pos. Env", quad->pos_env, PROPTYPE_INT_STEP, -1, editor.map.envelopes.len()},
{"Pos. TO", quad->pos_env_offset, PROPTYPE_INT_SCROLL, -1000000, 1000000},
{"Color Env", quad->color_env, PROPTYPE_INT_STEP, -1, editor.map.envelopes.len()},
{"Color TO", quad->color_env_offset, PROPTYPE_INT_SCROLL, -1000000, 1000000},
{0},
};
static int ids[NUM_PROPS] = {0};
int new_val = 0;
int prop = editor.do_properties(&view, props, ids, &new_val);
if(prop == PROP_POS_ENV) quad->pos_env = clamp(new_val, -1, editor.map.envelopes.len()-1);
if(prop == PROP_POS_ENV_OFFSET) quad->pos_env_offset = new_val;
if(prop == PROP_COLOR_ENV) quad->color_env = clamp(new_val, -1, editor.map.envelopes.len()-1);
if(prop == PROP_COLOR_ENV_OFFSET) quad->color_env_offset = new_val;
return 0;
}
int popup_point(RECT view)
{
QUAD *quad = editor.get_selected_quad();
enum
{
PROP_COLOR=0,
NUM_PROPS,
};
int color = 0;
for(int v = 0; v < 4; v++)
{
if(editor.selected_points&(1<<v))
{
color = 0;
color |= quad->colors[v].r<<24;
color |= quad->colors[v].g<<16;
color |= quad->colors[v].b<<8;
color |= quad->colors[v].a;
}
}
PROPERTY props[] = {
{"Color", color, PROPTYPE_COLOR, -1, editor.map.envelopes.len()},
{0},
};
static int ids[NUM_PROPS] = {0};
int new_val = 0;
int prop = editor.do_properties(&view, props, ids, &new_val);
if(prop == PROP_COLOR)
{
for(int v = 0; v < 4; v++)
{
if(editor.selected_points&(1<<v))
{
color = 0;
quad->colors[v].r = (new_val>>24)&0xff;
quad->colors[v].g = (new_val>>16)&0xff;
quad->colors[v].b = (new_val>>8)&0xff;
quad->colors[v].a = new_val&0xff;
}
}
}
return 0;
}
static int select_image_selected = -100;
static int select_image_current = -100;
int popup_select_image(RECT view)
{
RECT buttonbar, imageview;
ui_vsplit_l(&view, 80.0f, &buttonbar, &view);
ui_margin(&view, 10.0f, &imageview);
int show_image = select_image_current;
for(int i = -1; i < editor.map.images.len(); i++)
{
RECT button;
ui_hsplit_t(&buttonbar, 12.0f, &button, &buttonbar);
ui_hsplit_t(&buttonbar, 2.0f, 0, &buttonbar);
if(ui_mouse_inside(&button))
show_image = i;
if(i == -1)
{
if(do_editor_button(&editor.map.images[i], "None", i==select_image_current, &button, draw_editor_button_menuitem, 0, 0))
select_image_selected = -1;
}
else
{
if(do_editor_button(&editor.map.images[i], editor.map.images[i]->name, i==select_image_current, &button, draw_editor_button_menuitem, 0, 0))
select_image_selected = i;
}
}
if(show_image >= 0 && show_image < editor.map.images.len())
gfx_texture_set(editor.map.images[show_image]->tex_id);
else
gfx_texture_set(-1);
gfx_quads_begin();
gfx_quads_drawTL(imageview.x, imageview.y, imageview.w, imageview.h);
gfx_quads_end();
return 0;
}
void popup_select_image_invoke(int current, float x, float y)
{
static int select_image_popup_id = 0;
select_image_selected = -100;
select_image_current = current;
ui_invoke_popup_menu(&select_image_popup_id, 0, x, y, 400, 300, popup_select_image);
}
int popup_select_image_result()
{
if(select_image_selected == -100)
return -100;
select_image_current = select_image_selected;
select_image_selected = -100;
return select_image_current;
}

View File

@@ -0,0 +1,514 @@
/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */
#include <string.h>
#include "gamecore.hpp"
const char *TUNING_PARAMS::names[] =
{
#define MACRO_TUNING_PARAM(name,value) #name,
#include "tuning.hpp"
#undef MACRO_TUNING_PARAM
};
bool TUNING_PARAMS::set(int index, float value)
{
if(index < 0 || index >= num())
return false;
((tune_param *)this)[index] = value;
return true;
}
bool TUNING_PARAMS::get(int index, float *value)
{
if(index < 0 || index >= num())
return false;
*value = (float)((tune_param *)this)[index];
return true;
}
bool TUNING_PARAMS::set(const char *name, float value)
{
for(int i = 0; i < num(); i++)
if(strcmp(name, names[i]) == 0)
return set(i, value);
return false;
}
bool TUNING_PARAMS::get(const char *name, float *value)
{
for(int i = 0; i < num(); i++)
if(strcmp(name, names[i]) == 0)
return get(i, value);
return false;
}
// 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;
}
float hermite_basis1(float v)
{
return 2*v*v*v - 3*v*v+1;
}
float velocity_ramp(float value, float start, float range, float curvature)
{
if(value < start)
return 1.0f;
return 1.0f/pow(curvature, (value-start)/range);
}
void CHARACTER_CORE::reset()
{
pos = vec2(0,0);
vel = vec2(0,0);
hook_pos = vec2(0,0);
hook_dir = vec2(0,0);
hook_tick = 0;
hook_state = HOOK_IDLE;
hooked_player = -1;
jumped = 0;
triggered_events = 0;
}
void CHARACTER_CORE::tick(bool use_input)
{
float phys_size = 28.0f;
triggered_events = 0;
// get ground state
bool grounded = false;
if(col_check_point(pos.x+phys_size/2, pos.y+phys_size/2+5))
grounded = true;
if(col_check_point(pos.x-phys_size/2, pos.y+phys_size/2+5))
grounded = true;
vec2 target_direction = normalize(vec2(input.target_x, input.target_y));
vel.y += world->tuning.gravity;
float max_speed = grounded ? world->tuning.ground_control_speed : world->tuning.air_control_speed;
float accel = grounded ? world->tuning.ground_control_accel : world->tuning.air_control_accel;
float friction = grounded ? world->tuning.ground_friction : world->tuning.air_friction;
// handle input
if(use_input)
{
direction = input.direction;
// setup angle
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;
angle = (int)(a*256.0f);
// handle jump
if(input.jump)
{
if(!(jumped&1))
{
if(grounded)
{
triggered_events |= COREEVENT_GROUND_JUMP;
vel.y = -world->tuning.ground_jump_impulse;
jumped |= 1;
}
else if(!(jumped&2))
{
triggered_events |= COREEVENT_AIR_JUMP;
vel.y = -world->tuning.air_jump_impulse;
jumped |= 3;
}
}
}
else
jumped &= ~1;
// handle hook
if(input.hook)
{
if(hook_state == HOOK_IDLE)
{
hook_state = HOOK_FLYING;
hook_pos = pos+target_direction*phys_size*1.5f;
hook_dir = target_direction;
hooked_player = -1;
hook_tick = 0;
triggered_events |= COREEVENT_HOOK_LAUNCH;
}
}
else
{
hooked_player = -1;
hook_state = HOOK_IDLE;
hook_pos = pos;
}
}
// add the speed modification according to players wanted direction
if(direction < 0)
vel.x = saturated_add(-max_speed, max_speed, vel.x, -accel);
if(direction > 0)
vel.x = saturated_add(-max_speed, max_speed, vel.x, accel);
if(direction == 0)
vel.x *= friction;
// 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;
// do hook
if(hook_state == HOOK_IDLE)
{
hooked_player = -1;
hook_state = HOOK_IDLE;
hook_pos = pos;
}
else if(hook_state >= HOOK_RETRACT_START && hook_state < HOOK_RETRACT_END)
{
hook_state++;
}
else if(hook_state == HOOK_RETRACT_END)
{
hook_state = HOOK_RETRACTED;
triggered_events |= COREEVENT_HOOK_RETRACT;
hook_state = HOOK_RETRACTED;
}
else if(hook_state == HOOK_FLYING)
{
vec2 new_pos = hook_pos+hook_dir*world->tuning.hook_fire_speed;
if(distance(pos, new_pos) > world->tuning.hook_length)
{
hook_state = HOOK_RETRACT_START;
new_pos = pos + normalize(new_pos-pos) * world->tuning.hook_length;
}
// make sure that the hook doesn't go though the ground
bool going_to_hit_ground = false;
bool going_to_retract = false;
int hit = col_intersect_line(hook_pos, new_pos, &new_pos, 0);
if(hit)
{
if(hit&COLFLAG_NOHOOK)
going_to_retract = true;
else
going_to_hit_ground = true;
}
// Check against other players first
if(world && world->tuning.player_hooking)
{
float dist = 0.0f;
for(int i = 0; i < MAX_CLIENTS; i++)
{
CHARACTER_CORE *p = world->characters[i];
if(!p || p == this)
continue;
vec2 closest_point = closest_point_on_line(hook_pos, new_pos, p->pos);
if(distance(p->pos, closest_point) < phys_size+2.0f)
{
if (hooked_player == -1 || distance (hook_pos, p->pos) < dist)
{
triggered_events |= COREEVENT_HOOK_ATTACH_PLAYER;
hook_state = HOOK_GRABBED;
hooked_player = i;
dist = distance (hook_pos, p->pos);
}
}
}
}
if(hook_state == HOOK_FLYING)
{
// check against ground
if(going_to_hit_ground)
{
triggered_events |= COREEVENT_HOOK_ATTACH_GROUND;
hook_state = HOOK_GRABBED;
}
else if(going_to_retract)
{
triggered_events |= COREEVENT_HOOK_HIT_NOHOOK;
hook_state = HOOK_RETRACT_START;
}
hook_pos = new_pos;
}
}
if(hook_state == HOOK_GRABBED)
{
if(hooked_player != -1)
{
CHARACTER_CORE *p = world->characters[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();
}
// don't do this hook rutine when we are hook to a player
if(hooked_player == -1 && distance(hook_pos, pos) > 46.0f)
{
vec2 hookvel = normalize(hook_pos-pos)*world->tuning.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 && direction < 0) || (hookvel.x > 0 && direction > 0))
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) < world->tuning.hook_drag_speed || length(new_vel) < length(vel))
vel = new_vel; // no problem. apply
}
// release hook (max hook time is 1.25
hook_tick++;
if(hooked_player != -1 && (hook_tick > SERVER_TICK_SPEED+SERVER_TICK_SPEED/5 || !world->characters[hooked_player]))
{
hooked_player = -1;
hook_state = HOOK_RETRACTED;
hook_pos = pos;
}
}
if(world && world->tuning.player_collision)
{
for(int i = 0; i < MAX_CLIENTS; i++)
{
CHARACTER_CORE *p = world->characters[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.45f - d);
// make sure that we don't add excess force by checking the
// direction against the current velocity
vec2 veldir = normalize(vel);
float v = 1-(dot(veldir, dir)+1)/2;
vel = vel + dir*a*(v*0.75f);
vel = vel * 0.85f;
}
// handle hook influence
if(hooked_player == i)
{
if(d > phys_size*1.50f) // TODO: fix tweakable variable
{
float accel = world->tuning.hook_drag_accel * (d/world->tuning.hook_length);
float drag_speed = world->tuning.hook_drag_speed;
// add force to the hooked player
p->vel.x = saturated_add(-drag_speed, drag_speed, p->vel.x, accel*dir.x*1.5f);
p->vel.y = saturated_add(-drag_speed, drag_speed, p->vel.y, accel*dir.y*1.5f);
// add a little bit force to the guy who has the grip
vel.x = saturated_add(-drag_speed, drag_speed, vel.x, -accel*dir.x*0.25f);
vel.y = saturated_add(-drag_speed, drag_speed, vel.y, -accel*dir.y*0.25f);
}
}
}
}
// clamp the velocity to something sane
if(length(vel) > 6000)
vel = normalize(vel) * 6000;
}
void CHARACTER_CORE::move()
{
float rampvalue = velocity_ramp(length(vel)*50, world->tuning.velramp_start, world->tuning.velramp_range, world->tuning.velramp_curvature);
vel.x = vel.x*rampvalue;
move_box(&pos, &vel, vec2(28.0f, 28.0f), 0);
vel.x = vel.x*(1.0f/rampvalue);
}
void CHARACTER_CORE::write(NETOBJ_CHARACTER_CORE *obj_core)
{
obj_core->x = round(pos.x);
obj_core->y = round(pos.y);
obj_core->vx = round(vel.x*256.0f);
obj_core->vy = round(vel.y*256.0f);
obj_core->hook_state = hook_state;
obj_core->hook_tick = hook_tick;
obj_core->hook_x = round(hook_pos.x);
obj_core->hook_y = round(hook_pos.y);
obj_core->hook_dx = round(hook_dir.x*256.0f);
obj_core->hook_dy = round(hook_dir.y*256.0f);
obj_core->hooked_player = hooked_player;
obj_core->jumped = jumped;
obj_core->direction = direction;
obj_core->angle = angle;
}
void CHARACTER_CORE::read(const NETOBJ_CHARACTER_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;
direction = obj_core->direction;
angle = obj_core->angle;
}
void CHARACTER_CORE::quantize()
{
NETOBJ_CHARACTER_CORE c;
write(&c);
read(&c);
}

View File

@@ -0,0 +1,200 @@
/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */
#ifndef GAME_GAME_H
#define GAME_GAME_H
#include <base/system.h>
#include <base/math.hpp>
#include <engine/e_common_interface.h>
#include <math.h>
#include "collision.hpp"
#include <game/generated/g_protocol.hpp>
struct TUNING_PARAMS
{
TUNING_PARAMS()
{
const float ticks_per_second = 50.0f;
#define MACRO_TUNING_PARAM(name,value) name.set((int)(value*100.0f));
#include "tuning.hpp"
#undef MACRO_TUNING_PARAM
}
static const char *names[];
#define MACRO_TUNING_PARAM(name,value) tune_param name;
#include "tuning.hpp"
#undef MACRO_TUNING_PARAM
static int num() { return sizeof(TUNING_PARAMS)/sizeof(int); }
bool set(int index, float value);
bool set(const char *name, float value);
bool get(int index, float *value);
bool get(const char *name, float *value);
};
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 void str_to_ints(int *ints, int num, const char *str)
{
int index = 0;
while(num)
{
char buf[4] = {0,0,0,0};
for(int c = 0; c < 4 && str[index]; c++, index++)
buf[c] = str[index];
*ints = ((buf[0]+128)<<24)|((buf[1]+128)<<16)|((buf[2]+128)<<8)|(buf[3]+128);
ints++;
num--;
}
// null terminate
ints[-1] &= 0xffffff00;
}
inline void ints_to_str(const int *ints, int num, char *str)
{
while(num)
{
str[0] = (((*ints)>>24)&0xff)-128;
str[1] = (((*ints)>>16)&0xff)-128;
str[2] = (((*ints)>>8)&0xff)-128;
str[3] = ((*ints)&0xff)-128;
str += 4;
ints++;
num--;
}
// null terminate
str[-1] = 0;
}
inline vec2 calc_pos(vec2 p, vec2 v, float curvature, float speed, float t)
{
vec2 n;
t *= speed;
n.x = p.x + v.x*t;
n.y = p.y + v.y*t + curvature/10000*(t*t);
return n;
}
template<typename T>
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);
float velocity_ramp(float value, float start, float range, float curvature);
// hooking stuff
enum
{
HOOK_RETRACTED=-1,
HOOK_IDLE=0,
HOOK_RETRACT_START=1,
HOOK_RETRACT_END=3,
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_HIT_NOHOOK=0x20,
COREEVENT_HOOK_RETRACT=0x40,
};
class WORLD_CORE
{
public:
WORLD_CORE()
{
mem_zero(characters, sizeof(characters));
}
TUNING_PARAMS tuning;
class CHARACTER_CORE *characters[MAX_CLIENTS];
};
class CHARACTER_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;
int direction;
int angle;
NETOBJ_PLAYER_INPUT input;
int triggered_events;
void reset();
void tick(bool use_input);
void move();
void read(const NETOBJ_CHARACTER_CORE *obj_core);
void write(NETOBJ_CHARACTER_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_is_solid(round(x), round(y)) != 0; }
inline bool col_check_point(vec2 p) { return col_check_point(p.x, p.y); }
#endif

View File

@@ -0,0 +1,528 @@
#include <engine/e_common_interface.h>
#include "g_protocol.hpp"
const char *msg_failed_on = "";
const char *obj_corrected_on = "";
static int num_corrections = 0;
int netobj_num_corrections() { return num_corrections; }
const char *netobj_corrected_on() { return obj_corrected_on; }
const char *netmsg_failed_on() { return msg_failed_on; }
const int max_int = 0x7fffffff;
static int netobj_clamp_int(const char *error_msg, int v, int min, int max)
{
if(v<min) { obj_corrected_on = error_msg; num_corrections++; return min; }
if(v>max) { obj_corrected_on = error_msg; num_corrections++; return max; }
return v;
}
static const char *netobj_names[] = {
"invalid",
"player_input",
"projectile",
"laser",
"pickup",
"flag",
"game",
"character_core",
"character",
"player_info",
"client_info",
"common",
"explosion",
"spawn",
"hammerhit",
"death",
"soundglobal",
"soundworld",
"damageind",
""
};
static int netobj_sizes[] = {
0,
sizeof(NETOBJ_PLAYER_INPUT),
sizeof(NETOBJ_PROJECTILE),
sizeof(NETOBJ_LASER),
sizeof(NETOBJ_PICKUP),
sizeof(NETOBJ_FLAG),
sizeof(NETOBJ_GAME),
sizeof(NETOBJ_CHARACTER_CORE),
sizeof(NETOBJ_CHARACTER),
sizeof(NETOBJ_PLAYER_INFO),
sizeof(NETOBJ_CLIENT_INFO),
sizeof(NETEVENT_COMMON),
sizeof(NETEVENT_EXPLOSION),
sizeof(NETEVENT_SPAWN),
sizeof(NETEVENT_HAMMERHIT),
sizeof(NETEVENT_DEATH),
sizeof(NETEVENT_SOUNDGLOBAL),
sizeof(NETEVENT_SOUNDWORLD),
sizeof(NETEVENT_DAMAGEIND),
0
};
static int validate_player_input(void *data, int size)
{
NETOBJ_PLAYER_INPUT *obj = (NETOBJ_PLAYER_INPUT *)data;
if(sizeof(*obj) != size) return -1;
netobj_clamp_int("player_state", obj->player_state, 0, 4);
return 0;
}
static int validate_projectile(void *data, int size)
{
NETOBJ_PROJECTILE *obj = (NETOBJ_PROJECTILE *)data;
if(sizeof(*obj) != size) return -1;
netobj_clamp_int("type", obj->type, 0, NUM_WEAPONS-1);
netobj_clamp_int("start_tick", obj->start_tick, 0, max_int);
return 0;
}
static int validate_laser(void *data, int size)
{
NETOBJ_LASER *obj = (NETOBJ_LASER *)data;
if(sizeof(*obj) != size) return -1;
netobj_clamp_int("start_tick", obj->start_tick, 0, max_int);
return 0;
}
static int validate_pickup(void *data, int size)
{
NETOBJ_PICKUP *obj = (NETOBJ_PICKUP *)data;
if(sizeof(*obj) != size) return -1;
netobj_clamp_int("type", obj->type, 0, max_int);
netobj_clamp_int("subtype", obj->subtype, 0, max_int);
return 0;
}
static int validate_flag(void *data, int size)
{
NETOBJ_FLAG *obj = (NETOBJ_FLAG *)data;
if(sizeof(*obj) != size) return -1;
netobj_clamp_int("team", obj->team, 0, 1);
netobj_clamp_int("carried_by", obj->carried_by, -2, MAX_CLIENTS-1);
return 0;
}
static int validate_game(void *data, int size)
{
NETOBJ_GAME *obj = (NETOBJ_GAME *)data;
if(sizeof(*obj) != size) return -1;
netobj_clamp_int("flags", obj->flags, 0, 256);
netobj_clamp_int("round_start_tick", obj->round_start_tick, 0, max_int);
netobj_clamp_int("game_over", obj->game_over, 0, 1);
netobj_clamp_int("sudden_death", obj->sudden_death, 0, 1);
netobj_clamp_int("paused", obj->paused, 0, 1);
netobj_clamp_int("score_limit", obj->score_limit, 0, max_int);
netobj_clamp_int("time_limit", obj->time_limit, 0, max_int);
netobj_clamp_int("warmup", obj->warmup, 0, max_int);
netobj_clamp_int("round_num", obj->round_num, 0, max_int);
netobj_clamp_int("round_current", obj->round_current, 0, max_int);
return 0;
}
static int validate_character_core(void *data, int size)
{
NETOBJ_CHARACTER_CORE *obj = (NETOBJ_CHARACTER_CORE *)data;
if(sizeof(*obj) != size) return -1;
netobj_clamp_int("direction", obj->direction, -1, 1);
netobj_clamp_int("jumped", obj->jumped, 0, 3);
netobj_clamp_int("hooked_player", obj->hooked_player, 0, MAX_CLIENTS-1);
netobj_clamp_int("hook_state", obj->hook_state, -1, 5);
netobj_clamp_int("hook_tick", obj->hook_tick, 0, max_int);
return 0;
}
static int validate_character(void *data, int size)
{
NETOBJ_CHARACTER *obj = (NETOBJ_CHARACTER *)data;
if(sizeof(*obj) != size) return -1;
netobj_clamp_int("player_state", obj->player_state, 0, NUM_PLAYERSTATES-1);
netobj_clamp_int("health", obj->health, 0, 10);
netobj_clamp_int("armor", obj->armor, 0, 10);
netobj_clamp_int("ammocount", obj->ammocount, 0, 10);
netobj_clamp_int("weapon", obj->weapon, 0, NUM_WEAPONS-1);
netobj_clamp_int("emote", obj->emote, 0, 6);
netobj_clamp_int("attacktick", obj->attacktick, 0, max_int);
return 0;
}
static int validate_player_info(void *data, int size)
{
NETOBJ_PLAYER_INFO *obj = (NETOBJ_PLAYER_INFO *)data;
if(sizeof(*obj) != size) return -1;
netobj_clamp_int("local", obj->local, 0, 1);
netobj_clamp_int("cid", obj->cid, 0, MAX_CLIENTS-1);
netobj_clamp_int("team", obj->team, -1, 1);
return 0;
}
static int validate_client_info(void *data, int size)
{
NETOBJ_CLIENT_INFO *obj = (NETOBJ_CLIENT_INFO *)data;
if(sizeof(*obj) != size) return -1;
netobj_clamp_int("use_custom_color", obj->use_custom_color, 0, 1);
return 0;
}
static int validate_common(void *data, int size)
{
NETEVENT_COMMON *obj = (NETEVENT_COMMON *)data;
if(sizeof(*obj) != size) return -1;
return 0;
}
static int validate_explosion(void *data, int size)
{
NETEVENT_EXPLOSION *obj = (NETEVENT_EXPLOSION *)data;
if(sizeof(*obj) != size) return -1;
return 0;
}
static int validate_spawn(void *data, int size)
{
NETEVENT_SPAWN *obj = (NETEVENT_SPAWN *)data;
if(sizeof(*obj) != size) return -1;
return 0;
}
static int validate_hammerhit(void *data, int size)
{
NETEVENT_HAMMERHIT *obj = (NETEVENT_HAMMERHIT *)data;
if(sizeof(*obj) != size) return -1;
return 0;
}
static int validate_death(void *data, int size)
{
NETEVENT_DEATH *obj = (NETEVENT_DEATH *)data;
if(sizeof(*obj) != size) return -1;
netobj_clamp_int("cid", obj->cid, 0, MAX_CLIENTS-1);
return 0;
}
static int validate_soundglobal(void *data, int size)
{
NETEVENT_SOUNDGLOBAL *obj = (NETEVENT_SOUNDGLOBAL *)data;
if(sizeof(*obj) != size) return -1;
netobj_clamp_int("soundid", obj->soundid, 0, NUM_SOUNDS-1);
return 0;
}
static int validate_soundworld(void *data, int size)
{
NETEVENT_SOUNDWORLD *obj = (NETEVENT_SOUNDWORLD *)data;
if(sizeof(*obj) != size) return -1;
netobj_clamp_int("soundid", obj->soundid, 0, NUM_SOUNDS-1);
return 0;
}
static int validate_damageind(void *data, int size)
{
NETEVENT_DAMAGEIND *obj = (NETEVENT_DAMAGEIND *)data;
if(sizeof(*obj) != size) return -1;
return 0;
}
static void *secure_unpack_sv_motd()
{
static NETMSG_SV_MOTD msg;
msg.message = msg_unpack_string();
return &msg;
}
static void *secure_unpack_sv_broadcast()
{
static NETMSG_SV_BROADCAST msg;
msg.message = msg_unpack_string();
return &msg;
}
static void *secure_unpack_sv_chat()
{
static NETMSG_SV_CHAT msg;
msg.team = msg_unpack_int();
msg.cid = msg_unpack_int();
msg.message = msg_unpack_string();
if(msg.team < -1 || msg.team > 1) { msg_failed_on = "team"; return 0; }
if(msg.cid < -1 || msg.cid > MAX_CLIENTS-1) { msg_failed_on = "cid"; return 0; }
return &msg;
}
static void *secure_unpack_sv_killmsg()
{
static NETMSG_SV_KILLMSG msg;
msg.killer = msg_unpack_int();
msg.victim = msg_unpack_int();
msg.weapon = msg_unpack_int();
msg.mode_special = msg_unpack_int();
if(msg.killer < 0 || msg.killer > MAX_CLIENTS-1) { msg_failed_on = "killer"; return 0; }
if(msg.victim < 0 || msg.victim > MAX_CLIENTS-1) { msg_failed_on = "victim"; return 0; }
if(msg.weapon < -3 || msg.weapon > NUM_WEAPONS-1) { msg_failed_on = "weapon"; return 0; }
return &msg;
}
static void *secure_unpack_sv_soundglobal()
{
static NETMSG_SV_SOUNDGLOBAL msg;
msg.soundid = msg_unpack_int();
if(msg.soundid < 0 || msg.soundid > NUM_SOUNDS-1) { msg_failed_on = "soundid"; return 0; }
return &msg;
}
static void *secure_unpack_sv_tuneparams()
{
static NETMSG_SV_TUNEPARAMS msg;
return &msg;
}
static void *secure_unpack_sv_extraprojectile()
{
static NETMSG_SV_EXTRAPROJECTILE msg;
return &msg;
}
static void *secure_unpack_sv_readytoenter()
{
static NETMSG_SV_READYTOENTER msg;
return &msg;
}
static void *secure_unpack_sv_weaponpickup()
{
static NETMSG_SV_WEAPONPICKUP msg;
msg.weapon = msg_unpack_int();
if(msg.weapon < 0 || msg.weapon > NUM_WEAPONS-1) { msg_failed_on = "weapon"; return 0; }
return &msg;
}
static void *secure_unpack_sv_emoticon()
{
static NETMSG_SV_EMOTICON msg;
msg.cid = msg_unpack_int();
msg.emoticon = msg_unpack_int();
if(msg.cid < 0 || msg.cid > MAX_CLIENTS-1) { msg_failed_on = "cid"; return 0; }
if(msg.emoticon < 0 || msg.emoticon > NUM_EMOTICONS-1) { msg_failed_on = "emoticon"; return 0; }
return &msg;
}
static void *secure_unpack_sv_vote_clearoptions()
{
static NETMSG_SV_VOTE_CLEAROPTIONS msg;
return &msg;
}
static void *secure_unpack_sv_vote_option()
{
static NETMSG_SV_VOTE_OPTION msg;
msg.command = msg_unpack_string();
return &msg;
}
static void *secure_unpack_sv_vote_set()
{
static NETMSG_SV_VOTE_SET msg;
msg.timeout = msg_unpack_int();
msg.description = msg_unpack_string();
msg.command = msg_unpack_string();
if(msg.timeout < 0 || msg.timeout > 60) { msg_failed_on = "timeout"; return 0; }
return &msg;
}
static void *secure_unpack_sv_vote_status()
{
static NETMSG_SV_VOTE_STATUS msg;
msg.yes = msg_unpack_int();
msg.no = msg_unpack_int();
msg.pass = msg_unpack_int();
msg.total = msg_unpack_int();
if(msg.yes < 0 || msg.yes > MAX_CLIENTS) { msg_failed_on = "yes"; return 0; }
if(msg.no < 0 || msg.no > MAX_CLIENTS) { msg_failed_on = "no"; return 0; }
if(msg.pass < 0 || msg.pass > MAX_CLIENTS) { msg_failed_on = "pass"; return 0; }
if(msg.total < 0 || msg.total > MAX_CLIENTS) { msg_failed_on = "total"; return 0; }
return &msg;
}
static void *secure_unpack_cl_say()
{
static NETMSG_CL_SAY msg;
msg.team = msg_unpack_int();
msg.message = msg_unpack_string();
if(msg.team < 0 || msg.team > 1) { msg_failed_on = "team"; return 0; }
return &msg;
}
static void *secure_unpack_cl_setteam()
{
static NETMSG_CL_SETTEAM msg;
msg.team = msg_unpack_int();
if(msg.team < -1 || msg.team > 1) { msg_failed_on = "team"; return 0; }
return &msg;
}
static void *secure_unpack_cl_startinfo()
{
static NETMSG_CL_STARTINFO msg;
msg.name = msg_unpack_string();
msg.skin = msg_unpack_string();
msg.use_custom_color = msg_unpack_int();
msg.color_body = msg_unpack_int();
msg.color_feet = msg_unpack_int();
if(msg.use_custom_color < 0 || msg.use_custom_color > 1) { msg_failed_on = "use_custom_color"; return 0; }
return &msg;
}
static void *secure_unpack_cl_changeinfo()
{
static NETMSG_CL_CHANGEINFO msg;
msg.name = msg_unpack_string();
msg.skin = msg_unpack_string();
msg.use_custom_color = msg_unpack_int();
msg.color_body = msg_unpack_int();
msg.color_feet = msg_unpack_int();
if(msg.use_custom_color < 0 || msg.use_custom_color > 1) { msg_failed_on = "use_custom_color"; return 0; }
return &msg;
}
static void *secure_unpack_cl_kill()
{
static NETMSG_CL_KILL msg;
return &msg;
}
static void *secure_unpack_cl_emoticon()
{
static NETMSG_CL_EMOTICON msg;
msg.emoticon = msg_unpack_int();
if(msg.emoticon < 0 || msg.emoticon > NUM_EMOTICONS-1) { msg_failed_on = "emoticon"; return 0; }
return &msg;
}
static void *secure_unpack_cl_vote()
{
static NETMSG_CL_VOTE msg;
msg.vote = msg_unpack_int();
if(msg.vote < -1 || msg.vote > 1) { msg_failed_on = "vote"; return 0; }
return &msg;
}
static void *secure_unpack_cl_callvote()
{
static NETMSG_CL_CALLVOTE msg;
msg.type = msg_unpack_string();
msg.value = msg_unpack_string();
return &msg;
}
static int validate_invalid(void *data, int size) { return -1; }
typedef int(*VALIDATEFUNC)(void *data, int size);
static VALIDATEFUNC validate_funcs[] = {
validate_invalid,
validate_player_input,
validate_projectile,
validate_laser,
validate_pickup,
validate_flag,
validate_game,
validate_character_core,
validate_character,
validate_player_info,
validate_client_info,
validate_common,
validate_explosion,
validate_spawn,
validate_hammerhit,
validate_death,
validate_soundglobal,
validate_soundworld,
validate_damageind,
0x0
};
int netobj_validate(int type, void *data, int size)
{
if(type < 0 || type >= NUM_NETOBJTYPES) return -1;
return validate_funcs[type](data, size);
};
const char *netobj_get_name(int type)
{
if(type < 0 || type >= NUM_NETOBJTYPES) return "(out of range)";
return netobj_names[type];
};
int netobj_get_size(int type)
{
if(type < 0 || type >= NUM_NETOBJTYPES) return 0;
return netobj_sizes[type];
};
static void *secure_unpack_invalid() { return 0; }
typedef void *(*SECUREUNPACKFUNC)();
static SECUREUNPACKFUNC secure_unpack_funcs[] = {
secure_unpack_invalid,
secure_unpack_sv_motd,
secure_unpack_sv_broadcast,
secure_unpack_sv_chat,
secure_unpack_sv_killmsg,
secure_unpack_sv_soundglobal,
secure_unpack_sv_tuneparams,
secure_unpack_sv_extraprojectile,
secure_unpack_sv_readytoenter,
secure_unpack_sv_weaponpickup,
secure_unpack_sv_emoticon,
secure_unpack_sv_vote_clearoptions,
secure_unpack_sv_vote_option,
secure_unpack_sv_vote_set,
secure_unpack_sv_vote_status,
secure_unpack_cl_say,
secure_unpack_cl_setteam,
secure_unpack_cl_startinfo,
secure_unpack_cl_changeinfo,
secure_unpack_cl_kill,
secure_unpack_cl_emoticon,
secure_unpack_cl_vote,
secure_unpack_cl_callvote,
0x0
};
void *netmsg_secure_unpack(int type)
{
void *msg;
msg_failed_on = "";
if(type < 0 || type >= NUM_NETMSGTYPES) { msg_failed_on = "(type out of range)"; return 0; }
msg = secure_unpack_funcs[type]();
if(msg_unpack_error()) { msg_failed_on = "(unpack error)"; return 0; }
return msg;
};
static const char *message_names[] = {
"invalid",
"sv_motd",
"sv_broadcast",
"sv_chat",
"sv_killmsg",
"sv_soundglobal",
"sv_tuneparams",
"sv_extraprojectile",
"sv_readytoenter",
"sv_weaponpickup",
"sv_emoticon",
"sv_vote_clearoptions",
"sv_vote_option",
"sv_vote_set",
"sv_vote_status",
"cl_say",
"cl_setteam",
"cl_startinfo",
"cl_changeinfo",
"cl_kill",
"cl_emoticon",
"cl_vote",
"cl_callvote",
""
};
const char *netmsg_get_name(int type)
{
if(type < 0 || type >= NUM_NETMSGTYPES) return "(out of range)";
return message_names[type];
};

View File

@@ -0,0 +1,613 @@
#ifndef FILE_G_PROTOCOL_H
#define FILE_G_PROTOCOL_H
enum
{
INPUT_STATE_MASK=0x3f,
};
enum
{
PLAYERSTATE_UNKNOWN=0,
PLAYERSTATE_PLAYING,
PLAYERSTATE_IN_MENU,
PLAYERSTATE_CHATTING,
NUM_PLAYERSTATES
};
enum
{
EMOTE_NORMAL=0,
EMOTE_PAIN,
EMOTE_HAPPY,
EMOTE_SURPRISE,
EMOTE_ANGRY,
EMOTE_BLINK,
NUM_EMOTES
};
enum
{
POWERUP_HEALTH=0,
POWERUP_ARMOR,
POWERUP_WEAPON,
POWERUP_NINJA,
NUM_POWERUPS
};
enum
{
EMOTICON_1=0,
EMOTICON_2,
EMOTICON_3,
EMOTICON_4,
EMOTICON_5,
EMOTICON_6,
EMOTICON_7,
EMOTICON_8,
EMOTICON_9,
EMOTICON_10,
EMOTICON_11,
EMOTICON_12,
EMOTICON_13,
EMOTICON_14,
EMOTICON_15,
NUM_EMOTICONS
};
enum
{
GAMEFLAG_TEAMS = 1<<0,
GAMEFLAG_FLAGS = 1<<1,
};
enum
{
NETOBJ_INVALID=0,
NETOBJTYPE_PLAYER_INPUT,
NETOBJTYPE_PROJECTILE,
NETOBJTYPE_LASER,
NETOBJTYPE_PICKUP,
NETOBJTYPE_FLAG,
NETOBJTYPE_GAME,
NETOBJTYPE_CHARACTER_CORE,
NETOBJTYPE_CHARACTER,
NETOBJTYPE_PLAYER_INFO,
NETOBJTYPE_CLIENT_INFO,
NETEVENTTYPE_COMMON,
NETEVENTTYPE_EXPLOSION,
NETEVENTTYPE_SPAWN,
NETEVENTTYPE_HAMMERHIT,
NETEVENTTYPE_DEATH,
NETEVENTTYPE_SOUNDGLOBAL,
NETEVENTTYPE_SOUNDWORLD,
NETEVENTTYPE_DAMAGEIND,
NUM_NETOBJTYPES
};
enum
{
NETMSG_INVALID=0,
NETMSGTYPE_SV_MOTD,
NETMSGTYPE_SV_BROADCAST,
NETMSGTYPE_SV_CHAT,
NETMSGTYPE_SV_KILLMSG,
NETMSGTYPE_SV_SOUNDGLOBAL,
NETMSGTYPE_SV_TUNEPARAMS,
NETMSGTYPE_SV_EXTRAPROJECTILE,
NETMSGTYPE_SV_READYTOENTER,
NETMSGTYPE_SV_WEAPONPICKUP,
NETMSGTYPE_SV_EMOTICON,
NETMSGTYPE_SV_VOTE_CLEAROPTIONS,
NETMSGTYPE_SV_VOTE_OPTION,
NETMSGTYPE_SV_VOTE_SET,
NETMSGTYPE_SV_VOTE_STATUS,
NETMSGTYPE_CL_SAY,
NETMSGTYPE_CL_SETTEAM,
NETMSGTYPE_CL_STARTINFO,
NETMSGTYPE_CL_CHANGEINFO,
NETMSGTYPE_CL_KILL,
NETMSGTYPE_CL_EMOTICON,
NETMSGTYPE_CL_VOTE,
NETMSGTYPE_CL_CALLVOTE,
NUM_NETMSGTYPES
};
struct NETOBJ_PLAYER_INPUT
{
int direction;
int target_x;
int target_y;
int jump;
int fire;
int hook;
int player_state;
int wanted_weapon;
int next_weapon;
int prev_weapon;
};
struct NETOBJ_PROJECTILE
{
int x;
int y;
int vx;
int vy;
int type;
int start_tick;
};
struct NETOBJ_LASER
{
int x;
int y;
int from_x;
int from_y;
int start_tick;
};
struct NETOBJ_PICKUP
{
int x;
int y;
int type;
int subtype;
};
struct NETOBJ_FLAG
{
int x;
int y;
int team;
int carried_by;
};
struct NETOBJ_GAME
{
int flags;
int round_start_tick;
int game_over;
int sudden_death;
int paused;
int score_limit;
int time_limit;
int warmup;
int round_num;
int round_current;
int teamscore_red;
int teamscore_blue;
};
struct NETOBJ_CHARACTER_CORE
{
int tick;
int x;
int y;
int vx;
int vy;
int angle;
int direction;
int jumped;
int hooked_player;
int hook_state;
int hook_tick;
int hook_x;
int hook_y;
int hook_dx;
int hook_dy;
};
struct NETOBJ_CHARACTER : public NETOBJ_CHARACTER_CORE
{
int player_state;
int health;
int armor;
int ammocount;
int weapon;
int emote;
int attacktick;
};
struct NETOBJ_PLAYER_INFO
{
int local;
int cid;
int team;
int score;
int latency;
int latency_flux;
};
struct NETOBJ_CLIENT_INFO
{
int name0;
int name1;
int name2;
int name3;
int name4;
int name5;
int skin0;
int skin1;
int skin2;
int skin3;
int skin4;
int skin5;
int use_custom_color;
int color_body;
int color_feet;
};
struct NETEVENT_COMMON
{
int x;
int y;
};
struct NETEVENT_EXPLOSION : public NETEVENT_COMMON
{
};
struct NETEVENT_SPAWN : public NETEVENT_COMMON
{
};
struct NETEVENT_HAMMERHIT : public NETEVENT_COMMON
{
};
struct NETEVENT_DEATH : public NETEVENT_COMMON
{
int cid;
};
struct NETEVENT_SOUNDGLOBAL : public NETEVENT_COMMON
{
int soundid;
};
struct NETEVENT_SOUNDWORLD : public NETEVENT_COMMON
{
int soundid;
};
struct NETEVENT_DAMAGEIND : public NETEVENT_COMMON
{
int angle;
};
struct NETMSG_SV_MOTD
{
const char *message;
void pack(int flags)
{
msg_pack_start(NETMSGTYPE_SV_MOTD, flags);
msg_pack_string(message, -1);
msg_pack_end();
}
};
struct NETMSG_SV_BROADCAST
{
const char *message;
void pack(int flags)
{
msg_pack_start(NETMSGTYPE_SV_BROADCAST, flags);
msg_pack_string(message, -1);
msg_pack_end();
}
};
struct NETMSG_SV_CHAT
{
int team;
int cid;
const char *message;
void pack(int flags)
{
msg_pack_start(NETMSGTYPE_SV_CHAT, flags);
msg_pack_int(team);
msg_pack_int(cid);
msg_pack_string(message, -1);
msg_pack_end();
}
};
struct NETMSG_SV_KILLMSG
{
int killer;
int victim;
int weapon;
int mode_special;
void pack(int flags)
{
msg_pack_start(NETMSGTYPE_SV_KILLMSG, flags);
msg_pack_int(killer);
msg_pack_int(victim);
msg_pack_int(weapon);
msg_pack_int(mode_special);
msg_pack_end();
}
};
struct NETMSG_SV_SOUNDGLOBAL
{
int soundid;
void pack(int flags)
{
msg_pack_start(NETMSGTYPE_SV_SOUNDGLOBAL, flags);
msg_pack_int(soundid);
msg_pack_end();
}
};
struct NETMSG_SV_TUNEPARAMS
{
void pack(int flags)
{
msg_pack_start(NETMSGTYPE_SV_TUNEPARAMS, flags);
msg_pack_end();
}
};
struct NETMSG_SV_EXTRAPROJECTILE
{
void pack(int flags)
{
msg_pack_start(NETMSGTYPE_SV_EXTRAPROJECTILE, flags);
msg_pack_end();
}
};
struct NETMSG_SV_READYTOENTER
{
void pack(int flags)
{
msg_pack_start(NETMSGTYPE_SV_READYTOENTER, flags);
msg_pack_end();
}
};
struct NETMSG_SV_WEAPONPICKUP
{
int weapon;
void pack(int flags)
{
msg_pack_start(NETMSGTYPE_SV_WEAPONPICKUP, flags);
msg_pack_int(weapon);
msg_pack_end();
}
};
struct NETMSG_SV_EMOTICON
{
int cid;
int emoticon;
void pack(int flags)
{
msg_pack_start(NETMSGTYPE_SV_EMOTICON, flags);
msg_pack_int(cid);
msg_pack_int(emoticon);
msg_pack_end();
}
};
struct NETMSG_SV_VOTE_CLEAROPTIONS
{
void pack(int flags)
{
msg_pack_start(NETMSGTYPE_SV_VOTE_CLEAROPTIONS, flags);
msg_pack_end();
}
};
struct NETMSG_SV_VOTE_OPTION
{
const char *command;
void pack(int flags)
{
msg_pack_start(NETMSGTYPE_SV_VOTE_OPTION, flags);
msg_pack_string(command, -1);
msg_pack_end();
}
};
struct NETMSG_SV_VOTE_SET
{
int timeout;
const char *description;
const char *command;
void pack(int flags)
{
msg_pack_start(NETMSGTYPE_SV_VOTE_SET, flags);
msg_pack_int(timeout);
msg_pack_string(description, -1);
msg_pack_string(command, -1);
msg_pack_end();
}
};
struct NETMSG_SV_VOTE_STATUS
{
int yes;
int no;
int pass;
int total;
void pack(int flags)
{
msg_pack_start(NETMSGTYPE_SV_VOTE_STATUS, flags);
msg_pack_int(yes);
msg_pack_int(no);
msg_pack_int(pass);
msg_pack_int(total);
msg_pack_end();
}
};
struct NETMSG_CL_SAY
{
int team;
const char *message;
void pack(int flags)
{
msg_pack_start(NETMSGTYPE_CL_SAY, flags);
msg_pack_int(team);
msg_pack_string(message, -1);
msg_pack_end();
}
};
struct NETMSG_CL_SETTEAM
{
int team;
void pack(int flags)
{
msg_pack_start(NETMSGTYPE_CL_SETTEAM, flags);
msg_pack_int(team);
msg_pack_end();
}
};
struct NETMSG_CL_STARTINFO
{
const char *name;
const char *skin;
int use_custom_color;
int color_body;
int color_feet;
void pack(int flags)
{
msg_pack_start(NETMSGTYPE_CL_STARTINFO, flags);
msg_pack_string(name, -1);
msg_pack_string(skin, -1);
msg_pack_int(use_custom_color);
msg_pack_int(color_body);
msg_pack_int(color_feet);
msg_pack_end();
}
};
struct NETMSG_CL_CHANGEINFO
{
const char *name;
const char *skin;
int use_custom_color;
int color_body;
int color_feet;
void pack(int flags)
{
msg_pack_start(NETMSGTYPE_CL_CHANGEINFO, flags);
msg_pack_string(name, -1);
msg_pack_string(skin, -1);
msg_pack_int(use_custom_color);
msg_pack_int(color_body);
msg_pack_int(color_feet);
msg_pack_end();
}
};
struct NETMSG_CL_KILL
{
void pack(int flags)
{
msg_pack_start(NETMSGTYPE_CL_KILL, flags);
msg_pack_end();
}
};
struct NETMSG_CL_EMOTICON
{
int emoticon;
void pack(int flags)
{
msg_pack_start(NETMSGTYPE_CL_EMOTICON, flags);
msg_pack_int(emoticon);
msg_pack_end();
}
};
struct NETMSG_CL_VOTE
{
int vote;
void pack(int flags)
{
msg_pack_start(NETMSGTYPE_CL_VOTE, flags);
msg_pack_int(vote);
msg_pack_end();
}
};
struct NETMSG_CL_CALLVOTE
{
const char *type;
const char *value;
void pack(int flags)
{
msg_pack_start(NETMSGTYPE_CL_CALLVOTE, flags);
msg_pack_string(type, -1);
msg_pack_string(value, -1);
msg_pack_end();
}
};
enum
{
SOUND_GUN_FIRE=0,
SOUND_SHOTGUN_FIRE,
SOUND_GRENADE_FIRE,
SOUND_HAMMER_FIRE,
SOUND_HAMMER_HIT,
SOUND_NINJA_FIRE,
SOUND_GRENADE_EXPLODE,
SOUND_NINJA_HIT,
SOUND_RIFLE_FIRE,
SOUND_RIFLE_BOUNCE,
SOUND_WEAPON_SWITCH,
SOUND_PLAYER_PAIN_SHORT,
SOUND_PLAYER_PAIN_LONG,
SOUND_BODY_LAND,
SOUND_PLAYER_AIRJUMP,
SOUND_PLAYER_JUMP,
SOUND_PLAYER_DIE,
SOUND_PLAYER_SPAWN,
SOUND_PLAYER_SKID,
SOUND_TEE_CRY,
SOUND_HOOK_LOOP,
SOUND_HOOK_ATTACH_GROUND,
SOUND_HOOK_ATTACH_PLAYER,
SOUND_HOOK_NOATTACH,
SOUND_PICKUP_HEALTH,
SOUND_PICKUP_ARMOR,
SOUND_PICKUP_GRENADE,
SOUND_PICKUP_SHOTGUN,
SOUND_PICKUP_NINJA,
SOUND_WEAPON_SPAWN,
SOUND_WEAPON_NOAMMO,
SOUND_HIT,
SOUND_CHAT_SERVER,
SOUND_CHAT_CLIENT,
SOUND_CTF_DROP,
SOUND_CTF_RETURN,
SOUND_CTF_GRAB_PL,
SOUND_CTF_GRAB_EN,
SOUND_CTF_CAPTURE,
NUM_SOUNDS
};
enum
{
WEAPON_HAMMER=0,
WEAPON_GUN,
WEAPON_SHOTGUN,
WEAPON_GRENADE,
WEAPON_RIFLE,
WEAPON_NINJA,
NUM_WEAPONS
};
int netobj_validate(int type, void *data, int size);
const char *netobj_get_name(int type);
int netobj_get_size(int type);
void *netmsg_secure_unpack(int type);
const char *netmsg_get_name(int type);
const char *netmsg_failed_on();
int netobj_num_corrections();
const char *netobj_corrected_on();
#endif // FILE_G_PROTOCOL_H

View File

@@ -0,0 +1,469 @@
#include "gc_data.hpp"
static SOUND x1890[] = {
/* x1890[0] */ { 0, "audio/wp_gun_fire-01.wv", },
/* x1890[1] */ { 0, "audio/wp_gun_fire-02.wv", },
/* x1890[2] */ { 0, "audio/wp_gun_fire-03.wv", },
};
static SOUND x1906[] = {
/* x1906[0] */ { 0, "audio/wp_shotty_fire-01.wv", },
/* x1906[1] */ { 0, "audio/wp_shotty_fire-02.wv", },
/* x1906[2] */ { 0, "audio/wp_shotty_fire-03.wv", },
};
static SOUND x1922[] = {
/* x1922[0] */ { 0, "audio/wp_flump_launch-01.wv", },
/* x1922[1] */ { 0, "audio/wp_flump_launch-02.wv", },
/* x1922[2] */ { 0, "audio/wp_flump_launch-03.wv", },
};
static SOUND x1938[] = {
/* x1938[0] */ { 0, "audio/wp_hammer_swing-01.wv", },
/* x1938[1] */ { 0, "audio/wp_hammer_swing-02.wv", },
/* x1938[2] */ { 0, "audio/wp_hammer_swing-03.wv", },
};
static SOUND x1954[] = {
/* x1954[0] */ { 0, "audio/wp_hammer_hit-01.wv", },
/* x1954[1] */ { 0, "audio/wp_hammer_hit-02.wv", },
/* x1954[2] */ { 0, "audio/wp_hammer_hit-03.wv", },
};
static SOUND x1970[] = {
/* x1970[0] */ { 0, "audio/wp_ninja_attack-01.wv", },
/* x1970[1] */ { 0, "audio/wp_ninja_attack-02.wv", },
/* x1970[2] */ { 0, "audio/wp_ninja_attack-03.wv", },
};
static SOUND x1986[] = {
/* x1986[0] */ { 0, "audio/wp_flump_explo-01.wv", },
/* x1986[1] */ { 0, "audio/wp_flump_explo-02.wv", },
/* x1986[2] */ { 0, "audio/wp_flump_explo-03.wv", },
};
static SOUND x2002[] = {
/* x2002[0] */ { 0, "audio/wp_ninja_hit-01.wv", },
/* x2002[1] */ { 0, "audio/wp_ninja_hit-02.wv", },
/* x2002[2] */ { 0, "audio/wp_ninja_hit-03.wv", },
};
static SOUND x2018[] = {
/* x2018[0] */ { 0, "audio/wp_rifle_fire-01.wv", },
/* x2018[1] */ { 0, "audio/wp_rifle_fire-02.wv", },
/* x2018[2] */ { 0, "audio/wp_rifle_fire-03.wv", },
};
static SOUND x2034[] = {
/* x2034[0] */ { 0, "audio/wp_rifle_bnce-01.wv", },
/* x2034[1] */ { 0, "audio/wp_rifle_bnce-02.wv", },
/* x2034[2] */ { 0, "audio/wp_rifle_bnce-03.wv", },
};
static SOUND x2050[] = {
/* x2050[0] */ { 0, "audio/wp_switch-01.wv", },
/* x2050[1] */ { 0, "audio/wp_switch-02.wv", },
/* x2050[2] */ { 0, "audio/wp_switch-03.wv", },
};
static SOUND x2066[] = {
/* x2066[0] */ { 0, "audio/vo_teefault_pain_short-01.wv", },
/* x2066[1] */ { 0, "audio/vo_teefault_pain_short-02.wv", },
/* x2066[2] */ { 0, "audio/vo_teefault_pain_short-03.wv", },
/* x2066[3] */ { 0, "audio/vo_teefault_pain_short-04.wv", },
/* x2066[4] */ { 0, "audio/vo_teefault_pain_short-05.wv", },
/* x2066[5] */ { 0, "audio/vo_teefault_pain_short-06.wv", },
/* x2066[6] */ { 0, "audio/vo_teefault_pain_short-07.wv", },
/* x2066[7] */ { 0, "audio/vo_teefault_pain_short-08.wv", },
/* x2066[8] */ { 0, "audio/vo_teefault_pain_short-09.wv", },
/* x2066[9] */ { 0, "audio/vo_teefault_pain_short-10.wv", },
/* x2066[10] */ { 0, "audio/vo_teefault_pain_short-11.wv", },
/* x2066[11] */ { 0, "audio/vo_teefault_pain_short-12.wv", },
};
static SOUND x2109[] = {
/* x2109[0] */ { 0, "audio/vo_teefault_pain_long-01.wv", },
/* x2109[1] */ { 0, "audio/vo_teefault_pain_long-02.wv", },
};
static SOUND x2122[] = {
/* x2122[0] */ { 0, "audio/foley_land-01.wv", },
/* x2122[1] */ { 0, "audio/foley_land-02.wv", },
/* x2122[2] */ { 0, "audio/foley_land-03.wv", },
/* x2122[3] */ { 0, "audio/foley_land-04.wv", },
};
static SOUND x2141[] = {
/* x2141[0] */ { 0, "audio/foley_dbljump-01.wv", },
/* x2141[1] */ { 0, "audio/foley_dbljump-02.wv", },
/* x2141[2] */ { 0, "audio/foley_dbljump-03.wv", },
};
static SOUND x2157[] = {
/* x2157[0] */ { 0, "audio/foley_foot_left-01.wv", },
/* x2157[1] */ { 0, "audio/foley_foot_left-02.wv", },
/* x2157[2] */ { 0, "audio/foley_foot_left-03.wv", },
/* x2157[3] */ { 0, "audio/foley_foot_left-04.wv", },
/* x2157[4] */ { 0, "audio/foley_foot_right-01.wv", },
/* x2157[5] */ { 0, "audio/foley_foot_right-02.wv", },
/* x2157[6] */ { 0, "audio/foley_foot_right-03.wv", },
/* x2157[7] */ { 0, "audio/foley_foot_right-04.wv", },
};
static SOUND x2188[] = {
/* x2188[0] */ { 0, "audio/foley_body_splat-01.wv", },
/* x2188[1] */ { 0, "audio/foley_body_splat-02.wv", },
/* x2188[2] */ { 0, "audio/foley_body_splat-03.wv", },
};
static SOUND x2204[] = {
/* x2204[0] */ { 0, "audio/vo_teefault_spawn-01.wv", },
/* x2204[1] */ { 0, "audio/vo_teefault_spawn-02.wv", },
/* x2204[2] */ { 0, "audio/vo_teefault_spawn-03.wv", },
/* x2204[3] */ { 0, "audio/vo_teefault_spawn-04.wv", },
/* x2204[4] */ { 0, "audio/vo_teefault_spawn-05.wv", },
/* x2204[5] */ { 0, "audio/vo_teefault_spawn-06.wv", },
/* x2204[6] */ { 0, "audio/vo_teefault_spawn-07.wv", },
};
static SOUND x2232[] = {
/* x2232[0] */ { 0, "audio/sfx_skid-01.wv", },
/* x2232[1] */ { 0, "audio/sfx_skid-02.wv", },
/* x2232[2] */ { 0, "audio/sfx_skid-03.wv", },
/* x2232[3] */ { 0, "audio/sfx_skid-04.wv", },
};
static SOUND x2251[] = {
/* x2251[0] */ { 0, "audio/vo_teefault_cry-01.wv", },
/* x2251[1] */ { 0, "audio/vo_teefault_cry-02.wv", },
};
static SOUND x2264[] = {
/* x2264[0] */ { 0, "audio/hook_loop-01.wv", },
/* x2264[1] */ { 0, "audio/hook_loop-02.wv", },
};
static SOUND x2277[] = {
/* x2277[0] */ { 0, "audio/hook_attach-01.wv", },
/* x2277[1] */ { 0, "audio/hook_attach-02.wv", },
/* x2277[2] */ { 0, "audio/hook_attach-03.wv", },
};
static SOUND x2293[] = {
/* x2293[0] */ { 0, "audio/foley_body_impact-01.wv", },
/* x2293[1] */ { 0, "audio/foley_body_impact-02.wv", },
/* x2293[2] */ { 0, "audio/foley_body_impact-03.wv", },
};
static SOUND x2309[] = {
/* x2309[0] */ { 0, "audio/hook_noattach-01.wv", },
/* x2309[1] */ { 0, "audio/hook_noattach-02.wv", },
};
static SOUND x2322[] = {
/* x2322[0] */ { 0, "audio/sfx_pickup_hrt-01.wv", },
/* x2322[1] */ { 0, "audio/sfx_pickup_hrt-02.wv", },
};
static SOUND x2335[] = {
/* x2335[0] */ { 0, "audio/sfx_pickup_arm-01.wv", },
/* x2335[1] */ { 0, "audio/sfx_pickup_arm-02.wv", },
/* x2335[2] */ { 0, "audio/sfx_pickup_arm-03.wv", },
/* x2335[3] */ { 0, "audio/sfx_pickup_arm-04.wv", },
};
static SOUND x2354[] = {
/* x2354[0] */ { 0, "audio/sfx_pickup_launcher.wv", },
};
static SOUND x2364[] = {
/* x2364[0] */ { 0, "audio/sfx_pickup_sg.wv", },
};
static SOUND x2374[] = {
/* x2374[0] */ { 0, "audio/sfx_pickup_ninja.wv", },
};
static SOUND x2384[] = {
/* x2384[0] */ { 0, "audio/sfx_spawn_wpn-01.wv", },
/* x2384[1] */ { 0, "audio/sfx_spawn_wpn-02.wv", },
/* x2384[2] */ { 0, "audio/sfx_spawn_wpn-03.wv", },
};
static SOUND x2400[] = {
/* x2400[0] */ { 0, "audio/wp_noammo-01.wv", },
/* x2400[1] */ { 0, "audio/wp_noammo-02.wv", },
/* x2400[2] */ { 0, "audio/wp_noammo-03.wv", },
/* x2400[3] */ { 0, "audio/wp_noammo-04.wv", },
/* x2400[4] */ { 0, "audio/wp_noammo-05.wv", },
};
static SOUND x2422[] = {
/* x2422[0] */ { 0, "audio/sfx_hit_weak-01.wv", },
/* x2422[1] */ { 0, "audio/sfx_hit_weak-02.wv", },
};
static SOUND x2435[] = {
/* x2435[0] */ { 0, "audio/sfx_msg-server.wv", },
};
static SOUND x2445[] = {
/* x2445[0] */ { 0, "audio/sfx_msg-client.wv", },
};
static SOUND x2455[] = {
/* x2455[0] */ { 0, "audio/sfx_ctf_drop.wv", },
};
static SOUND x2465[] = {
/* x2465[0] */ { 0, "audio/sfx_ctf_rtn.wv", },
};
static SOUND x2475[] = {
/* x2475[0] */ { 0, "audio/sfx_ctf_grab_pl.wv", },
};
static SOUND x2485[] = {
/* x2485[0] */ { 0, "audio/sfx_ctf_grab_en.wv", },
};
static SOUND x2495[] = {
/* x2495[0] */ { 0, "audio/sfx_ctf_cap_pl.wv", },
};
static SOUNDSET x9[] = {
/* x9[0] */ { "gun_fire", 3,x1890, -1, },
/* x9[1] */ { "shotgun_fire", 3,x1906, -1, },
/* x9[2] */ { "grenade_fire", 3,x1922, -1, },
/* x9[3] */ { "hammer_fire", 3,x1938, -1, },
/* x9[4] */ { "hammer_hit", 3,x1954, -1, },
/* x9[5] */ { "ninja_fire", 3,x1970, -1, },
/* x9[6] */ { "grenade_explode", 3,x1986, -1, },
/* x9[7] */ { "ninja_hit", 3,x2002, -1, },
/* x9[8] */ { "rifle_fire", 3,x2018, -1, },
/* x9[9] */ { "rifle_bounce", 3,x2034, -1, },
/* x9[10] */ { "weapon_switch", 3,x2050, -1, },
/* x9[11] */ { "player_pain_short", 12,x2066, -1, },
/* x9[12] */ { "player_pain_long", 2,x2109, -1, },
/* x9[13] */ { "body_land", 4,x2122, -1, },
/* x9[14] */ { "player_airjump", 3,x2141, -1, },
/* x9[15] */ { "player_jump", 8,x2157, -1, },
/* x9[16] */ { "player_die", 3,x2188, -1, },
/* x9[17] */ { "player_spawn", 7,x2204, -1, },
/* x9[18] */ { "player_skid", 4,x2232, -1, },
/* x9[19] */ { "tee_cry", 2,x2251, -1, },
/* x9[20] */ { "hook_loop", 2,x2264, -1, },
/* x9[21] */ { "hook_attach_ground", 3,x2277, -1, },
/* x9[22] */ { "hook_attach_player", 3,x2293, -1, },
/* x9[23] */ { "hook_noattach", 2,x2309, -1, },
/* x9[24] */ { "pickup_health", 2,x2322, -1, },
/* x9[25] */ { "pickup_armor", 4,x2335, -1, },
/* x9[26] */ { "pickup_grenade", 1,x2354, -1, },
/* x9[27] */ { "pickup_shotgun", 1,x2364, -1, },
/* x9[28] */ { "pickup_ninja", 1,x2374, -1, },
/* x9[29] */ { "weapon_spawn", 3,x2384, -1, },
/* x9[30] */ { "weapon_noammo", 5,x2400, -1, },
/* x9[31] */ { "hit", 2,x2422, -1, },
/* x9[32] */ { "chat_server", 1,x2435, -1, },
/* x9[33] */ { "chat_client", 1,x2445, -1, },
/* x9[34] */ { "ctf_drop", 1,x2455, -1, },
/* x9[35] */ { "ctf_return", 1,x2465, -1, },
/* x9[36] */ { "ctf_grab_pl", 1,x2475, -1, },
/* x9[37] */ { "ctf_grab_en", 1,x2485, -1, },
/* x9[38] */ { "ctf_capture", 1,x2495, -1, },
};
static IMAGE x14[] = {
/* x14[0] */ { "null", "", -1, },
/* x14[1] */ { "game", "game.png", -1, },
/* x14[2] */ { "particles", "particles.png", -1, },
/* x14[3] */ { "cursor", "gui_cursor.png", -1, },
/* x14[4] */ { "banner", "gui_logo.png", -1, },
/* x14[5] */ { "emoticons", "emoticons.png", -1, },
/* x14[6] */ { "browseicons", "browse_icons.png", -1, },
/* x14[7] */ { "console_bg", "console.png", -1, },
/* x14[8] */ { "console_bar", "console_bar.png", -1, },
};
static PICKUPSPEC x19[] = {
/* x19[0] */ { "health", 15, 0, },
/* x19[1] */ { "armor", 15, 0, },
/* x19[2] */ { "weapon", 15, 0, },
/* x19[3] */ { "ninja", 90, 90, },
};
static SPRITESET x28[] = {
/* x28[0] */ { &x14[2], 8, 8, },
/* x28[1] */ { &x14[1], 32, 16, },
/* x28[2] */ { &x14[0], 8, 4, },
/* x28[3] */ { &x14[6], 4, 1, },
/* x28[4] */ { &x14[5], 4, 4, },
};
static SPRITE x44[] = {
/* x44[0] */ { "part_slice", &x28[0], 0, 0, 1, 1, },
/* x44[1] */ { "part_ball", &x28[0], 1, 0, 1, 1, },
/* x44[2] */ { "part_splat01", &x28[0], 2, 0, 1, 1, },
/* x44[3] */ { "part_splat02", &x28[0], 3, 0, 1, 1, },
/* x44[4] */ { "part_splat03", &x28[0], 4, 0, 1, 1, },
/* x44[5] */ { "part_smoke", &x28[0], 0, 1, 1, 1, },
/* x44[6] */ { "part_shell", &x28[0], 0, 2, 2, 2, },
/* x44[7] */ { "part_expl01", &x28[0], 0, 4, 4, 4, },
/* x44[8] */ { "part_airjump", &x28[0], 2, 2, 2, 2, },
/* x44[9] */ { "health_full", &x28[1], 21, 0, 2, 2, },
/* x44[10] */ { "health_empty", &x28[1], 23, 0, 2, 2, },
/* x44[11] */ { "armor_full", &x28[1], 21, 2, 2, 2, },
/* x44[12] */ { "armor_empty", &x28[1], 23, 2, 2, 2, },
/* x44[13] */ { "star1", &x28[1], 15, 0, 2, 2, },
/* x44[14] */ { "star2", &x28[1], 17, 0, 2, 2, },
/* x44[15] */ { "star3", &x28[1], 19, 0, 2, 2, },
/* x44[16] */ { "part1", &x28[1], 6, 0, 1, 1, },
/* x44[17] */ { "part2", &x28[1], 6, 1, 1, 1, },
/* x44[18] */ { "part3", &x28[1], 7, 0, 1, 1, },
/* x44[19] */ { "part4", &x28[1], 7, 1, 1, 1, },
/* x44[20] */ { "part5", &x28[1], 8, 0, 1, 1, },
/* x44[21] */ { "part6", &x28[1], 8, 1, 1, 1, },
/* x44[22] */ { "part7", &x28[1], 9, 0, 2, 2, },
/* x44[23] */ { "part8", &x28[1], 11, 0, 2, 2, },
/* x44[24] */ { "part9", &x28[1], 13, 0, 2, 2, },
/* x44[25] */ { "weapon_gun_body", &x28[1], 2, 4, 4, 2, },
/* x44[26] */ { "weapon_gun_cursor", &x28[1], 0, 4, 2, 2, },
/* x44[27] */ { "weapon_gun_proj", &x28[1], 6, 4, 2, 2, },
/* x44[28] */ { "weapon_gun_muzzle1", &x28[1], 8, 4, 3, 2, },
/* x44[29] */ { "weapon_gun_muzzle2", &x28[1], 12, 4, 3, 2, },
/* x44[30] */ { "weapon_gun_muzzle3", &x28[1], 16, 4, 3, 2, },
/* x44[31] */ { "weapon_shotgun_body", &x28[1], 2, 6, 8, 2, },
/* x44[32] */ { "weapon_shotgun_cursor", &x28[1], 0, 6, 2, 2, },
/* x44[33] */ { "weapon_shotgun_proj", &x28[1], 10, 6, 2, 2, },
/* x44[34] */ { "weapon_shotgun_muzzle1", &x28[1], 12, 6, 3, 2, },
/* x44[35] */ { "weapon_shotgun_muzzle2", &x28[1], 16, 6, 3, 2, },
/* x44[36] */ { "weapon_shotgun_muzzle3", &x28[1], 20, 6, 3, 2, },
/* x44[37] */ { "weapon_grenade_body", &x28[1], 2, 8, 7, 2, },
/* x44[38] */ { "weapon_grenade_cursor", &x28[1], 0, 8, 2, 2, },
/* x44[39] */ { "weapon_grenade_proj", &x28[1], 10, 8, 2, 2, },
/* x44[40] */ { "weapon_hammer_body", &x28[1], 2, 1, 4, 3, },
/* x44[41] */ { "weapon_hammer_cursor", &x28[1], 0, 0, 2, 2, },
/* x44[42] */ { "weapon_hammer_proj", &x28[1], 0, 0, 0, 0, },
/* x44[43] */ { "weapon_ninja_body", &x28[1], 2, 10, 7, 2, },
/* x44[44] */ { "weapon_ninja_cursor", &x28[1], 0, 10, 2, 2, },
/* x44[45] */ { "weapon_ninja_proj", &x28[1], 0, 0, 0, 0, },
/* x44[46] */ { "weapon_rifle_body", &x28[1], 2, 12, 7, 3, },
/* x44[47] */ { "weapon_rifle_cursor", &x28[1], 0, 12, 2, 2, },
/* x44[48] */ { "weapon_rifle_proj", &x28[1], 10, 12, 2, 2, },
/* x44[49] */ { "hook_chain", &x28[1], 2, 0, 1, 1, },
/* x44[50] */ { "hook_head", &x28[1], 3, 0, 2, 1, },
/* x44[51] */ { "weapon_ninja_muzzle1", &x28[1], 25, 0, 7, 4, },
/* x44[52] */ { "weapon_ninja_muzzle2", &x28[1], 25, 4, 7, 4, },
/* x44[53] */ { "weapon_ninja_muzzle3", &x28[1], 25, 8, 7, 4, },
/* x44[54] */ { "pickup_health", &x28[1], 10, 2, 2, 2, },
/* x44[55] */ { "pickup_armor", &x28[1], 12, 2, 2, 2, },
/* x44[56] */ { "pickup_weapon", &x28[1], 3, 0, 6, 2, },
/* x44[57] */ { "pickup_ninja", &x28[1], 3, 10, 7, 2, },
/* x44[58] */ { "flag_blue", &x28[1], 12, 8, 4, 8, },
/* x44[59] */ { "flag_red", &x28[1], 16, 8, 4, 8, },
/* x44[60] */ { "tee_body", &x28[2], 0, 0, 3, 3, },
/* x44[61] */ { "tee_body_outline", &x28[2], 3, 0, 3, 3, },
/* x44[62] */ { "tee_foot", &x28[2], 6, 1, 2, 1, },
/* x44[63] */ { "tee_foot_outline", &x28[2], 6, 2, 2, 1, },
/* x44[64] */ { "tee_hand", &x28[2], 6, 0, 1, 1, },
/* x44[65] */ { "tee_hand_outline", &x28[2], 7, 0, 1, 1, },
/* x44[66] */ { "tee_eye_normal", &x28[2], 2, 3, 1, 1, },
/* x44[67] */ { "tee_eye_angry", &x28[2], 3, 3, 1, 1, },
/* x44[68] */ { "tee_eye_pain", &x28[2], 4, 3, 1, 1, },
/* x44[69] */ { "tee_eye_happy", &x28[2], 5, 3, 1, 1, },
/* x44[70] */ { "tee_eye_dead", &x28[2], 6, 3, 1, 1, },
/* x44[71] */ { "tee_eye_surprise", &x28[2], 7, 3, 1, 1, },
/* x44[72] */ { "oop", &x28[4], 0, 0, 1, 1, },
/* x44[73] */ { "exclamation", &x28[4], 1, 0, 1, 1, },
/* x44[74] */ { "hearts", &x28[4], 2, 0, 1, 1, },
/* x44[75] */ { "drop", &x28[4], 3, 0, 1, 1, },
/* x44[76] */ { "dotdot", &x28[4], 0, 1, 1, 1, },
/* x44[77] */ { "music1", &x28[4], 1, 1, 1, 1, },
/* x44[78] */ { "music2", &x28[4], 2, 1, 1, 1, },
/* x44[79] */ { "ghost", &x28[4], 3, 1, 1, 1, },
/* x44[80] */ { "sushi", &x28[4], 0, 2, 1, 1, },
/* x44[81] */ { "splattee", &x28[4], 1, 2, 1, 1, },
/* x44[82] */ { "deviltee", &x28[4], 2, 2, 1, 1, },
/* x44[83] */ { "zomg", &x28[4], 3, 2, 1, 1, },
/* x44[84] */ { "zzz", &x28[4], 0, 3, 1, 1, },
/* x44[85] */ { "blank1", &x28[4], 1, 3, 1, 1, },
/* x44[86] */ { "deadtee", &x28[4], 2, 3, 1, 1, },
/* x44[87] */ { "blank2", &x28[4], 3, 3, 1, 1, },
/* x44[88] */ { "browse_lock", &x28[3], 0, 0, 1, 1, },
/* x44[89] */ { "browse_heart", &x28[3], 1, 0, 1, 1, },
/* x44[90] */ { "browse_unpure", &x28[3], 3, 0, 1, 1, },
};
static ANIM_KEYFRAME x3965[] = {
/* x3965[0] */ { 0.000000, 0.000000, -4.000000, 0.000000, },
};
static ANIM_KEYFRAME x3972[] = {
/* x3972[0] */ { 0.000000, 0.000000, 10.000000, 0.000000, },
};
static ANIM_KEYFRAME x3979[] = {
/* x3979[0] */ { 0.000000, 0.000000, 10.000000, 0.000000, },
};
static ANIM_KEYFRAME *x3986 = 0;
static ANIM_KEYFRAME *x4010 = 0;
static ANIM_KEYFRAME x4017[] = {
/* x4017[0] */ { 0.000000, -7.000000, 0.000000, 0.000000, },
};
static ANIM_KEYFRAME x4024[] = {
/* x4024[0] */ { 0.000000, 7.000000, 0.000000, 0.000000, },
};
static ANIM_KEYFRAME *x4031 = 0;
static ANIM_KEYFRAME *x4050 = 0;
static ANIM_KEYFRAME x4057[] = {
/* x4057[0] */ { 0.000000, -3.000000, 0.000000, -0.100000, },
};
static ANIM_KEYFRAME x4064[] = {
/* x4064[0] */ { 0.000000, 3.000000, 0.000000, -0.100000, },
};
static ANIM_KEYFRAME *x4071 = 0;
static ANIM_KEYFRAME x4090[] = {
/* x4090[0] */ { 0.000000, 0.000000, 0.000000, 0.000000, },
/* x4090[1] */ { 0.200000, 0.000000, -1.000000, 0.000000, },
/* x4090[2] */ { 0.400000, 0.000000, 0.000000, 0.000000, },
/* x4090[3] */ { 0.600000, 0.000000, 0.000000, 0.000000, },
/* x4090[4] */ { 0.800000, 0.000000, -1.000000, 0.000000, },
/* x4090[5] */ { 1.000000, 0.000000, 0.000000, 0.000000, },
};
static ANIM_KEYFRAME x4097[] = {
/* x4097[0] */ { 0.000000, 8.000000, 0.000000, 0.000000, },
/* x4097[1] */ { 0.200000, -8.000000, 0.000000, 0.000000, },
/* x4097[2] */ { 0.400000, -10.000000, -4.000000, 0.200000, },
/* x4097[3] */ { 0.600000, -8.000000, -8.000000, 0.300000, },
/* x4097[4] */ { 0.800000, 4.000000, -4.000000, -0.200000, },
/* x4097[5] */ { 1.000000, 8.000000, 0.000000, 0.000000, },
};
static ANIM_KEYFRAME x4104[] = {
/* x4104[0] */ { 0.000000, -10.000000, -4.000000, 0.200000, },
/* x4104[1] */ { 0.200000, -8.000000, -8.000000, 0.300000, },
/* x4104[2] */ { 0.400000, 4.000000, -4.000000, -0.200000, },
/* x4104[3] */ { 0.600000, 8.000000, 0.000000, 0.000000, },
/* x4104[4] */ { 0.800000, 8.000000, 0.000000, 0.000000, },
/* x4104[5] */ { 1.000000, -10.000000, -4.000000, 0.200000, },
};
static ANIM_KEYFRAME *x4111 = 0;
static ANIM_KEYFRAME *x4210 = 0;
static ANIM_KEYFRAME *x4217 = 0;
static ANIM_KEYFRAME *x4224 = 0;
static ANIM_KEYFRAME x4231[] = {
/* x4231[0] */ { 0.000000, 0.000000, 0.000000, -0.100000, },
/* x4231[1] */ { 0.300000, 0.000000, 0.000000, 0.250000, },
/* x4231[2] */ { 0.400000, 0.000000, 0.000000, 0.300000, },
/* x4231[3] */ { 0.500000, 0.000000, 0.000000, 0.250000, },
/* x4231[4] */ { 1.000000, 0.000000, 0.000000, -0.100000, },
};
static ANIM_KEYFRAME *x4265 = 0;
static ANIM_KEYFRAME *x4272 = 0;
static ANIM_KEYFRAME *x4279 = 0;
static ANIM_KEYFRAME x4286[] = {
/* x4286[0] */ { 0.000000, 0.000000, 0.000000, -0.250000, },
/* x4286[1] */ { 0.100000, 0.000000, 0.000000, -0.050000, },
/* x4286[2] */ { 0.150000, 0.000000, 0.000000, 0.350000, },
/* x4286[3] */ { 0.420000, 0.000000, 0.000000, 0.400000, },
/* x4286[4] */ { 0.500000, 0.000000, 0.000000, 0.350000, },
/* x4286[5] */ { 1.000000, 0.000000, 0.000000, -0.250000, },
};
static ANIMATION x75[] = {
/* x75[0] */ { "base", /* x75[0].body */ { 1,x3965, }, /* x75[0].back_foot */ { 1,x3972, }, /* x75[0].front_foot */ { 1,x3979, }, /* x75[0].attach */ { 0,x3986, }, },
/* x75[1] */ { "idle", /* x75[1].body */ { 0,x4010, }, /* x75[1].back_foot */ { 1,x4017, }, /* x75[1].front_foot */ { 1,x4024, }, /* x75[1].attach */ { 0,x4031, }, },
/* x75[2] */ { "inair", /* x75[2].body */ { 0,x4050, }, /* x75[2].back_foot */ { 1,x4057, }, /* x75[2].front_foot */ { 1,x4064, }, /* x75[2].attach */ { 0,x4071, }, },
/* x75[3] */ { "walk", /* x75[3].body */ { 6,x4090, }, /* x75[3].back_foot */ { 6,x4097, }, /* x75[3].front_foot */ { 6,x4104, }, /* x75[3].attach */ { 0,x4111, }, },
/* x75[4] */ { "hammer_swing", /* x75[4].body */ { 0,x4210, }, /* x75[4].back_foot */ { 0,x4217, }, /* x75[4].front_foot */ { 0,x4224, }, /* x75[4].attach */ { 5,x4231, }, },
/* x75[5] */ { "ninja_swing", /* x75[5].body */ { 0,x4265, }, /* x75[5].back_foot */ { 0,x4272, }, /* x75[5].front_foot */ { 0,x4279, }, /* x75[5].attach */ { 6,x4286, }, },
};
static SPRITE* *x4443 = 0;
static SPRITE* x4580[] = {
&x44[28],
&x44[29],
&x44[30],
};
static SPRITE* x4765[] = {
&x44[34],
&x44[35],
&x44[36],
};
static SPRITE* *x4950 = 0;
static SPRITE* *x5087 = 0;
static SPRITE* x5224[] = {
&x44[51],
&x44[52],
&x44[53],
};
static WEAPONSPEC x1884[] = {
/* x1884[0] */ { "hammer", &x44[40], &x44[41], &x44[42], 0,x4443, 96, 125, 10, 0, 3, 4.000000, -20.000000, 0.000000, 0.000000, 5.000000, },
/* x1884[1] */ { "gun", &x44[25], &x44[26], &x44[27], 3,x4580, 64, 125, 10, 500, 1, 32.000000, -4.000000, 50.000000, 6.000000, 5.000000, },
/* x1884[2] */ { "shotgun", &x44[31], &x44[32], &x44[33], 3,x4765, 96, 500, 10, 0, 1, 24.000000, -2.000000, 70.000000, 6.000000, 5.000000, },
/* x1884[3] */ { "grenade", &x44[37], &x44[38], &x44[39], 0,x4950, 96, 500, 10, 0, 1, 24.000000, -2.000000, 0.000000, 0.000000, 5.000000, },
/* x1884[4] */ { "rifle", &x44[46], &x44[47], &x44[48], 0,x5087, 92, 800, 10, 0, 5, 24.000000, -2.000000, 0.000000, 0.000000, 5.000000, },
/* x1884[5] */ { "ninja", &x44[43], &x44[44], &x44[45], 3,x5224, 96, 800, 10, 0, 9, 0.000000, 0.000000, 40.000000, -4.000000, 5.000000, },
};
DATACONTAINER datacontainer =
/* datacontainer */ {
39,x9,
9,x14,
4,x19,
5,x28,
91,x44,
6,x75,
/* datacontainer.weapons */ { /* datacontainer.weapons.hammer */ { &x1884[0], }, /* datacontainer.weapons.gun */ { &x1884[1], }, /* datacontainer.weapons.shotgun */ { &x1884[2], 1.250000, 2200.000000, 0.800000, 0.250000, }, /* datacontainer.weapons.grenade */ { &x1884[3], 7.000000, 1000.000000, 2.000000, }, /* datacontainer.weapons.rifle */ { &x1884[4], 800.000000, 150, 1, 0.000000, }, /* datacontainer.weapons.ninja */ { &x1884[5], 15000, 200, 50, }, 6,x1884, },
}
;
DATACONTAINER *data = &datacontainer;

View File

@@ -0,0 +1,268 @@
#ifndef CLIENT_CONTENT_HEADER
#define CLIENT_CONTENT_HEADER
struct SOUND
{
int id;
const char* filename;
};
struct SOUNDSET
{
const char* name;
int num_sounds;
SOUND *sounds;
int last;
};
struct IMAGE
{
const char* name;
const char* filename;
int id;
};
struct SPRITESET
{
IMAGE* image;
int gridx;
int gridy;
};
struct SPRITE
{
const char* name;
SPRITESET* set;
int x;
int y;
int w;
int h;
};
struct PICKUPSPEC
{
const char* name;
int respawntime;
int spawndelay;
};
struct ANIM_KEYFRAME
{
float time;
float x;
float y;
float angle;
};
struct ANIM_SEQUENCE
{
int num_frames;
ANIM_KEYFRAME *frames;
};
struct ANIMATION
{
const char* name;
ANIM_SEQUENCE body;
ANIM_SEQUENCE back_foot;
ANIM_SEQUENCE front_foot;
ANIM_SEQUENCE attach;
};
struct WEAPONSPEC
{
const char* name;
SPRITE* sprite_body;
SPRITE* sprite_cursor;
SPRITE* sprite_proj;
int num_sprite_muzzles;
SPRITE* *sprite_muzzles;
int visual_size;
int firedelay;
int maxammo;
int ammoregentime;
int damage;
float offsetx;
float offsety;
float muzzleoffsetx;
float muzzleoffsety;
float muzzleduration;
};
struct WEAPONSPEC_HAMMER
{
WEAPONSPEC* base;
};
struct WEAPONSPEC_GUN
{
WEAPONSPEC* base;
float curvature;
float speed;
float lifetime;
};
struct WEAPONSPEC_SHOTGUN
{
WEAPONSPEC* base;
float curvature;
float speed;
float speeddiff;
float lifetime;
};
struct WEAPONSPEC_GRENADE
{
WEAPONSPEC* base;
float curvature;
float speed;
float lifetime;
};
struct WEAPONSPEC_RIFLE
{
WEAPONSPEC* base;
float reach;
int bounce_delay;
int bounce_num;
float bounce_cost;
};
struct WEAPONSPEC_NINJA
{
WEAPONSPEC* base;
int duration;
int movetime;
int velocity;
};
struct WEAPONSPECS
{
WEAPONSPEC_HAMMER hammer;
WEAPONSPEC_HAMMER gun;
WEAPONSPEC_SHOTGUN shotgun;
WEAPONSPEC_GRENADE grenade;
WEAPONSPEC_RIFLE rifle;
WEAPONSPEC_NINJA ninja;
int num_id;
WEAPONSPEC *id;
};
struct DATACONTAINER
{
int num_sounds;
SOUNDSET *sounds;
int num_images;
IMAGE *images;
int num_pickups;
PICKUPSPEC *pickups;
int num_spritesets;
SPRITESET *spritesets;
int num_sprites;
SPRITE *sprites;
int num_animations;
ANIMATION *animations;
WEAPONSPECS weapons;
};
extern DATACONTAINER *data;
enum
{
IMAGE_NULL=0,
IMAGE_GAME,
IMAGE_PARTICLES,
IMAGE_CURSOR,
IMAGE_BANNER,
IMAGE_EMOTICONS,
IMAGE_BROWSEICONS,
IMAGE_CONSOLE_BG,
IMAGE_CONSOLE_BAR,
NUM_IMAGES
};
enum
{
ANIM_BASE=0,
ANIM_IDLE,
ANIM_INAIR,
ANIM_WALK,
ANIM_HAMMER_SWING,
ANIM_NINJA_SWING,
NUM_ANIMS
};
enum
{
SPRITE_PART_SLICE=0,
SPRITE_PART_BALL,
SPRITE_PART_SPLAT01,
SPRITE_PART_SPLAT02,
SPRITE_PART_SPLAT03,
SPRITE_PART_SMOKE,
SPRITE_PART_SHELL,
SPRITE_PART_EXPL01,
SPRITE_PART_AIRJUMP,
SPRITE_HEALTH_FULL,
SPRITE_HEALTH_EMPTY,
SPRITE_ARMOR_FULL,
SPRITE_ARMOR_EMPTY,
SPRITE_STAR1,
SPRITE_STAR2,
SPRITE_STAR3,
SPRITE_PART1,
SPRITE_PART2,
SPRITE_PART3,
SPRITE_PART4,
SPRITE_PART5,
SPRITE_PART6,
SPRITE_PART7,
SPRITE_PART8,
SPRITE_PART9,
SPRITE_WEAPON_GUN_BODY,
SPRITE_WEAPON_GUN_CURSOR,
SPRITE_WEAPON_GUN_PROJ,
SPRITE_WEAPON_GUN_MUZZLE1,
SPRITE_WEAPON_GUN_MUZZLE2,
SPRITE_WEAPON_GUN_MUZZLE3,
SPRITE_WEAPON_SHOTGUN_BODY,
SPRITE_WEAPON_SHOTGUN_CURSOR,
SPRITE_WEAPON_SHOTGUN_PROJ,
SPRITE_WEAPON_SHOTGUN_MUZZLE1,
SPRITE_WEAPON_SHOTGUN_MUZZLE2,
SPRITE_WEAPON_SHOTGUN_MUZZLE3,
SPRITE_WEAPON_GRENADE_BODY,
SPRITE_WEAPON_GRENADE_CURSOR,
SPRITE_WEAPON_GRENADE_PROJ,
SPRITE_WEAPON_HAMMER_BODY,
SPRITE_WEAPON_HAMMER_CURSOR,
SPRITE_WEAPON_HAMMER_PROJ,
SPRITE_WEAPON_NINJA_BODY,
SPRITE_WEAPON_NINJA_CURSOR,
SPRITE_WEAPON_NINJA_PROJ,
SPRITE_WEAPON_RIFLE_BODY,
SPRITE_WEAPON_RIFLE_CURSOR,
SPRITE_WEAPON_RIFLE_PROJ,
SPRITE_HOOK_CHAIN,
SPRITE_HOOK_HEAD,
SPRITE_WEAPON_NINJA_MUZZLE1,
SPRITE_WEAPON_NINJA_MUZZLE2,
SPRITE_WEAPON_NINJA_MUZZLE3,
SPRITE_PICKUP_HEALTH,
SPRITE_PICKUP_ARMOR,
SPRITE_PICKUP_WEAPON,
SPRITE_PICKUP_NINJA,
SPRITE_FLAG_BLUE,
SPRITE_FLAG_RED,
SPRITE_TEE_BODY,
SPRITE_TEE_BODY_OUTLINE,
SPRITE_TEE_FOOT,
SPRITE_TEE_FOOT_OUTLINE,
SPRITE_TEE_HAND,
SPRITE_TEE_HAND_OUTLINE,
SPRITE_TEE_EYE_NORMAL,
SPRITE_TEE_EYE_ANGRY,
SPRITE_TEE_EYE_PAIN,
SPRITE_TEE_EYE_HAPPY,
SPRITE_TEE_EYE_DEAD,
SPRITE_TEE_EYE_SURPRISE,
SPRITE_OOP,
SPRITE_EXCLAMATION,
SPRITE_HEARTS,
SPRITE_DROP,
SPRITE_DOTDOT,
SPRITE_MUSIC1,
SPRITE_MUSIC2,
SPRITE_GHOST,
SPRITE_SUSHI,
SPRITE_SPLATTEE,
SPRITE_DEVILTEE,
SPRITE_ZOMG,
SPRITE_ZZZ,
SPRITE_BLANK1,
SPRITE_DEADTEE,
SPRITE_BLANK2,
SPRITE_BROWSE_LOCK,
SPRITE_BROWSE_HEART,
SPRITE_BROWSE_UNPURE,
NUM_SPRITES
};
#endif

View File

@@ -0,0 +1,268 @@
#ifndef SERVER_CONTENT_HEADER
#define SERVER_CONTENT_HEADER
struct SOUND
{
int id;
const char* filename;
};
struct SOUNDSET
{
const char* name;
int num_sounds;
SOUND *sounds;
int last;
};
struct IMAGE
{
const char* name;
const char* filename;
int id;
};
struct SPRITESET
{
IMAGE* image;
int gridx;
int gridy;
};
struct SPRITE
{
const char* name;
SPRITESET* set;
int x;
int y;
int w;
int h;
};
struct PICKUPSPEC
{
const char* name;
int respawntime;
int spawndelay;
};
struct ANIM_KEYFRAME
{
float time;
float x;
float y;
float angle;
};
struct ANIM_SEQUENCE
{
int num_frames;
ANIM_KEYFRAME *frames;
};
struct ANIMATION
{
const char* name;
ANIM_SEQUENCE body;
ANIM_SEQUENCE back_foot;
ANIM_SEQUENCE front_foot;
ANIM_SEQUENCE attach;
};
struct WEAPONSPEC
{
const char* name;
SPRITE* sprite_body;
SPRITE* sprite_cursor;
SPRITE* sprite_proj;
int num_sprite_muzzles;
SPRITE* *sprite_muzzles;
int visual_size;
int firedelay;
int maxammo;
int ammoregentime;
int damage;
float offsetx;
float offsety;
float muzzleoffsetx;
float muzzleoffsety;
float muzzleduration;
};
struct WEAPONSPEC_HAMMER
{
WEAPONSPEC* base;
};
struct WEAPONSPEC_GUN
{
WEAPONSPEC* base;
float curvature;
float speed;
float lifetime;
};
struct WEAPONSPEC_SHOTGUN
{
WEAPONSPEC* base;
float curvature;
float speed;
float speeddiff;
float lifetime;
};
struct WEAPONSPEC_GRENADE
{
WEAPONSPEC* base;
float curvature;
float speed;
float lifetime;
};
struct WEAPONSPEC_RIFLE
{
WEAPONSPEC* base;
float reach;
int bounce_delay;
int bounce_num;
float bounce_cost;
};
struct WEAPONSPEC_NINJA
{
WEAPONSPEC* base;
int duration;
int movetime;
int velocity;
};
struct WEAPONSPECS
{
WEAPONSPEC_HAMMER hammer;
WEAPONSPEC_HAMMER gun;
WEAPONSPEC_SHOTGUN shotgun;
WEAPONSPEC_GRENADE grenade;
WEAPONSPEC_RIFLE rifle;
WEAPONSPEC_NINJA ninja;
int num_id;
WEAPONSPEC *id;
};
struct DATACONTAINER
{
int num_sounds;
SOUNDSET *sounds;
int num_images;
IMAGE *images;
int num_pickups;
PICKUPSPEC *pickups;
int num_spritesets;
SPRITESET *spritesets;
int num_sprites;
SPRITE *sprites;
int num_animations;
ANIMATION *animations;
WEAPONSPECS weapons;
};
extern DATACONTAINER *data;
enum
{
IMAGE_NULL=0,
IMAGE_GAME,
IMAGE_PARTICLES,
IMAGE_CURSOR,
IMAGE_BANNER,
IMAGE_EMOTICONS,
IMAGE_BROWSEICONS,
IMAGE_CONSOLE_BG,
IMAGE_CONSOLE_BAR,
NUM_IMAGES
};
enum
{
ANIM_BASE=0,
ANIM_IDLE,
ANIM_INAIR,
ANIM_WALK,
ANIM_HAMMER_SWING,
ANIM_NINJA_SWING,
NUM_ANIMS
};
enum
{
SPRITE_PART_SLICE=0,
SPRITE_PART_BALL,
SPRITE_PART_SPLAT01,
SPRITE_PART_SPLAT02,
SPRITE_PART_SPLAT03,
SPRITE_PART_SMOKE,
SPRITE_PART_SHELL,
SPRITE_PART_EXPL01,
SPRITE_PART_AIRJUMP,
SPRITE_HEALTH_FULL,
SPRITE_HEALTH_EMPTY,
SPRITE_ARMOR_FULL,
SPRITE_ARMOR_EMPTY,
SPRITE_STAR1,
SPRITE_STAR2,
SPRITE_STAR3,
SPRITE_PART1,
SPRITE_PART2,
SPRITE_PART3,
SPRITE_PART4,
SPRITE_PART5,
SPRITE_PART6,
SPRITE_PART7,
SPRITE_PART8,
SPRITE_PART9,
SPRITE_WEAPON_GUN_BODY,
SPRITE_WEAPON_GUN_CURSOR,
SPRITE_WEAPON_GUN_PROJ,
SPRITE_WEAPON_GUN_MUZZLE1,
SPRITE_WEAPON_GUN_MUZZLE2,
SPRITE_WEAPON_GUN_MUZZLE3,
SPRITE_WEAPON_SHOTGUN_BODY,
SPRITE_WEAPON_SHOTGUN_CURSOR,
SPRITE_WEAPON_SHOTGUN_PROJ,
SPRITE_WEAPON_SHOTGUN_MUZZLE1,
SPRITE_WEAPON_SHOTGUN_MUZZLE2,
SPRITE_WEAPON_SHOTGUN_MUZZLE3,
SPRITE_WEAPON_GRENADE_BODY,
SPRITE_WEAPON_GRENADE_CURSOR,
SPRITE_WEAPON_GRENADE_PROJ,
SPRITE_WEAPON_HAMMER_BODY,
SPRITE_WEAPON_HAMMER_CURSOR,
SPRITE_WEAPON_HAMMER_PROJ,
SPRITE_WEAPON_NINJA_BODY,
SPRITE_WEAPON_NINJA_CURSOR,
SPRITE_WEAPON_NINJA_PROJ,
SPRITE_WEAPON_RIFLE_BODY,
SPRITE_WEAPON_RIFLE_CURSOR,
SPRITE_WEAPON_RIFLE_PROJ,
SPRITE_HOOK_CHAIN,
SPRITE_HOOK_HEAD,
SPRITE_WEAPON_NINJA_MUZZLE1,
SPRITE_WEAPON_NINJA_MUZZLE2,
SPRITE_WEAPON_NINJA_MUZZLE3,
SPRITE_PICKUP_HEALTH,
SPRITE_PICKUP_ARMOR,
SPRITE_PICKUP_WEAPON,
SPRITE_PICKUP_NINJA,
SPRITE_FLAG_BLUE,
SPRITE_FLAG_RED,
SPRITE_TEE_BODY,
SPRITE_TEE_BODY_OUTLINE,
SPRITE_TEE_FOOT,
SPRITE_TEE_FOOT_OUTLINE,
SPRITE_TEE_HAND,
SPRITE_TEE_HAND_OUTLINE,
SPRITE_TEE_EYE_NORMAL,
SPRITE_TEE_EYE_ANGRY,
SPRITE_TEE_EYE_PAIN,
SPRITE_TEE_EYE_HAPPY,
SPRITE_TEE_EYE_DEAD,
SPRITE_TEE_EYE_SURPRISE,
SPRITE_OOP,
SPRITE_EXCLAMATION,
SPRITE_HEARTS,
SPRITE_DROP,
SPRITE_DOTDOT,
SPRITE_MUSIC1,
SPRITE_MUSIC2,
SPRITE_GHOST,
SPRITE_SUSHI,
SPRITE_SPLATTEE,
SPRITE_DEVILTEE,
SPRITE_ZOMG,
SPRITE_ZZZ,
SPRITE_BLANK1,
SPRITE_DEADTEE,
SPRITE_BLANK2,
SPRITE_BROWSE_LOCK,
SPRITE_BROWSE_HEART,
SPRITE_BROWSE_UNPURE,
NUM_SPRITES
};
#endif

View File

@@ -0,0 +1 @@
#define GAME_NETVERSION_HASH "b67d1f1a1eea234e"

View File

@@ -0,0 +1,57 @@
#include <engine/e_common_interface.h>
#include "layers.hpp"
static MAPITEM_LAYER_TILEMAP *game_layer = 0;
static MAPITEM_GROUP *game_group = 0;
static int groups_start = 0;
static int groups_num = 0;
static int layers_start = 0;
static int layers_num = 0;
void layers_init()
{
map_get_type(MAPITEMTYPE_GROUP, &groups_start, &groups_num);
map_get_type(MAPITEMTYPE_LAYER, &layers_start, &layers_num);
for(int g = 0; g < layers_num_groups(); g++)
{
MAPITEM_GROUP *group = layers_get_group(g);
for(int l = 0; l < group->num_layers; l++)
{
MAPITEM_LAYER *layer = layers_get_layer(group->start_layer+l);
if(layer->type == LAYERTYPE_TILES)
{
MAPITEM_LAYER_TILEMAP *tilemap = (MAPITEM_LAYER_TILEMAP *)layer;
if(tilemap->flags&1)
{
game_layer = tilemap;
game_group = group;
}
}
}
}
}
int layers_num_groups() { return groups_num; }
MAPITEM_GROUP *layers_get_group(int index)
{
return (MAPITEM_GROUP *)map_get_item(groups_start+index, 0, 0);
}
MAPITEM_LAYER *layers_get_layer(int index)
{
return (MAPITEM_LAYER *)map_get_item(layers_start+index, 0, 0);
}
MAPITEM_LAYER_TILEMAP *layers_game_layer()
{
return game_layer;
}
MAPITEM_GROUP *layers_game_group()
{
return game_group;
}

View File

@@ -0,0 +1,12 @@
#include "mapitems.hpp"
void layers_init();
MAPITEM_LAYER_TILEMAP *layers_game_layer();
MAPITEM_GROUP *layers_game_group();
int layers_num_groups();
MAPITEM_GROUP *layers_get_group(int index);
MAPITEM_LAYER *layers_get_layer(int index);

View File

@@ -0,0 +1,178 @@
/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */
#ifndef GAME_MAPITEMS_H
#define GAME_MAPITEMS_H
// layer types
enum
{
LAYERTYPE_INVALID=0,
LAYERTYPE_GAME, // not used
LAYERTYPE_TILES,
LAYERTYPE_QUADS,
MAPITEMTYPE_VERSION=0,
MAPITEMTYPE_INFO,
MAPITEMTYPE_IMAGE,
MAPITEMTYPE_ENVELOPE,
MAPITEMTYPE_GROUP,
MAPITEMTYPE_LAYER,
MAPITEMTYPE_ENVPOINTS,
CURVETYPE_STEP=0,
CURVETYPE_LINEAR,
CURVETYPE_SLOW,
CURVETYPE_FAST,
CURVETYPE_SMOOTH,
NUM_CURVETYPES,
// game layer tiles
ENTITY_NULL=0,
ENTITY_SPAWN,
ENTITY_SPAWN_RED,
ENTITY_SPAWN_BLUE,
ENTITY_FLAGSTAND_RED,
ENTITY_FLAGSTAND_BLUE,
ENTITY_ARMOR_1,
ENTITY_HEALTH_1,
ENTITY_WEAPON_SHOTGUN,
ENTITY_WEAPON_GRENADE,
ENTITY_POWERUP_NINJA,
ENTITY_WEAPON_RIFLE,
NUM_ENTITIES,
TILE_AIR=0,
TILE_SOLID,
TILE_DEATH,
TILE_NOHOOK,
TILEFLAG_VFLIP=1,
TILEFLAG_HFLIP=2,
TILEFLAG_OPAQUE=4,
LAYERFLAG_DETAIL=1,
ENTITY_OFFSET=255-16*4,
};
typedef struct
{
int x, y; // 22.10 fixed point
} POINT;
typedef struct
{
int r, g, b, a;
} COLOR;
typedef struct
{
POINT points[5];
COLOR colors[4];
POINT texcoords[4];
int pos_env;
int pos_env_offset;
int color_env;
int color_env_offset;
} QUAD;
typedef struct
{
unsigned char index;
unsigned char flags;
unsigned char skip;
unsigned char reserved;
} TILE;
typedef struct
{
int version;
int width;
int height;
int external;
int image_name;
int image_data;
} MAPITEM_IMAGE;
struct MAPITEM_GROUP_v1
{
int version;
int offset_x;
int offset_y;
int parallax_x;
int parallax_y;
int start_layer;
int num_layers;
} ;
struct MAPITEM_GROUP : public MAPITEM_GROUP_v1
{
enum { CURRENT_VERSION=2 };
int use_clipping;
int clip_x;
int clip_y;
int clip_w;
int clip_h;
} ;
typedef struct
{
int version;
int type;
int flags;
} MAPITEM_LAYER;
typedef struct
{
MAPITEM_LAYER layer;
int version;
int width;
int height;
int flags;
COLOR color;
int color_env;
int color_env_offset;
int image;
int data;
} MAPITEM_LAYER_TILEMAP;
typedef struct
{
MAPITEM_LAYER layer;
int version;
int num_quads;
int data;
int image;
} MAPITEM_LAYER_QUADS;
typedef struct
{
int version;
} MAPITEM_VERSION;
typedef struct
{
int time; // in ms
int curvetype;
int values[4]; // 1-4 depending on envelope (22.10 fixed point)
} ENVPOINT;
typedef struct
{
int version;
int channels;
int start_point;
int num_points;
int name;
} MAPITEM_ENVELOPE;
#endif

View File

@@ -0,0 +1,838 @@
#include <new>
#include <engine/e_server_interface.h>
#include <engine/e_config.h>
#include <game/server/gamecontext.hpp>
#include <game/mapitems.hpp>
#include "character.hpp"
#include "laser.hpp"
#include "projectile.hpp"
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;
}
MACRO_ALLOC_POOL_ID_IMPL(CHARACTER, MAX_CLIENTS)
// player
CHARACTER::CHARACTER()
: ENTITY(NETOBJTYPE_CHARACTER)
{
proximity_radius = phys_size;
}
void CHARACTER::reset()
{
destroy();
}
bool CHARACTER::spawn(PLAYER *player, vec2 pos, int team)
{
player_state = PLAYERSTATE_UNKNOWN;
emote_stop = -1;
last_action = -1;
active_weapon = WEAPON_GUN;
last_weapon = WEAPON_HAMMER;
queued_weapon = -1;
//clear();
this->player = player;
this->pos = pos;
this->team = team;
core.reset();
core.world = &game.world.core;
core.pos = pos;
game.world.core.characters[player->client_id] = &core;
reckoning_tick = 0;
mem_zero(&sendcore, sizeof(sendcore));
mem_zero(&reckoningcore, sizeof(reckoningcore));
game.world.insert_entity(this);
alive = true;
player->force_balanced = false;
game.controller->on_character_spawn(this);
return true;
}
void CHARACTER::destroy()
{
game.world.core.characters[player->client_id] = 0;
alive = false;
}
void CHARACTER::set_weapon(int w)
{
if(w == active_weapon)
return;
last_weapon = active_weapon;
queued_weapon = -1;
active_weapon = w;
if(active_weapon < 0 || active_weapon >= NUM_WEAPONS)
active_weapon = 0;
game.create_sound(pos, SOUND_WEAPON_SWITCH);
}
bool CHARACTER::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;
}
int CHARACTER::handle_ninja()
{
if(active_weapon != WEAPON_NINJA)
return 0;
vec2 direction = normalize(vec2(latest_input.target_x, latest_input.target_y));
if ((server_tick() - ninja.activationtick) > (data->weapons.ninja.duration * server_tickspeed() / 1000))
{
// time's up, return
weapons[WEAPON_NINJA].got = false;
active_weapon = last_weapon;
if(active_weapon == WEAPON_NINJA)
active_weapon = WEAPON_GUN;
set_weapon(active_weapon);
return 0;
}
// force ninja weapon
set_weapon(WEAPON_NINJA);
ninja.currentmovetime--;
if (ninja.currentmovetime == 0)
{
// reset player velocity
core.vel *= 0.2f;
//return MODIFIER_RETURNFLAGS_OVERRIDEWEAPON;
}
if (ninja.currentmovetime > 0)
{
// Set player velocity
core.vel = ninja.activationdir * data->weapons.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 ((ninja.currentmovetime % 2) == 0)
{
//create_smoke(pos);
}
// check if we hit anything along the way
{
CHARACTER *ents[64];
vec2 dir = pos - oldpos;
float radius = phys_size * 2.0f; //length(dir * 0.5f);
vec2 center = oldpos + dir * 0.5f;
int num = game.world.find_entities(center, radius, (ENTITY**)ents, 64, NETOBJTYPE_CHARACTER);
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...
game.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.ninja.base->damage, player->client_id,WEAPON_NINJA);
}
}
return 0;
}
return 0;
}
void CHARACTER::do_weaponswitch()
{
if(reload_timer != 0) // make sure we have reloaded
return;
if(queued_weapon == -1) // check for a queued weapon
return;
if(weapons[WEAPON_NINJA].got) // if we have ninja, no weapon selection is possible
return;
// switch weapon
set_weapon(queued_weapon);
}
void CHARACTER::handle_weaponswitch()
{
int wanted_weapon = active_weapon;
if(queued_weapon != -1)
wanted_weapon = queued_weapon;
// select weapon
int next = count_input(latest_previnput.next_weapon, latest_input.next_weapon).presses;
int prev = count_input(latest_previnput.prev_weapon, latest_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--;
}
}
// direct weapon selection
if(latest_input.wanted_weapon)
wanted_weapon = input.wanted_weapon-1;
// check for insane values
if(wanted_weapon >= 0 && wanted_weapon < NUM_WEAPONS && wanted_weapon != active_weapon && weapons[wanted_weapon].got)
queued_weapon = wanted_weapon;
do_weaponswitch();
}
void CHARACTER::fire_weapon()
{
if(reload_timer != 0)
return;
do_weaponswitch();
vec2 direction = normalize(vec2(latest_input.target_x, latest_input.target_y));
bool fullauto = false;
if(active_weapon == WEAPON_GRENADE || active_weapon == WEAPON_SHOTGUN || active_weapon == WEAPON_RIFLE)
fullauto = true;
// check if we gonna fire
bool will_fire = false;
if(count_input(latest_previnput.fire, latest_input.fire).presses) will_fire = true;
if(fullauto && (latest_input.fire&1) && weapons[active_weapon].ammo) will_fire = true;
if(!will_fire)
return;
// check for ammo
if(!weapons[active_weapon].ammo)
{
// 125ms is a magical limit of how fast a human can click
reload_timer = 125 * server_tickspeed() / 1000;;
game.create_sound(pos, SOUND_WEAPON_NOAMMO);
return;
}
vec2 projectile_startpos = pos+direction*phys_size*0.75f;
switch(active_weapon)
{
case WEAPON_HAMMER:
{
// reset objects hit
numobjectshit = 0;
game.create_sound(pos, SOUND_HAMMER_FIRE);
CHARACTER *ents[64];
int hits = 0;
int num = game.world.find_entities(pos+direction*phys_size*0.75f, phys_size*0.5f, (ENTITY**)ents, 64, NETOBJTYPE_CHARACTER);
for (int i = 0; i < num; i++)
{
CHARACTER *target = ents[i];
if (target == this)
continue;
// hit a player, give him damage and stuffs...
vec2 fdir = normalize(ents[i]->pos - pos);
// set his velocity to fast upward (for now)
game.create_hammerhit(pos);
ents[i]->take_damage(vec2(0,-1.0f), data->weapons.hammer.base->damage, player->client_id, active_weapon);
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;
hits++;
}
// if we hit anything, we have to wait for the reload
if(hits)
reload_timer = server_tickspeed()/3;
} break;
case WEAPON_GUN:
{
PROJECTILE *proj = new PROJECTILE(WEAPON_GUN,
player->client_id,
projectile_startpos,
direction,
(int)(server_tickspeed()*tuning.gun_lifetime),
1, 0, 0, -1, WEAPON_GUN);
// pack the projectile and send it to the client directly
NETOBJ_PROJECTILE p;
proj->fill_info(&p);
msg_pack_start(NETMSGTYPE_SV_EXTRAPROJECTILE, 0);
msg_pack_int(1);
for(unsigned i = 0; i < sizeof(NETOBJ_PROJECTILE)/sizeof(int); i++)
msg_pack_int(((int *)&p)[i]);
msg_pack_end();
server_send_msg(player->client_id);
game.create_sound(pos, SOUND_GUN_FIRE);
} break;
case WEAPON_SHOTGUN:
{
int shotspread = 2;
msg_pack_start(NETMSGTYPE_SV_EXTRAPROJECTILE, 0);
msg_pack_int(shotspread*2+1);
for(int i = -shotspread; i <= shotspread; i++)
{
float spreading[] = {-0.185f, -0.070f, 0, 0.070f, 0.185f};
float a = get_angle(direction);
a += spreading[i+2];
float v = 1-(abs(i)/(float)shotspread);
float speed = mix((float)tuning.shotgun_speeddiff, 1.0f, v);
PROJECTILE *proj = new PROJECTILE(WEAPON_SHOTGUN,
player->client_id,
projectile_startpos,
vec2(cosf(a), sinf(a))*speed,
(int)(server_tickspeed()*tuning.shotgun_lifetime),
1, 0, 0, -1, WEAPON_SHOTGUN);
// pack the projectile and send it to the client directly
NETOBJ_PROJECTILE p;
proj->fill_info(&p);
for(unsigned i = 0; i < sizeof(NETOBJ_PROJECTILE)/sizeof(int); i++)
msg_pack_int(((int *)&p)[i]);
}
msg_pack_end();
server_send_msg(player->client_id);
game.create_sound(pos, SOUND_SHOTGUN_FIRE);
} break;
case WEAPON_GRENADE:
{
PROJECTILE *proj = new PROJECTILE(WEAPON_GRENADE,
player->client_id,
projectile_startpos,
direction,
(int)(server_tickspeed()*tuning.grenade_lifetime),
1, PROJECTILE::PROJECTILE_FLAGS_EXPLODE, 0, SOUND_GRENADE_EXPLODE, WEAPON_GRENADE);
// pack the projectile and send it to the client directly
NETOBJ_PROJECTILE p;
proj->fill_info(&p);
msg_pack_start(NETMSGTYPE_SV_EXTRAPROJECTILE, 0);
msg_pack_int(1);
for(unsigned i = 0; i < sizeof(NETOBJ_PROJECTILE)/sizeof(int); i++)
msg_pack_int(((int *)&p)[i]);
msg_pack_end();
server_send_msg(player->client_id);
game.create_sound(pos, SOUND_GRENADE_FIRE);
} break;
case WEAPON_RIFLE:
{
new LASER(pos, direction, tuning.laser_reach, player->client_id);
game.create_sound(pos, SOUND_RIFLE_FIRE);
} break;
case WEAPON_NINJA:
{
attack_tick = server_tick();
ninja.activationdir = direction;
ninja.currentmovetime = data->weapons.ninja.movetime * server_tickspeed() / 1000;
//reload_timer = data->weapons.ninja.base->firedelay * server_tickspeed() / 1000 + server_tick();
// reset hit objects
numobjectshit = 0;
game.create_sound(pos, SOUND_NINJA_FIRE);
} break;
}
if(weapons[active_weapon].ammo > 0) // -1 == unlimited
weapons[active_weapon].ammo--;
attack_tick = server_tick();
if(!reload_timer)
reload_timer = data->weapons.id[active_weapon].firedelay * server_tickspeed() / 1000;
}
int CHARACTER::handle_weapons()
{
vec2 direction = normalize(vec2(latest_input.target_x, latest_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--;
} */
//if(active_weapon == WEAPON_NINJA)
handle_ninja();
// 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();
}*/
// fire weapon, if wanted
fire_weapon();
// ammo regen
int ammoregentime = data->weapons.id[active_weapon].ammoregentime;
if(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) >= ammoregentime * server_tickspeed() / 1000)
{
// Add some ammo
weapons[active_weapon].ammo = min(weapons[active_weapon].ammo + 1, 10);
weapons[active_weapon].ammoregenstart = -1;
}
}
else
{
weapons[active_weapon].ammoregenstart = -1;
}
}
return 0;
}
void CHARACTER::on_predicted_input(NETOBJ_PLAYER_INPUT *new_input)
{
// check for changes
if(mem_comp(&input, new_input, sizeof(NETOBJ_PLAYER_INPUT)) != 0)
last_action = server_tick();
// copy new input
mem_copy(&input, new_input, sizeof(input));
num_inputs++;
// or are not allowed to aim in the center
if(input.target_x == 0 && input.target_y == 0)
input.target_y = -1;
}
void CHARACTER::on_direct_input(NETOBJ_PLAYER_INPUT *new_input)
{
mem_copy(&latest_previnput, &latest_input, sizeof(latest_input));
mem_copy(&latest_input, new_input, sizeof(latest_input));
if(num_inputs > 2 && team != -1)
{
handle_weaponswitch();
fire_weapon();
}
mem_copy(&latest_previnput, &latest_input, sizeof(latest_input));
}
void CHARACTER::tick()
{
if(player->force_balanced)
{
char buf[128];
str_format(buf, sizeof(buf), "You were moved to %s due to team balancing", game.controller->get_team_name(team));
game.send_broadcast(buf, player->client_id);
player->force_balanced = false;
}
//player_core core;
//core.pos = pos;
//core.jumped = jumped;
core.input = input;
core.tick(true);
float phys_size = 28.0f;
// handle death-tiles
if(col_get((int)(pos.x+phys_size/2), (int)(pos.y-phys_size/2))&COLFLAG_DEATH ||
col_get((int)(pos.x+phys_size/2), (int)(pos.y+phys_size/2))&COLFLAG_DEATH ||
col_get((int)(pos.x-phys_size/2), (int)(pos.y-phys_size/2))&COLFLAG_DEATH ||
col_get((int)(pos.x-phys_size/2), (int)(pos.y+phys_size/2))&COLFLAG_DEATH)
{
die(player->client_id, WEAPON_WORLD);
}
// handle weapons
handle_weapons();
player_state = input.player_state;
// Previnput
previnput = input;
return;
}
void CHARACTER::tick_defered()
{
// advance the dummy
{
WORLD_CORE tempworld;
reckoningcore.world = &tempworld;
reckoningcore.tick(false);
reckoningcore.move();
reckoningcore.quantize();
}
//lastsentcore;
/*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(player->client_id);
if(events&COREEVENT_GROUND_JUMP) game.create_sound(pos, SOUND_PLAYER_JUMP, mask);
//if(events&COREEVENT_HOOK_LAUNCH) snd_play_random(CHN_WORLD, SOUND_HOOK_LOOP, 1.0f, pos);
if(events&COREEVENT_HOOK_ATTACH_PLAYER) game.create_sound(pos, SOUND_HOOK_ATTACH_PLAYER, cmask_all());
if(events&COREEVENT_HOOK_ATTACH_GROUND) game.create_sound(pos, SOUND_HOOK_ATTACH_GROUND, mask);
if(events&COREEVENT_HOOK_HIT_NOHOOK) game.create_sound(pos, SOUND_HOOK_NOATTACH, 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;
}
// update the sendcore if needed
{
NETOBJ_CHARACTER predicted;
NETOBJ_CHARACTER current;
mem_zero(&predicted, sizeof(predicted));
mem_zero(&current, sizeof(current));
reckoningcore.write(&predicted);
core.write(&current);
// only allow dead reackoning for a top of 3 seconds
if(reckoning_tick+server_tickspeed()*3 < server_tick() || mem_comp(&predicted, &current, sizeof(NETOBJ_CHARACTER)) != 0)
{
reckoning_tick = server_tick();
sendcore = core;
reckoningcore = core;
}
}
}
bool CHARACTER::increase_health(int amount)
{
if(health >= 10)
return false;
health = clamp(health+amount, 0, 10);
return true;
}
bool CHARACTER::increase_armor(int amount)
{
if(armor >= 10)
return false;
armor = clamp(armor+amount, 0, 10);
return true;
}
void CHARACTER::die(int killer, int weapon)
{
/*if (dead || team == -1)
return;*/
int mode_special = game.controller->on_character_death(this, game.players[killer], weapon);
dbg_msg("game", "kill killer='%d:%s' victim='%d:%s' weapon=%d special=%d",
killer, server_clientname(killer),
player->client_id, server_clientname(player->client_id), weapon, mode_special);
// send the kill message
NETMSG_SV_KILLMSG msg;
msg.killer = killer;
msg.victim = player->client_id;
msg.weapon = weapon;
msg.mode_special = mode_special;
msg.pack(MSGFLAG_VITAL);
server_send_msg(-1);
// a nice sound
game.create_sound(pos, SOUND_PLAYER_DIE);
// set dead state
// TODO: do stuff here
/*
die_pos = pos;
dead = true;
*/
// this is for auto respawn after 3 secs
player->die_tick = server_tick();
alive = false;
game.world.remove_entity(this);
game.world.core.characters[player->client_id] = 0;
game.create_death(pos, player->client_id);
// we got to wait 0.5 secs before respawning
player->respawn_tick = server_tick()+server_tickspeed()/2;
}
bool CHARACTER::take_damage(vec2 force, int dmg, int from, int weapon)
{
core.vel += force;
if(game.controller->is_friendly_fire(player->client_id, from) && !config.sv_teamdamage)
return false;
// player only inflicts half damage on self
if(from == player->client_id)
dmg = max(1, dmg/2);
damage_taken++;
// create healthmod indicator
if(server_tick() < damage_taken_tick+25)
{
// make sure that the damage indicators doesn't group together
game.create_damageind(pos, damage_taken*0.25f, dmg);
}
else
{
damage_taken = 0;
game.create_damageind(pos, 0, dmg);
}
if(dmg)
{
if(armor)
{
if(dmg > 1)
{
health--;
dmg--;
}
if(dmg > armor)
{
dmg -= armor;
armor = 0;
}
else
{
armor -= dmg;
dmg = 0;
}
}
health -= dmg;
}
damage_taken_tick = server_tick();
// do damage hit sound
if(from >= 0 && from != player->client_id && game.players[from])
game.create_sound(game.players[from]->view_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 != player->client_id && game.players[from])
{
CHARACTER *chr = game.players[from]->get_character();
if (chr)
{
chr->emote_type = EMOTE_HAPPY;
chr->emote_stop = server_tick() + server_tickspeed();
}
}
return false;
}
if (dmg > 2)
game.create_sound(pos, SOUND_PLAYER_PAIN_LONG);
else
game.create_sound(pos, SOUND_PLAYER_PAIN_SHORT);
emote_type = EMOTE_PAIN;
emote_stop = server_tick() + 500 * server_tickspeed() / 1000;
// spawn blood?
return true;
}
void CHARACTER::snap(int snapping_client)
{
if(networkclipped(snapping_client))
return;
NETOBJ_CHARACTER *character = (NETOBJ_CHARACTER *)snap_new_item(NETOBJTYPE_CHARACTER, player->client_id, sizeof(NETOBJ_CHARACTER));
// write down the core
if(game.world.paused)
{
// no dead reckoning when paused because the client doesn't know
// how far to perform the reckoning
character->tick = 0;
core.write(character);
}
else
{
character->tick = reckoning_tick;
sendcore.write(character);
}
// set emote
if (emote_stop < server_tick())
{
emote_type = EMOTE_NORMAL;
emote_stop = -1;
}
character->emote = emote_type;
character->ammocount = 0;
character->health = 0;
character->armor = 0;
character->weapon = active_weapon;
character->attacktick = attack_tick;
character->direction = input.direction;
if(player->client_id == snapping_client)
{
character->health = health;
character->armor = armor;
if(weapons[active_weapon].ammo > 0)
character->ammocount = weapons[active_weapon].ammo;
}
if (character->emote == EMOTE_NORMAL)
{
if(250 - ((server_tick() - last_action)%(250)) < 5)
character->emote = EMOTE_BLINK;
}
character->player_state = player_state;
}

View File

@@ -0,0 +1,134 @@
/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */
#ifndef GAME_SERVER_ENTITY_CHARACTER_H
#define GAME_SERVER_ENTITY_CHARACTER_H
#include <game/server/entity.hpp>
#include <game/generated/gs_data.hpp>
#include <game/generated/g_protocol.hpp>
#include <game/gamecore.hpp>
enum
{
WEAPON_GAME = -3, // team switching etc
WEAPON_SELF = -2, // console kill command
WEAPON_WORLD = -1, // death tiles etc
};
class CHARACTER : public ENTITY
{
MACRO_ALLOC_POOL_ID()
public:
// player controlling this character
class PLAYER *player;
bool alive;
// weapon info
ENTITY *hitobjects[10];
int numobjectshit;
struct WEAPONSTAT
{
int ammoregenstart;
int ammo;
int ammocost;
bool got;
} weapons[NUM_WEAPONS];
int active_weapon;
int last_weapon;
int queued_weapon;
int reload_timer;
int attack_tick;
int damage_taken;
int emote_type;
int emote_stop;
// TODO: clean this up
char skin_name[64];
int use_custom_color;
int color_body;
int color_feet;
int last_action; // last tick that the player took any action ie some input
// these are non-heldback inputs
NETOBJ_PLAYER_INPUT latest_previnput;
NETOBJ_PLAYER_INPUT latest_input;
// input
NETOBJ_PLAYER_INPUT previnput;
NETOBJ_PLAYER_INPUT input;
int num_inputs;
int jumped;
int damage_taken_tick;
int health;
int armor;
// ninja
struct
{
vec2 activationdir;
int activationtick;
int currentmovetime;
} ninja;
//
//int score;
int team;
int player_state; // if the client is chatting, accessing a menu or so
// the player core for the physics
CHARACTER_CORE core;
// info for dead reckoning
int reckoning_tick; // tick that we are performing dead reckoning from
CHARACTER_CORE sendcore; // core that we should send
CHARACTER_CORE reckoningcore; // the dead reckoning core
//
CHARACTER();
virtual void reset();
virtual void destroy();
bool is_grounded();
void set_weapon(int w);
void handle_weaponswitch();
void do_weaponswitch();
int handle_weapons();
int handle_ninja();
void on_predicted_input(NETOBJ_PLAYER_INPUT *new_input);
void on_direct_input(NETOBJ_PLAYER_INPUT *new_input);
void fire_weapon();
void die(int killer, int weapon);
bool take_damage(vec2 force, int dmg, int from, int weapon);
bool spawn(PLAYER *player, vec2 pos, int team);
//bool init_tryspawn(int team);
bool remove();
static const int phys_size = 28;
virtual void tick();
virtual void tick_defered();
virtual void snap(int snapping_client);
bool increase_health(int amount);
bool increase_armor(int amount);
};
#endif

View File

@@ -0,0 +1,115 @@
/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */
#include <engine/e_server_interface.h>
#include <game/generated/g_protocol.hpp>
#include <game/server/gamecontext.hpp>
#include "laser.hpp"
//////////////////////////////////////////////////
// laser
//////////////////////////////////////////////////
LASER::LASER(vec2 pos, vec2 direction, float start_energy, int owner)
: ENTITY(NETOBJTYPE_LASER)
{
this->pos = pos;
this->owner = owner;
energy = start_energy;
dir = direction;
bounces = 0;
do_bounce();
game.world.insert_entity(this);
}
bool LASER::hit_character(vec2 from, vec2 to)
{
vec2 at;
CHARACTER *owner_char = game.get_player_char(owner);
CHARACTER *hit = game.world.intersect_character(pos, to, 0.0f, at, owner_char);
if(!hit)
return false;
this->from = from;
pos = at;
energy = -1;
hit->take_damage(vec2(0,0), tuning.laser_damage, owner, WEAPON_RIFLE);
return true;
}
void LASER::do_bounce()
{
eval_tick = server_tick();
if(energy < 0)
{
//dbg_msg("laser", "%d removed", server_tick());
game.world.destroy_entity(this);
return;
}
vec2 to = pos + dir*energy;
vec2 org_to = to;
if(col_intersect_line(pos, to, 0x0, &to))
{
if(!hit_character(pos, to))
{
// intersected
from = pos;
pos = to;
vec2 temp_pos = pos;
vec2 temp_dir = dir*4.0f;
move_point(&temp_pos, &temp_dir, 1.0f, 0);
pos = temp_pos;
dir = normalize(temp_dir);
energy -= distance(from, pos) + tuning.laser_bounce_cost;
bounces++;
if(bounces > tuning.laser_bounce_num)
energy = -1;
game.create_sound(pos, SOUND_RIFLE_BOUNCE);
}
}
else
{
if(!hit_character(pos, to))
{
from = pos;
pos = to;
energy = -1;
}
}
//dbg_msg("laser", "%d done %f %f %f %f", server_tick(), from.x, from.y, pos.x, pos.y);
}
void LASER::reset()
{
game.world.destroy_entity(this);
}
void LASER::tick()
{
if(server_tick() > eval_tick+(server_tickspeed()*tuning.laser_bounce_delay)/1000.0f)
{
do_bounce();
}
}
void LASER::snap(int snapping_client)
{
if(networkclipped(snapping_client))
return;
NETOBJ_LASER *obj = (NETOBJ_LASER *)snap_new_item(NETOBJTYPE_LASER, id, sizeof(NETOBJ_LASER));
obj->x = (int)pos.x;
obj->y = (int)pos.y;
obj->from_x = (int)from.x;
obj->from_y = (int)from.y;
obj->start_tick = eval_tick;
}

View File

@@ -0,0 +1,31 @@
/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */
#ifndef GAME_SERVER_ENTITY_LASER_H
#define GAME_SERVER_ENTITY_LASER_H
#include <game/server/entity.hpp>
class CHARACTER;
class LASER : public ENTITY
{
vec2 from;
vec2 dir;
float energy;
int bounces;
int eval_tick;
int owner;
bool hit_character(vec2 from, vec2 to);
void do_bounce();
public:
LASER(vec2 pos, vec2 direction, float start_energy, int owner);
virtual void reset();
virtual void tick();
virtual void snap(int snapping_client);
};
#endif

View File

@@ -0,0 +1,144 @@
#include <engine/e_server_interface.h>
#include <game/generated/g_protocol.hpp>
#include <game/server/gamecontext.hpp>
#include "pickup.hpp"
//////////////////////////////////////////////////
// pickup
//////////////////////////////////////////////////
PICKUP::PICKUP(int _type, int _subtype)
: ENTITY(NETOBJTYPE_PICKUP)
{
type = _type;
subtype = _subtype;
proximity_radius = phys_size;
reset();
// TODO: should this be done here?
game.world.insert_entity(this);
}
void PICKUP::reset()
{
if (data->pickups[type].spawndelay > 0)
spawntick = server_tick() + server_tickspeed() * data->pickups[type].spawndelay;
else
spawntick = -1;
}
void PICKUP::tick()
{
// wait for respawn
if(spawntick > 0)
{
if(server_tick() > spawntick)
{
// respawn
spawntick = -1;
if(type == POWERUP_WEAPON)
game.create_sound(pos, SOUND_WEAPON_SPAWN);
}
else
return;
}
// Check if a player intersected us
CHARACTER *chr = game.world.closest_character(pos, 20.0f, 0);
if(chr && chr->alive)
{
// player picked us up, is someone was hooking us, let them go
int respawntime = -1;
switch (type)
{
case POWERUP_HEALTH:
if(chr->increase_health(1))
{
game.create_sound(pos, SOUND_PICKUP_HEALTH);
respawntime = data->pickups[type].respawntime;
}
break;
case POWERUP_ARMOR:
if(chr->increase_armor(1))
{
game.create_sound(pos, SOUND_PICKUP_ARMOR);
respawntime = data->pickups[type].respawntime;
}
break;
case POWERUP_WEAPON:
if(subtype >= 0 && subtype < NUM_WEAPONS)
{
if(chr->weapons[subtype].ammo < data->weapons.id[subtype].maxammo || !chr->weapons[subtype].got)
{
chr->weapons[subtype].got = true;
chr->weapons[subtype].ammo = min(data->weapons.id[subtype].maxammo, chr->weapons[subtype].ammo + 10);
respawntime = data->pickups[type].respawntime;
// TODO: data compiler should take care of stuff like this
if(subtype == WEAPON_GRENADE)
game.create_sound(pos, SOUND_PICKUP_GRENADE);
else if(subtype == WEAPON_SHOTGUN)
game.create_sound(pos, SOUND_PICKUP_SHOTGUN);
else if(subtype == WEAPON_RIFLE)
game.create_sound(pos, SOUND_PICKUP_SHOTGUN);
if(chr->player)
game.send_weapon_pickup(chr->player->client_id, subtype);
}
}
break;
case POWERUP_NINJA:
{
// activate ninja on target player
chr->ninja.activationtick = server_tick();
chr->weapons[WEAPON_NINJA].got = true;
chr->weapons[WEAPON_NINJA].ammo = -1;
chr->last_weapon = chr->active_weapon;
chr->active_weapon = WEAPON_NINJA;
respawntime = data->pickups[type].respawntime;
game.create_sound(pos, SOUND_PICKUP_NINJA);
// loop through all players, setting their emotes
ENTITY *ents[64];
int num = game.world.find_entities(vec2(0, 0), 1000000, ents, 64, NETOBJTYPE_CHARACTER);
for (int i = 0; i < num; i++)
{
CHARACTER *c = (CHARACTER *)ents[i];
if (c != chr)
{
c->emote_type = EMOTE_SURPRISE;
c->emote_stop = server_tick() + server_tickspeed();
}
}
chr->emote_type = EMOTE_ANGRY;
chr->emote_stop = server_tick() + 1200 * server_tickspeed() / 1000;
break;
}
default:
break;
};
if(respawntime >= 0)
{
dbg_msg("game", "pickup player='%d:%s' item=%d/%d",
chr->player->client_id, server_clientname(chr->player->client_id), type, subtype);
spawntick = server_tick() + server_tickspeed() * respawntime;
}
}
}
void PICKUP::snap(int snapping_client)
{
if(spawntick != -1)
return;
NETOBJ_PICKUP *up = (NETOBJ_PICKUP *)snap_new_item(NETOBJTYPE_PICKUP, id, sizeof(NETOBJ_PICKUP));
up->x = (int)pos.x;
up->y = (int)pos.y;
up->type = type; // TODO: two diffrent types? what gives?
up->subtype = subtype;
}

View File

@@ -0,0 +1,24 @@
/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */
#ifndef GAME_SERVER_ENTITY_PICKUP_H
#define GAME_SERVER_ENTITY_PICKUP_H
#include <game/server/entity.hpp>
// TODO: move to seperate file
class PICKUP : public ENTITY
{
public:
static const int phys_size = 14;
int type;
int subtype; // weapon type for instance?
int spawntick;
PICKUP(int _type, int _subtype = 0);
virtual void reset();
virtual void tick();
virtual void snap(int snapping_client);
};
#endif

View File

@@ -0,0 +1,105 @@
#include <engine/e_server_interface.h>
#include <game/generated/g_protocol.hpp>
#include <game/server/gamecontext.hpp>
#include "projectile.hpp"
//////////////////////////////////////////////////
// projectile
//////////////////////////////////////////////////
PROJECTILE::PROJECTILE(int type, int owner, vec2 pos, vec2 dir, int span,
int damage, int flags, float force, int sound_impact, int weapon)
: ENTITY(NETOBJTYPE_PROJECTILE)
{
this->type = type;
this->pos = pos;
this->direction = dir;
this->lifespan = span;
this->owner = owner;
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();
game.world.insert_entity(this);
}
void PROJECTILE::reset()
{
game.world.destroy_entity(this);
}
vec2 PROJECTILE::get_pos(float time)
{
float curvature = 0;
float speed = 0;
if(type == WEAPON_GRENADE)
{
curvature = tuning.grenade_curvature;
speed = tuning.grenade_speed;
}
else if(type == WEAPON_SHOTGUN)
{
curvature = tuning.shotgun_curvature;
speed = tuning.shotgun_speed;
}
else if(type == WEAPON_GUN)
{
curvature = tuning.gun_curvature;
speed = tuning.gun_speed;
}
return calc_pos(pos, direction, curvature, speed, time);
}
void PROJECTILE::tick()
{
float pt = (server_tick()-start_tick-1)/(float)server_tickspeed();
float ct = (server_tick()-start_tick)/(float)server_tickspeed();
vec2 prevpos = get_pos(pt);
vec2 curpos = get_pos(ct);
lifespan--;
int collide = col_intersect_line(prevpos, curpos, &curpos, 0);
//int collide = col_check_point((int)curpos.x, (int)curpos.y);
CHARACTER *ownerchar = game.get_player_char(owner);
CHARACTER *targetchr = game.world.intersect_character(prevpos, curpos, 6.0f, curpos, ownerchar);
if(targetchr || collide || lifespan < 0)
{
if(lifespan >= 0 || weapon == WEAPON_GRENADE)
game.create_sound(curpos, sound_impact);
if(flags & PROJECTILE_FLAGS_EXPLODE)
game.create_explosion(curpos, owner, weapon, false);
else if(targetchr)
targetchr->take_damage(direction * max(0.001f, force), damage, owner, weapon);
game.world.destroy_entity(this);
}
}
void PROJECTILE::fill_info(NETOBJ_PROJECTILE *proj)
{
proj->x = (int)pos.x;
proj->y = (int)pos.y;
proj->vx = (int)(direction.x*100.0f);
proj->vy = (int)(direction.y*100.0f);
proj->start_tick = start_tick;
proj->type = type;
}
void PROJECTILE::snap(int snapping_client)
{
float ct = (server_tick()-start_tick)/(float)server_tickspeed();
if(networkclipped(snapping_client, get_pos(ct)))
return;
NETOBJ_PROJECTILE *proj = (NETOBJ_PROJECTILE *)snap_new_item(NETOBJTYPE_PROJECTILE, id, sizeof(NETOBJ_PROJECTILE));
fill_info(proj);
}

View File

@@ -0,0 +1,37 @@
/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */
#ifndef GAME_SERVER_ENTITY_PROJECTILE_H
#define GAME_SERVER_ENTITY_PROJECTILE_H
class PROJECTILE : public ENTITY
{
public:
enum
{
PROJECTILE_FLAGS_EXPLODE = 1 << 0,
};
vec2 direction;
int lifespan;
int owner;
int type;
int flags;
int damage;
int sound_impact;
int weapon;
int bounce;
float force;
int start_tick;
PROJECTILE(int type, int owner, vec2 pos, vec2 vel, int span,
int damage, int flags, float force, int sound_impact, int weapon);
vec2 get_pos(float time);
void fill_info(NETOBJ_PROJECTILE *proj);
virtual void reset();
virtual void tick();
virtual void snap(int snapping_client);
};
#endif

View File

@@ -0,0 +1,49 @@
#include <engine/e_server_interface.h>
#include "entity.hpp"
#include "gamecontext.hpp"
//////////////////////////////////////////////////
// Entity
//////////////////////////////////////////////////
ENTITY::ENTITY(int objtype)
{
this->objtype = objtype;
pos = vec2(0,0);
proximity_radius = 0;
marked_for_destroy = false;
id = snap_new_id();
next_entity = 0;
prev_entity = 0;
prev_type_entity = 0;
next_type_entity = 0;
}
ENTITY::~ENTITY()
{
game.world.remove_entity(this);
snap_free_id(id);
}
int ENTITY::networkclipped(int snapping_client)
{
return networkclipped(snapping_client, pos);
}
int ENTITY::networkclipped(int snapping_client, vec2 check_pos)
{
if(snapping_client == -1)
return 0;
float dx = game.players[snapping_client]->view_pos.x-check_pos.x;
float dy = game.players[snapping_client]->view_pos.y-check_pos.y;
if(fabs(dx) > 1000.0f || fabs(dy) > 800.0f)
return 1;
if(distance(game.players[snapping_client]->view_pos, check_pos) > 1100.0f)
return 1;
return 0;
}

View File

@@ -0,0 +1,145 @@
#ifndef GAME_SERVER_ENTITY_H
#define GAME_SERVER_ENTITY_H
#include <new>
#include <base/vmath.hpp>
#define MACRO_ALLOC_HEAP() \
public: \
void *operator new(size_t size) \
{ \
void *p = mem_alloc(size, 1); \
/*dbg_msg("", "++ %p %d", p, size);*/ \
mem_zero(p, size); \
return p; \
} \
void operator delete(void *p) \
{ \
/*dbg_msg("", "-- %p", p);*/ \
mem_free(p); \
} \
private:
#define MACRO_ALLOC_POOL_ID() \
public: \
void *operator new(size_t size, int id); \
void operator delete(void *p); \
private:
#define MACRO_ALLOC_POOL_ID_IMPL(POOLTYPE, poolsize) \
static char pool_data_##POOLTYPE[poolsize][sizeof(POOLTYPE)] = {{0}}; \
static int pool_used_##POOLTYPE[poolsize] = {0}; \
void *POOLTYPE::operator new(size_t size, int id) \
{ \
dbg_assert(sizeof(POOLTYPE) == size, "size error"); \
dbg_assert(!pool_used_##POOLTYPE[id], "already used"); \
/*dbg_msg("pool", "++ %s %d", #POOLTYPE, id);*/ \
pool_used_##POOLTYPE[id] = 1; \
mem_zero(pool_data_##POOLTYPE[id], size); \
return pool_data_##POOLTYPE[id]; \
} \
void POOLTYPE::operator delete(void *p) \
{ \
int id = (POOLTYPE*)p - (POOLTYPE*)pool_data_##POOLTYPE; \
dbg_assert(pool_used_##POOLTYPE[id], "not used"); \
/*dbg_msg("pool", "-- %s %d", #POOLTYPE, id);*/ \
pool_used_##POOLTYPE[id] = 0; \
mem_zero(pool_data_##POOLTYPE[id], sizeof(POOLTYPE)); \
}
/*
Class: Entity
Basic entity class.
*/
class ENTITY
{
MACRO_ALLOC_HEAP()
private:
friend class GAMEWORLD; // thy these?
ENTITY *prev_entity;
ENTITY *next_entity;
ENTITY *prev_type_entity;
ENTITY *next_type_entity;
protected:
bool marked_for_destroy;
int id;
int objtype;
public:
ENTITY(int objtype);
virtual ~ENTITY();
ENTITY *typenext() { return next_type_entity; }
ENTITY *typeprev() { return prev_type_entity; }
/*
Function: destroy
Destorys the entity.
*/
virtual void destroy() { delete this; }
/*
Function: reset
Called when the game resets the map. Puts the entity
back to it's starting state or perhaps destroys it.
*/
virtual void reset() {}
/*
Function: tick
Called progress the entity to the next tick. Updates
and moves the entity to it's new state and position.
*/
virtual void tick() {}
/*
Function: tick_defered
Called after all entities tick() function has been called.
*/
virtual void tick_defered() {}
/*
Function: snap
Called when a new snapshot is being generated for a specific
client.
Arguments:
snapping_client - ID of the client which snapshot is
being generated. Could be -1 to create a complete
snapshot of everything in the game for demo
recording.
*/
virtual void snap(int snapping_client) {}
/*
Function: networkclipped(int snapping_client)
Performs a series of test to see if a client can see the
entity.
Arguments:
snapping_client - ID of the client which snapshot is
being generated. Could be -1 to create a complete
snapshot of everything in the game for demo
recording.
Returns:
Non-zero if the entity doesn't have to be in the snapshot.
*/
int networkclipped(int snapping_client);
int networkclipped(int snapping_client, vec2 check_pos);
/*
Variable: proximity_radius
Contains the physical size of the entity.
*/
float proximity_radius;
/*
Variable: pos
Contains the current posititon of the entity.
*/
vec2 pos;
};
#endif

View File

@@ -0,0 +1,49 @@
#include "eventhandler.hpp"
#include "gamecontext.hpp"
//////////////////////////////////////////////////
// Event handler
//////////////////////////////////////////////////
EVENTHANDLER::EVENTHANDLER()
{
clear();
}
void *EVENTHANDLER::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 EVENTHANDLER::clear()
{
num_events = 0;
current_offset = 0;
}
void EVENTHANDLER::snap(int snapping_client)
{
for(int i = 0; i < num_events; i++)
{
if(snapping_client == -1 || cmask_is_set(client_masks[i], snapping_client))
{
NETEVENT_COMMON *ev = (NETEVENT_COMMON *)&data[offsets[i]];
if(snapping_client == -1 || distance(game.players[snapping_client]->view_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]);
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More