From 1c1677f02300e5ab10bca9c74ce7f49d4605b9d6 Mon Sep 17 00:00:00 2001 From: Magnus Auvinen Date: Sat, 12 Jan 2008 12:08:26 +0000 Subject: merged 0.3.3 changes over to trunk --- src/game/client/gc_client.cpp | 129 +++++++++++++++++++++++----------- src/game/client/gc_menu.cpp | 159 +++++++++++++++++++++++++++--------------- src/game/g_variables.h | 2 +- src/game/g_version.h | 2 +- src/game/server/gs_common.h | 5 ++ src/game/server/gs_server.cpp | 39 +++++++---- 6 files changed, 223 insertions(+), 113 deletions(-) (limited to 'src/game') diff --git a/src/game/client/gc_client.cpp b/src/game/client/gc_client.cpp index 5598c44f..68c6db00 100644 --- a/src/game/client/gc_client.cpp +++ b/src/game/client/gc_client.cpp @@ -33,6 +33,8 @@ data_container *data = 0x0; static player_input input_data = {0}; static int input_target_lock = 0; +static int64 debug_firedelay = 0; + extern void modmenu_render(); extern void menu_init(); @@ -79,8 +81,25 @@ static struct client_data int emoticon_start; player_core predicted; - tee_render_info skin_info; + tee_render_info skin_info; // this is what the server reports + tee_render_info render_info; // this is what we use + void update_render_info() + { + render_info = skin_info; + + // force team colors + if(gameobj && gameobj->gametype != GAMETYPE_DM) + { + const int team_colors[2] = {65387, 10223467}; + if(team >= 0 || team <= 1) + { + render_info.texture = skin_get(skin_id)->color_texture; + render_info.color_body = skin_get_color(team_colors[team]); + render_info.color_feet = skin_get_color(team_colors[team]); + } + } + } } client_datas[MAX_CLIENTS]; class client_effects @@ -915,6 +934,9 @@ extern "C" void modc_newsnapshot() if(item.type == OBJTYPE_PLAYER_INFO) { const obj_player_info *info = (const obj_player_info *)data; + + client_datas[info->clientid].team = info->team; + if(info->local) { local_info = info; @@ -938,6 +960,9 @@ extern "C" void modc_newsnapshot() } } } + + for(int i = 0; i < MAX_CLIENTS; i++) + client_datas[i].update_render_info(); } void send_info(bool start) @@ -963,8 +988,15 @@ void send_emoticon(int emoticon) client_send_msg(); } -static void render_projectile(const obj_projectile *prev, const obj_projectile *current, int itemid) +static void render_projectile(const obj_projectile *current, int itemid) { + if(debug_firedelay) + { + debug_firedelay = time_get()-debug_firedelay; + dbg_msg("game", "firedelay=%.2f ms", debug_firedelay/(float)time_freq()*1000.0f); + debug_firedelay = 0; + } + gfx_texture_set(data->images[IMAGE_GAME].id); gfx_quads_begin(); @@ -1143,10 +1175,10 @@ void anim_eval_add(animstate *state, animation *anim, float time, float amount) anim_add(state, &add, amount); } -static void render_hand(int skin_id, vec2 center_pos, vec2 dir, float angle_offset, vec2 post_rot_offset) +static void 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); + //const skin *s = skin_get(skin_id); float basesize = 10.0f; //dir = normalize(hook_pos-pos); @@ -1168,8 +1200,9 @@ static void render_hand(int skin_id, vec2 center_pos, vec2 dir, float angle_offs hand_pos += diry * post_rot_offset.y; //gfx_texture_set(data->images[IMAGE_CHAR_DEFAULT].id); - gfx_texture_set(s->color_texture); + 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++) @@ -1382,11 +1415,6 @@ static void render_player( } } - // TODO: proper skin selection - int skin_id = client_datas[info.clientid].skin_id; //charids[info.clientid]; - //if(gametype != GAMETYPE_DM) - //skin_id = info.team*9; // 0 or 9 - vec2 direction = get_direction(player.angle); float angle = player.angle/256.0f; vec2 position = mix(vec2(prev.x, prev.y), vec2(player.x, player.y), intratick); @@ -1463,7 +1491,7 @@ static void render_player( gfx_quads_setrotation(0); gfx_quads_end(); - render_hand(skin_id, position, normalize(hook_pos-pos), -pi/2, vec2(20, 0)); + render_hand(&client_datas[info.clientid].render_info, position, normalize(hook_pos-pos), -pi/2, vec2(20, 0)); } // draw gun @@ -1581,9 +1609,9 @@ static void render_player( switch (player.weapon) { - case WEAPON_GUN: render_hand(skin_id, p, direction, -3*pi/4, vec2(-15, 4)); break; - case WEAPON_SHOTGUN: render_hand(skin_id, p, direction, -pi/2, vec2(-5, 4)); break; - case WEAPON_ROCKET: render_hand(skin_id, p, direction, -pi/2, vec2(-4, 7)); break; + case WEAPON_GUN: render_hand(&client_datas[info.clientid].render_info, p, direction, -3*pi/4, vec2(-15, 4)); break; + case WEAPON_SHOTGUN: render_hand(&client_datas[info.clientid].render_info, p, direction, -pi/2, vec2(-5, 4)); break; + case WEAPON_ROCKET: render_hand(&client_datas[info.clientid].render_info, p, direction, -pi/2, vec2(-4, 7)); break; } } @@ -1592,14 +1620,14 @@ static void render_player( if(info.local && config.debug) { vec2 ghost_position = mix(vec2(prev_char->x, prev_char->y), vec2(player_char->x, player_char->y), client_intratick()); - tee_render_info ghost = client_datas[info.clientid].skin_info; + tee_render_info ghost = client_datas[info.clientid].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 the tee - render_tee(&state, &client_datas[info.clientid].skin_info, player.emote, direction, position); + render_tee(&state, &client_datas[info.clientid].render_info, player.emote, direction, position); if(player.state == STATE_CHATTING) { @@ -2103,7 +2131,7 @@ void render_scoreboard(float x, float y, float w, int team, const char *title) gfx_quads_end(); } - render_tee(&idlestate, &client_datas[info->clientid].skin_info, EMOTE_NORMAL, vec2(1,0), vec2(x+90, y+28)); + render_tee(&idlestate, &client_datas[info->clientid].render_info, EMOTE_NORMAL, vec2(1,0), vec2(x+90, y+28)); y += 50.0f; @@ -2202,9 +2230,9 @@ void render_world(float center_x, float center_y, float zoom) if(item.type == OBJTYPE_PROJECTILE) { - const void *prev = snap_find_item(SNAP_PREV, item.type, item.id); - if(prev) - render_projectile((const obj_projectile *)prev, (const obj_projectile *)data, item.id); + //const void *prev = snap_find_item(SNAP_PREV, item.type, item.id); + //if(prev) + render_projectile((const obj_projectile *)data, item.id); } else if(item.type == OBJTYPE_POWERUP) { @@ -2235,8 +2263,6 @@ void render_world(float center_x, float center_y, float zoom) const void *prev_info = snap_find_item(SNAP_PREV, OBJTYPE_PLAYER_INFO, item.id); const void *info = snap_find_item(SNAP_CURRENT, OBJTYPE_PLAYER_INFO, item.id); - client_datas[((const obj_player_info *)info)->clientid].team = ((const obj_player_info *)info)->team; - if(prev && prev_info && info) { render_player( @@ -2364,28 +2390,29 @@ void render_game() chat_mode = CHATMODE_NONE; } - int c = inp_last_char(); - int k = inp_last_key(); - - if (!(c >= 0 && c < 32)) + for(int i = 0; i < inp_num_events(); i++) { - if (chat_input_len < sizeof(chat_input) - 1) + INPUTEVENT e = inp_get_event(i); + + if (!(e.ch >= 0 && e.ch < 32)) { - chat_input[chat_input_len] = c; - chat_input[chat_input_len+1] = 0; - chat_input_len++; + if (chat_input_len < sizeof(chat_input) - 1) + { + chat_input[chat_input_len] = e.ch; + chat_input[chat_input_len+1] = 0; + chat_input_len++; + } } - } - if(k == KEY_BACKSPACE) - { - if(chat_input_len > 0) + if(e.key == KEY_BACKSPACE) { - chat_input[chat_input_len-1] = 0; - chat_input_len--; + if(chat_input_len > 0) + { + chat_input[chat_input_len-1] = 0; + chat_input_len--; + } } } - } else { @@ -2413,7 +2440,7 @@ void render_game() } if (!menu_active) - inp_clear(); + inp_clear_events(); // fetch new input if(!menu_active && !emoticon_selector_active) @@ -2451,6 +2478,8 @@ void render_game() // update some input if(!menu_active && chat_mode == CHATMODE_NONE) { + bool do_direct = false; + if(!emoticon_selector_active) { if(do_input(&input_data.fire, config.key_fire)) @@ -2460,6 +2489,17 @@ void render_game() input_data.target_x = (int)mouse_pos.x; input_data.target_y = (int)mouse_pos.y; input_target_lock = 1; + + if(inp_key_presses(config.key_fire)) + { + if(config.dbg_firedelay) + { + if(debug_firedelay == 0) + debug_firedelay = time_get(); + } + + do_direct = true; + } } } @@ -2480,6 +2520,9 @@ void render_game() if(inp_key_presses(config.key_weapon5)) input_data.wanted_weapon = 5; if(inp_key_presses(config.key_weapon6)) input_data.wanted_weapon = 6; } + + if(do_direct) // do direct input if wanted + client_direct_input((int *)&input_data, sizeof(input_data)); } @@ -2720,7 +2763,7 @@ void render_game() } } - render_tee(&idlestate, &client_datas[killmsgs[r].victim].skin_info, EMOTE_PAIN, vec2(-1,0), vec2(x, y+28)); + render_tee(&idlestate, &client_datas[killmsgs[r].victim].render_info, EMOTE_PAIN, vec2(-1,0), vec2(x, y+28)); x -= 32.0f; // render weapon @@ -2756,7 +2799,7 @@ void render_game() // render killer tee x -= 24.0f; - render_tee(&idlestate, &client_datas[killmsgs[r].killer].skin_info, EMOTE_ANGRY, vec2(1,0), vec2(x, y+28)); + render_tee(&idlestate, &client_datas[killmsgs[r].killer].render_info, EMOTE_ANGRY, vec2(1,0), vec2(x, y+28)); x -= 32.0f; // render killer name @@ -2913,7 +2956,7 @@ void render_game() const char *name = client_datas[id].name; float w = gfx_pretty_text_width(10, name, -1); gfx_pretty_text(whole-40-5-w, 300-40-15+t*20+2, 10, name, -1); - tee_render_info info = client_datas[id].skin_info; + tee_render_info info = client_datas[id].render_info; info.size = 18.0f; render_tee(&idlestate, &info, EMOTE_NORMAL, vec2(1,0), @@ -3117,6 +3160,8 @@ extern "C" void modc_statechange(int state, int old) menu_do_disconnected(); menu_game_active = false; } + else if(state == CLIENTSTATE_LOADING) + menu_do_connecting(); else if(state == CLIENTSTATE_CONNECTING) menu_do_connecting(); else if (state == CLIENTSTATE_ONLINE) @@ -3171,6 +3216,8 @@ extern "C" void modc_message(int msg) client_datas[cid].skin_info.color_body = vec4(1,1,1,1); client_datas[cid].skin_info.color_feet = vec4(1,1,1,1); } + + client_datas[cid].update_render_info(); } else if(msg == MSG_WEAPON_PICKUP) { diff --git a/src/game/client/gc_menu.cpp b/src/game/client/gc_menu.cpp index 9101fc93..17716bd6 100644 --- a/src/game/client/gc_menu.cpp +++ b/src/game/client/gc_menu.cpp @@ -451,6 +451,7 @@ static void ui2_draw_checkbox(const void *id, const char *text, int checked, con ui2_draw_checkbox_common(id, text, checked?"X":"", r); } + static void ui2_draw_checkbox_number(const void *id, const char *text, int checked, const RECT *r, void *extra) { char buf[16]; @@ -466,8 +467,6 @@ int ui2_do_edit_box(void *id, const RECT *rect, char *str, int str_size, bool hi if(ui_last_active_item() == id) { - int c = inp_last_char(); - int k = inp_last_key(); int len = strlen(str); if (inside && ui_mouse_button(0)) @@ -489,35 +488,42 @@ int ui2_do_edit_box(void *id, const RECT *rect, char *str, int str_size, bool hi if (at_index > len) at_index = len; - - if (!(c >= 0 && c < 32)) + + for(int i = 0; i < inp_num_events(); i++) { - if (len < str_size - 1 && at_index < str_size - 1) + INPUTEVENT e = inp_get_event(i); + char c = e.ch; + int k = e.key; + + if (!(c >= 0 && c < 32)) { - memmove(str + at_index + 1, str + at_index, len - at_index + 1); - str[at_index] = c; - at_index++; + if (len < str_size - 1 && at_index < str_size - 1) + { + memmove(str + at_index + 1, str + at_index, len - at_index + 1); + str[at_index] = c; + at_index++; + } } - } - if (k == KEY_BACKSPACE && at_index > 0) - { - memmove(str + at_index - 1, str + at_index, len - at_index + 1); - at_index--; + if (k == KEY_BACKSPACE && at_index > 0) + { + memmove(str + at_index - 1, str + at_index, len - at_index + 1); + at_index--; + } + else if (k == KEY_DEL && at_index < len) + memmove(str + at_index, str + at_index + 1, len - at_index); + else if (k == KEY_ENTER) + ui_clear_last_active_item(); + else if (k == KEY_LEFT && at_index > 0) + at_index--; + else if (k == KEY_RIGHT && at_index < len) + at_index++; + else if (k == KEY_HOME) + at_index = 0; + else if (k == KEY_END) + at_index = len; } - else if (k == KEY_DEL && at_index < len) - memmove(str + at_index, str + at_index + 1, len - at_index); - else if (k == KEY_ENTER) - ui_clear_last_active_item(); - else if (k == KEY_LEFT && at_index > 0) - at_index--; - else if (k == KEY_RIGHT && at_index < len) - at_index++; - else if (k == KEY_HOME) - at_index = 0; - else if (k == KEY_END) - at_index = len; - + r = 1; } @@ -698,14 +704,17 @@ int ui2_do_key_reader(void *id, const RECT *rect, int key) if(ui_active_item() == id) { - int k = inp_last_key(); - if (k) + for(int i = 0; i < inp_num_events(); i++) { - if(k != KEY_ESC) - new_key = k; - - ui_set_active_item(0); - mouse_released = false; + INPUTEVENT e = inp_get_event(i); + if(e.key && e.key != KEY_ESC) + { + new_key = e.key; + ui_set_active_item(0); + mouse_released = false; + inp_clear_events(); + break; + } } } else if(ui_hot_item() == id) @@ -912,14 +921,15 @@ static void menu2_render_serverbrowser(RECT main_view) RECT server_scoreboard; //ui2_hsplit_t(&view, 20.0f, &status, &view); - ui2_hsplit_b(&view, 90.0f, &view, &filters); + ui2_hsplit_b(&view, 110.0f, &view, &filters); // split off a piece for details and scoreboard ui2_vsplit_r(&view, 200.0f, &view, &server_details); // server list ui2_hsplit_t(&view, 20.0f, &headers, &view); - ui2_hsplit_b(&view, 5.0f, &view, 0x0); + //ui2_hsplit_b(&view, 110.0f, &view, &filters); + ui2_hsplit_b(&view, 5.0f, &view, 0); ui2_hsplit_b(&view, 20.0f, &view, &status); //ui2_vsplit_r(&filters, 300.0f, &filters, &toolbox); @@ -1031,7 +1041,7 @@ static void menu2_render_serverbrowser(RECT main_view) int num = (int)(view.h/cols[0].rect.h); static int scrollbar = 0; static float scrollvalue = 0; - static int selected_index = -1; + //static int selected_index = -1; ui2_hmargin(&scroll, 5.0f, &scroll); scrollvalue = ui2_do_scrollbar_v(&scrollbar, &scroll, scrollvalue); @@ -1059,8 +1069,8 @@ static void menu2_render_serverbrowser(RECT main_view) RECT original_view = view; view.y -= scrollvalue*scrollnum*cols[0].rect.h; - //int r = -1; - int new_selected = selected_index; + int new_selected = -1; + int selected_index = -1; for (int i = 0; i < num_servers; i++) { @@ -1069,10 +1079,12 @@ static void menu2_render_serverbrowser(RECT main_view) RECT row; RECT select_hit_box; - int l = selected_index==item_index; + int selected = strcmp(item->address, config.ui_server_address) == 0; //selected_index==item_index; - if(l) + if(selected) { + selected_index = i; + // selected server, draw the players on it RECT whole; int h = (item->num_players+2)/3; @@ -1129,13 +1141,9 @@ static void menu2_render_serverbrowser(RECT main_view) select_hit_box.y = original_view.y; } - if(ui2_do_button(item, "", l, &select_hit_box, 0, 0)) + if(ui2_do_button(item, "", selected, &select_hit_box, 0, 0)) { new_selected = item_index; - dbg_msg("dbg", "addr = %s", item->address); - strncpy(config.ui_server_address, item->address, sizeof(config.ui_server_address)); - if(inp_mouse_doubleclick()) - client_connect(config.ui_server_address); } } @@ -1197,21 +1205,20 @@ static void menu2_render_serverbrowser(RECT main_view) else if(item->game_type == GAMETYPE_CTF) type = "CTF"; ui2_do_label(&button, type, 15.0f, 0); } - /* - if(s) - { - new_selected = item_index; - dbg_msg("dbg", "addr = %s", item->address); - strncpy(config.ui_server_address, item->address, sizeof(config.ui_server_address)); - }*/ } } ui2_clip_disable(); - selected_index = new_selected; + if(new_selected != -1) + { + // select the new server + SERVER_INFO *item = client_serverbrowse_sorted_get(new_selected); + strncpy(config.ui_server_address, item->address, sizeof(config.ui_server_address)); + if(inp_mouse_doubleclick()) + client_connect(config.ui_server_address); + } - SERVER_INFO *selected_server = client_serverbrowse_sorted_get(selected_index); RECT server_header; @@ -1267,7 +1274,10 @@ static void menu2_render_serverbrowser(RECT main_view) char temp[16]; - sprintf(temp, "%d%%", selected_server->progression); + if(selected_server->progression < 0) + sprintf(temp, "N/A"); + else + sprintf(temp, "%d%%", selected_server->progression); ui2_hsplit_t(&right_column, 15.0f, &row, &right_column); ui2_do_label(&row, temp, 13.0f, -1); @@ -1304,13 +1314,15 @@ static void menu2_render_serverbrowser(RECT main_view) } } - // render quick search RECT button; + RECT types; ui2_hsplit_t(&filters, 20.0f, &button, &filters); ui2_do_label(&button, "Quick search: ", 14.0f, -1); ui2_vsplit_l(&button, 95.0f, 0, &button); ui2_do_edit_box(&config.b_filter_string, &button, config.b_filter_string, sizeof(config.b_filter_string)); + ui2_vsplit_l(&filters, 180.0f, &filters, &types); + // render filters ui2_hsplit_t(&filters, 20.0f, &button, &filters); if (ui2_do_button(&config.b_filter_empty, "Has people playing", config.b_filter_empty, &button, ui2_draw_checkbox, 0)) @@ -1321,9 +1333,36 @@ static void menu2_render_serverbrowser(RECT main_view) config.b_filter_full ^= 1; ui2_hsplit_t(&filters, 20.0f, &button, &filters); - if (ui2_do_button(&config.b_filter_pw, "Is not password protected", config.b_filter_pw, &button, ui2_draw_checkbox, 0)) + if (ui2_do_button(&config.b_filter_pw, "No password", config.b_filter_pw, &button, ui2_draw_checkbox, 0)) config.b_filter_pw ^= 1; + ui2_hsplit_t(&filters, 2.0f, &button, &filters); // ping + ui2_hsplit_t(&filters, 20.0f, &button, &filters); + { + RECT editbox; + ui2_vsplit_l(&button, 40.0f, &editbox, &button); + ui2_vsplit_l(&button, 5.0f, &button, &button); + + char buf[8]; + sprintf(buf, "%d", config.b_filter_ping); + ui2_do_edit_box(&config.b_filter_ping, &editbox, buf, sizeof(buf)); + config.b_filter_ping = atoi(buf); + + ui2_do_label(&button, "Maximum ping", 14.0f, -1); + } + + ui2_hsplit_t(&types, 20.0f, &button, &types); + if (ui2_do_button(&config.b_filter_gametype, "DM", config.b_filter_gametype&(1<friendly_team != -1 && players[c].team == eval->friendly_team) - continue; + scoremod = 0.5f; float d = distance(pos, players[c].pos); if(d == 0) @@ -734,7 +735,7 @@ bool player::is_grounded() int player::handle_ninja() { - vec2 direction = normalize(vec2(input.target_x, input.target_y)); + vec2 direction = normalize(vec2(latest_input.target_x, latest_input.target_y)); if ((server_tick() - ninja_activationtick) > (data->weapons[WEAPON_NINJA].duration * server_tickspeed() / 1000)) { @@ -746,7 +747,7 @@ int player::handle_ninja() } // Check if it should activate - if (count_input(previnput.fire, input.fire).presses && (server_tick() > currentcooldown)) + if (count_input(latest_previnput.fire, latest_input.fire).presses && (server_tick() > currentcooldown)) { // ok then, activate ninja attack_tick = server_tick(); @@ -958,7 +959,7 @@ int player::handle_bomb() int player::handle_weapons() { - vec2 direction = normalize(vec2(input.target_x, input.target_y)); + vec2 direction = normalize(vec2(latest_input.target_x, latest_input.target_y)); if(config.dbg_stress) { @@ -986,8 +987,8 @@ int player::handle_weapons() } // select weapon - int next = count_input(previnput.next_weapon, input.next_weapon).presses; - int prev = count_input(previnput.prev_weapon, input.prev_weapon).presses; + 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 { @@ -1009,7 +1010,7 @@ int player::handle_weapons() } } - if(input.wanted_weapon) // direct weapon selection + if(latest_input.wanted_weapon) // direct weapon selection wanted_weapon = input.wanted_weapon-1; if(wanted_weapon < 0 || wanted_weapon >= NUM_WEAPONS) @@ -1041,7 +1042,7 @@ int player::handle_weapons() if(active_weapon == WEAPON_ROCKET || active_weapon == WEAPON_SHOTGUN) fullauto = true; - if(count_input(previnput.fire, input.fire).presses || ((fullauto && input.fire&1) && weapons[active_weapon].ammo)) + if(count_input(latest_previnput.fire, latest_input.fire).presses || ((fullauto && latest_input.fire&1) && weapons[active_weapon].ammo)) { // fire! if(weapons[active_weapon].ammo) @@ -1211,12 +1212,26 @@ int player::handle_weapons() void player::tick() { server_setclientscore(client_id, score); + + // grab latest input + { + int size = 0; + int *input = server_latestinput(client_id, &size); + if(input) + { + mem_copy(&latest_previnput, &latest_input, sizeof(latest_input)); + mem_copy(&latest_input, input, sizeof(latest_input)); + } + } // check if we have enough input // this is to prevent initial weird clicks if(num_inputs < 2) + { + latest_previnput = latest_input; previnput = input; - + } + // do latency stuff { CLIENT_INFO info; @@ -1254,7 +1269,7 @@ void player::tick() // TODO: rework the input to be more robust if(dead) { - if(server_tick()-die_tick >= server_tickspeed()/2 && count_input(previnput.fire, input.fire).presses) + if(server_tick()-die_tick >= server_tickspeed()/2 && count_input(latest_previnput.fire, latest_input.fire).presses) die_tick = -1; if(server_tick()-die_tick >= server_tickspeed()*5) // auto respawn after 3 sec respawn(); @@ -1466,7 +1481,7 @@ void player::snap(int snaping_client) info->local = 1; } - if(health > 0 && distance(players[snaping_client].pos, pos) < 1000.0f) + if(health > 0 && team >= 0 && distance(players[snaping_client].pos, pos) < 1000.0f) { obj_player_character *character = (obj_player_character *)snap_new_item(OBJTYPE_PLAYER_CHARACTER, client_id, sizeof(obj_player_character)); -- cgit 1.4.1