void RECORD_STATE::on_render() { if (!demorec_isrecording()) return; if (client_tick()%50 < 25) return; gfx_texture_set(-1); float width = 300.0f * gfx_screenaspect(); gfx_mapscreen(0.0f, 0.0f, width, 300.0f); gfx_quads_begin(); gfx_setcolor(1.0f, 0.0f, 0.0f, 1.0f); draw_round_rect(width - 15.0f, 10.0f, 5.0f, 5.0f, 2.5f); gfx_quads_end(); }
void KILLMESSAGES::on_message(int msgtype, void *rawmsg) { if(msgtype == NETMSGTYPE_SV_KILLMSG) { NETMSG_SV_KILLMSG *msg = (NETMSG_SV_KILLMSG *)rawmsg; // unpack messages KILLMSG kill; kill.killer = msg->killer; kill.victim = msg->victim; kill.weapon = msg->weapon; kill.mode_special = msg->mode_special; kill.tick = client_tick(); // add the message killmsg_current = (killmsg_current+1)%killmsg_max; killmsgs[killmsg_current] = kill; if(!gameclient.freeview && (kill.victim == gameclient.spectate_cid) && gameclient.snap.characters[kill.killer].active) gameclient.killer_cid = kill.killer; } }
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(); }
void GAMECLIENT::on_predict() { // store the previous values so we can detect prediction errors CHARACTER_CORE before_prev_char = predicted_prev_char; CHARACTER_CORE before_char = predicted_char; // we can't predict without our own id or own character if(snap.local_cid == -1 || !snap.characters[snap.local_cid].active) return; // don't predict anything if we are paused if(snap.gameobj && snap.gameobj->paused) { if(snap.local_character) predicted_char.read(snap.local_character); if(snap.local_prev_character) predicted_prev_char.read(snap.local_prev_character); return; } // repredict character WORLD_CORE world; world.tuning = tuning; // search for players for(int i = 0; i < MAX_CLIENTS; i++) { if(!snap.characters[i].active) continue; gameclient.clients[i].predicted.world = &world; world.characters[i] = &gameclient.clients[i].predicted; gameclient.clients[i].predicted.read(&snap.characters[i].cur); } // predict for(int tick = client_tick()+1; tick <= client_predtick(); tick++) { // fetch the local if(tick == client_predtick() && world.characters[snap.local_cid]) predicted_prev_char = *world.characters[snap.local_cid]; // first calculate where everyone should move for(int c = 0; c < MAX_CLIENTS; c++) { if(!world.characters[c]) continue; mem_zero(&world.characters[c]->input, sizeof(world.characters[c]->input)); if(snap.local_cid == c) { // apply player input int *input = client_get_input(tick); if(input) world.characters[c]->input = *((NETOBJ_PLAYER_INPUT*)input); world.characters[c]->tick(true); } else world.characters[c]->tick(false); } // move all players and quantize their data for(int c = 0; c < MAX_CLIENTS; c++) { if(!world.characters[c]) continue; world.characters[c]->move(); world.characters[c]->quantize(); } // check if we want to trigger effects if(tick > last_new_predicted_tick) { last_new_predicted_tick = tick; new_predicted_tick = true; if(snap.local_cid != -1 && world.characters[snap.local_cid]) { vec2 pos = world.characters[snap.local_cid]->pos; int events = world.characters[snap.local_cid]->triggered_events; if(events&COREEVENT_GROUND_JUMP) gameclient.sounds->play_and_record(SOUNDS::CHN_WORLD, SOUND_PLAYER_JUMP, 1.0f, pos); /*if(events&COREEVENT_AIR_JUMP) { gameclient.effects->air_jump(pos); gameclient.sounds->play_and_record(SOUNDS::CHN_WORLD, SOUND_PLAYER_AIRJUMP, 1.0f, pos); }*/ //if(events&COREEVENT_HOOK_LAUNCH) snd_play_random(CHN_WORLD, SOUND_HOOK_LOOP, 1.0f, pos); //if(events&COREEVENT_HOOK_ATTACH_PLAYER) snd_play_random(CHN_WORLD, SOUND_HOOK_ATTACH_PLAYER, 1.0f, pos); if(events&COREEVENT_HOOK_ATTACH_GROUND) gameclient.sounds->play_and_record(SOUNDS::CHN_WORLD, SOUND_HOOK_ATTACH_GROUND, 1.0f, pos); if(events&COREEVENT_HOOK_HIT_NOHOOK) gameclient.sounds->play_and_record(SOUNDS::CHN_WORLD, SOUND_HOOK_NOATTACH, 1.0f, pos); //if(events&COREEVENT_HOOK_RETRACT) snd_play_random(CHN_WORLD, SOUND_PLAYER_JUMP, 1.0f, pos); } } if(tick == client_predtick() && world.characters[snap.local_cid]) predicted_char = *world.characters[snap.local_cid]; } if(config.debug && config.cl_predict && predicted_tick == client_predtick()) { NETOBJ_CHARACTER_CORE before = {0}, now = {0}, before_prev = {0}, now_prev = {0}; before_char.write(&before); before_prev_char.write(&before_prev); predicted_char.write(&now); predicted_prev_char.write(&now_prev); if(mem_comp(&before, &now, sizeof(NETOBJ_CHARACTER_CORE)) != 0) { dbg_msg("client", "prediction error"); for(unsigned i = 0; i < sizeof(NETOBJ_CHARACTER_CORE)/sizeof(int); i++) if(((int *)&before)[i] != ((int *)&now)[i]) { dbg_msg("", "\t%d %d %d (%d %d)", i, ((int *)&before)[i], ((int *)&now)[i], ((int *)&before_prev)[i], ((int *)&now_prev)[i]); } } } predicted_tick = client_predtick(); }
void GAMECLIENT::on_snapshot() { new_tick = true; // clear out the invalid pointers mem_zero(&gameclient.snap, sizeof(gameclient.snap)); snap.local_cid = -1; // secure snapshot { int num = snap_num_items(SNAP_CURRENT); for(int index = 0; index < num; index++) { SNAP_ITEM item; void *data = snap_get_item(SNAP_CURRENT, index, &item); if(netobj_validate(item.type, data, item.datasize) != 0) { if(config.debug) dbg_msg("game", "invalidated index=%d type=%d (%s) size=%d id=%d", index, item.type, netobj_get_name(item.type), item.datasize, item.id); snap_invalidate_item(SNAP_CURRENT, index); } } } process_events(); if(config.dbg_stress) { if((client_tick()%100) == 0) { char message[64]; int msglen = rand()%(sizeof(message)-1); for(int i = 0; i < msglen; i++) message[i] = 'a'+(rand()%('z'-'a')); message[msglen] = 0; NETMSG_CL_SAY msg; msg.team = rand()&1; msg.message = message; msg.pack(MSGFLAG_VITAL); client_send_msg(); } } // go trough all the items in the snapshot and gather the info we want { snap.team_size[0] = snap.team_size[1] = 0; // TeeComp. for(int i=0; i<MAX_CLIENTS; i++) stats[i].active = false; int num = snap_num_items(SNAP_CURRENT); for(int i = 0; i < num; i++) { SNAP_ITEM item; const void *data = snap_get_item(SNAP_CURRENT, i, &item); if(item.type == NETOBJTYPE_CLIENT_INFO) { const NETOBJ_CLIENT_INFO *info = (const NETOBJ_CLIENT_INFO *)data; int cid = item.id; ints_to_str(&info->name0, 6, clients[cid].name); ints_to_str(&info->skin0, 6, clients[cid].skin_name); clients[cid].use_custom_color = info->use_custom_color; clients[cid].color_body = info->color_body; clients[cid].color_feet = info->color_feet; // prepare the info if(clients[cid].skin_name[0] == 'x' || clients[cid].skin_name[1] == '_') str_copy(clients[cid].skin_name, "default", 64); clients[cid].skin_info.color_body = skins->get_color(clients[cid].color_body); clients[cid].skin_info.color_feet = skins->get_color(clients[cid].color_feet); clients[cid].skin_info.size = 64; // find new skin clients[cid].skin_id = gameclient.skins->find(clients[cid].skin_name); if(clients[cid].skin_id < 0) { clients[cid].skin_id = gameclient.skins->find("default"); if(clients[cid].skin_id < 0) clients[cid].skin_id = 0; } if(clients[cid].use_custom_color) clients[cid].skin_info.texture = gameclient.skins->get(clients[cid].skin_id)->color_texture; else { clients[cid].skin_info.texture = gameclient.skins->get(clients[cid].skin_id)->org_texture; clients[cid].skin_info.color_body = vec4(1,1,1,1); clients[cid].skin_info.color_feet = vec4(1,1,1,1); } clients[cid].update_render_info(cid); gameclient.snap.num_players++; } else if(item.type == NETOBJTYPE_PLAYER_INFO) { const NETOBJ_PLAYER_INFO *info = (const NETOBJ_PLAYER_INFO *)data; clients[info->cid].team = info->team; snap.player_infos[info->cid] = info; if(info->local) { snap.local_cid = item.id; snap.local_info = info; if (info->team == -1) snap.spectate = true; } // calculate team-balance if(info->team != -1) { snap.team_size[info->team]++; stats[info->cid].active = true; } } else if(item.type == NETOBJTYPE_CHARACTER) { const void *old = snap_find_item(SNAP_PREV, NETOBJTYPE_CHARACTER, item.id); if(old) { snap.characters[item.id].active = true; snap.characters[item.id].prev = *((const NETOBJ_CHARACTER *)old); snap.characters[item.id].cur = *((const NETOBJ_CHARACTER *)data); if(snap.characters[item.id].prev.tick) evolve(&snap.characters[item.id].prev, client_prevtick()); if(snap.characters[item.id].cur.tick) evolve(&snap.characters[item.id].cur, client_tick()); } } else if(item.type == NETOBJTYPE_GAME) { snap.gameobj = (NETOBJ_GAME *)data; if(snap.gameobj->game_over != last_game_over) { if(!last_game_over) on_game_over(); else on_game_restart(); last_game_over = snap.gameobj->game_over; } if((snap.gameobj->warmup > 0) != last_warmup) { if(last_warmup) on_warmup_end(); last_warmup = snap.gameobj->warmup > 0; } } else if(item.type == NETOBJTYPE_FLAG) { int fid = item.id%2; snap.flags[fid] = (const NETOBJ_FLAG *)data; if(snap.flags[fid]->carried_by != last_flag_carrier[fid]) { if(snap.flags[fid]->carried_by >= 0) on_flag_grab(fid); last_flag_carrier[fid] = snap.flags[fid]->carried_by; } } } // TeeComp for(int i=0; i<MAX_CLIENTS; i++) { if(stats[i].active && !stats[i].was_active) { stats[i].reset(); // Client connected, reset stats. stats[i].active = true; stats[i].join_date = client_tick(); } stats[i].was_active = stats[i].active; } } // setup local pointers if(snap.local_cid >= 0) { SNAPSTATE::CHARACTERINFO *c = &snap.characters[snap.local_cid]; if(c->active) { snap.local_character = &c->cur; snap.local_prev_character = &c->prev; local_character_pos = vec2(snap.local_character->x, snap.local_character->y); } } else snap.spectate = true; TUNING_PARAMS standard_tuning; SERVER_INFO current_server_info; client_serverinfo(¤t_server_info); if(current_server_info.gametype[0] != '0') { if(strcmp(current_server_info.gametype, "DM") != 0 && strcmp(current_server_info.gametype, "TDM") != 0 && strcmp(current_server_info.gametype, "CTF") != 0) servermode = SERVERMODE_MOD; else if(memcmp(&standard_tuning, &tuning, sizeof(TUNING_PARAMS)) == 0) servermode = SERVERMODE_PURE; else servermode = SERVERMODE_PUREMOD; } // update render info for(int i = 0; i < MAX_CLIENTS; i++) clients[i].update_render_info(i); }
void GAMECLIENT::on_message(int msgtype) { // special messages if(msgtype == NETMSGTYPE_SV_EXTRAPROJECTILE) { /* int num = msg_unpack_int(); for(int k = 0; k < num; k++) { NETOBJ_PROJECTILE proj; for(unsigned i = 0; i < sizeof(NETOBJ_PROJECTILE)/sizeof(int); i++) ((int *)&proj)[i] = msg_unpack_int(); if(msg_unpack_error()) return; if(extraproj_num != MAX_EXTRA_PROJECTILES) { extraproj_projectiles[extraproj_num] = proj; extraproj_num++; } } return;*/ } else if(msgtype == NETMSGTYPE_SV_TUNEPARAMS) { // unpack the new tuning TUNING_PARAMS new_tuning; int *params = (int *)&new_tuning; for(unsigned i = 0; i < sizeof(TUNING_PARAMS)/sizeof(int); i++) params[i] = msg_unpack_int(); // check for unpacking errors if(msg_unpack_error()) return; servermode = SERVERMODE_PURE; // apply new tuning tuning = new_tuning; return; } void *rawmsg = netmsg_secure_unpack(msgtype); if(!rawmsg) { dbg_msg("client", "dropped weird message '%s' (%d), failed on '%s'", netmsg_get_name(msgtype), msgtype, netmsg_failed_on()); return; } // TODO: this should be done smarter for(int i = 0; i < all.num; i++) all.components[i]->on_message(msgtype, rawmsg); if(msgtype == NETMSGTYPE_SV_READYTOENTER) { client_entergame(); } else if (msgtype == NETMSGTYPE_SV_EMOTICON) { NETMSG_SV_EMOTICON *msg = (NETMSG_SV_EMOTICON *)rawmsg; // apply clients[msg->cid].emoticon = msg->emoticon; clients[msg->cid].emoticon_start = client_tick(); } else if(msgtype == NETMSGTYPE_SV_SOUNDGLOBAL) { if(suppress_events) return; NETMSG_SV_SOUNDGLOBAL *msg = (NETMSG_SV_SOUNDGLOBAL *)rawmsg; gameclient.sounds->play(SOUNDS::CHN_GLOBAL, msg->soundid, 1.0f, vec2(0,0)); } }
int main(int argc, char *argv[]) { char *host = NULL; char *renderer = NULL; int width = 800; int height = 600; int fullscreen = 0; #ifdef EVENT_HOST host = EVENT_HOST; #endif #ifdef DEFAULT_RENDERER renderer = TOSTRING(DEFAULT_RENDERER); #endif // GUI Environment setzt default Renderer um. if (getenv("GUI")) renderer = getenv("GUI"); #ifdef WIN32 char *sep = strrchr(argv[0], '\\'); if (sep) { *sep = '\0'; chdir(argv[0]); } // Spezialfaelle fuer Windows Screensaver Aufrufe if (argc == 2 && stricmp(argv[1], "/s") == 0) { host = "infon.dividuum.de"; width = 1024, height = 768, fullscreen = 1; goto screen_saver_start; } else if (argc == 3 && stricmp(argv[1], "/p") == 0) { exit(EXIT_SUCCESS); } else if (argc == 2 && strstr(argv[1], "/c:") == argv[1]) { die("There are no settings"); } #endif // Keine Fehler auf stderr opterr = 0; int opt; while ((opt = getopt(argc, argv, ":fvx:y:r:h")) != -1) { switch (opt) { case '?': die("you specified an unknown option -%c.", optopt); case ':': die("missing argument to option -%c.", optopt); case 'r': renderer = optarg; break; case 'f': fullscreen = 1; break; case 'x': width = atoi(optarg); break; case 'y': height = atoi(optarg); break; case 'h': die("usage: %s [-r <renderer>] [-f] [-x <width>] [-y <height>] [-v] [-h] <server[:port]>\n" "\n" " -r <renderer> - renderer to use (sdl_gui, gl_gui, ...)\n" " -x <width> - initial screen width.\n" " -y <height> - initial screen height.\n" " -f - start in fullscreen mode.\n" " -v - display version information.\n" " -h - this help.\n" "\n" "<server[:port]> - ip/hostname of an infon game server.\n" " if no port is given, 1234 is used.\n", argv[0]); case 'v': info(); exit(EXIT_SUCCESS); } } switch (argc - optind) { case 0: break; case 1: host = argv[optind]; break; default: die("you specified more than one game server hostname"); } if (!renderer) die("no renderer specified. use the '-r <renderer>' option"); #ifdef WIN32 if (!host) { if (yesno("You did not specify a game server.\nConnect to 'infon.dividuum.de:1234'?")) host = "infon.dividuum.de"; else die("You must supply the game servers hostname\n" "as a command line parameter.\n\n" "Example: 'infon.exe infon.dividuum.de'\n\n" "Visit http://infon.dividuum.de/ for help."); } #else if (!host) die("usage: %s [options] <server[:port]>\n" "see %s -h for a full list of options", argv[0], argv[0]); #endif #ifndef WIN32 signal(SIGTERM, sighandler); signal(SIGINT, sighandler); signal(SIGPIPE, SIG_IGN); #else screen_saver_start: #endif srand(time(NULL)); gettimeofday(&start, NULL); if (!renderer_init(renderer)) die("cannot initialize the renderer '%s'", renderer); if (!renderer_open(width, height, fullscreen)) die("cannot start the renderer '%s'. sorry", renderer); client_init(host); client_game_init(); int lastticks = get_tick(); while (!signal_received && !renderer_wants_shutdown() && client_is_connected()) { int nowticks = get_tick(); int delta = nowticks - lastticks; if (nowticks < lastticks || nowticks > lastticks + 1000) { // Timewarp? lastticks = nowticks; continue; } lastticks = nowticks; // IO Lesen/Schreiben client_tick(delta); client_creature_move(delta); renderer_tick(game_time, delta); game_time += delta; } client_game_shutdown(); client_shutdown(); renderer_close(); renderer_shutdown(); return EXIT_SUCCESS; }