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 new_tick = gameclient.new_tick; // 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) { 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; // just use the direct input if it's local player we are rendering if(info.local && client_state() != CLIENTSTATE_DEMOPLAYBACK) angle = get_angle(gameclient.controls->mouse_pos); // 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 ) ) { // 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 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(); if(config.cl_render_time && !config.cl_clear_hud && !config.cl_clear_all) { 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((config.cl_render_time && !config.cl_clear_hud && !config.cl_clear_all) && 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((config.cl_render_score && !config.cl_clear_hud && !config.cl_clear_all) && !(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(!config.tc_hud_match) { if(t == 0) gfx_setcolor(1,0,0,0.25f); else gfx_setcolor(0,0,1,0.25f); } else { vec3 col = TeecompUtils::getTeamColor(t, gameclient.snap.local_info->team, config.tc_colored_tees_team1, config.tc_colored_tees_team2, config.tc_colored_tees_method); gfx_setcolor(col.r, col.g, col.b, 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(); if(config.tc_colored_flags) gfx_texture_set(data->images[IMAGE_GAME_GRAY].id); else 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); if(config.tc_colored_flags) { vec3 col = TeecompUtils::getTeamColor(t, gameclient.snap.local_info->team, config.tc_colored_tees_team1, config.tc_colored_tees_team2, config.tc_colored_tees_method); gfx_setcolor(col.r, col.g, col.b, 1.0f); } 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((config.cl_render_warmup && !config.cl_clear_all) && 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); } }