diff --git a/src/engine/e_config.c b/src/engine/e_config.c index a4c47ec1b..44008b776 100644 --- a/src/engine/e_config.c +++ b/src/engine/e_config.c @@ -7,7 +7,6 @@ #include "e_system.h" #include "e_config.h" - /* buffered stream for reading lines, should perhaps be something smaller */ typedef struct { diff --git a/src/engine/e_config.h b/src/engine/e_config.h index 63933660a..b6fbc93d6 100644 --- a/src/engine/e_config.h +++ b/src/engine/e_config.h @@ -17,11 +17,17 @@ typedef struct extern CONFIGURATION config; +void config_init(); void config_set(const char *line); void config_reset(); void config_load(const char *filename); void config_save(const char *filename); +typedef int (*config_int_getter)(CONFIGURATION *c); +typedef const char *(*config_str_getter)(CONFIGURATION *c); +typedef void (*config_int_setter)(CONFIGURATION *c, int val); +typedef void (*config_str_setter)(CONFIGURATION *c, const char *str); + #define MACRO_CONFIG_INT(name,def,min,max) int config_get_ ## name (CONFIGURATION *c); #define MACRO_CONFIG_STR(name,len,def) const char *config_get_ ## name (CONFIGURATION *c); #include "e_config_variables.h" diff --git a/src/engine/e_console.c b/src/engine/e_console.c new file mode 100644 index 000000000..edcd90fd0 --- /dev/null +++ b/src/engine/e_console.c @@ -0,0 +1,368 @@ +#include "e_console.h" +#include "e_config.h" +#include +#include +#include + +enum +{ + STATE_START, + STATE_INT, + STATE_FLOAT, + STATE_POT_FLOAT, + STATE_STRING, + STATE_QUOTED, + STATE_ESCAPE +}; + +static const char *store_string(struct lexer_result *res, const char *str, int len) +{ + const char *ptr = res->next_string; + + memcpy(res->next_string, str, len); + res->next_string[len] = 0; + res->next_string += len+1; + + return ptr; +} + +static void save_token(struct lexer_result *res, int *index, const char **start, const char *end, int *state, int type) +{ + /* printf("Saving token with length %d\n", end - *start); */ + struct token *tok = &res->tokens[*index]; + tok->stored_string = store_string(res, *start, end - *start); + tok->type = type; + ++res->num_tokens; + + *start = end + 1; + *state = STATE_START; + ++*index; +} + +int digit(char c) +{ + return '0' <= c && c <= '9'; +} + +int lex(const char *line, struct lexer_result *res) +{ + int state = STATE_START, i = 0; + int length_left = CONSOLE_MAX_STR_LENGTH; + const char *start, *c; + res->num_tokens = 0; + + memset(res, 0, sizeof(*res)); + res->next_string = res->string_storage; + + for (c = start = line; *c != '\0' && res->num_tokens < MAX_TOKENS && length_left; ++c, --length_left) + { + /* printf("State: %d\n", state); */ + switch (state) + { + case STATE_START: + if (*c == ' ') + start = c + 1; + else if (digit(*c)) + state = STATE_INT; + else if (*c == '.') + state = STATE_POT_FLOAT; + else + state = STATE_STRING; + break; + + case STATE_INT: + if (digit(*c)) + ; + else if (*c == '.') + state = STATE_FLOAT; + else if (*c == ' ') + save_token(res, &i, &start, c, &state, TOKEN_INT); + else + state = STATE_STRING; + break; + + case STATE_FLOAT: + if (digit(*c)) + ; + else if (*c == ' ') + save_token(res, &i, &start, c, &state, TOKEN_FLOAT); + else + state = STATE_STRING; + break; + + case STATE_POT_FLOAT: + if (digit(*c)) + state = STATE_FLOAT; + else if (*c == ' ') + save_token(res, &i, &start, c, &state, TOKEN_STRING); + else + state = STATE_STRING; + break; + + case STATE_STRING: + if (*c == ' ') + save_token(res, &i, &start, c, &state, TOKEN_STRING); + break; + + case STATE_QUOTED: + if (*c == '"') + save_token(res, &i, &start, c, &state, TOKEN_STRING); + else if (*c == '\\') + state = STATE_ESCAPE; + break; + + case STATE_ESCAPE: + if (*c != ' ') + state = STATE_QUOTED; + break; + } + } + + switch (state) + { + case STATE_INT: + save_token(res, &i, &start, c, &state, TOKEN_INT); + break; + case STATE_FLOAT: + save_token(res, &i, &start, c, &state, TOKEN_FLOAT); + break; + case STATE_STRING: + case STATE_QUOTED: + case STATE_POT_FLOAT: + save_token(res, &i, &start, c, &state, TOKEN_STRING); + break; + case STATE_ESCAPE: + puts("LOL MALFORMED"); + break; + default: + break; + } + + return 0; +} + +int extract_result_string(struct lexer_result *result, int index, const char **str) +{ + if (index < 0 || index >= result->num_tokens) + return -1; + else + { + struct token *t = &result->tokens[index]; + + *str = t->stored_string; + + return 0; + } +} + +int extract_result_int(struct lexer_result *result, int index, int *i) +{ + if (index < 0 || index >= result->num_tokens) + return -1; + else + { + struct token *t = &result->tokens[index]; + const char *str; + + if (t->type != TOKEN_INT) + return -2; + + extract_result_string(result, index, &str); + + *i = atoi(str); + + return 0; + } +} + +int extract_result_float(struct lexer_result *result, int index, float *f) +{ + if (index < 0 || index >= result->num_tokens) + return -1; + else + { + struct token *t = &result->tokens[index]; + const char *str; + + if (t->type != TOKEN_INT && t->type != TOKEN_FLOAT) + return -2; + + extract_result_string(result, index, &str); + + *f = atof(str); + + return 0; + } +} + +static COMMAND *first_command = 0x0; + +COMMAND *console_find_command(const char *name) +{ + COMMAND *cmd; + for (cmd = first_command; cmd; cmd = cmd->next) + if (strcmp(cmd->name, name) == 0) + return cmd; + + return 0x0; +} + +void console_register(COMMAND *cmd) +{ + cmd->next = first_command; + first_command = cmd; +} + + +static int console_validate(COMMAND *command, struct lexer_result *result) +{ + const char *c = command->params; + int i = 1; + + const char *dummy_s; + int dummy_i; + float dummy_f; + + while (*c && *c != '?') + { + switch (*c) + { + case 's': + if (extract_result_string(result, i, &dummy_s)) + return -1; + break; + case 'i': + if (extract_result_int(result, i, &dummy_i)) + return -1; + break; + case 'f': + if (extract_result_float(result, i, &dummy_f)) + return -1; + break; + default: + // unknown char, so just continue... + c++; + continue; + } + + i++; + c++; + } + + return 0; +} + +static void (*print_callback)(const char *) = 0x0; + +void console_register_print_callback(void (*callback)(const char *)) +{ + print_callback = callback; +} + +void console_print(const char *str) +{ + if (print_callback) + print_callback(str); +} + +void console_execute(const char *str) +{ + struct lexer_result result; + int error; + + if ((error = lex(str, &result))) + printf("ERROR: %d\n", error); + else if (result.num_tokens > 0) + { + const char *name; + extract_result_string(&result, 0, &name); + + COMMAND *command = console_find_command(name); + + if (command) + { + if (console_validate(command, &result)) + { + char buf[256]; + sprintf(buf, "Invalid arguments... Usage: %s %s", command->name, command->params); + console_print(buf); + } + else + command->callback(&result, command->user_data); + } + else + { + char buf[256]; + sprintf(buf, "No such command: %s.", name); + console_print(buf); + } + } +} + +static void echo_command(struct lexer_result *result, void *user_data) +{ + const char *str; + extract_result_string(result, 1, &str); + + console_print(str); +} + + +struct int_variable_data +{ + config_int_getter getter; + config_int_setter setter; +}; + +struct str_variable_data +{ + config_str_getter getter; + config_str_setter setter; +}; + +static void int_variable_command(struct lexer_result *result, void *user_data) +{ + struct int_variable_data *data = (struct int_variable_data *)user_data; + int new_val; + + if (extract_result_int(result, 1, &new_val)) + { + char buf[256]; + sprintf(buf, "Value: %d", data->getter(&config)); + console_print(buf); + } + else + { + data->setter(&config, new_val); + } +} + +static void str_variable_command(struct lexer_result *result, void *user_data) +{ + struct str_variable_data *data = (struct str_variable_data *)user_data; + const char *new_val; + + if (extract_result_string(result, 1, &new_val)) + { + char buf[256]; + sprintf(buf, "Value: %s", data->getter(&config)); + console_print(buf); + } + else + { + data->setter(&config, new_val); + } +} + +void console_init() +{ + MACRO_REGISTER_COMMAND("echo", "s", echo_command, 0x0); + + #define MACRO_CONFIG_INT(name,def,min,max) { static struct int_variable_data data = { &config_get_ ## name, &config_set_ ## name }; MACRO_REGISTER_COMMAND(#name, "?i", int_variable_command, &data) } + #define MACRO_CONFIG_STR(name,len,def) { static struct str_variable_data data = { &config_get_ ## name, &config_set_ ## name }; MACRO_REGISTER_COMMAND(#name, "?s", str_variable_command, &data) } + + #include "e_config_variables.h" + + #undef MACRO_CONFIG_INT + #undef MACRO_CONFIG_STR +} diff --git a/src/engine/e_console.h b/src/engine/e_console.h new file mode 100644 index 000000000..c383e8b52 --- /dev/null +++ b/src/engine/e_console.h @@ -0,0 +1,64 @@ +#ifndef _CONSOLE_H +#define _CONSOLE_H + +#ifdef __cplusplus +extern "C"{ +#endif + +#define CONSOLE_MAX_STR_LENGTH 255 +/* the maximum number of tokens occurs in a string of length CONSOLE_MAX_STR_LENGTH with tokens size 1 separated by single spaces */ +#define MAX_TOKENS (CONSOLE_MAX_STR_LENGTH+1)/2 + +enum +{ + TOKEN_INT, + TOKEN_FLOAT, + TOKEN_STRING +}; + +struct token +{ + int type; + const char *stored_string; +}; + +struct lexer_result +{ + char string_storage[CONSOLE_MAX_STR_LENGTH+1]; + char *next_string; + + struct token tokens[MAX_TOKENS]; + unsigned int num_tokens; +}; + +int lex(const char *line, struct lexer_result *result); + +int extract_result_string(struct lexer_result *result, int index, const char **str); +int extract_result_int(struct lexer_result *result, int index, int *i); +int extract_result_float(struct lexer_result *result, int index, float *f); + +typedef void (*console_callback)(struct lexer_result *result, void *user_data); + +typedef struct COMMAND +{ + const char *name; + const char *params; + console_callback callback; + void *user_data; + struct COMMAND *next; + +} COMMAND; + +void console_init(); +void console_register(COMMAND *cmd); +void console_execute(const char *str); +void console_print(const char *str); +void console_register_print_callback(void (*callback)(const char *)); + +#define MACRO_REGISTER_COMMAND(name, params, func, ptr) { static COMMAND cmd = { name, params, func, ptr, 0x0 }; console_register(&cmd); } + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/engine/e_engine.c b/src/engine/e_engine.c index dc6484d62..06b82f7f5 100644 --- a/src/engine/e_engine.c +++ b/src/engine/e_engine.c @@ -5,6 +5,7 @@ #include #include #include +#include static char application_save_path[512] = {0}; @@ -39,6 +40,9 @@ void engine_init(const char *appname, int argc, char **argv) fs_makedir(path); } } + + /* init console */ + console_init(); /* reset the config */ config_reset(); diff --git a/src/engine/e_system.c b/src/engine/e_system.c index bf445c9c2..e9c96fa36 100644 --- a/src/engine/e_system.c +++ b/src/engine/e_system.c @@ -8,6 +8,7 @@ #include "e_detect.h" #include "e_system.h" +#include "e_console.h" #if defined(CONF_FAMILY_UNIX) #include @@ -88,6 +89,18 @@ void dbg_msg(const char *sys, const char *fmt, ...) va_end(args); printf("\n"); fflush(stdout); + + { + char str[2048]; + + sprintf(str, "[%s]: ", sys); + + va_start(args, fmt); + vsprintf(str+strlen(str), fmt, args); + va_end(args); + + console_print(str); + } } int memory_alloced = 0; diff --git a/src/game/client/gc_client.cpp b/src/game/client/gc_client.cpp index e460abe0c..d72efe8e1 100644 --- a/src/game/client/gc_client.cpp +++ b/src/game/client/gc_client.cpp @@ -20,6 +20,7 @@ extern "C" { #include "gc_client.h" #include "gc_render.h" #include "gc_anim.h" +#include "gc_console.h" struct data_container *data = 0; static int64 debug_firedelay = 0; diff --git a/src/game/client/gc_console.cpp b/src/game/client/gc_console.cpp new file mode 100644 index 000000000..277cb675e --- /dev/null +++ b/src/game/client/gc_console.cpp @@ -0,0 +1,136 @@ +#include "gc_console.h" + +extern "C" { + #include + #include + #include + #include + #include +} + +#include +#include + +#include "gc_ui.h" + +static unsigned int console_input_len = 0; +static char console_input[256] = {0}; +static int active = 0; + +static char backlog[256][256] = {0}; +static int backlog_len; + +static void client_console_print(const char *str) +{ + int len = strlen(str); + + if (len > 255) + len = 255; + + if (backlog_len >= 256) + { + puts("console backlog full"); + } + + memcpy(backlog[backlog_len], str, len); + backlog[backlog_len][len] = 0; + + backlog_len++; + + //dbg_msg("console", "FROM CLIENT!! %s", str); +} + +void client_console_init() +{ + console_register_print_callback(client_console_print); +} + +void console_handle_input() +{ + for(int i = 0; i < inp_num_events(); i++) + { + INPUTEVENT e = inp_get_event(i); + + if (e.key == KEY_F3) + { + console_toggle(); + } + + if (active) + { + if (!(e.ch >= 0 && e.ch < 32)) + { + if (console_input_len < sizeof(console_input) - 1) + { + console_input[console_input_len] = e.ch; + console_input[console_input_len+1] = 0; + console_input_len++; + } + } + + if(e.key == KEY_BACKSPACE) + { + if(console_input_len > 0) + { + console_input[console_input_len-1] = 0; + console_input_len--; + } + } + else if(e.key == KEY_ENTER) + { + console_execute(console_input); + console_input[0] = 0; + console_input_len = 0; + } + } + } + + if (active) + inp_clear_events(); +} + +void console_toggle() +{ + active ^= 1; +} + +void console_render() +{ + RECT screen = *ui_screen(); + float console_height = screen.h*3/5.0f; + gfx_mapscreen(screen.x, screen.y, screen.w, screen.h); + + gfx_texture_set(-1); + gfx_quads_begin(); + gfx_setcolor(0.4,0.2,0.2,0.8); + gfx_quads_drawTL(0,0,screen.w,console_height); + gfx_quads_end(); + + { + float font_size = 12.0f; + float row_spacing = font_size*1.4f; + float width = gfx_text_width(0, 12, console_input, -1); + float x = 3, y = console_height - row_spacing - 2; + int backlog_index = backlog_len-1; + + gfx_text(0, x, y, font_size, console_input, -1); + gfx_text(0, x+width+1, y, font_size, "_", -1); + + y -= row_spacing; + + while (y > 0.0f && backlog_index >= 0) + { + const char *line = backlog[backlog_index]; + gfx_text(0, x, y, font_size, line, -1); + + backlog_index--; + y -= row_spacing; + } + } +} + +int console_active() +{ + return active; +} + diff --git a/src/game/client/gc_console.h b/src/game/client/gc_console.h new file mode 100644 index 000000000..87628a2ae --- /dev/null +++ b/src/game/client/gc_console.h @@ -0,0 +1,10 @@ +#ifndef _GC_CONSOLE_H +#define _GC_CONSOLE_H + +void console_handle_input(); +void console_toggle(); +void console_render(); +int console_active(); +void client_console_init(); + +#endif diff --git a/src/game/client/gc_hooks.cpp b/src/game/client/gc_hooks.cpp index a58b554a6..d70e84a59 100644 --- a/src/game/client/gc_hooks.cpp +++ b/src/game/client/gc_hooks.cpp @@ -17,6 +17,7 @@ extern "C" { #include "gc_skin.h" #include "gc_render.h" #include "gc_map_image.h" +#include "gc_console.h" extern unsigned char internal_data[]; @@ -36,6 +37,8 @@ extern "C" void modc_init() gfx_text_set_default_font(&default_font); menu_init(); + + client_console_init(); // setup sound channels snd_set_channel(CHN_GUI, 1.0f, 0.0f); @@ -288,10 +291,10 @@ extern "C" void modc_newsnapshot() client_datas[i].update_render_info(); } - - extern "C" void modc_render() { + console_handle_input(); + // this should be moved around abit if(client_state() == CLIENTSTATE_ONLINE) { @@ -310,6 +313,11 @@ extern "C" void modc_render() else // if (client_state() != CLIENTSTATE_CONNECTING && client_state() != CLIENTSTATE_LOADING) { menu_render(); + if (console_active()) + { + console_render(); + return; + } } // diff --git a/src/game/g_variables.h b/src/game/g_variables.h index 6a9fb35dd..08abe3134 100644 --- a/src/game/g_variables.h +++ b/src/game/g_variables.h @@ -23,6 +23,7 @@ MACRO_CONFIG_INT(key_teamchat, 'Y', 32, 512) MACRO_CONFIG_INT(key_console, 256+2, 32, 512) MACRO_CONFIG_INT(key_remoteconsole, 256+3, 32, 512) +MACRO_CONFIG_INT(key_toggleconsole, 256+4, 32, 512) MACRO_CONFIG_INT(dbg_bots, 0, 0, 11)