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(config.cl_render_ammo) { // 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 if(config.cl_render_hp) { 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 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(); }
void MENUS::ui_draw_browse_icon(int what, const RECT *r) { gfx_texture_set(data->images[IMAGE_BROWSEICONS].id); gfx_quads_begin(); select_sprite(what); gfx_quads_drawTL(r->x,r->y,r->w,r->h); gfx_quads_end(); }
void ITEMS::render_flag(const NETOBJ_FLAG *prev, const NETOBJ_FLAG *current) { float angle = 0.0f; float size = 42.0f; if(gameclient.snap.local_info && current->carried_by == gameclient.snap.local_info->cid && config.tc_hide_carrying) return; 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(config.tc_colored_flags && gameclient.snap.local_info) { vec3 col = TeecompUtils::getTeamColor(current->team, 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); } 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_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 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 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(); }
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(); }
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); } }
void KILLMESSAGES::on_render() { if(config.cl_render_kill && !config.cl_clear_all) { 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(); 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(gameclient.clients[killmsgs[r].victim].team == 0) select_sprite(SPRITE_FLAG_BLUE); else select_sprite(SPRITE_FLAG_RED); if(config.tc_colored_flags) { vec3 col = TeecompUtils::getTeamColor(1-gameclient.clients[killmsgs[r].victim].team, 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 = 56.0f; gfx_quads_drawTL(x, y-16, size/2, size); gfx_quads_end(); } } // anti rainbow TEE_RENDER_INFO victim = gameclient.clients[killmsgs[r].victim].render_info; if(config.cl_anti_rainbow && (gameclient.clients[killmsgs[r].victim].color_change_count > config.cl_anti_rainbow_count)) { if(config.tc_force_skin_team1) victim.texture = gameclient.skins->get(max(0, gameclient.skins->find(config.tc_forced_skin1)))->org_texture; else victim.texture = gameclient.skins->get(gameclient.clients[killmsgs[r].victim].skin_id)->org_texture; victim.color_body = vec4(1,1,1,1); victim.color_feet = vec4(1,1,1,1); } render_tee(ANIMSTATE::get_idle(), &victim, 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(); 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(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); if(config.tc_colored_flags) { vec3 col = TeecompUtils::getTeamColor(1-gameclient.clients[killmsgs[r].killer].team, 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 = 56.0f; gfx_quads_drawTL(x-56, y-16, size/2, size); gfx_quads_end(); } } // anti rainbow TEE_RENDER_INFO killer = gameclient.clients[killmsgs[r].killer].render_info; if(config.cl_anti_rainbow && (gameclient.clients[killmsgs[r].killer].color_change_count > config.cl_anti_rainbow_count)) { if(config.tc_force_skin_team1) killer.texture = gameclient.skins->get(max(0, gameclient.skins->find(config.tc_forced_skin1)))->org_texture; else killer.texture = gameclient.skins->get(gameclient.clients[killmsgs[r].killer].skin_id)->org_texture; killer.color_body = vec4(1,1,1,1); killer.color_feet = vec4(1,1,1,1); } // render killer tee x -= 24.0f; render_tee(ANIMSTATE::get_idle(), &killer, 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; } } }
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( (config.tc_laser_color_outer>>16)/255.0f, ((config.tc_laser_color_outer>>8)&0xff)/255.0f, (config.tc_laser_color_outer&0xff)/255.0f, 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( (config.tc_laser_color_inner>>16)/255.0f, ((config.tc_laser_color_inner>>8)&0xff)/255.0f, (config.tc_laser_color_inner&0xff)/255.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::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); ///--- Added by Tigra // Draw shadows of grenades bool local_player_in_game = gameclient.clients[gameclient.snap.local_cid].team != -1; if(config.cl_antiping && current->type == WEAPON_GRENADE && local_player_in_game) { // Calculate average prediction offset, because client_predtick() gets varial values :((( // Must be there is a normal way to realize it, but I'm too lazy to find it. ^) if (gameclient.average_prediction_offset == -1) { int offset = client_predtick() - client_tick(); gameclient.prediction_offset_summ += offset; gameclient.prediction_offset_count++; if (gameclient.prediction_offset_count >= 100) { gameclient.average_prediction_offset = round((float)gameclient.prediction_offset_summ / gameclient.prediction_offset_count); } } // Draw shadow only if grenade directed to local player int local_cid = gameclient.snap.local_cid; NETOBJ_CHARACTER& cur_char = gameclient.snap.characters[local_cid].cur; NETOBJ_CHARACTER& prev_char = gameclient.snap.characters[local_cid].prev; vec2 server_pos = mix(vec2(prev_char.x, prev_char.y), vec2(cur_char.x, cur_char.y), client_intratick()); float d1 = distance(pos, server_pos); float d2 = distance(prevpos, server_pos); if (d1 < 0) d1 *= -1; if (d2 < 0) d2 *= -1; bool grenade_directed_to_local_player = d1 < d2; if (gameclient.average_prediction_offset != -1 && grenade_directed_to_local_player) { int predicted_tick = client_prevtick() + gameclient.average_prediction_offset; float predicted_ct = (predicted_tick - current->start_tick)/(float)SERVER_TICK_SPEED + client_ticktime(); if (predicted_ct >= 0) { int shadow_type = WEAPON_GUN; // Pistol bullet sprite is used for marker of shadow. TODO: use something custom. select_sprite(data->weapons.id[clamp(shadow_type, 0, NUM_WEAPONS-1)].sprite_proj); vec2 predicted_pos = calc_pos(startpos, startvel, curvature, speed, predicted_ct); gfx_quads_draw(predicted_pos.x, predicted_pos.y, 32, 32); } } } ///--- gfx_quads_setrotation(0); gfx_quads_end(); }
/********************************** Draw a single sprite if image_file_name is not NULL, this file is used as an image rather than the normal sprite image **********************************/ static void set_up_sprite(context_t * ctx, const char * image_file_name) { anim_t ** sprite_list; anim_t ** sprite_move_list; item_t * item; int px; int py; int opx; int opy; Uint32 current_time; int angle; int flip; int force_flip; int move_status; char * zoom_str = NULL; double zoom = 1.0; int sprite_align = ALIGN_CENTER; int sprite_offset_y = 0; bool force_position = false; context_t * player_context = context_get_player(); if( ctx->map == NULL ) { return; } if( ctx->in_game == false ) { return; } if( strcmp(ctx->map,player_context->map)) { return; } item = item_list_add(&item_list); current_time = sdl_get_global_time(); // Force position when the player has changed map if( change_map == true ) { ctx->move_start_tick = current_time; ctx->animation_tick = current_time; force_position = true; } // Force position when this context has changed map if( ctx->change_map == true ) { ctx->move_start_tick = current_time; ctx->animation_tick = current_time; ctx->change_map = false; force_position = true; } if( ctx->animation_tick == 0 ) { ctx->animation_tick = current_time; } if( ctx->cur_pos_px == INT_MAX || ctx->cur_pos_py == INT_MAX ) { force_position = true; } // Detect sprite movement, initiate animation if( ctx->pos_changed && force_position == false ) { ctx->pos_changed = false; ctx->move_start_tick = current_time; ctx->start_pos_px = ctx->cur_pos_px; ctx->start_pos_py = ctx->cur_pos_py; /* flip need to remember previous direction to avoid resetting a east -> west flip when a sprite goes to north for instance. On the contrary rotation must not remember previous state, or the rotation will be wrong. Hence the distinction between orientation (no memory) and direction (memory). */ ctx->orientation = 0; // Compute direction if( ctx->pos_tx > ctx->prev_pos_tx ) { ctx->direction &= ~WEST; ctx->direction |= EAST; ctx->orientation |= EAST; } if( ctx->pos_tx < ctx->prev_pos_tx ) { ctx->direction &= ~EAST; ctx->direction |= WEST; ctx->orientation |= WEST; } if( ctx->pos_ty > ctx->prev_pos_ty ) { ctx->direction &= ~NORTH; ctx->direction |= SOUTH; ctx->orientation |= SOUTH; } if( ctx->pos_ty < ctx->prev_pos_ty ) { ctx->direction &= ~SOUTH; ctx->direction |= NORTH; ctx->orientation |= NORTH; } } // Select sprite to display sprite_list = select_sprite(ctx,image_file_name); if( sprite_list == NULL ) { return; } if( sprite_list[0] == NULL ) { free(sprite_list); return; } sprite_move_list = select_sprite_move(ctx,image_file_name); // Get position in pixel px = map_t2p_x(ctx->pos_tx,ctx->pos_ty,default_layer); py = map_t2p_y(ctx->pos_tx,ctx->pos_ty,default_layer); // Get per sprite zoom if(entry_read_string(CHARACTER_TABLE,ctx->id,&zoom_str,CHARACTER_KEY_ZOOM,NULL) == RET_OK ) { zoom = atof(zoom_str); free(zoom_str); } // Align sprite on tile entry_read_int(CHARACTER_TABLE,ctx->id,&sprite_align,CHARACTER_KEY_ALIGN,NULL); if( sprite_align == ALIGN_CENTER ) { px -= ((sprite_list[0]->w*default_layer->map_zoom*zoom)-default_layer->tile_width)/2; py -= ((sprite_list[0]->h*default_layer->map_zoom*zoom)-default_layer->tile_height)/2; } if( sprite_align == ALIGN_LOWER ) { px -= ((sprite_list[0]->w*default_layer->map_zoom*zoom)-default_layer->tile_width)/2; py -= (sprite_list[0]->h*default_layer->map_zoom*zoom)-default_layer->tile_height ; } // Add Y offset entry_read_int(CHARACTER_TABLE,ctx->id,&sprite_offset_y,CHARACTER_KEY_OFFSET_Y,NULL); py += sprite_offset_y; // Set sprite to item item_set_anim_start_tick(item,ctx->animation_tick); if( force_position == true ) { ctx->start_pos_px = px; ctx->cur_pos_px = px; ctx->start_pos_py = py; ctx->cur_pos_py = py; } opx = ctx->start_pos_px; opy = ctx->start_pos_py; item_set_move(item,opx,opy,px,py,ctx->move_start_tick,VIRTUAL_ANIM_DURATION); item_set_save_coordinate(item,&ctx->cur_pos_px,&ctx->cur_pos_py); item_set_anim_array(item,sprite_list); free(sprite_list); item_set_anim_move_array(item,sprite_move_list); free(sprite_move_list); // Get rotation configuration angle = 0; if( ctx->orientation & NORTH && ctx->orientation & EAST ) { entry_read_int(CHARACTER_TABLE,ctx->id,&angle,CHARACTER_KEY_DIR_NE_ROT,NULL); item_set_angle(item,(double)angle); } else if ( ctx->orientation & SOUTH && ctx->orientation & EAST ) { entry_read_int(CHARACTER_TABLE,ctx->id,&angle,CHARACTER_KEY_DIR_SE_ROT,NULL); item_set_angle(item,(double)angle); } else if ( ctx->orientation & SOUTH && ctx->orientation & WEST ) { entry_read_int(CHARACTER_TABLE,ctx->id,&angle,CHARACTER_KEY_DIR_SW_ROT,NULL); item_set_angle(item,(double)angle); } else if ( ctx->orientation & NORTH && ctx->orientation & WEST ) { entry_read_int(CHARACTER_TABLE,ctx->id,&angle,CHARACTER_KEY_DIR_NW_ROT,NULL); item_set_angle(item,(double)angle); } else if ( ctx->orientation & NORTH ) { entry_read_int(CHARACTER_TABLE,ctx->id,&angle,CHARACTER_KEY_DIR_N_ROT,NULL); item_set_angle(item,(double)angle); } else if ( ctx->orientation & SOUTH ) { entry_read_int(CHARACTER_TABLE,ctx->id,&angle,CHARACTER_KEY_DIR_S_ROT,NULL); item_set_angle(item,(double)angle); } else if ( ctx->orientation & WEST ) { entry_read_int(CHARACTER_TABLE,ctx->id,&angle,CHARACTER_KEY_DIR_W_ROT,NULL); item_set_angle(item,(double)angle); } else if ( ctx->orientation & EAST ) { entry_read_int(CHARACTER_TABLE,ctx->id,&angle,CHARACTER_KEY_DIR_E_ROT,NULL); item_set_angle(item,(double)angle); } // Get flip configuration force_flip = 0; entry_read_int(CHARACTER_TABLE,ctx->id,&force_flip,CHARACTER_KEY_FORCE_FLIP,NULL); move_status = ctx->direction; if( force_flip == true ) { move_status = ctx->orientation; } flip = 0; if( angle == 0 ) { if( move_status & NORTH ) { entry_read_int(CHARACTER_TABLE,ctx->id,&flip,CHARACTER_KEY_DIR_N_FLIP,NULL); } if( move_status & SOUTH ) { entry_read_int(CHARACTER_TABLE,ctx->id,&flip,CHARACTER_KEY_DIR_S_FLIP,NULL); } if( move_status & WEST ) { entry_read_int(CHARACTER_TABLE,ctx->id,&flip,CHARACTER_KEY_DIR_W_FLIP,NULL); } if( move_status & EAST ) { entry_read_int(CHARACTER_TABLE,ctx->id,&flip,CHARACTER_KEY_DIR_E_FLIP,NULL); } switch(flip) { case 1: item_set_flip(item,SDL_FLIP_HORIZONTAL); break; case 2: item_set_flip(item,SDL_FLIP_VERTICAL); break; case 3: item_set_flip(item,SDL_FLIP_HORIZONTAL|SDL_FLIP_VERTICAL); break; default: item_set_flip(item,SDL_FLIP_NONE); } } item_set_click_left(item,cb_select_sprite,ctx->id,NULL); item_set_click_right(item,cb_redo_sprite,item,NULL); item_set_zoom_x(item,zoom * default_layer->map_zoom ); item_set_zoom_y(item,zoom * default_layer->map_zoom ); }
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); if(config.cl_scoreboard_client_id) { str_format(buf, sizeof(buf), "%d | %s", info->cid, gameclient.clients[info->cid].name); gfx_text(0, x+128, y, font_size, buf, -1); } else { 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(); 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(info->team == 0) select_sprite(SPRITE_FLAG_BLUE, SPRITE_FLAG_FLIP_X); else select_sprite(SPRITE_FLAG_RED, SPRITE_FLAG_FLIP_X); if(config.tc_colored_flags) { vec3 col = TeecompUtils::getTeamColor(1-info->team, 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 = 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; } }