thirtyflightsofloving/3zb2/p_view.c
Knightmare66 95295401a4 Added support for custom client railgun colors in 3ZB2, Awakening2, and Zaero DLLs.
Added weapon balancing and tech control cvars to 3ZB2 DLL.
Added Vampire and Ammogen techs to 3ZB2 game DLL.
Fixed func_door_secret movement bug (when built with origin brush) in Zaero DLL.
Fixed armor stacking bug in default Lazarus, missionpack, and Zaero DLLs.
Removed unused tech cvars in default Lazarus DLL.
Changed parsing of TE_BLASTER_COLORED tempent.
2021-10-31 03:53:09 -04:00

1586 lines
39 KiB
C

#include "g_local.h"
#include "m_player.h"
#include "bot.h"
static edict_t *current_player;
static gclient_t *current_client;
static vec3_t forward, right, up;
float xyspeed;
float bobmove;
int bobcycle; // odd cycles are right foot going forward
float bobfracsin; // sin(bobfrac*M_PI)
/*
===============
SV_CalcRoll
===============
*/
float SV_CalcRoll (vec3_t angles, vec3_t velocity)
{
float sign;
float side;
float value;
side = DotProduct (velocity, right);
sign = side < 0 ? -1 : 1;
side = fabs(side);
value = sv_rollangle->value;
if (side < sv_rollspeed->value)
side = side * value / sv_rollspeed->value;
else
side = value;
return side*sign;
}
/*
===============
P_DamageFeedback
Handles color blends and view kicks
===============
*/
void P_DamageFeedback (edict_t *player)
{
gclient_t *client;
float side;
float realcount, count, kick;
vec3_t v;
int r, l;
static vec3_t power_color = {0.0, 1.0, 0.0};
static vec3_t acolor = {1.0, 1.0, 1.0};
static vec3_t bcolor = {1.0, 0.0, 0.0};
client = player->client;
// flash the backgrounds behind the status numbers
client->ps.stats[STAT_FLASHES] = 0;
if (client->damage_blood)
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;
// total points of damage shot at the player this frame
count = (client->damage_blood + client->damage_armor + client->damage_parmor);
if (count == 0)
return; // didn't take any damage
// start a pain animation if still in the player model
if (client->anim_priority < ANIM_PAIN && player->s.modelindex == MAX_MODELS-1)
{
static int i;
client->anim_priority = ANIM_PAIN;
if (client->ps.pmove.pm_flags & PMF_DUCKED)
{
player->s.frame = FRAME_crpain1-1;
client->anim_end = FRAME_crpain4;
}
else
{
i = (i+1)%3;
switch (i)
{
case 0:
player->s.frame = FRAME_pain101-1;
client->anim_end = FRAME_pain104;
break;
case 1:
player->s.frame = FRAME_pain201-1;
client->anim_end = FRAME_pain204;
break;
case 2:
player->s.frame = FRAME_pain301-1;
client->anim_end = FRAME_pain304;
break;
}
}
}
realcount = count;
if (count < 10)
count = 10; // always make a visible effect
// play an apropriate pain sound
if ((level.time > player->pain_debounce_time) && !(player->flags & FL_GODMODE) && (client->invincible_framenum <= level.framenum))
{
r = 1 + (rand()&1);
player->pain_debounce_time = level.time + 0.7;
if (player->health < 25)
l = 25;
else if (player->health < 50)
l = 50;
else if (player->health < 75)
l = 75;
else
l = 100;
gi.sound (player, CHAN_VOICE, gi.soundindex(va("*pain%i_%i.wav", l, r)), 1, ATTN_NORM, 0);
}
// the total alpha of the blend is always proportional to count
if (client->damage_alpha < 0)
client->damage_alpha = 0;
client->damage_alpha += count*0.01;
if (client->damage_alpha < 0.2)
client->damage_alpha = 0.2;
if (client->damage_alpha > 0.6)
client->damage_alpha = 0.6; // don't go too saturated
// the color of the blend will vary based on how much was absorbed
// by different armors
VectorClear (v);
if (client->damage_parmor)
VectorMA (v, (float)client->damage_parmor/realcount, power_color, v);
if (client->damage_armor)
VectorMA (v, (float)client->damage_armor/realcount, acolor, v);
if (client->damage_blood)
VectorMA (v, (float)client->damage_blood/realcount, bcolor, v);
VectorCopy (v, client->damage_blend);
//
// calculate view angle kicks
//
kick = abs(client->damage_knockback);
if (kick && player->health > 0) // kick of 0 means no view adjust at all
{
kick = kick * 100 / player->health;
if (kick < count*0.5)
kick = count*0.5;
if (kick > 50)
kick = 50;
VectorSubtract (client->damage_from, player->s.origin, v);
VectorNormalize (v);
side = DotProduct (v, right);
client->v_dmg_roll = kick*side*0.3;
side = -DotProduct (v, forward);
client->v_dmg_pitch = kick*side*0.3;
client->v_dmg_time = level.time + DAMAGE_TIME;
}
//
// clear totals
//
client->damage_blood = 0;
client->damage_armor = 0;
client->damage_parmor = 0;
client->damage_knockback = 0;
}
/*
===============
SV_CalcViewOffset
Auto pitching on slopes?
fall from 128: 400 = 160000
fall from 256: 580 = 336400
fall from 384: 720 = 518400
fall from 512: 800 = 640000
fall from 640: 960 =
damage = deltavelocity*deltavelocity * 0.0001
===============
*/
void SV_CalcViewOffset (edict_t *ent)
{
float *angles;
float bob;
float ratio;
float delta;
vec3_t v;
//===================================
// base angles
angles = ent->client->ps.kick_angles;
// if dead, fix the angle and don't add any kick
if (ent->deadflag)
{
VectorClear (angles);
ent->client->ps.viewangles[ROLL] = 40;
ent->client->ps.viewangles[PITCH] = -15;
ent->client->ps.viewangles[YAW] = ent->client->killer_yaw;
}
else
{
// add angles based on weapon kick
VectorCopy (ent->client->kick_angles, angles);
// add angles based on damage kick
ratio = (ent->client->v_dmg_time - level.time) / DAMAGE_TIME;
if (ratio < 0)
{
ratio = 0;
ent->client->v_dmg_pitch = 0;
ent->client->v_dmg_roll = 0;
}
angles[PITCH] += ratio * ent->client->v_dmg_pitch;
angles[ROLL] += ratio * ent->client->v_dmg_roll;
// add pitch based on fall kick
ratio = (ent->client->fall_time - level.time) / FALL_TIME;
if (ratio < 0)
ratio = 0;
angles[PITCH] += ratio * ent->client->fall_value;
// add angles based on velocity
delta = DotProduct (ent->velocity, forward);
angles[PITCH] += delta*run_pitch->value;
delta = DotProduct (ent->velocity, right);
angles[ROLL] += delta*run_roll->value;
// add angles based on bob
delta = bobfracsin * bob_pitch->value * xyspeed;
if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
delta *= 6; // crouching
angles[PITCH] += delta;
delta = bobfracsin * bob_roll->value * xyspeed;
if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
delta *= 6; // crouching
if (bobcycle & 1)
delta = -delta;
angles[ROLL] += delta;
}
//===================================
// base origin
VectorClear (v);
// add view height
v[2] += ent->viewheight;
// add fall height
ratio = (ent->client->fall_time - level.time) / FALL_TIME;
if (ratio < 0)
ratio = 0;
v[2] -= ratio * ent->client->fall_value * 0.4;
// add bob height
bob = bobfracsin * xyspeed * bob_up->value;
if (bob > 6)
bob = 6;
//gi.DebugGraph (bob *2, 255);
v[2] += bob;
// add kick offset
VectorAdd (v, ent->client->kick_origin, v);
// absolutely bound offsets
// so the view can never be outside the player box
if (v[0] < -14)
v[0] = -14;
else if (v[0] > 14)
v[0] = 14;
if (v[1] < -14)
v[1] = -14;
else if (v[1] > 14)
v[1] = 14;
if (v[2] < -22)
v[2] = -22;
else if (v[2] > 30)
v[2] = 30;
VectorCopy (v, ent->client->ps.viewoffset);
}
/*
==============
SV_CalcGunOffset
==============
*/
void SV_CalcGunOffset (edict_t *ent)
{
int i;
float delta;
// gun angles from bobbing
ent->client->ps.gunangles[ROLL] = xyspeed * bobfracsin * 0.005;
ent->client->ps.gunangles[YAW] = xyspeed * bobfracsin * 0.01;
if (bobcycle & 1)
{
ent->client->ps.gunangles[ROLL] = -ent->client->ps.gunangles[ROLL];
ent->client->ps.gunangles[YAW] = -ent->client->ps.gunangles[YAW];
}
ent->client->ps.gunangles[PITCH] = xyspeed * bobfracsin * 0.005;
// gun angles from delta movement
for (i=0 ; i<3 ; i++)
{
delta = ent->client->oldviewangles[i] - ent->client->ps.viewangles[i];
if (delta > 180)
delta -= 360;
if (delta < -180)
delta += 360;
if (delta > 45)
delta = 45;
if (delta < -45)
delta = -45;
if (i == YAW)
ent->client->ps.gunangles[ROLL] += 0.1*delta;
ent->client->ps.gunangles[i] += 0.2 * delta;
}
// gun height
VectorClear (ent->client->ps.gunoffset);
// ent->ps->gunorigin[2] += bob;
// gun_x / gun_y / gun_z are development tools
for (i=0 ; i<3 ; i++)
{
ent->client->ps.gunoffset[i] += forward[i]*(gun_y->value);
ent->client->ps.gunoffset[i] += right[i]*gun_x->value;
ent->client->ps.gunoffset[i] += up[i]* (-gun_z->value);
}
}
/*
=============
SV_AddBlend
=============
*/
void SV_AddBlend (float r, float g, float b, float a, float *v_blend)
{
float a2, a3;
if (a <= 0)
return;
a2 = v_blend[3] + (1-v_blend[3])*a; // new total alpha
a3 = v_blend[3]/a2; // fraction of color from old
v_blend[0] = v_blend[0]*a3 + r*(1-a3);
v_blend[1] = v_blend[1]*a3 + g*(1-a3);
v_blend[2] = v_blend[2]*a3 + b*(1-a3);
v_blend[3] = a2;
}
/*
=============
SV_CalcBlend
=============
*/
void SV_CalcBlend (edict_t *ent)
{
int contents;
vec3_t vieworg;
int remaining;
ent->client->ps.blend[0] = ent->client->ps.blend[1] =
ent->client->ps.blend[2] = ent->client->ps.blend[3] = 0;
// add for contents
VectorAdd (ent->s.origin, ent->client->ps.viewoffset, vieworg);
contents = gi.pointcontents (vieworg);
if (contents & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER) )
ent->client->ps.rdflags |= RDF_UNDERWATER;
else
ent->client->ps.rdflags &= ~RDF_UNDERWATER;
if (contents & (CONTENTS_SOLID|CONTENTS_LAVA))
SV_AddBlend (1.0, 0.3, 0.0, 0.6, ent->client->ps.blend);
else if (contents & CONTENTS_SLIME)
SV_AddBlend (0.0, 0.1, 0.05, 0.6, ent->client->ps.blend);
else if (contents & CONTENTS_WATER)
SV_AddBlend (0.5, 0.3, 0.2, 0.4, ent->client->ps.blend);
// add for powerups
if (ent->client->quad_framenum > level.framenum)
{
remaining = ent->client->quad_framenum - level.framenum;
if (remaining == 30) // beginning to fade
gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage2.wav"), 1, ATTN_NORM, 0);
if (remaining > 30 || (remaining & 4) )
SV_AddBlend (0, 0, 1, 0.08, ent->client->ps.blend);
}
// Knightmare- addded double damage
else if (ent->client->double_framenum > level.framenum)
{
remaining = ent->client->double_framenum - level.framenum;
if (remaining == 30) // beginning to fade
gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/ddamage2.wav"), 1, ATTN_NORM, 0);
if (remaining > 30 || (remaining & 4) )
SV_AddBlend (0.9, 0.7, 0, 0.08, ent->client->ps.blend);
}
// RAFAEL
else if (ent->client->quadfire_framenum > level.framenum)
{
remaining = ent->client->quadfire_framenum - level.framenum;
if (remaining == 30) // beginning to fade
gi.sound(ent, CHAN_ITEM, gi.soundindex("items/quadfire2.wav"), 1, ATTN_NORM, 0);
if (remaining > 30 || (remaining & 4) )
SV_AddBlend (1, 0.2, 0.5, 0.08, ent->client->ps.blend);
}
else if (ent->client->invincible_framenum > level.framenum)
{
remaining = ent->client->invincible_framenum - level.framenum;
if (remaining == 30) // beginning to fade
gi.sound(ent, CHAN_ITEM, gi.soundindex("items/protect2.wav"), 1, ATTN_NORM, 0);
if (remaining > 30 || (remaining & 4) )
SV_AddBlend (1, 1, 0, 0.08, ent->client->ps.blend);
}
else if (ent->client->enviro_framenum > level.framenum)
{
remaining = ent->client->enviro_framenum - level.framenum;
if (remaining == 30) // beginning to fade
gi.sound(ent, CHAN_ITEM, gi.soundindex("items/airout.wav"), 1, ATTN_NORM, 0);
if (remaining > 30 || (remaining & 4) )
SV_AddBlend (0, 1, 0, 0.08, ent->client->ps.blend);
}
else if (ent->client->breather_framenum > level.framenum)
{
remaining = ent->client->breather_framenum - level.framenum;
if (remaining == 30) // beginning to fade
gi.sound(ent, CHAN_ITEM, gi.soundindex("items/airout.wav"), 1, ATTN_NORM, 0);
if (remaining > 30 || (remaining & 4) )
SV_AddBlend (0.4, 1, 0.4, 0.04, ent->client->ps.blend);
}
// add for damage
if (ent->client->damage_alpha > 0)
SV_AddBlend (ent->client->damage_blend[0],ent->client->damage_blend[1]
,ent->client->damage_blend[2], ent->client->damage_alpha, ent->client->ps.blend);
if (ent->client->bonus_alpha > 0)
SV_AddBlend (0.85, 0.7, 0.3, ent->client->bonus_alpha, ent->client->ps.blend);
// drop the damage value
ent->client->damage_alpha -= 0.06;
if (ent->client->damage_alpha < 0)
ent->client->damage_alpha = 0;
// drop the bonus value
ent->client->bonus_alpha -= 0.1;
if (ent->client->bonus_alpha < 0)
ent->client->bonus_alpha = 0;
}
/*
=================
P_FallingDamage
=================
*/
void P_FallingDamage (edict_t *ent)
{
float delta;
int damage;
vec3_t dir;
if (ent->s.modelindex != MAX_MODELS-1)
return; // not in the player model
if (ent->movetype == MOVETYPE_NOCLIP)
return;
if ((ent->client->oldvelocity[2] < 0) && (ent->velocity[2] > ent->client->oldvelocity[2]) && (!ent->groundentity))
{
delta = ent->client->oldvelocity[2];
}
else
{
if (!ent->groundentity)
return;
delta = ent->velocity[2] - ent->client->oldvelocity[2];
}
delta = delta*delta * 0.0001;
//ZOID
// never take damage if just release grapple or on grapple
if (level.time - ent->client->ctf_grapplereleasetime <= FRAMETIME * 2 ||
(ent->client->ctf_grapple &&
ent->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY))
return;
//ZOID
// 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 < 1)
return;
if (delta < 15)
{
ent->s.event = EV_FOOTSTEP;
PlayerNoise(ent, ent->s.origin, PNOISE_SELF); //ponko
return;
}
ent->client->fall_value = delta*0.5;
if (ent->client->fall_value > 40)
ent->client->fall_value = 40;
ent->client->fall_time = level.time + FALL_TIME;
if (delta > 30)
{
if (ent->health > 0)
{
if (delta >= 55)
ent->s.event = EV_FALLFAR;
else
ent->s.event = EV_FALL;
PlayerNoise(ent, ent->s.origin, PNOISE_SELF); //ponko
}
ent->pain_debounce_time = level.time; // no normal pain sound
damage = (delta-30)/2;
if (damage < 1)
damage = 1;
VectorSet (dir, 0, 0, 1);
if (!deathmatch->value || !((int)dmflags->value & DF_NO_FALLING) )
T_Damage (ent, world, world, dir, ent->s.origin, vec3_origin, damage, 0, 0, MOD_FALLING);
}
else
{
ent->s.event = EV_FALLSHORT;
PlayerNoise(ent, ent->s.origin, PNOISE_SELF); //ponko
return;
}
}
/*
=============
P_WorldEffects
=============
*/
void P_WorldEffects (void)
{
qboolean breather;
qboolean envirosuit;
int waterlevel, old_waterlevel;
if (current_player->movetype == MOVETYPE_NOCLIP)
{
current_player->air_finished = level.time + 12; // don't need air
return;
}
waterlevel = current_player->waterlevel;
old_waterlevel = current_client->old_waterlevel;
current_client->old_waterlevel = waterlevel;
breather = current_client->breather_framenum > level.framenum;
envirosuit = current_client->enviro_framenum > level.framenum;
//
// if just entered a water volume, play a sound
//
if (!old_waterlevel && waterlevel)
{
PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF);
if (current_player->watertype & CONTENTS_LAVA)
gi.sound (current_player, CHAN_BODY, gi.soundindex("player/lava_in.wav"), 1, ATTN_NORM, 0);
else if (current_player->watertype & CONTENTS_SLIME)
gi.sound (current_player, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0);
else if (current_player->watertype & CONTENTS_WATER)
gi.sound (current_player, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0);
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)
{
PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF);
gi.sound (current_player, CHAN_BODY, gi.soundindex("player/watr_out.wav"), 1, ATTN_NORM, 0);
current_player->flags &= ~FL_INWATER;
}
//
// check for head just going under water
//
if (old_waterlevel != 3 && waterlevel == 3)
{
gi.sound (current_player, CHAN_BODY, gi.soundindex("player/watr_un.wav"), 1, ATTN_NORM, 0);
}
//
// check for head just coming out of water
//
if (old_waterlevel == 3 && waterlevel != 3)
{
if (current_player->air_finished < level.time)
{ // gasp for air
gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/gasp1.wav"), 1, ATTN_NORM, 0);
PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF);
}
else if (current_player->air_finished < level.time + 11)
{ // just break surface
gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/gasp2.wav"), 1, ATTN_NORM, 0);
}
}
//
// check for drowning
//
if (waterlevel == 3)
{
// breather or envirosuit give air
if (breather || envirosuit)
{
current_player->air_finished = level.time + 10;
if (((int)(current_client->breather_framenum - level.framenum) % 25) == 0)
{
if (!current_client->breather_sound)
gi.sound (current_player, CHAN_AUTO, gi.soundindex("player/u_breath1.wav"), 1, ATTN_NORM, 0);
else
gi.sound (current_player, CHAN_AUTO, gi.soundindex("player/u_breath2.wav"), 1, ATTN_NORM, 0);
current_client->breather_sound ^= 1;
PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF);
//FIXME: release a bubble?
}
}
// if out of air, start drowning
if (current_player->air_finished < level.time)
{ // drown!
if (current_player->client->next_drown_time < level.time
&& current_player->health > 0)
{
current_player->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;
// play a gurp sound instead of a normal pain sound
if (current_player->health <= current_player->dmg)
gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/drown1.wav"), 1, ATTN_NORM, 0);
else if (rand()&1)
gi.sound (current_player, CHAN_VOICE, gi.soundindex("*gurp1.wav"), 1, ATTN_NORM, 0);
else
gi.sound (current_player, CHAN_VOICE, gi.soundindex("*gurp2.wav"), 1, ATTN_NORM, 0);
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)
{
if (current_player->health > 0
&& current_player->pain_debounce_time <= level.time
&& current_client->invincible_framenum < level.framenum)
{
if (rand()&1)
gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/burn1.wav"), 1, ATTN_NORM, 0);
else
gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/burn2.wav"), 1, ATTN_NORM, 0);
current_player->pain_debounce_time = level.time + 1;
}
if (envirosuit) // take 1/3 damage with envirosuit
T_Damage (current_player, world, world, vec3_origin, current_player->s.origin, vec3_origin, 1*waterlevel, 0, 0, MOD_LAVA);
else
T_Damage (current_player, world, world, vec3_origin, current_player->s.origin, vec3_origin, 3*waterlevel, 0, 0, MOD_LAVA);
}
if (current_player->watertype & CONTENTS_SLIME)
{
if (!envirosuit)
{ // no damage from slime with envirosuit
T_Damage (current_player, world, world, vec3_origin, current_player->s.origin, vec3_origin, 1*waterlevel, 0, 0, MOD_SLIME);
}
}
}
}
/*
===============
G_SetClientEffects
===============
*/
void G_SetClientEffects (edict_t *ent)
{
int pa_type;
int remaining;
ent->s.effects = 0;
ent->s.renderfx = 0;
if (ent->health <= 0 || level.intermissiontime)
return;
if (ent->powerarmor_time > level.time)
{
pa_type = PowerArmorType (ent);
if (pa_type == POWER_ARMOR_SCREEN)
{
ent->s.effects |= EF_POWERSCREEN;
}
else if (pa_type == POWER_ARMOR_SHIELD)
{
ent->s.effects |= (EF_SPHERETRANS);
//ponko ent->s.effects |= EF_COLOR_SHELL;
ent->s.renderfx |= RF_SHELL_GREEN;
}
}
//ZOID
CTFEffects(ent);
//ZOID
if (ent->client->quad_framenum > level.framenum
//ZOID
&& (level.framenum & 8)
//ZOID
)
{
remaining = ent->client->quad_framenum - level.framenum;
if (remaining > 30 || (remaining & 4) )
ent->s.effects |= EF_QUAD;
if (remaining == 30 && (ent->svflags & SVF_MONSTER)) // beginning to fade
gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage2.wav"), 1, ATTN_NORM, 0);
}
// Knightmare- addded double damage
if (ent->client->double_framenum > level.framenum)
{
remaining = ent->client->double_framenum - level.framenum;
if (remaining > 30 || (remaining & 4) )
ent->s.effects |= EF_DOUBLE;
if (remaining == 30 && (ent->svflags & SVF_MONSTER)) // beginning to fade
gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/ddamage2.wav"), 1, ATTN_NORM, 0);
}
// RAFAEL
if (ent->client->quadfire_framenum > level.framenum)
{
remaining = ent->client->quadfire_framenum - level.framenum;
if (remaining > 30 || (remaining & 4) )
ent->s.effects |= EF_QUAD;
if (remaining == 30 && (ent->svflags & SVF_MONSTER)) // beginning to fade
gi.sound(ent, CHAN_ITEM, gi.soundindex("items/quadfire2.wav"), 1, ATTN_NORM, 0);
}
if (ent->client->invincible_framenum > level.framenum
//ZOID
&& (level.framenum & 8)
//ZOID
)
{
remaining = ent->client->invincible_framenum - level.framenum;
if (remaining > 30 || (remaining & 4) )
ent->s.effects |= EF_PENT;
if (remaining == 30 && (ent->svflags & SVF_MONSTER)) // beginning to fade
gi.sound(ent, CHAN_ITEM, gi.soundindex("items/protect2.wav"), 1, ATTN_NORM, 0);
}
// show cheaters!!!
if (ent->flags & FL_GODMODE)
{
ent->s.effects |= EF_COLOR_SHELL;
ent->s.renderfx |= (RF_SHELL_RED|RF_SHELL_GREEN|RF_SHELL_BLUE);
}
}
/*
===============
G_SetClientEvent
===============
*/
void G_SetClientEvent (edict_t *ent)
{
if (ent->s.event)
return;
if ( ent->groundentity && xyspeed > 225)
{
if ( (int)(current_client->bobtime+bobmove) != bobcycle )
{
ent->s.event = EV_FOOTSTEP;
PlayerNoise(ent, ent->s.origin, PNOISE_SELF); //ponko
}
}
}
/*
===============
G_SetClientSound
===============
*/
void G_SetClientSound (edict_t *ent)
{
char *weap;
if (ent->client->resp.game_helpchanged != game.helpchanged)
{
ent->client->resp.game_helpchanged = game.helpchanged;
ent->client->resp.helpchanged = 1;
}
// help beep (no more than three times)
if (ent->client->resp.helpchanged && ent->client->resp.helpchanged <= 3 && !(level.framenum&63) )
{
ent->client->resp.helpchanged++;
gi.sound (ent, CHAN_VOICE, gi.soundindex ("misc/pc_up.wav"), 1, ATTN_STATIC, 0);
}
if (ent->client->pers.weapon)
weap = ent->client->pers.weapon->classname;
else
weap = "";
if (ent->waterlevel && (ent->watertype&(CONTENTS_LAVA|CONTENTS_SLIME)) )
ent->s.sound = snd_fry;
else if (strcmp(weap, "weapon_railgun") == 0)
ent->s.sound = gi.soundindex("weapons/rg_hum.wav");
else if (strcmp(weap, "weapon_bfg") == 0)
ent->s.sound = gi.soundindex("weapons/bfg_hum.wav");
// RAFAEL
else if (strcmp (weap, "weapon_phalanx") == 0)
ent->s.sound = gi.soundindex ("weapons/phaloop.wav");
#ifdef KMQUAKE2_ENGINE_MOD
// Knightmare - ambient sounds for ION Ripper and Shockwave
else if ( (strcmp (weap, "weapon_boomer") == 0) && sk_ionripper_extra_sounds->value)
ent->s.sound = gi.soundindex ("weapons/ion_hum.wav");
// else if (strcmp (weap, "weapon_shockwave") == 0)
// ent->s.sound = gi.soundindex ("weapons/shock_hum.wav");
// SKWiD MOD
// else if (strcmp(weap, "weapon_plasma") == 0)
// ent->s.sound = gi.soundindex(PLASMA_SOUND_IDLE);
// END
#endif
else if (ent->client->weapon_sound)
ent->s.sound = ent->client->weapon_sound;
else
ent->s.sound = 0;
}
/*
===============
G_SetClientFrame
===============
*/
void G_SetClientFrame (edict_t *ent)
{
gclient_t *client;
qboolean duck, run;
if (ent->s.modelindex != MAX_MODELS-1)
return; // not in the player model
client = ent->client;
if (client->ps.pmove.pm_flags & PMF_DUCKED)
duck = true;
else
duck = false;
if (xyspeed)
run = true;
else
run = false;
// check for stand/duck and stop/go transitions
if (duck != client->anim_duck && client->anim_priority < ANIM_DEATH)
goto newanim;
if (run != client->anim_run && client->anim_priority == ANIM_BASIC)
goto newanim;
if (!ent->groundentity && client->anim_priority <= ANIM_WAVE)
goto newanim;
// ### Hentai ### BEGIN
if(client->anim_priority == ANIM_REVERSE)
{
if(ent->s.frame > client->anim_end)
{
ent->s.frame--;
return;
}
}
else if (ent->s.frame < client->anim_end) // ### Hentai ### END
{ // continue an animation
ent->s.frame++;
return;
}
if (client->anim_priority == ANIM_DEATH)
return; // stay there
if (client->anim_priority == ANIM_JUMP)
{
if (!ent->groundentity)
return; // stay there
ent->client->anim_priority = ANIM_WAVE;
ent->s.frame = FRAME_jump3;
ent->client->anim_end = FRAME_jump6;
return;
}
newanim:
// return to either a running or standing frame
client->anim_priority = ANIM_BASIC;
client->anim_duck = duck;
client->anim_run = run;
if (!ent->groundentity)
{
//ZOID: if on grapple, don't go into jump frame, go into standing
//frame
if (client->ctf_grapple) {
ent->s.frame = FRAME_stand01;
client->anim_end = FRAME_stand40;
} else {
//ZOID
client->anim_priority = ANIM_JUMP;
if (ent->s.frame != FRAME_jump2)
ent->s.frame = FRAME_jump1;
client->anim_end = FRAME_jump2;
}
}
else if (run)
{ // running
if (duck)
{
ent->s.frame = FRAME_crwalk1;
client->anim_end = FRAME_crwalk6;
}
else
{
ent->s.frame = FRAME_run1;
client->anim_end = FRAME_run6;
}
}
else
{ // standing
if (duck)
{
ent->s.frame = FRAME_crstnd01;
client->anim_end = FRAME_crstnd19;
}
else
{
ent->s.frame = FRAME_stand01;
client->anim_end = FRAME_stand40;
}
}
}
/*
=================
ClientEndServerFrame
Called for each player at the end of the server frame
and right after spawning
=================
*/
void ClientEndServerFrame (edict_t *ent)
{
float bobtime;
int i;
current_player = ent;
current_client = ent->client;
//
// 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]*8.0;
current_client->ps.pmove.velocity[i] = ent->velocity[i]*8.0;
}
//
// If the end of unit layout is displayed, don't give
// the player any normal movement attributes
//
if (level.intermissiontime)
{
// FIXME: add view drifting here?
current_client->ps.blend[3] = 0;
current_client->ps.fov = 90;
G_SetStats (ent);
return;
}
AngleVectors (ent->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
//
if (ent->client->v_angle[PITCH] > 180)
ent->s.angles[PITCH] = (-360 + ent->client->v_angle[PITCH])/3;
else
ent->s.angles[PITCH] = ent->client->v_angle[PITCH]/3;
ent->s.angles[YAW] = ent->client->v_angle[YAW];
ent->s.angles[ROLL] = 0;
ent->s.angles[ROLL] = SV_CalcRoll (ent->s.angles, ent->velocity)*4;
//
// calculate speed and cycle to be used for
// all cyclic walking effects
//
xyspeed = sqrt(ent->velocity[0]*ent->velocity[0] + ent->velocity[1]*ent->velocity[1]);
if (xyspeed < 5)
{
bobmove = 0;
current_client->bobtime = 0; // start at beginning of cycle again
}
else if (ent->groundentity)
{ // so bobbing only cycles when on ground
if (xyspeed > 210)
bobmove = 0.25;
else if (xyspeed > 100)
bobmove = 0.125;
else
bobmove = 0.0625;
}
bobtime = (current_client->bobtime += bobmove);
if (current_client->ps.pmove.pm_flags & PMF_DUCKED)
bobtime *= 4;
bobcycle = (int)bobtime;
bobfracsin = fabs(sin(bobtime*M_PI));
// detect hitting the floor
P_FallingDamage (ent);
// apply all the damage taken this frame
P_DamageFeedback (ent);
// determine the view offsets
SV_CalcViewOffset (ent);
// determine the gun offsets
SV_CalcGunOffset (ent);
// determine the full screen color blend
// must be after viewoffset, so eye contents can be
// accurately determined
// FIXME: with client prediction, the contents
// should be determined by the client
SV_CalcBlend (ent);
// chase cam stuff
if (ent->client->resp.spectator)
G_SetSpectatorStats(ent);
else
G_SetStats (ent);
G_CheckChaseStats(ent);
G_SetClientEvent (ent);
G_SetClientEffects (ent);
G_SetClientSound (ent);
G_SetClientFrame (ent);
VectorCopy (ent->velocity, ent->client->oldvelocity);
VectorCopy (ent->client->ps.viewangles, ent->client->oldviewangles);
// clear weapon kicks
VectorClear (ent->client->kick_origin);
VectorClear (ent->client->kick_angles);
// if the scoreboard is up, update it
if (ent->client->showscores && !(level.framenum & 31)
&& !(ent->svflags & SVF_MONSTER))
{
//ZOID
if (ent->client->menu) {
PMenu_Update(ent);
} else
//ZOID
DeathmatchScoreboardMessage (ent, ent->enemy);
gi.unicast (ent, false);
}
}
//-------------------------------------------------------------------------
// BOT AREA
/*
=============
B_WorldEffects
=============
*/
void B_WorldEffects (edict_t *ent)
{
qboolean breather;
qboolean envirosuit;
int waterlevel, old_waterlevel;
gclient_t *client;
client = ent->client;
if (ent->movetype == MOVETYPE_NOCLIP)
{
ent->air_finished = level.time + 12; // don't need air
return;
}
waterlevel = ent->waterlevel;
old_waterlevel = client->old_waterlevel;
client->old_waterlevel = waterlevel;
breather = client->breather_framenum > level.framenum;
envirosuit = client->enviro_framenum > level.framenum;
//
// if just entered a water volume, play a sound
//
if (!old_waterlevel && waterlevel)
{
PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF);
if (ent->watertype & CONTENTS_LAVA)
gi.sound (current_player, CHAN_BODY, gi.soundindex("player/lava_in.wav"), 1, ATTN_NORM, 0);
else if (ent->watertype & CONTENTS_SLIME)
gi.sound (ent, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0);
else if (ent->watertype & CONTENTS_WATER)
gi.sound (current_player, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0);
ent->flags |= FL_INWATER;
// clear damage_debounce, so the pain sound will play immediately
ent->damage_debounce_time = level.time - 1;
}
//
// if just completely exited a water volume, play a sound
//
if (old_waterlevel && ! waterlevel)
{
PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF);
gi.sound (current_player, CHAN_BODY, gi.soundindex("player/watr_out.wav"), 1, ATTN_NORM, 0);
current_player->flags &= ~FL_INWATER;
}
//
// check for head just going under water
//
if (old_waterlevel != 3 && waterlevel == 3)
{
gi.sound (current_player, CHAN_BODY, gi.soundindex("player/watr_un.wav"), 1, ATTN_NORM, 0);
}
//
// check for head just coming out of water
//
if (old_waterlevel == 3 && waterlevel != 3)
{
if (current_player->air_finished < level.time)
{ // gasp for air
gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/gasp1.wav"), 1, ATTN_NORM, 0);
PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF);
}
else if (current_player->air_finished < level.time + 11)
{ // just break surface
gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/gasp2.wav"), 1, ATTN_NORM, 0);
}
}
//
// check for drowning
//
if (waterlevel == 3)
{
// breather or envirosuit give air
if (breather || envirosuit)
{
current_player->air_finished = level.time + 10;
if (((int)(current_client->breather_framenum - level.framenum) % 25) == 0)
{
if (!current_client->breather_sound)
gi.sound (current_player, CHAN_AUTO, gi.soundindex("player/u_breath1.wav"), 1, ATTN_NORM, 0);
else
gi.sound (current_player, CHAN_AUTO, gi.soundindex("player/u_breath2.wav"), 1, ATTN_NORM, 0);
current_client->breather_sound ^= 1;
PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF);
//FIXME: release a bubble?
}
}
// if out of air, start drowning
if (current_player->air_finished < level.time)
{ // drown!
if (current_player->client->next_drown_time < level.time
&& current_player->health > 0)
{
current_player->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;
// play a gurp sound instead of a normal pain sound
if (current_player->health <= current_player->dmg)
gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/drown1.wav"), 1, ATTN_NORM, 0);
else if (rand()&1)
gi.sound (current_player, CHAN_VOICE, gi.soundindex("*gurp1.wav"), 1, ATTN_NORM, 0);
else
gi.sound (current_player, CHAN_VOICE, gi.soundindex("*gurp2.wav"), 1, ATTN_NORM, 0);
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)
{
if (current_player->health > 0
&& current_player->pain_debounce_time <= level.time
&& current_client->invincible_framenum < level.framenum)
{
if (rand()&1)
gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/burn1.wav"), 1, ATTN_NORM, 0);
else
gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/burn2.wav"), 1, ATTN_NORM, 0);
current_player->pain_debounce_time = level.time + 1;
}
if (envirosuit) // take 1/3 damage with envirosuit
T_Damage (current_player, world, world, vec3_origin, current_player->s.origin, vec3_origin, 1*waterlevel, 0, 0, MOD_LAVA);
else
T_Damage (current_player, world, world, vec3_origin, current_player->s.origin, vec3_origin, 3*waterlevel, 0, 0, MOD_LAVA);
}
if (current_player->watertype & CONTENTS_SLIME)
{
if (!envirosuit)
{ // no damage from slime with envirosuit
T_Damage (current_player, world, world, vec3_origin, current_player->s.origin, vec3_origin, 1*waterlevel, 0, 0, MOD_SLIME);
}
}
}
}
/*
===============
B_DamageFeedback
Handles color blends and view kicks
===============
*/
void B_DamageFeedback (edict_t *player)
{
gclient_t *client;
float realcount, count;
int r, l;
client = player->client;
if(player->deadflag) return;
// total points of damage shot at the player this frame
count = (client->damage_blood + client->damage_armor + client->damage_parmor);
if (count == 0)
return; // didn't take any damage
// start a pain animation if still in the player model
if (client->anim_priority < ANIM_PAIN && player->s.modelindex == MAX_MODELS-1)
{
static int i;
client->anim_priority = ANIM_PAIN;
if (client->ps.pmove.pm_flags & PMF_DUCKED)
{
player->s.frame = FRAME_crpain1-1;
client->anim_end = FRAME_crpain4;
}
else
{
i = (i+1)%3;
switch (i)
{
case 0:
player->s.frame = FRAME_pain101-1;
client->anim_end = FRAME_pain104;
break;
case 1:
player->s.frame = FRAME_pain201-1;
client->anim_end = FRAME_pain204;
break;
case 2:
player->s.frame = FRAME_pain301-1;
client->anim_end = FRAME_pain304;
break;
}
}
}
realcount = count;
if (count < 10)
count = 10; // always make a visible effect
// play an apropriate pain sound
if ((level.time > player->pain_debounce_time) && !(player->flags & FL_GODMODE) && (client->invincible_framenum <= level.framenum))
{
r = 1 + (rand()&1);
player->pain_debounce_time = level.time + 0.7;
if (player->health < 25)
l = 25;
else if (player->health < 50)
l = 50;
else if (player->health < 75)
l = 75;
else
l = 100;
gi.sound (player, CHAN_VOICE, gi.soundindex(va("*pain%i_%i.wav", l, r)), 1, ATTN_NORM, 0);
}
//
// clear totals
//
client->damage_blood = 0;
client->damage_armor = 0;
client->damage_parmor = 0;
client->damage_knockback = 0;
}
/*
=================
BotEndServerFrame
Called for each player at the end of the server frame
and right after spawning
=================
*/
void BotEndServerFrame (edict_t *ent)
{
float bobtime;
vec3_t v;
current_player = ent;
current_client = ent->client;
//
// 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]*8.0;
// current_client->ps.pmove.velocity[i] = ent->velocity[i]*8.0;
// }
//
// If the end of unit layout is displayed, don't give
// the player any normal movement attributes
//
if (level.intermissiontime)
{
// FIXME: add view drifting here?
// current_client->ps.blend[3] = 0;
current_client->ps.fov = 90;
G_SetStats (ent);
return;
}
AngleVectors (ent->client->v_angle, forward, right, up);
// burn from lava, etc
B_WorldEffects (ent);
//
// set model angles from view angles so other things in
// the world can tell which direction you are looking
//
/* if (ent->client->v_angle[PITCH] > 180)
ent->s.angles[PITCH] = (-360 + ent->client->v_angle[PITCH])/3;
else
ent->s.angles[PITCH] = ent->client->v_angle[PITCH]/3;
ent->s.angles[YAW] = ent->client->v_angle[YAW];
ent->s.angles[ROLL] = 0SV_CalcRoll (ent->s.angles, ent->velocity)*4;
*/
ent->s.angles[ROLL] = 0;
//
// calculate speed and cycle to be used for
// all cyclic walking effects
//
// xyspeed = sqrt(ent->velocity[0]*ent->velocity[0] + ent->velocity[1]*ent->velocity[1]);
VectorSubtract(ent->s.origin,ent->s.old_origin,v);
v[2] = 0;
xyspeed = VectorLength(v) * 10;
if (xyspeed < 5)
{
bobmove = 0;
current_client->bobtime = 0; // start at beginning of cycle again
}
else if (ent->groundentity)
{ // so bobbing only cycles when on ground
if (xyspeed > 210)
bobmove = 0.25;
else if (xyspeed > 100)
bobmove = 0.125;
else
bobmove = 0.0625;
}
bobtime = (current_client->bobtime += bobmove);
if (current_client->ps.pmove.pm_flags & PMF_DUCKED)
bobtime *= 4;
bobcycle = (int)bobtime;
bobfracsin = fabs(sin(bobtime*M_PI));
// detect hitting the floor
P_FallingDamage (ent);
// apply all the damage taken this frame
B_DamageFeedback (ent);
if(!ent->deadflag)
{
// determine the view offsets
SV_CalcViewOffset (ent);
// determine the gun offsets
SV_CalcGunOffset (ent);
// determine the full screen color blend
// must be after viewoffset, so eye contents can be
// accurately determined
// FIXME: with client prediction, the contents
// should be determined by the client
// SV_CalcBlend (ent);
G_SetClientEvent (ent);
G_SetClientEffects (ent);
G_SetClientSound (ent);
}
if(ent->deadflag)
{
G_SetClientEffects (ent);
ent->client->anim_priority = ANIM_DEATH;
if(ent->s.modelindex != skullindex && ent->s.modelindex != headindex)
{
if(ent->s.frame < ent->client->anim_end) G_SetClientFrame (ent);
}
else ent->s.frame = 0;
}
else G_SetClientFrame (ent);
VectorCopy (ent->velocity, ent->client->oldvelocity);
// VectorCopy (ent->client->ps.viewangles, ent->client->oldviewangles);
// clear weapon kicks
VectorClear (ent->client->kick_origin);
VectorClear (ent->client->kick_angles);
// if the scoreboard is up, update it
/* if (ent->client->showscores && !(level.framenum & 31) )
{
//ZOID
if (ent->client->menu) {
PMenu_Update(ent);
} else
//ZOID
DeathmatchScoreboardMessage (ent, ent->enemy);
gi.unicast (ent, false);
}*/
}