/* Copyright (C) 1997-2001 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "g_local.h" #include "g_gametypes.h" static edict_t *current_player; static gclient_t *current_client; trace_t pmtrace; static vec3_t forward, right, up; float xyspeed; /* =============== P_DamageFeedback Handles color blends and view kicks =============== */ void P_DamageFeedback( edict_t *ent ) { gclient_t *client; client = ent->r.client; //jalfixme: STAT_FLASHES is never used anywhere in client side // flash the backgrounds behind the status numbers // client->ps.stats[STAT_FLASHES] = 0; // if (client->damage_taken) // client->ps.stats[STAT_FLASHES] |= 1; //if (client->damage_armor && !(player->flags & FL_GODMODE) && (client->invincible_framenum <= level.framenum)) // client->ps.stats[STAT_FLASHES] |= 2; if( client->damage_taken ) { // play an apropriate pain sound if ((level.time > ent->pain_debounce_time) && !(ent->flags & FL_GODMODE) ) { ent->pain_debounce_time = level.time + 0.7; if (!G_IsDead(ent)) { if (ent->health < 25) G_AddEvent (ent, EV_PAIN, PAIN_25, qtrue); else if (ent->health < 50) G_AddEvent (ent, EV_PAIN, PAIN_50, qtrue); else if (ent->health < 75) G_AddEvent (ent, EV_PAIN, PAIN_75, qtrue); else G_AddEvent (ent, EV_PAIN, PAIN_100, qtrue); } } } } //=============== //SV_CalcViewOffset //=============== void SV_CalcViewOffset( edict_t *ent ) { ent->r.client->ps.viewheight = ent->viewheight; } /* ================= P_FallingDamage ================= */ // wsw : jal : brand new version void P_FallingDamage( edict_t *ent ) { //#define FALL_DAMAGE_PRINTS float delta; float damage; int dflags; vec3_t dir = { 0.0f, 0.0f, 1.0f }; if (ent->movetype == MOVETYPE_NOCLIP) return; if( ent->groundentity ) { delta = ent->r.client->fall_value; } else { // bounced ? if( ent->r.client->fall_value < 0.0f && ent->velocity[2] >= 0.0f ) { #ifdef FALL_DAMAGE_PRINTS G_Printf( "Bounced\n" ); #endif delta = ent->r.client->fall_value; // bounced and continued falling } else if ( ent->r.client->fall_value < 0.0f && ent->velocity[2] > ent->r.client->fall_value ) { #ifdef FALL_DAMAGE_PRINTS G_Printf( "Bounced and continued\n" ); #endif delta = ent->r.client->fall_value - ent->velocity[2]; } else delta = 0.0f; } ent->r.client->fall_value = ent->velocity[2]; if( delta >= 0.0f ) return; delta = delta * delta * 0.0001; // make positive and reescale //adjust delta *= 0.80f; #ifdef FALL_DAMAGE_PRINTS G_Printf( "FALL DELTA:%f\n", delta ); #endif // scale delta if was pushed by jump pad if ( ent->r.client->jumppad_time && ent->r.client->jumppad_time < level.time ) { delta /= (1 + level.time - ent->r.client->jumppad_time) * 0.5; ent->r.client->jumppad_time = 0; } // never take falling damage if completely underwater if (ent->waterlevel == 3) return; if (ent->waterlevel == 2) delta *= 0.25; if (ent->waterlevel == 1) delta *= 0.5; if (delta < 15) return; // never take damage if ( !ent->r.client->fall_fatal && (delta < 35 || pmtrace.surfFlags & SURF_NODAMAGE || !G_Gametype_CanFallDamage()) ) { G_AddEvent ( ent, EV_FALL, FALL_SHORT, qfalse ); return; } // find and apply damage ent->pain_debounce_time = level.time; // no normal pain sound damage = (delta-35)*0.60f; if (damage < 1.0f) damage = 1.0f; // limit max fall damage in multiplayer if (damage > 30.0f) damage = 30.0f; if (ent->r.client->fall_fatal) { damage = ceil(ent->health) - GIB_HEALTH + 1; dflags = DAMAGE_NO_PROTECTION; } else { dflags = 0; } #ifdef FALL_DAMAGE_PRINTS G_Printf( "FALL DAMAGE:%f\n", damage ); #endif T_Damage (ent, world, world, dir, ent->s.origin, vec3_origin, damage, 0, dflags, MOD_FALLING); if (!G_IsDead(ent)) { if( damage > 15.0f ) G_AddEvent (ent, EV_FALL, FALL_FAR, qtrue); else G_AddEvent (ent, EV_FALL, FALL_MEDIUM, qtrue); } else G_AddEvent (ent, EV_FALL, FALL_SHORT, qfalse); } /* ============= P_WorldEffects ============= */ void P_WorldEffects( void ) { int waterlevel, old_waterlevel; int watertype, old_watertype; if (current_player->movetype == MOVETYPE_NOCLIP) { current_player->air_finished = level.time + 12; // don't need air return; } waterlevel = current_player->waterlevel; watertype = current_player->watertype; old_waterlevel = current_client->old_waterlevel; old_watertype = current_client->old_watertype; current_client->old_waterlevel = waterlevel; current_client->old_watertype = watertype; // // if just entered a water volume, play a sound // if (!old_waterlevel && waterlevel) { if (current_player->watertype & CONTENTS_LAVA) G_Sound (current_player, CHAN_BODY, trap_SoundIndex(S_WORLD_LAVA_IN), 1, ATTN_NORM); else if (current_player->watertype & CONTENTS_SLIME) G_Sound (current_player, CHAN_BODY, trap_SoundIndex(S_WORLD_SLIME_IN), 1, ATTN_NORM); else if (current_player->watertype & CONTENTS_WATER) G_Sound (current_player, CHAN_BODY, trap_SoundIndex(S_WORLD_WATER_IN), 1, ATTN_NORM); current_player->flags |= FL_INWATER; // clear damage_debounce, so the pain sound will play immediately current_player->damage_debounce_time = level.time - 1; } // // if just completely exited a water volume, play a sound // if (old_waterlevel && ! waterlevel) { if (old_watertype & CONTENTS_LAVA) G_Sound (current_player, CHAN_BODY, trap_SoundIndex(S_WORLD_LAVA_OUT), 1, ATTN_NORM); else if (old_watertype & CONTENTS_SLIME) G_Sound (current_player, CHAN_BODY, trap_SoundIndex(S_WORLD_SLIME_OUT), 1, ATTN_NORM); else if (old_watertype & CONTENTS_WATER) G_Sound (current_player, CHAN_BODY, trap_SoundIndex(S_WORLD_WATER_OUT), 1, ATTN_NORM); current_player->flags &= ~FL_INWATER; } // // check for head just coming out of water // if (old_waterlevel == 3 && waterlevel != 3) { if (current_player->air_finished < level.time) { // gasp for air // wsw : jal : todo : better variations of gasp sounds G_AddEvent( current_player, EV_SEXEDSOUND, 1, qtrue ); } else if (current_player->air_finished < level.time + 11) { // just break surface // wsw : jal : todo : better variations of gasp sounds G_AddEvent( current_player, EV_SEXEDSOUND, 2, qtrue ); } } // // check for drowning // if( waterlevel == 3 ) { // if out of air, start drowning if( current_player->air_finished < level.time ) { // drown! if( current_client->next_drown_time < level.time && !G_IsDead(current_player) ) { current_client->next_drown_time = level.time + 1; // take more damage the longer underwater current_player->dmg += 2; if (current_player->dmg > 15) current_player->dmg = 15; // wsw : jal : todo : better variations of gasp sounds // play a gurp sound instead of a normal pain sound if( HEALTH_TO_INT(current_player->health - current_player->dmg) <= 0 ) G_AddEvent( current_player, EV_SEXEDSOUND, 2, qtrue ); else G_AddEvent( current_player, EV_SEXEDSOUND, 1, qtrue ); current_player->pain_debounce_time = level.time; T_Damage (current_player, world, world, vec3_origin, current_player->s.origin, vec3_origin, current_player->dmg, 0, DAMAGE_NO_ARMOR, MOD_WATER); } } } else { current_player->air_finished = level.time + 12; current_player->dmg = 2; } // // check for sizzle damage // if( waterlevel && ( current_player->watertype & (CONTENTS_LAVA|CONTENTS_SLIME) ) ) { if( current_player->watertype & CONTENTS_LAVA ) { /* wsw: Medar: We don't have the sounds yet and this seems to overwrite the normal pain sounds if( !G_IsDead(current_player) && current_player->pain_debounce_time <= level.time ) { G_Sound( current_player, CHAN_VOICE, trap_SoundIndex(va(S_PLAYER_BURN_1_to_2, (rand()&1)+1)), 1, ATTN_NORM ); current_player->pain_debounce_time = level.time + 1; }*/ T_Damage( current_player, world, world, vec3_origin, current_player->s.origin, vec3_origin, (30*waterlevel)*FRAMETIME, 0, 0, MOD_LAVA ); } if ( current_player->watertype & CONTENTS_SLIME ) { T_Damage( current_player, world, world, vec3_origin, current_player->s.origin, vec3_origin, (10*waterlevel)*FRAMETIME, 0, 0, MOD_SLIME ); } } } /* =============== G_SetClientEffects =============== */ void G_SetClientEffects( edict_t *ent ) { int remaining; gclient_t *client = ent->r.client; ent->s.effects = 0; ent->s.renderfx = 0; if( G_IsDead(ent) || match.state >= MATCH_STATE_POSTMATCH ) return; //ZOID //newgametypes G_Gametype_CTF_Effects(ent); //ZOID if (client->quad_timeout > level.timemsec) { remaining = (client->quad_timeout - level.timemsec)/game.framemsec; if( remaining > 30 || (remaining & 4) ) G_Gametype_CTF_SetPowerUpEffect( ent ); } // show cheaters!!! if (ent->flags & FL_GODMODE) { ent->s.renderfx |= (RF_SHELL_RED|RF_SHELL_GREEN|RF_SHELL_BLUE); } // wsw : jal : powering up the weapon // jalfixme: find a better way of sending this one? // mdr: added hax to disable powering effect in timeout and countdown if( client->weapon_powered && !gtimeout.active && match.state != MATCH_STATE_COUNTDOWN ) ent->s.effects |= EF_WEAPON_POWERING; // jal : add chating icon effect if( client->buttons & BUTTON_BUSYICON || client->latched_buttons & BUTTON_BUSYICON ) ent->s.effects |= EF_BUSYICON; } /* =============== G_SetClientEvent =============== */ void G_SetClientEvent( edict_t *ent ) { if ( G_IsDead(ent) ) { return; } // wsw : jal : was cyclic footstep play } /* =============== G_SetClientSound =============== */ void G_SetClientSound( edict_t *ent ) { gclient_t *client = ent->r.client; if (ent->waterlevel == 3) { if (ent->watertype & CONTENTS_LAVA) ent->s.sound = trap_SoundIndex(S_WORLD_UNDERLAVA); else if (ent->watertype & CONTENTS_SLIME) ent->s.sound = trap_SoundIndex(S_WORLD_UNDERSLIME); else if (ent->watertype & CONTENTS_WATER) ent->s.sound = trap_SoundIndex(S_WORLD_UNDERWATER); } else if (client->weapon_sound) ent->s.sound = client->weapon_sound; else ent->s.sound = 0; } /* =============== G_SetClientFrame - SPLITMODELS =============== */ void G_SetClientFrame( edict_t *ent ); //=============== //G_CLUpdateLaserGunTrail //=============== void G_CLUpdateLaserGunTrail( edict_t *ent ) { gclient_t *client; firedef_t *firedef = g_weaponInfos[WEAP_LASERGUN].firedef_weak; vec3_t start; vec3_t dir; client = ent->r.client; AngleVectors( client->v_angle, dir, NULL, NULL ); VectorSet( start, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2] + ent->viewheight ); VectorMA( start, firedef->timeout, dir, client->lasergunTrail[(level.framenum+1) & LASERGUN_WEAK_TRAIL_MASK ] ); } //=============== //G_SetClientPSEvent - wsw : jal //=============== void G_SetClientPSEvent( edict_t *ent ) { int i; ent->r.client->ps.event = ent->r.client->events[0]; if( ent->r.client->events[0] == PSEV_NONE ) return; // Pick the first event in the queue. // Move all them one position down. // Put a no-event on top. for( i = 0; i < 15; i++ ) { ent->r.client->events[i] = ent->r.client->events[i+1]; if( ent->r.client->events[i] == PSEV_NONE ) break; } ent->r.client->events[15] = PSEV_NONE; } /* ================= ClientEndServerFrame Called for each player at the end of the server frame and right after spawning ================= */ void ClientEndServerFrame( edict_t *ent ) { int i; vec3_t dir; current_player = ent; current_client = ent->r.client; // the the view POV id ent->r.client->ps.POVnum = ENTNUM(ent); // // If the origin or velocity have changed since ClientThink(), // update the pmove values. This will happen when the client // is pushed by a bmodel or kicked by an explosion. // // If it wasn't updated here, the view position would lag a frame // behind the body position when pushed -- "sinking into plats" // for( i=0; i<3; i++ ) { current_client->ps.pmove.origin[i] = ent->s.origin[i]*16; current_client->ps.pmove.velocity[i] = ent->velocity[i]*16; } VectorSet ( dir, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2] - 0.25 ); if( !G_IsDead(ent) ) { if( ent->groundentity ) { trap_Trace( &pmtrace, ent->s.origin, ent->r.mins, ent->r.maxs, dir, ent, MASK_PLAYERSOLID ); } else { trap_Trace( &pmtrace, ent->s.origin, ent->r.mins, ent->r.maxs, ent->s.origin, ent, MASK_PLAYERSOLID ); } } else { trap_Trace( &pmtrace, ent->s.origin, ent->r.mins, ent->r.maxs, ent->s.origin, ent, MASK_DEADSOLID ); } // // If the end of unit layout is displayed, don't give // the player any normal movement attributes // if( match.state >= MATCH_STATE_POSTMATCH ) { current_client->ps.fov = 90; G_SetStats( ent ); return; } current_client->ps.fov = current_client->pers.fov; AngleVectors( current_client->v_angle, forward, right, up ); // burn from lava, etc P_WorldEffects(); // // set model angles from view angles so other things in // the world can tell which direction you are looking // //splitmodels: jal[start]: send the total angles value ent->s.angles[YAW] = current_client->v_angle[YAW]; if( ent->deadflag ) { ent->s.angles[PITCH] = 0; ent->s.angles[ROLL] = 0; } else { ent->s.angles[PITCH] = current_client->v_angle[PITCH]; ent->s.angles[ROLL] = 0; //jal: roll is ignored clientside } //jal[end] // detect hitting the floor P_FallingDamage( ent ); // apply all the damage taken this frame P_DamageFeedback( ent ); // determine the view offsets SV_CalcViewOffset( ent ); // #ifdef PROTOCOL_NOKICKS_NOBLENDS // add colorblend from accumulated damage if( ent->r.client->damage_taken ) { //clamp if( ent->r.client->damage_taken < 10 ) ent->r.client->damage_taken = 10; if( ent->r.client->damage_taken > 80 ) ent->r.client->damage_taken = 80; G_AddPlayerStateEvent( ent->r.client, PSEV_DAMAGE_BLEND, HEALTH_TO_INT(ent->r.client->damage_taken) ); ent->r.client->damage_taken = 0; } // does nothing to armor saved damage (add some effect? A sound maybe?) if( ent->r.client->damage_saved ) ent->r.client->damage_saved = 0; // add hitbeeps from given damage if( ent->r.client->damage_given || ent->r.client->damageteam_given ) { // we can make team damage at the same time as we do damage // let's determine what's more relevant if( ent->r.client->damageteam_given > 2 * ent->r.client->damage_given || ent->r.client->damageteam_given > 50 ) { G_AddPlayerStateEvent( ent->r.client, PSEV_HIT, 4 ); } else { if( ent->r.client->damage_given > 75 ) G_AddPlayerStateEvent( ent->r.client, PSEV_HIT, 0 ); else if( ent->r.client->damage_given > 50 ) G_AddPlayerStateEvent( ent->r.client, PSEV_HIT, 1 ); else if( ent->r.client->damage_given > 25 ) G_AddPlayerStateEvent( ent->r.client, PSEV_HIT, 2 ); else G_AddPlayerStateEvent( ent->r.client, PSEV_HIT, 3 ); } ent->r.client->damage_given = 0; ent->r.client->damageteam_given = 0; } // #endif // PROTOCOL_NOKICKS_NOBLENDS G_SetStats( ent ); G_SetClientEvent( ent ); G_SetClientEffects( ent ); G_SetClientSound( ent ); G_SetClientFrame( ent ); VectorCopy( ent->velocity, ent->r.client->oldvelocity); VectorCopy( ent->r.client->ps.viewangles, ent->r.client->oldviewangles); G_SetClientPSEvent( ent ); G_CLUpdateLaserGunTrail( ent ); }