bool CHARACTER::is_grounded() { if(col_check_point((int)(pos.x+phys_size/2), (int)(pos.y+phys_size/2+5))) return true; if(col_check_point((int)(pos.x-phys_size/2), (int)(pos.y+phys_size/2+5))) return true; return false; }
bool test_box(vec2 pos, vec2 size) { size *= 0.5f; if(col_check_point(pos.x-size.x, pos.y-size.y)) return true; if(col_check_point(pos.x+size.x, pos.y-size.y)) return true; if(col_check_point(pos.x-size.x, pos.y+size.y)) return true; if(col_check_point(pos.x+size.x, pos.y+size.y)) return true; return false; }
// TODO: OPT: rewrite this smarter! void move_point(vec2 *inout_pos, vec2 *inout_vel, float elasticity, int *bounces) { if(bounces) *bounces = 0; vec2 pos = *inout_pos; vec2 vel = *inout_vel; if(col_check_point(pos + vel)) { int affected = 0; if(col_check_point(pos.x + vel.x, pos.y)) { inout_vel->x *= -elasticity; if(bounces) (*bounces)++; affected++; } if(col_check_point(pos.x, pos.y + vel.y)) { inout_vel->y *= -elasticity; if(bounces) (*bounces)++; affected++; } if(affected == 0) { inout_vel->x *= -elasticity; inout_vel->y *= -elasticity; } } else if(col_check_platte(pos.x, pos.y + vel.y)) { if(bounces) (*bounces)++; inout_vel->x *= -elasticity; inout_vel->y *= -elasticity; } else { *inout_pos = pos + vel; } }
void CHARACTER_CORE::tick(bool use_input) { float phys_size = 28.0f; triggered_events = 0; // get ground state bool grounded = false; if(col_check_point(pos.x+phys_size/2, pos.y+phys_size/2+5)) grounded = true; if(col_check_point(pos.x-phys_size/2, pos.y+phys_size/2+5)) grounded = true; //platte if(col_check_platte(pos.x+phys_size/2, pos.y+phys_size/2+5)) grounded = true; if(col_check_platte(pos.x-phys_size/2, pos.y+phys_size/2+5)) grounded = true; vec2 target_direction = normalize(vec2(input.target_x, input.target_y)); vel.y += world->tuning.gravity; float max_speed = grounded ? world->tuning.ground_control_speed : world->tuning.air_control_speed; float accel = grounded ? world->tuning.ground_control_accel : world->tuning.air_control_accel; float friction = grounded ? world->tuning.ground_friction : world->tuning.air_friction; // handle input if(use_input) { direction = input.direction; // setup angle float a = 0; if(input.target_x == 0) a = atan((float)input.target_y); else a = atan((float)input.target_y/(float)input.target_x); if(input.target_x < 0) a = a+pi; angle = (int)(a*256.0f); // handle jump if(input.jump) { if(!(jumped&1)) { if(grounded) { triggered_events |= COREEVENT_GROUND_JUMP; vel.y = -world->tuning.ground_jump_impulse; jumped |= 1; } else if(!(jumped&2)) { triggered_events |= COREEVENT_AIR_JUMP; vel.y = -world->tuning.air_jump_impulse; jumped |= 3; } } } else jumped &= ~1; // handle hook if(input.hook) { if(hook_state == HOOK_IDLE) { hook_state = HOOK_FLYING; hook_pos = pos+target_direction*phys_size*1.5f; hook_dir = target_direction; hooked_player = -1; hook_tick = 0; triggered_events |= COREEVENT_HOOK_LAUNCH; } } else { hooked_player = -1; hook_state = HOOK_IDLE; hook_pos = pos; } } // add the speed modification according to players wanted direction if(direction < 0) vel.x = saturated_add(-max_speed, max_speed, vel.x, -accel); if(direction > 0) vel.x = saturated_add(-max_speed, max_speed, vel.x, accel); if(direction == 0) vel.x *= friction; // handle jumping // 1 bit = to keep track if a jump has been made on this input // 2 bit = to keep track if a air-jump has been made if(grounded) jumped &= ~2; // do hook if(hook_state == HOOK_IDLE) { hooked_player = -1; hook_state = HOOK_IDLE; hook_pos = pos; } else if(hook_state >= HOOK_RETRACT_START && hook_state < HOOK_RETRACT_END) { hook_state++; } else if(hook_state == HOOK_RETRACT_END) { hook_state = HOOK_RETRACTED; triggered_events |= COREEVENT_HOOK_RETRACT; hook_state = HOOK_RETRACTED; } else if(hook_state == HOOK_FLYING) { vec2 new_pos = hook_pos+hook_dir*world->tuning.hook_fire_speed; if(distance(pos, new_pos) > world->tuning.hook_length) { hook_state = HOOK_RETRACT_START; new_pos = pos + normalize(new_pos-pos) * world->tuning.hook_length; } // make sure that the hook doesn't go though the ground bool going_to_hit_ground = false; bool going_to_retract = false; int hit = col_intersect_line(hook_pos, new_pos, &new_pos, 0); if(hit) { if(hit&COLFLAG_NOHOOK) going_to_retract = true; else going_to_hit_ground = true; } // Check against other players first if(world && world->tuning.player_hooking) { float dist = 0.0f; for(int i = 0; i < MAX_CLIENTS; i++) { CHARACTER_CORE *p = world->characters[i]; if(!p || p == this) continue; vec2 closest_point = closest_point_on_line(hook_pos, new_pos, p->pos); if(distance(p->pos, closest_point) < phys_size+2.0f) { if (hooked_player == -1 || distance (hook_pos, p->pos) < dist) { triggered_events |= COREEVENT_HOOK_ATTACH_PLAYER; hook_state = HOOK_GRABBED; hooked_player = i; dist = distance (hook_pos, p->pos); } } } } if(hook_state == HOOK_FLYING) { // check against ground if(going_to_hit_ground) { triggered_events |= COREEVENT_HOOK_ATTACH_GROUND; hook_state = HOOK_GRABBED; } else if(going_to_retract) { triggered_events |= COREEVENT_HOOK_HIT_NOHOOK; hook_state = HOOK_RETRACT_START; } hook_pos = new_pos; } } if(hook_state == HOOK_GRABBED) { if(hooked_player != -1) { CHARACTER_CORE *p = world->characters[hooked_player]; if(p) hook_pos = p->pos; else { // release hook hooked_player = -1; hook_state = HOOK_RETRACTED; hook_pos = pos; } // keep players hooked for a max of 1.5sec //if(server_tick() > hook_tick+(server_tickspeed()*3)/2) //release_hooked(); } // don't do this hook rutine when we are hook to a player if(hooked_player == -1 && distance(hook_pos, pos) > 46.0f) { vec2 hookvel = normalize(hook_pos-pos)*world->tuning.hook_drag_accel; // the hook as more power to drag you up then down. // this makes it easier to get on top of an platform if(hookvel.y > 0) hookvel.y *= 0.3f; // the hook will boost it's power if the player wants to move // in that direction. otherwise it will dampen everything abit if((hookvel.x < 0 && direction < 0) || (hookvel.x > 0 && direction > 0)) hookvel.x *= 0.95f; else hookvel.x *= 0.75f; vec2 new_vel = vel+hookvel; // check if we are under the legal limit for the hook if(length(new_vel) < world->tuning.hook_drag_speed || length(new_vel) < length(vel)) vel = new_vel; // no problem. apply } // release hook (max hook time is 1.25 hook_tick++; if(hooked_player != -1 && (hook_tick > SERVER_TICK_SPEED+SERVER_TICK_SPEED/5 || !world->characters[hooked_player])) { hooked_player = -1; hook_state = HOOK_RETRACTED; hook_pos = pos; } } if(world && world->tuning.player_collision) { for(int i = 0; i < MAX_CLIENTS; i++) { CHARACTER_CORE *p = world->characters[i]; if(!p) continue; //player *p = (player*)ent; if(p == this) // || !(p->flags&FLAG_ALIVE) continue; // make sure that we don't nudge our self // handle player <-> player collision float d = distance(pos, p->pos); vec2 dir = normalize(pos - p->pos); if(d < phys_size*1.25f && d > 1.0f) { float a = (phys_size*1.45f - d); // make sure that we don't add excess force by checking the // direction against the current velocity vec2 veldir = normalize(vel); float v = 1-(dot(veldir, dir)+1)/2; vel = vel + dir*a*(v*0.75f); vel = vel * 0.85f; } // handle hook influence if(hooked_player == i) { if(d > phys_size*1.50f) // TODO: fix tweakable variable { float accel = world->tuning.hook_drag_accel * (d/world->tuning.hook_length); float drag_speed = world->tuning.hook_drag_speed; // add force to the hooked player p->vel.x = saturated_add(-drag_speed, drag_speed, p->vel.x, accel*dir.x*1.5f); p->vel.y = saturated_add(-drag_speed, drag_speed, p->vel.y, accel*dir.y*1.5f); // add a little bit force to the guy who has the grip vel.x = saturated_add(-drag_speed, drag_speed, vel.x, -accel*dir.x*0.25f); vel.y = saturated_add(-drag_speed, drag_speed, vel.y, -accel*dir.y*0.25f); } } } } // clamp the velocity to something sane if(length(vel) > 6000) vel = normalize(vel) * 6000; }
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(); } }