quakec/source/server/weapons/weapon_core.qc
Peter0x44 572654a2f3 SERVER: Refactor W_Give_Ammo, ensure only the needed ammo gets added to the magazine
The previous logic added the whole reserve to the magazine, which could
overfill magazines when the reserve didn't have enough to fill both guns
2025-02-09 07:37:04 +00:00

2195 lines
55 KiB
C++

/*
server/weapons/weapon_core.qc
weapon logic
Copyright (C) 2021-2024 NZ:P Team
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:
Free Software Foundation, Inc.
59 Temple Place - Suite 330
Boston, MA 02111-1307, USA
*/
void() W_PutOut;
float(entity who, float weapon) Zombie_HeadCanGib;
void(float side) W_Reload;
void() GrenadeExplode;
void() W_SprintStart;
#ifdef FTE
void() AI_SetAllEnemiesBBOX;
void() AI_RevertEnemySolidState
float() push_away_zombies;
#endif // FTE
//
// W_HideCrosshair(person)
// Toggles off the crosshair for provided client.
// This is considered "nasty" by use of stuffcmd,
// however non-FTE has no way of better controlling
// cvars for individual clients.
//
void(entity person) W_HideCrosshair =
{
if (person == world)
return;
stuffcmd(person, "crosshair 0\n");
}
//
// W_ShowCrosshair(person)
// Toggles on the crosshair for provided client.
// This is considered "nasty" by use of stuffcmd,
// however non-FTE has no way of better controlling
// cvars for individual clients.
//
void(entity person) W_ShowCrosshair =
{
if (person == world)
return;
// Grab the type of crosshair
float crosshair_type;
// Grenades have a special crosshair type that can pulse.
if (person.grenade_delay) {
crosshair_type = 4;
} else
crosshair_type = WepDef_GetWeaponCrosshairType(person.weapon);
string crosshair_string = "crosshair ";
crosshair_string = strzone(strcat(crosshair_string, ftos(crosshair_type)));
stuffcmd(person, strcat(crosshair_string, "\n"));
strunzone(crosshair_string);
}
void() ReturnWeaponModel =
{
self.weaponmodel = GetWeaponModel(self.weapon, 0);
if (IsDualWeapon(self.weapon)) {
self.weapon2model = GetLeftWeaponModel(self.weapon);
} else if (self.weapon == W_KAR_SCOPE || self.weapon == W_HEADCRACKER) {
self.weapon2model = "models/weapons/kar/v_karscope.mdl";
}
// Always try to reload after any action.
if (self.weapons[0].weapon_magazine == 0 && self.weapons[0].weapon_reserve != 0)
W_Reload(S_RIGHT);
if (IsDualWeapon(self.weapon) && self.weapons[0].weapon_magazine_left == 0 && self.weapons[0].weapon_reserve != 0)
W_Reload(S_LEFT);
// If the person is swapping, play the sprint anim if they're sprinting after swap. Otherwise it plays idle
if (self.sprinting == TRUE)
W_SprintStart();
W_ShowCrosshair(self);
// Slight rumble whenever we pull our weapon out again.
nzp_rumble(self, 1000, 1200, 75);
}
void() W_PlayTakeOut =
{
W_HideCrosshair(self);
Weapon_PlayViewModelAnimation(ANIM_TAKE_OUT, ReturnWeaponModel, 0);
}
.float scopetime;
void() W_AimIn =
{
if (WepDef_DoesNotADS(self.weapon))
return;
if ((self.stance == PLAYER_STANCE_PRONE) && self.velocity)
return;
if (IsDualWeapon(self.weapon) ||
self.reload_delay > time ||
self.knife_delay > time ||
self.new_anim_stop) {
return;
}
if (self.sprinting) {
W_SprintStop();
// FIXME: When we eventually allow custom delay values
// for every weapon animation, this hardcoded frame
// timer can be removed.
float sprint_fire_delay = fabs(GetFrame(self.weapon, SPRINT_OUT_END) - GetFrame(self.weapon, SPRINT_OUT_START))/10;
self.fire_delay = self.fire_delay2 = time + sprint_fire_delay;
}
float ads_frame = GetFrame(self.weapon, AIM_IN);
if (ads_frame != 0 && self.fire_delay < time) {
self.weaponframe_end = self.weaponframe = ads_frame;
} else if (self.zoom) {
return;
}
if (WepDef_HasSniperScore(self.weapon))
self.scopetime = time + 0.2;
// Even if the weapon is scoped, we should still initialize
// the Zoom factor of one so it begins moving into position.
self.zoom = 1;
}
void() W_AimOut =
{
if (!self.zoom ||
self.new_anim_stop ||
self.sprinting) {
return;
}
if (self.weapon == W_KAR_SCOPE || self.weapon == W_PTRS ||
self.weapon == W_HEADCRACKER || self.weapon == W_PENETRATOR) {
ReturnWeaponModel();
}
self.zoom = 0;
// Update sniper zoom-out time instantly
self.scopetime = 0;
// Always set ads_release to false, to avoid complicating
// actions where the player is forced to Aim Out with
// Toggled ADS.
self.ads_release = false;
}
void() W_SprintStop =
{
if (self.isBuying || !self.sprinting)
return;
Weapon_PlayViewModelAnimation(ANIM_SPRINT_STOP, ReturnWeaponModel, 0);
// Run Walk for a few frames to simulate an ease in velocity
PAnim_Walk6();
self.zoom = 0;
self.tp_anim_time = 0;
self.sprinting = 0;
self.into_sprint = 0;
self.reload_delay2 = self.fire_delay2 = self.reload_delay = self.fire_delay = 0;
self.sprint_stop_time = time;
self.sprint_duration = self.sprint_timer;
}
void W_SprintStart () {
if (self.speed_penalty_time > time || self.zoom != 0)
return;
self.sprint_start_time = time;
if (self.sprint_rest_time > sprint_max_time)
self.sprint_duration = 0.0;
else
self.sprint_duration -= self.sprint_rest_time;
if (!self.sprintflag) {
return;
}
if (self.fire_delay > time ||
self.fire_delay2 > time ||
self.new_anim_stop ||
self.new_anim2_stop ||
self.isBuying ||
self.downed ||
!(self.flags & FL_ONGROUND) ||
self.sprint_delay > time) {
return;
}
Weapon_PlayViewModelAnimation(ANIM_SPRINT_START, ContinueRun, 0);
self.zoom = 3;
self.sprint_delay = time + 1;
self.sprinting = true;
self.reload_delay2 = self.fire_delay2 = self.reload_delay = self.fire_delay = 0;
}
void() W_PutOutHack =
{
Weapon_SwapWeapons(true);
}
void() W_PutOut =
{
// We don't hold more than one weapon
if (self.weapons[1].weapon_id == 0 || self.downed ||
self.switch_delay > time)
return;
float duration = getWeaponDelay(self.weapon, PUTOUT);
self.switch_delay = duration + time;
self.semi_actions |= SEMIACTION_WEAPONSWAP;
W_AimOut();
W_HideCrosshair(self);
if (self.stance == 2)
PAnim_Swap();
if (self.weapon_count != 1 && !self.new_anim_stop)
Weapon_PlayViewModelAnimation(ANIM_PUT_AWAY, W_PutOutHack, duration);
}
void() W_TakeOut =
{
self.isBuying = false;
float duration = getWeaponDelay(self.weapon, TAKEOUT);
self.switch_delay = duration + time;
W_AimOut();
W_HideCrosshair(self);
Weapon_PlayViewModelAnimation(ANIM_TAKE_OUT, ReturnWeaponModel, duration);
}
//RELOAD
void(float side) W_Give_Ammo =
{
// Prevent ammo duplication when we have a low reserve
// amount when attempting to reload a Dual Wield weapon.
if (self.weapons[0].weapon_reserve == 0)
return;
float ammo_needed, max_mag, loadammo;
max_mag = getWeaponMag(self.weapon);
// Determine how much ammo is needed to reload the selected side
if (side == S_LEFT) {
ammo_needed = max_mag - self.weapons[0].weapon_magazine_left;
} else {
ammo_needed = max_mag - self.weapons[0].weapon_magazine;
}
// Ensure we don't take more ammo than is in the reserve
if (ammo_needed > self.weapons[0].weapon_reserve)
{
loadammo = self.weapons[0].weapon_reserve;
// Deplete reserve
self.weapons[0].weapon_reserve = 0;
}
else
{
// Load the ammo needed to fill the magazine, and subtract it from the reserve
loadammo = ammo_needed;
self.weapons[0].weapon_reserve -= ammo_needed;
}
// Apply ammo load to the correct side
if (side == S_LEFT) {
self.weapons[0].weapon_magazine_left += loadammo;
} else {
self.weapons[0].weapon_magazine += loadammo;
}
};
void () W_LoadAmmo;
void() ContinueReload = //Special reloads
{
if (self.new_anim_stop)
return;
if (self.weapon == W_GUT && self.weapons[0].weapon_magazine == 10)
return;
W_HideCrosshair(self);
float delay = 0;
string modelname = GetWeaponModel(self.weapon, 0);
float startframe = 0;
float endframe = 0;
void(optional float t) endanimfunc = SUB_Null;
if (self.weapons[0].weapon_magazine >= getWeaponMag(self.weapon) || !self.weapons[0].weapon_reserve || self.reloadinterupted) {
if (self.weapon == W_KAR_SCOPE || self.weapon == W_HEADCRACKER)
{
delay = 1;
startframe = 24;
endframe = 28;
} else if (self.weapon == W_TRENCH || self.weapon == W_GUT) {
delay = 0.5;
startframe = 24;
endframe = 26;
endanimfunc = W_LoadAmmo;
}
self.reloadinterupted = FALSE;
} else if (self.weapons[0].weapon_magazine < getWeaponMag(self.weapon)) {
if (self.weapon == W_KAR_SCOPE || self.weapon == W_HEADCRACKER) {
self.weapons[0].weapon_magazine++;
self.weapons[0].weapon_reserve = self.weapons[0].weapon_reserve - 1;
delay = 0.8;
startframe = 19;
endframe = 24;
endanimfunc = ContinueReload;
} else if (self.weapon == W_TRENCH || self.weapon == W_GUT) {
if (self.weapon == W_GUT && self.weapons[0].weapon_reserve >= 2 && self.weapons[0].weapon_magazine < 9) {
self.weapons[0].weapon_magazine = self.weapons[0].weapon_magazine + 2;
self.weapons[0].weapon_reserve = self.weapons[0].weapon_reserve - 2;
} else {
self.weapons[0].weapon_magazine++;
self.weapons[0].weapon_reserve = self.weapons[0].weapon_reserve - 1;
}
delay = 0.5;
startframe = 18;
endframe = 23;
endanimfunc = ContinueReload;
}
}
if (delay) {
if (self.perks & P_SPEED) {
delay *= 0.5;
}
self.reload_delay = time + delay;
Set_W_Frame (startframe, endframe, delay, 0, RELOAD, endanimfunc, modelname, false, S_RIGHT, false);
}
}
void(float side) W_AdvanceAnim =
{
float startframe, endframe, delay, reloadcancelframe;
string modelname;
startframe = GetFrame(self.weapon, RELOAD_START);
endframe = GetFrame(self.weapon, RELOAD_END);
delay = getWeaponDelay(self.weapon, RELOAD);
reloadcancelframe = GetFrame(self.weapon, RELOAD_CANCEL);
if (self.perks & P_SPEED) delay *= 0.5;
modelname = GetWeaponModel(self.weapon, 0);
self.reload_delay = self.reload_delay2 = delay + time;
Set_W_Frame (startframe, endframe, delay, reloadcancelframe, RELOAD, W_Give_Ammo, modelname, false, side, false);
}
void(float side) W_Reload =
{
if (self.zoom == 2)
return;
SetUpdate(self, UT_HUD, 7, 0, 0);
if (GetFiretype(self.weapon) == FIRETYPE_FLAME)
return;
if (side == S_BOTH) {
W_Reload(S_RIGHT);
if (IsDualWeapon(self.weapon)) {
W_Reload(S_LEFT);
}
return;
}
if (side == S_RIGHT &&
(self.reload_delay > time ||
self.new_anim_stop ||
!self.weapons[0].weapon_reserve ||
self.weapons[0].weapon_magazine >= getWeaponMag(self.weapon))){
return;
}
if (side == S_LEFT &&
(self.reload_delay2 > time ||
self.new_anim2_stop ||
!self.weapons[0].weapon_reserve ||
self.weapons[0].weapon_magazine_left >= getWeaponMag(self.weapon))) {
return;
}
self.zoom = false;
W_AimOut();
W_SprintStop();
// If it's a dual wielded weapon, we don't want to hide the crosshair
// unless both weapons are being reloaded.
if (!IsDualWeapon(self.weapon) || (self.reload_delay > time || self.reload_delay2 > time))
W_HideCrosshair(self);
float endframe, startframe, reloadcancelframe, delay;
string modelname = "";
if (side == S_RIGHT) {
modelname = GetWeaponModel(self.weapon, 0);
} else {
if (IsDualWeapon(self.weapon)) {
modelname = GetLeftWeaponModel(self.weapon);
}
}
if (self.weapons[0].weapon_reserve)
{
switch(self.stance) {
case 2: PAnim_Reload(); break;
case 1: PAnim_CrReload(); break;
case 0: if (!self.downed) PAnim_PrReload(); break;
}
startframe = GetFrame(self.weapon,RELOAD_START);
endframe = GetFrame(self.weapon,RELOAD_END);
reloadcancelframe = GetFrame(self.weapon,RELOAD_CANCEL);
delay = getWeaponDelay(self.weapon,RELOAD);
void(optional float t) endanimfunc = SUB_Null;
if (!reloadcancelframe)
reloadcancelframe = endframe;
if (self.weapon == W_KAR_SCOPE || self.weapon == W_HEADCRACKER ){
startframe = 14;
endframe = 18;
reloadcancelframe = 0;
delay = 0.8;
endanimfunc = ContinueReload;
}
// Check for special reload type
else if (GetFrame(self.weapon, RELOAD_EMPTY_START) != 0) {
if (self.weapons[0].weapon_magazine > 0) {
startframe = GetFrame(self.weapon, RELOAD_PART_START);
endframe = GetFrame(self.weapon, RELOAD_PART_END);
delay = getWeaponDelay(self.weapon, RELOAD_PAR);
} else {
startframe = GetFrame(self.weapon, RELOAD_EMPTY_START);
endframe = GetFrame(self.weapon, RELOAD_EMPTY_END);
delay = getWeaponDelay(self.weapon, RELOAD_EMP);
}
endanimfunc = W_AdvanceAnim;
}
else if (self.weapon == W_TRENCH || self.weapon == W_GUT) {
// cypress -- world at war depicts the trench gun as needing to
// pump after reload regardless of context, so i removed a reference
// that would only do it if the mag was empty. tradeoff: now we're
// pumping 2 shells away instead of 1 if not empty. oh well.
self.NeedLoad = true;
startframe = 14;
endframe = 17;
reloadcancelframe = 0;
delay = 0.8;
endanimfunc = ContinueReload;
} else {
endanimfunc = W_Give_Ammo;
}
if (self.perks & P_SPEED)
delay = delay*0.5;
if (side == S_RIGHT) {
self.reload_delay = delay + time;
} else {
self.reload_delay2 = delay + time;
}
// Sometimes we hit a race condition of sorts where if we have a weapon
// designed not to fill the magazine until the end of the reload,
// reload_delay is improperly zero because of server tick rate.
// Firing while this occurs causes the reload to cancel and the mag to not
// fill. So we delay when we can action again to ensure we reach the mag
// fill state. -- cypress (30 Nov 2024)
if (endframe == reloadcancelframe) {
self.reload_delay += 0.5;
self.reload_delay2 += 0.5;
}
Set_W_Frame (startframe, endframe, delay, reloadcancelframe, RELOAD, endanimfunc, modelname, false, side, false);
}
if (self.weapon != W_TRENCH) {
self.NeedLoad = false;
}
};
void () W_LoadAmmoDone =
{
self.NeedLoad = false;
}
void () W_LoadAmmo =
{
if (!self.NeedLoad)
return;
if (!self.weapons[0].weapon_magazine)
{
W_Reload(S_BOTH);
return;
}
string modelname;
float endframe, startframe, reloadcancelframe, delay;
modelname = GetWeaponModel(self.weapon, 0);
startframe = GetFrame(self.weapon,RELOAD_START);
endframe = GetFrame(self.weapon,RELOAD_END);
reloadcancelframe = GetFrame(self.weapon,RELOAD_CANCEL);
delay = getWeaponDelay(self.weapon,RELOAD);
void(optional float t) endanimfunc = SUB_Null;
if (self.weapon == W_TRENCH || self.weapon == W_GUT)
{
startframe = 4;
endframe = 14;
reloadcancelframe = 12;
delay = 0.9;
endanimfunc = W_LoadAmmoDone;
}
else if (self.weapon == W_KAR || self.weapon == W_ARMAGEDDON || self.weapon == W_KAR_SCOPE || self.weapon == W_HEADCRACKER || self.weapon == W_SPRING || self.weapon == W_PULVERIZER)
{
startframe = 4;
endframe = 13;
reloadcancelframe = 9;
delay = 1.2;
endanimfunc = W_LoadAmmoDone;
}
if (delay) {
if (self.perks & P_DOUBLE)
delay *= 0.66;
Set_W_Frame (startframe, endframe, delay, reloadcancelframe, FIRE, W_LoadAmmoDone, modelname, false, S_RIGHT, false);
self.fire_delay = delay + time;
}
}
//
// WeaponCore_CheckForReload()
// Determines if weapon should automatically
// reload after firing, or if weapon is empty
// and needs swapped out.
//
void() WeaponCore_CheckForReload =
{
// Save some array lookup time -- copy into temporary structs.
float wep_cur_mag = self.weapons[0].weapon_magazine;
float wep_cur_mag2 = self.weapons[0].weapon_magazine_left;
float wep_cur_res = self.weapons[0].weapon_reserve;
float wep_nex_mag = self.weapons[1].weapon_magazine;
float wep_nex_mag2 = self.weapons[1].weapon_magazine_left;
float wep_nex_res = self.weapons[1].weapon_reserve;
// Check if left weapon needs reloaded
if (!wep_cur_mag2 && IsDualWeapon(self.weapon)) {
W_Reload(S_LEFT);
}
// Check if right weapon needs reloaded
if (!wep_cur_mag && wep_cur_res) {
W_Reload(S_RIGHT);
}
// Check if a weapon swap is necessary
if (!wep_cur_mag && !wep_cur_res && !wep_cur_mag2 &&
(wep_nex_mag || wep_nex_mag2 || wep_nex_res)) {
W_PutOut();
}
}
/*
================
SpawnBlood
================
*/
void(vector org, vector vel, float damage) SpawnBlood =
{
local entity f;
#ifdef FTE
FTE_RunParticleEffect(world, CSQC_PART_BLOODIMPACT, org, 0, world);
#else
particle (org, vel*0.1, 73, damage*2);
#endif // FTE
f = find (world, classname, "player");
f.hitcount = f.hitcount + 1;
};
void(vector where) spawn_gibs = {
#ifndef FTE
particle(where, where*0.1, 225, 1);
#else
FTE_RunParticleEffect(world, CSQC_PART_ZOMBIEGIB, where, 0, world);
#endif // FTE
}
void Parse_Damage () = // DO NOT TOUCH
{
entity ent;
float total_dmg;
entity body_ent;
float head_hit;
//warn
head_hit = 0;
ent = findfloat (world, washit, 1);
while (ent) {
if (ent.classname != "radio" && ent.classname != "explosive_barrel") {
if (ent.classname == "ai_zombie_head")
head_hit = 1;
if (ent.classname != "ai_zombie" && ent.classname != "ai_dog") //limb
body_ent = ent.owner;
else
body_ent = ent;
total_dmg = body_ent.hitamount;
if (body_ent.head.washit) {
total_dmg += body_ent.head.hitamount;
body_ent.head.health -= body_ent.head.hitamount;
head_hit = 1;
// Check if we should Gib the head, before this would
// be a really weird check to "ressurect" a zombie if
// it was out of health. I *think* this is more accurate
// now. -- cypress (5 nov 2023)
if (Zombie_HeadCanGib(body_ent, self.weapon)) {
makevectors(body_ent.head.owner.angles);
vector where = body_ent.origin + (body_ent.head.view_ofs_x * v_right) + (body_ent.head.view_ofs_y * v_forward) + (body_ent.head.view_ofs_z * v_up);
spawn_gibs(where);
body_ent.head.deadflag = false;
body_ent.head.solid = SOLID_NOT;
setmodel(body_ent.head, "");
body_ent.head.frame = 0;
body_ent.usedent = self;
body_ent.bleedingtime = time + 1;
#ifndef FTE
updateLimb (body_ent.head.owner, 0, world);
#endif // FTE
}
body_ent.head.washit = 0;
body_ent.head.hitamount = 0;
}
if (body_ent.larm.washit) {
total_dmg += body_ent.larm.hitamount;
body_ent.larm.health -= body_ent.larm.hitamount;
if (WepDef_WeaponCanGibEnemy(self.weapon) && body_ent.larm.owner.rarm.deadflag && body_ent.larm.deadflag)
{
makevectors(body_ent.larm.owner.angles);
where = body_ent.larm.owner.origin + (body_ent.larm.view_ofs_x * v_right) + (body_ent.larm.view_ofs_y * v_forward) + (body_ent.larm.view_ofs_z * v_up);
spawn_gibs(where);
body_ent.larm.deadflag = false;
body_ent.larm.solid = SOLID_NOT;
setmodel(body_ent.larm,"");
body_ent.larm.frame = 0;
#ifndef FTE
updateLimb (body_ent.larm.owner, 1, world);
#endif // FTE
}
body_ent.larm.washit = 0;
body_ent.larm.hitamount = 0;
}
if (body_ent.rarm.washit) {
total_dmg += body_ent.rarm.hitamount;
body_ent.rarm.health -= body_ent.rarm.hitamount;
if (WepDef_WeaponCanGibEnemy(self.weapon) && body_ent.rarm.owner.larm.deadflag && body_ent.rarm.deadflag) {
makevectors(body_ent.rarm.owner.angles);
where = body_ent.rarm.owner.origin + (body_ent.rarm.view_ofs_x * v_right) + (body_ent.rarm.view_ofs_y * v_forward) + (body_ent.rarm.view_ofs_z * v_up);
spawn_gibs(where);
body_ent.rarm.deadflag = false;
body_ent.rarm.solid = SOLID_NOT;
setmodel(body_ent.rarm,"");
body_ent.rarm.frame = 0;
#ifndef FTE
updateLimb (body_ent.rarm.owner, 2, world);
#endif // FTE
}
body_ent.rarm.washit = 0;
body_ent.rarm.hitamount = 0;
}
if (instakill_finished > time) {
if(body_ent.head.deadflag) {
makevectors(body_ent.head.owner.angles);
where = body_ent.origin + (body_ent.head.view_ofs_x * v_right) + (body_ent.head.view_ofs_y * v_forward) + (body_ent.head.view_ofs_z * v_up);
spawn_gibs(where);
body_ent.head.deadflag = false;
body_ent.head.solid = SOLID_NOT;
setmodel(body_ent.head,"");
body_ent.head.frame = 0;
#ifndef FTE
updateLimb (body_ent.head.owner, 0, world);
#endif // FTE
}
}
if (head_hit)
DamageHandler(body_ent,self, total_dmg, DMG_TYPE_HEADSHOT);
else {
if (body_ent.hit_region == HIT_REGION_TORSO_LOWER)
DamageHandler(body_ent,self, total_dmg, DMG_TYPE_LOWERTORSO);
else
DamageHandler(body_ent,self, total_dmg, DMG_TYPE_UPPERTORSO);
}
if (body_ent != world) {
body_ent.washit = 0;
body_ent.hitamount = 0;
body_ent.hit_region = 0;
}
}
ent = findfloat (ent, washit, 1);
}
}
void(float damage, vector dir, vector org, vector plane, entity hit_ent, float side) TraceAttack =
{
vector vel;
float f_damage = 0;
vel = normalize(dir);
vel = vel + 2*plane;
vel = vel * 200;
if (hit_ent.takedamage) {
if (trace_fraction >= 1) {
return;
}
if(hit_ent.classname == "item_radio" || hit_ent.classname == "teddy_spawn")
{
entity oldself;
oldself = self;
self = hit_ent;
self.th_die();
self = oldself;
return;
} else if (hit_ent.classname == "explosive_barrel") {
hit_ent.enemy = self;
oldself = self;
self = hit_ent;
self.health = self.health - damage;
Barrel_Hit();
self = oldself;
return;
} else if (hit_ent.solid == SOLID_BSP) {
oldself = self;
self = hit_ent;
self.health -= damage;
self.enemy = oldself;
if (self.health < 0)
self.th_die();
self = oldself;
return;
}
SpawnBlood (org, vel, damage*10);
switch(hit_ent.classname) {
case "ai_zombie_head":
f_damage = damage*getWeaponMultiplier(self.weapon, HEAD_X);
hit_ent.hit_region = HIT_REGION_HEAD;
break;
case "ai_zombie_larm":
case "ai_zombie_rarm":
f_damage = damage*getWeaponMultiplier(self.weapon, LIMBS_X);
hit_ent.hit_region = HIT_REGION_ARM;
break;
case "ai_zombie":
if (trace_endpos_z < hit_ent.origin_z) {
f_damage = damage*getWeaponMultiplier(self.weapon, LOWER_TORSO_X);
hit_ent.hit_region = HIT_REGION_TORSO_LOWER;
}
else {
f_damage = damage*getWeaponMultiplier(self.weapon, UPPER_TORSO_X);
hit_ent.hit_region = HIT_REGION_TORSO_UPPER;
}
break;
default:
f_damage = damage;
break;
}
hit_ent.washit = 1;
hit_ent.hitamount = hit_ent.hitamount + f_damage;
} else {
#ifndef FTE
WriteByte (MSG_BROADCAST, SVC_TEMPENTITY);
WriteByte (MSG_BROADCAST, TE_GUNSHOT);
WriteCoord (MSG_BROADCAST, org_x);
WriteCoord (MSG_BROADCAST, org_y);
WriteCoord (MSG_BROADCAST, org_z);
#endif // FTE
}
#ifdef FTE
FTE_RunParticleEffect(self, CSQC_PART_MUZZLEFLASH, org, side, hit_ent);
#endif // FTE
};
void (float shotcount, float sprd, float Damage, float side) FireTrace =
{
float count, r, ru, penetration_times, do_break;
vector dir, src, trace_start_org;
entity hitent;
count = 0;
makevectors(self.v_angle);
src = self.origin + self.view_ofs;
CL_SendWeaponFire(self, self.weapon);
while (count < shotcount)
{
dir = aim (self,0);
r = random() * sprd*10;
if (random() < 0.5)
r = r*-1;
ru = random() * sprd*10;
if (random() < 0.5)
ru = ru*-1;
dir = dir*2048;
dir = dir + v_right*r + v_up*ru;
trace_start_org = src;
trace_ent = self;
do_break = 0;
penetration_times = 0;
hitent = world;
while (!do_break && random() < getWeaponPenetration(self.weapon, penetration_times))
{
#ifdef FTE
AI_SetAllEnemiesBBOX();
#endif // FTE
traceline (trace_start_org, trace_start_org + dir, MOVE_HITMODEL, trace_ent);
#ifdef FTE
AI_RevertEnemySolidState();
#endif // FTE
if (trace_fraction != 1.0)
TraceAttack (Damage, dir, trace_endpos, trace_plane_normal, trace_ent, side);
trace_start_org = trace_endpos;
penetration_times++;
if (!trace_ent.takedamage)
do_break = 1;
if (trace_ent.classname == "ai_zombie_head" || trace_ent.classname == "ai_zombie_larm" || trace_ent.classname == "ai_zombie_rarm")
hitent = trace_ent.owner;
else
hitent = trace_ent;
}
hitent = world;
count++;
}
if (self.weapon != W_TESLA)
Parse_Damage();
}
void(float side) W_Fire =
{
if (GetFiretype(self.weapon) == FIRETYPE_FLAME && self.cooldown)
return;
if (self.switch_delay > time || self.dive || self.isBuying)
return;
//First check that we can actualy fire
if (side == S_RIGHT &&
(time < self.fire_delay ||
self.new_anim_stop ||
self.reload_delay > time || !self.weapons[0].weapon_magazine)) {
return;
}
if (side == S_LEFT &&
(time < self.fire_delay2 ||
self.new_anim2_stop ||
self.reload_delay2 > time)) {
return;
}
if (self.sprinting) {
W_SprintStop();
return;
}
float startframe = 0;
float endframe = 0;
float firetype;
float damage;
float shotcount;
string modelname = "";
string soundname;
float spread;
float delay;
//Update the basic vectors
makevectors(self.v_angle);
//make sure magazine is loading
if (!self.weapons[0].weapon_magazine && side == S_RIGHT)
{
W_Reload(S_RIGHT);
return;
} else if (!self.weapons[0].weapon_magazine_left && side == S_LEFT)
{
W_Reload(S_LEFT);
return;
}
//Dont fire if the gun has to cycle
if (self.NeedLoad && (self.weapon == W_TRENCH || self.weapon == W_GUT || self.weapon == W_KAR_SCOPE || self.weapon == W_KAR || self.weapon == W_ARMAGEDDON || self.weapon == W_HEADCRACKER || self.weapon == W_SPRING || self.weapon == W_PULVERIZER))
{
W_LoadAmmo();
return;
}
if (self.downed)
PAnim_LastFire();
else {
switch(self.stance) {
case 2: PAnim_Fire(); break;
case 1: PAnim_CrouchFire(); break;
case 0: break;
}
}
//get some basic info
damage = getWeaponDamage(self.weapon);
if (global_trace_damage_multiplier != 0)
damage *= global_trace_damage_multiplier;
firetype = GetFiretype (self.weapon);
if (side == S_RIGHT) {
modelname = GetWeaponModel(self.weapon, 0);
} else {
if (IsDualWeapon(self.weapon)) {
modelname = GetLeftWeaponModel(self.weapon);
}
}
shotcount = GetWeaponShotcount(self.weapon);
soundname = GetWeaponSound(self.weapon);
delay = getWeaponDelay(self.weapon, FIRE);
// Double Tap 2.0
if (self.perks & P_DOUBLE && self.has_doubletap_damage_buff)
shotcount *= 2;
spread = WepDef_WeaponSpread(self.weapon, self.stance);
if (self.perks & P_DEAD) {
spread *= 0.65;
}
if (self.zoom == 1 && W_SpreadAffectedByADS(self.weapon)) {
spread *= 0.02;
} else if (self.zoom == 2) {
if (W_HighPrecisionWhenADS(self.weapon))
spread = 0;
else
spread *= 0.15;
}
// Check if weapon is semi-automatic and if it is, if it's ready to be fired.
if (Weapon_IsSemiAutomatic(self.weapon)) {
if (side == S_RIGHT) {
if (self.semi_actions & SEMIACTION_FIRE_RIGHT) return;
self.semi_actions |= SEMIACTION_FIRE_RIGHT;
} else if (side == S_LEFT) {
if (self.semi_actions & SEMIACTION_FIRE_LEFT) return;
self.semi_actions |= SEMIACTION_FIRE_LEFT;
}
}
// Weapon Projectile/Trace Logic.
if (Weapon_FiresTraceshot(self.weapon)) {
#ifdef FTE
self.dimension_hit = HITBOX_DIM_LIMBS | HITBOX_DIM_ZOMBIES;
#endif // FTE
FireTrace(shotcount, spread, damage, side);
#ifdef FTE
self.dimension_hit = HITBOX_DIM_ZOMBIES;
#endif // FTE
}
switch(firetype) {
case FIRETYPE_ROCKET:
W_FireRocket();
break;
case FIRETYPE_GRENADE:
W_FireGrenade(side);
break;
case FIRETYPE_RAYBEAM:
W_FireRay();
break;
case FIRETYPE_TESLA:
W_FireTesla();
break;
case FIRETYPE_FLAME:
W_FireFlame();
break;
default: break;
}
// Play weapon animation and sound
if (GetFrame(self.weapon, FIRE_HOLD) == 0) {
if (self.zoom && GetFrame(self.weapon, AIM_FIRE_START) != 0) {
startframe = GetFrame(self.weapon, AIM_FIRE_START);
endframe = GetFrame(self.weapon, AIM_FIRE_END);
} else {
startframe = GetFrame(self.weapon, FIRE_START);
endframe = GetFrame(self.weapon, FIRE_END);
}
} else {
startframe = endframe = GetFrame(self.weapon, FIRE_HOLD);
}
// Rumble the GamePad a fixed amount when we fire a weapon.
nzp_rumble(self, 1400, 2000, 200);
// Increment the amount of shots fired while downed
if (self.downed == true)
self.teslacount++;
// Cypress -- sources suggest Double Tap decreases fire rate
// by 33% (meaning 66%), but testing has concluded it is instead
// a rate of 23%, or 77% of it's original rate. There could be
// quite a few reasons for this discrepancy, framerate-tied
// weapon firing being one of them (the same reason why in
// World at War, the PPSh-41 is completely unaffected by
// Double Tap), but technically this IS the correct-and
// -reproducible factor due to whatever went wrong there.
if (self.perks & P_DOUBLE) {
delay *= 0.77;
}
// Players shouldn't be allowed to move while firing and prone.
if (self.stance == 0) {
self.speed_penalty = 0.01;
self.speed_penalty_time = time + delay;
}
if (self.weapon == W_GUT || self.weapon == W_KAR_SCOPE || self.weapon == W_TRENCH || self.weapon == W_KAR || self.weapon == W_ARMAGEDDON || self.weapon == W_HEADCRACKER || self.weapon == W_SPRING || self.weapon == W_PULVERIZER)
{
Set_W_Frame (startframe, endframe, delay, 0, FIRE, W_LoadAmmo, modelname, FALSE, side, false);
self.NeedLoad = true;
} else {
Set_W_Frame (startframe, endframe, delay, 0, FIRE, WeaponCore_CheckForReload, modelname, FALSE, side, false);
}
Sound_PlaySound(self, soundname, SOUND_TYPE_WEAPON_FIRE, SOUND_PRIORITY_PLAYALWAYS);
if (side == S_RIGHT) {
self.weapons[0].weapon_magazine = self.weapons[0].weapon_magazine - 1;
self.fire_delay = delay + time;
} else {
self.weapons[0].weapon_magazine_left = self.weapons[0].weapon_magazine_left - 1;
self.fire_delay2 = delay + time;
}
if (IsPapWeapon(self.weapon) && !WepDef_DoesNotPlayUpgradedSound(self.weapon)) {
Sound_PlaySound(self, "sounds/weapons/papfire.wav", SOUND_TYPE_WEAPON_PACK, SOUND_PRIORITY_PLAYALWAYS);
}
SetUpdate(self, UT_HUD, 7, 0, 0);
SetUpdate(self, UT_CROSSHAIR, 5, 0, 0);
// REFORMAT THIS
#ifndef FTE
if (side == S_LEFT)
self.Flash_Offset = WepDef_GetLeftFlashOffset(self.weapon);
else
self.Flash_Offset = GetWeaponFlash_Offset(self.weapon);
self.effects = self.effects | EF_MUZZLEFLASH;
#endif // FTE
}
//
// WeaponCore_Melee()
// Performs the melee action, intelligent
// against type of weaponry and lunge velocity
// applied.
//
#define WEAPONCORE_MELEE_LUNGEMIN 40 // Minimum amount of units away before lunge should be triggered.
void() WeaponCore_Melee =
{
// Do not trigger if we're Aiming down the Sight, or
// already melee-ing, or doing some other action.
if (self.knife_delay > time || self.new_anim_stop ||
self.new_anim2_stop || self.zoom || in_endgame_sequence)
return;
// Perform the third person animation if standing
if (self.stance == PLAYER_STANCE_STAND)
PAnim_Melee();
// Hide crosshair -- irrelevant during melee
W_HideCrosshair(self);
// Stop sprinting if we are
W_SprintStop();
vector applied_velocity = '0 0 0'; // The lunge velocity we intend to apply to the player.
float melee_range = WepDef_GetWeaponMeleeRange(self.weapon); // Returns the range of the traceline to perform for hit detection.
float did_lunge = false;
// Perform the traceline
makevectors(self.v_angle);
vector trace_source = self.origin + self.view_ofs;
traceline(trace_source, trace_source + v_forward * melee_range, 0, self);
if (trace_endpos_z - trace_source_z <= 15) {
// Check if this is a Zombie limb, set to the body if so
if (trace_ent.owner.head == trace_ent || trace_ent.owner.larm == trace_ent || trace_ent.owner.rarm == trace_ent)
trace_ent = trace_ent.owner;
// Target does not take damage -- no need to go any further
if (!trace_ent.takedamage && !(trace_ent.flags & FL_CLIENT)) {
// Hit a solid surface, so play hard melee sound.
if (trace_fraction < 1.0)
Sound_PlaySound(self, "sounds/weapons/knife/knife_hit.wav", SOUND_TYPE_WEAPON_OTHER, SOUND_PRIORITY_PLAYALWAYS);
// Dead air, swing!
else
Sound_PlaySound(self, "sounds/weapons/knife/knife.wav", SOUND_TYPE_WEAPON_OTHER, SOUND_PRIORITY_PLAYALWAYS);
} else {
// AI has a more involved routine than other entities
if (trace_ent.aistatus == "1") {
// Play a flesh-y impact sound, spawn blood.
Sound_PlaySound(self, "sounds/weapons/knife/knife_hitbod.wav", SOUND_TYPE_WEAPON_OTHER, SOUND_PRIORITY_PLAYALWAYS);
SpawnBlood(trace_source + (v_forward * 20), trace_source + (v_forward * 20), 50);
// Calculate distance to use for the Lunge velocity
float lunge_factor = vlen(trace_source - (trace_endpos - v_forward*4));
// Perform lunge, exclusive to AI.
if (lunge_factor > WEAPONCORE_MELEE_LUNGEMIN) {
did_lunge = true;
applied_velocity = v_forward * melee_range * 6;
applied_velocity_z = 0;
}
}
// Some other ent (player, button, etc.)
else {
// Never lunge in this circumstance.
did_lunge = false;
// Is this a player? Play the body hit sound and spawn blood, otherwise, solid swing.
if (trace_ent.flags & FL_CLIENT) {
Sound_PlaySound(self, "sounds/weapons/knife/knife_hitbod.wav", SOUND_TYPE_WEAPON_OTHER, SOUND_PRIORITY_PLAYALWAYS);
SpawnBlood(trace_source + (v_forward * 20), trace_source + (v_forward * 20), 50);
} else {
Sound_PlaySound(self, "sounds/weapons/knife/knife_hit.wav", SOUND_TYPE_WEAPON_OTHER, SOUND_PRIORITY_PLAYALWAYS);
}
}
// Apply damage to the entity.
if (trace_ent.takedamage) {
float melee_damage = WepDef_CalculateMeleeDamage(self.weapon, self.has_bowie_knife);
DamageHandler (trace_ent, self, melee_damage, DMG_TYPE_MELEE);
}
}
}
// Grab animation stats for our melee weapon.
float start_frame = WepDef_GetMeleeFirstFrame(self.weapon, did_lunge, self.has_bowie_knife);
float end_frame = WepDef_GetMeleeLastFrame(self.weapon, did_lunge, self.has_bowie_knife);
float anim_duration = WepDef_GetMeleeAnimDuration(self.weapon, did_lunge, self.has_bowie_knife);
string model_path = WepDef_GetMeleeModel(self.weapon, self.has_bowie_knife);
// Ensure we play the Take Out animation if the melee model is not our active weapon
void() end_func;
if (!WepDef_IsMeleeWeapon(self.weapon)) {
end_func = W_PlayTakeOut;
} else {
end_func = __NULL__;
}
// Apply camera punch, and begin playback.
self.punchangle_x = -5;
self.punchangle_y = -10;
Set_W_Frame (start_frame, end_frame, anim_duration, 0, KNIFE, end_func, model_path, false, S_RIGHT, true);
self.knife_delay = anim_duration + time;
// Now apply the lunge velocity, but only if we're Standing.
if (self.stance == PLAYER_STANCE_STAND && did_lunge)
self.velocity = applied_velocity;
}
/******************************
* W_Grenade *
******************************/
void() switch_nade =
{
if (self.downed || self.isBuying || !(self.grenades & 2) || self.grenade_delay > time)
return;
if (self.pri_grenade_state == 0)
{
centerprint (self, "Bouncing Betties Selected");
self.bk_nade = 1;
}
if (self.pri_grenade_state == 1)
{
centerprint (self, "Frag Grenades Selected");
self.bk_nade = 0;
}
self.pri_grenade_state = self.bk_nade;
};
void() GrenadeExplode =
{
Sound_PlaySound(self, "sounds/weapons/grenade/explode.wav", SOUND_TYPE_WEAPON_EXPLODE, SOUND_PRIORITY_ALLOWSKIP);
if (self.classname == "grenade")
DamgageExplode (self, self.owner, 200, 50, 200);
else if (self.classname == "betty")
DamgageExplode (self, self.owner, 12000, 11000, 128);
else
DamgageExplode (self, self.owner, 1200, 1000, 75);
#ifdef FTE
te_customflash(self.origin, 200, 300, '1 1 1');
#endif // FTE
// Really stupid hack: For betties, let's dramatically increase
// the explosion effect.
if (self.classname == "betty") {
CallExplosion(self.origin);
CallExplosion(self.origin);
CallExplosion(self.origin);
CallExplosion(self.origin);
} else {
CallExplosion(self.origin);
}
if (self.classname != "player")
SUB_Remove();
};
void() NadeStraighten =
{
self.angles_x = (self.angles_x > 0) ? 90 : -90;
if (self.pri_grenade_state == 1)
return;
};
void() Velocity_reduce =
{
// Do one (1) damage on monster contact, to kill when Insta-Kill is active.
if (other.flags & FL_MONSTER)
DamageHandler(other, self.owner, 1, DMG_TYPE_OTHER);
if (!other.solid || other.solid == SOLID_TRIGGER)
if (other != world)
return;
self.velocity = self.velocity*0.5;
NadeStraighten();
};
void() W_ThrowGrenade =
{
string modelname;
float startframe;
float endframe;
local entity nade;
W_HideCrosshair(self);
if (self.pri_grenade_state == 0) {
nade = spawn ();
nade.owner = self;
nade.grenade_delay = nade.owner.grenade_delay;
nade.owner.grenade_delay = 0;
nade.movetype = MOVETYPE_BOUNCE;
nade.solid = SOLID_BBOX;
nade.classname = "grenade";
// set nade speed
makevectors (self.v_angle);
nade.velocity = v_forward*1400;
nade.avelocity = '400 -400 400';
//nade.angles = vectoangles(nade.velocity);
//nade.angles_z += (nade.angles_z + 180 < 360)? 180 : -180;
nade.angles = vectoangles(v_forward);
nade.angles_z -= 15;
// set nade duration
nade.nextthink = nade.grenade_delay;
nade.think = GrenadeExplode;
nade.touch = Velocity_reduce;
setmodel (nade, "models/weapons/grenade/g_grenade.mdl");
setsize (nade, '0 0 0', '0 0 0');
nade.origin = self.origin + self.view_ofs;
nade.origin += v_forward * 12;
setorigin (nade, nade.origin);
self.animend = ReturnWeaponModel;
self.callfuncat = 0;
self.isBuying = false;
} else if (self.pri_grenade_state == 1) {
Betty_Drop();
} else {
centerprint (other, "No grenadetype defined...\n");
}
startframe = GetFrame(self.weapon,TAKE_OUT_START);
endframe = GetFrame(self.weapon,TAKE_OUT_END);
modelname = GetWeaponModel(self.weapon, 0);
Set_W_Frame (startframe, endframe, 0, 0, 0, ReturnWeaponModel, modelname, false, S_BOTH, false);
SetUpdate(self, UT_HUD, 7, 0, 0);
}
void() checkHold =
{
if (self.pri_grenade_state == 1) {
Betty_CheckForRelease();
return;
}
if (!self.button3 || self.grenade_delay < time)
{
if(self.grenade_delay < time)
self.grenade_delay = time + 0.05;
self.isBuying = true;
Set_W_Frame (3, 6, 0, 5, GRENADE, W_ThrowGrenade, "models/weapons/grenade/v_grenade.mdl", true, S_RIGHT, true);
Sound_PlaySound(self, "sounds/weapons/grenade/throw.wav", SOUND_TYPE_WEAPON_OTHER, SOUND_PRIORITY_PLAYALWAYS);
self.reload_delay2 = self.fire_delay2 = self.reload_delay = self.fire_delay = time + 0.4;
self.throw_delay = time + 0.9;
nzp_rumble(self, 1000, 1200, 75);
}
else
{
if (self.isBuying == false) {
W_ShowCrosshair(self);
self.isBuying = true;
}
// Pulse the grenade crosshair every 1 second
float difference = rint(self.grenade_delay - time);
if (difference != self.beingrevived) {
self.beingrevived = difference;
grenade_pulse(self);
}
Set_W_Frame (2, 2, 0, 0, GRENADE, checkHold, "models/weapons/grenade/v_grenade.mdl", true, S_RIGHT, true);
}
}
void() W_Grenade =
{
if (self.throw_delay > time || self.zoom || self.downed || self.primary_grenades < 1 || self.isBuying)
return;
// Prevent the Player from Sprinting and also avoid issues with
// the equipment being completely cancelled..
W_SprintStop();
W_HideCrosshair(self);
Set_W_Frame (0, 2, 0.6, 0, GRENADE, checkHold, "models/weapons/grenade/v_grenade.mdl", true, S_RIGHT, true);
Sound_PlaySound(self, "sounds/weapons/grenade/prime.wav", SOUND_TYPE_WEAPON_OTHER, SOUND_PRIORITY_PLAYALWAYS);
self.primary_grenades -= 1;
self.reload_delay2 = self.fire_delay2 = self.throw_delay = self.reload_delay = self.fire_delay = time + 6;
self.grenade_delay = time + 5;
nzp_rumble(self, 1000, 1200, 75);
}
void(float wepnum) CheckButton = {
W_PutOut();
}
void() Change_Stance = {
if (self.downed || !(self.flags & FL_ONGROUND) || self.changestance == true || self.new_ofs_z != self.view_ofs_z)
return;
switch(self.stance) {
case PLAYER_STANCE_STAND:
Player_SetStance(self, PLAYER_STANCE_CROUCH, true);
break;
case 1:
// Prohibit Proning if Drinking a Perk-A-Cola, just stand.
if (self.isBuying) {
Player_SetStance(self, PLAYER_STANCE_STAND, true);
}
// Stance flag isn't set, so go Prone.
else if (!self.stancereset) {
Player_SetStance(self, PLAYER_STANCE_PRONE, true);
}
// Stance flag IS set, so stand if applicable.
else if (Player_CanStandHere(self)) {
Player_SetStance(self, PLAYER_STANCE_STAND, true);
self.stancereset = false;
}
// There's no room to stand, just prone again.
else {
Player_SetStance(self, PLAYER_STANCE_PRONE, true);
}
break;
case 0:
Player_SetStance(self, PLAYER_STANCE_CROUCH, true);
self.stancereset = true;
break;
default: break;
}
}
void() dolphin_dive = //naievil
{
if (self.stance != 2)
return;
if ((map_compatibility_mode == MAP_COMPAT_BETA && self.view_ofs_z != VIEW_OFS_QK[2]) ||
(!map_compatibility_mode && self.view_ofs_z != VIEW_OFS_HL[2]))
return;
if (self.flags & FL_WATERJUMP)
return;
if (self.waterlevel >= 2)
{
if (self.watertype == CONTENT_WATER)
self.velocity_z = 100;
else if (self.watertype == CONTENT_SLIME)
self.velocity_z = 80;
else
self.velocity_z = 50;
}
if (!(self.flags & FL_ONGROUND))
return;
if ( !(self.flags & FL_JUMPRELEASED) )
return; // don't pogo stick
self.flags = self.flags - (self.flags & FL_JUMPRELEASED);
self.flags = self.flags - FL_ONGROUND; // don't stairwalk
makevectors (self.v_angle);
self.velocity = self.velocity * 1.1;
self.velocity_z = self.velocity_z + 270;
if (self.button2)
self.button2 = 0;
self.dive = 1;
W_SprintStop();
PAnim_EnterDive();
Sound_PlaySound(self, "sounds/player/jump.wav", SOUND_TYPE_PLAYER_VOICE, SOUND_PRIORITY_PLAYALWAYS);
self.oldz = self.origin_z;
Player_SetStance(self, PLAYER_STANCE_PRONE, false);
}
void () Impulse_Functions =
{
if (!self.impulse)
return;
switch (self.impulse)
{
case 10:
#ifdef FTE
// FTE handles jumping, which we don't want.. so we use impulses to override.
JumpCheck(1);
#endif // FTE
break;
case 22:
if (cheats_have_been_activated) {
Player_AddScore(self, 10000, true);
rounds += 4;
}
break;
case 23:
#ifdef FTE
if (!checkMovement(forward))
return;
#endif // FTE
if (self.dive || self.downed || !Player_CanStandHere(self))
return;
Player_SetStance(self, PLAYER_STANCE_STAND, false);
self.sprintflag = true;
W_SprintStart();
break;
case 24:
self.sprintflag = false;
W_SprintStop();
break;
case 25:
switch_nade();
break;
case 26:
self.ads_release = !self.ads_release;
self.has_ads_toggle = true;
break;
case 33:
W_PrimeBetty();
break;
case 26:
case 30:
if (self.sprinting)
dolphin_dive();
else
Change_Stance();
break;
case 100:
cvar_set("waypoint_mode", "1");
localcmd("restart\n");
break;
default:
#ifdef FTE
bprint(PRINT_HIGH, "Unknown impulse: ", ftos(self.impulse));
#endif // FTE
break;
}
self.impulse = 0;
};
//
// WeaponCore_PowerUpBlink(blink_delay, powerup_endtime)
// Determines whether or not the Power-Up HUD icon should be
// visible.
// FIXME: This should be moved out of weapon_core, ideally.
//
float(__inout float blink_delay, float powerup_endtime) WeaponCore_PowerUpBlink =
{
// If the Power-Up is still active
if (powerup_endtime >= time) {
float should_be_visible = false;
// If there's less than five seconds remaining,
// blink every 0.2s.
if (powerup_endtime < time + 5) {
if (blink_delay < time - 0.2)
blink_delay = time + 0.2;
}
// If there's less than 10 seconds remaining,
// blink every 0.4s.
else if (powerup_endtime < time + 10) {
if (blink_delay < time - 0.4)
blink_delay = time + 0.4;
}
if (blink_delay < time)
return true;
}
// End of the line, return false.
return false;
};
void() CheckPlayer =
{
// Update Power-Up blinking
self.insta_icon = WeaponCore_PowerUpBlink(insta_blink, instakill_finished);
self.x2_icon = WeaponCore_PowerUpBlink(x2_blink, x2_finished);
CheckRevive(self);
if (self.sprinting) {
makevectors(self.angles);
// We should stop sprinting if we are falling, technically
// CoD continues the animation but applies a move speed
// penalty, however, that makes no sense! This also applies
// as a better implementation to stop sprinting while jumping.
if (fabs(self.velocity_z) >= 150)
W_SprintStop();
// Otherwise, also stop sprinting if we are moving at a base below
// predicted for our slowest possible weapon. This removes the need
// for an engine-side hack for toggle sprinting turning off when
// you're applying less pressure to the analog stick.
else if (vlen(self.velocity) <= 130 && self.sprint_delay <= time + 0.75)
W_SprintStop();
// Another sprinting caveat -- if we are moving a considerabe distance
// horizontally, much more than reasonable, it is very obviously impossible
// to actually sprint.
else if (fabs(self.velocity * v_right) >= fabs(self.velocity * v_forward) * 0.75)
W_SprintStop();
}
// FIXME - can't do frame independent stance changing.. ofs starts spazzing..
if (self.view_ofs_z > self.new_ofs_z) {
self.changestance = true;
self.view_ofs_z = self.view_ofs_z - 1;
} else if (self.view_ofs_z < self.new_ofs_z) {
self.changestance = true;
self.view_ofs_z = self.view_ofs_z + 1;
} else {
self.changestance = false;
}
if (self.view_ofs_z > 32) {
self.view_ofs_z = 32;
self.changestance = false;
}
// Diving on top of other players :)
if (self.dive && !(self.flags & FL_ONGROUND)) {
makevectors(self.angles);
traceline(self.origin, self.origin + (v_up * -40), 0, self);
if (trace_ent.classname == "player") {
DamageHandler(self, trace_ent, 100000, DMG_TYPE_OTHER);
DamageHandler(trace_ent, self, 100000, DMG_TYPE_OTHER);
}
}
if (!self.button2 && self.flags & FL_ONGROUND) {
float dist = fabs(self.oldz - self.origin_z);
if (self.dive) {
if (self.perks & P_FLOP && dist >= 64) {
Sound_PlaySound(self, "sounds/weapons/grenade/explode.wav", SOUND_TYPE_WEAPON_EXPLODE, SOUND_PRIORITY_PLAYALWAYS);
DamgageExplode (self, self, 5000, 1000, 300);
#ifdef FTE
te_customflash(self.origin, 300, 300, '1 0.6 0.1');
#endif // FTE
CallExplosion(self.origin);
CallExplosion(self.origin);
CallExplosion(self.origin);
}
PAnim_Flop();
self.dive = 0;
}
if (dist + 64 > 176 && !(self.perks & P_FLOP)) {
float height = dist + 64;
float damage = height*(0.68);
if (damage > 98) damage = 98;
DamageHandler (self, other, damage, DMG_TYPE_OTHER);
if (self.health <= 5)
GiveAchievement(7, self);
}
self.oldz = self.origin_z;
}
}
//
// WeaponCore_UpdateRecoilAndSpread()
// Somewhat-intelligent serverside Weapon Recoil
// update system that takes into account velocity
// and how long the weapon has been fired.
//
void() WeaponCore_UpdateRecoilAndSpread =
{
// Moving? Our weapon spread should be the maximum.
if (self.velocity)
self.cur_spread = WepDef_WeaponMaxSpread(self.weapon, self.stance);
// Slowly increase our spread based on how long the weapon
// has been fired.
else if (self.recoil_delay > time && self.recoil_delay)
{
self.cur_spread = self.cur_spread + 10;
if (self.cur_spread >= WepDef_WeaponMaxSpread(self.weapon, self.stance))
self.cur_spread = WepDef_WeaponMaxSpread(self.weapon, self.stance);
}
// Diminish spread over time as recoil decreases.
else if (self.recoil_delay < time && self.recoil_delay)
{
self.cur_spread = self.cur_spread - 4;
if (self.cur_spread <= 0)
{
self.cur_spread = 0;
self.recoil_delay = 0;
}
}
}
//
// WeaponCore_UpdateViewModels()
// Updates animation playback for View models,
// if applicable.
//
void() WeaponCore_UpdateViewModels =
{
// If the weapon has a "Hold Fire" frame (e.g., M2 Flamethrower)
// And it's actively firing, we shouldn't force an update.
float hold_fire_frame = GetFrame(self.weapon, FIRE_HOLD); // Avoid iterating through long switch twice.
if (self.weaponmodel == GetWeaponModel(self.weapon, false) &&
hold_fire_frame != 0 && self.weaponframe == hold_fire_frame)
return;
// Checks passed -- update view model animations.
W_Frame_Update();
W2_Frame_Update();
};
//
// WeaponCore_ResetHoldFireWeapons()
// Allows for a "Hold Fire" weapon to reach its base
// position again.
//
void() WeaponCore_ResetHoldFireWeapon =
{
float hold_fire_frame = GetFrame(self.weapon, FIRE_HOLD); // Avoid iterating through long switch twice.
if (hold_fire_frame != 0 && self.weaponframe == hold_fire_frame &&
self.weaponmodel == GetWeaponModel(self.weapon, 0)) {
self.weaponframe = 0;
}
};
//
// WeaponCore_UpdateSniperScope
// If eligible, reports to the client via
// the .zoom stat to draw the Sniper Scope
// HUD graphic.
//
void() WeaponCore_UpdateSniperScope =
{
if ((self.scopetime < time) && self.scopetime) {
self.scopetime = 0;
self.zoom = 2;
self.weapon2model = "";
self.weaponmodel = "";
}
};
//
// WeaponCore_UpdateWeaponStats()
// Specific weapon fields are used to network weapon
// stats to the client, since arrays and structs
// can't be used for this task. This is where we store
// them for pass-off.
//
inline void() WeaponCore_UpdateWeaponStats =
{
self.currentammo = self.weapons[0].weapon_reserve; // Weapon Reserve Ammo
self.currentmag = self.weapons[0].weapon_magazine; // Weapon Magazine
self.currentmag2 = self.weapons[0].weapon_magazine_left; // Left-side Weapon Magazine
// FTE-specific - update weapon2modelindex to simplify protocol
// and data exchange.
#ifdef FTE
self.weapon2modelindex = getmodelindex(self.weapon2model);
#endif // FTE
};
//
// WeaponCore_FireButtonPressed()
// Executed when WeaponCore_ClientLogic
// detects that button0 has been pressed.
//
void() WeaponCore_FireButtonPressed =
{
// With keyboard/mouse input, we want akimbo weapons to actually correspond
// to which click of the mouse you've provided, which is different behavior
// to single-hand weapons, where left click fires its right-side. (Hooray
// for ternaries!).
// For gamepads, we don't want this behavior, because the gun that
// fires will be the wrong side
#ifdef FTE
if (self.is_using_gamepad)
{
// Gamepads always fire the right side
W_Fire(S_RIGHT);
}
else
{
// Otherwise, fire the side of the mouse click for akimbo weapons
(IsDualWeapon(self.weapon)) ? W_Fire(S_LEFT) : W_Fire(S_RIGHT);
}
#else
// Always fire right-side elsewhere.
W_Fire(S_RIGHT);
#endif // FTE
};
//
// WeaponCore_FireButtonReleased()
// Executed when WeaponCore_ClientLogic
// detects that button0 has been released.
//
void() WeaponCore_FireButtonReleased =
{
// Allow semi-automatic fire again -- see WeaponCore_FireButtonPressed() for platform discrepancy.
#ifdef FTE
(IsDualWeapon(self.weapon)) ? self.semi_actions &= ~SEMIACTION_FIRE_LEFT : self.semi_actions &= ~SEMIACTION_FIRE_RIGHT;
#else
// Always allow right-side elsewhere.
self.semi_actions &= ~SEMIACTION_FIRE_RIGHT;
#endif // FTE
// Make sure Hold-special weapons are allowed
// back to base-frame.
WeaponCore_ResetHoldFireWeapon();
};
//
// WeaponCore_AimButtonPressed()
// Executed when WeaponCore_ClientLogic
// detects that button8 has been pressed.
//
void() WeaponCore_AimButtonPressed =
{
// If we're holding an akimbo weapon, we actually want to
// fire our secondary as opposed to Aiming down the Sight.
if (IsDualWeapon(self.weapon)) {
#ifdef FTE
// On KBM this is the right click, so it should fire the right weapon.
// On gamepads, this is the left trigger, so it should fire the left weapon.
if (self.is_using_gamepad)
{
W_Fire(S_LEFT);
}
else
{
W_Fire(S_RIGHT);
}
#else
W_Fire(S_LEFT);
#endif // FTE
}
// Don't permit Aiming down the Sights if the client
// is moving and Prone.
else if (!self.ads_release) {
W_AimIn();
self.has_ads_toggle = false;
}
};
//
// WeaponCore_AimButtonReleased()
// Executed when WeaponCore_ClientLogic
// detects that button8 has been released.
//
void() WeaponCore_AimButtonReleased =
{
// Lower weapon if Aiming down the Sight.
if (!self.ads_release)
W_AimOut();
// Allow akimbo semi-automatic weapons to fire again.
if (IsDualWeapon(self.weapon)) {
#ifdef FTE
self.semi_actions &= ~SEMIACTION_FIRE_RIGHT;
#else
self.semi_actions &= ~SEMIACTION_FIRE_LEFT;
#endif // FTE
}
};
//
// WeaponCore_GrenadeButtonPressed()
// Executed when WeaponCore_ClientLogic
// detects that button3 has been pressed.
//
void() WeaponCore_GrenadeButtonPressed =
{
// Leave if there is already a Grenade action
// being performed.
if (self.grenade_delay > time || (self.semi_actions & SEMIACTION_GRENADE))
return;
if (self.pri_grenade_state == 0)
W_Grenade();
else
W_PrimeBetty();
self.semi_actions |= SEMIACTION_GRENADE;
};
//
// WeaponCore_MeleeButtonPressed()
// Executed when WeaponCore_ClientLogic
// detects that button6 has been pressed.
//
void() WeaponCore_MeleeButtonPressed =
{
// On platforms with limited control schemes,
// performing a melee-action while sprinting
// triggers Dive-to-Prone.
#ifndef FTE
if (self.sprinting) {
dolphin_dive();
return;
}
#endif // FTE
if (self.semi_actions & SEMIACTION_MELEE)
return;
#ifdef FTE
// On FTE, we should resort to standard CoD
// behavior where pressing melee should cancel
// sprinting.
W_SprintStop();
#endif // FTE
WeaponCore_Melee();
self.semi_actions |= SEMIACTION_MELEE;
};
//
// WeaponCore_ClientLogic()
// Executes every frame on clients, dictates
// weapon behaviors such as receiving inputs
// or updating animation playback.
//
void() WeaponCore_ClientLogic =
{
// Make sure information about our weapon stats
// are being networked to the client.
WeaponCore_UpdateWeaponStats();
// Viewmodel animation playback.
WeaponCore_UpdateViewModels();
// Dynamic Weapon Recoil and Spread adjustment.
WeaponCore_UpdateRecoilAndSpread();
// Register changes to client's .impulse
Impulse_Functions();
// Tells client to draw Sniper HUD element.
WeaponCore_UpdateSniperScope();
// Call to the client to do some update
// checks specific to them.
CheckPlayer();
#ifdef FTE
// FTE-only: custom Punchangle Interpolation Implementation
FTE_InterpolatePunchAngle(self);
// FTE-only: update dynamic FOV
FTE_UpdateDynamicFOV(self);
#endif // FTE
// If the client is Aiming down the Sight while prone and
// moving, force them to stop
if (self.stance == PLAYER_STANCE_PRONE && self.velocity && self.zoom) {
W_AimOut();
}
// Fire Button Pressed
if (self.button0) {
WeaponCore_FireButtonPressed();
}
// Fire Button Released
else {
WeaponCore_FireButtonReleased();
}
// If Client has ADS bound to impulse 26 (toggle),
// check if the flag is set and W_AimIn()
if (self.ads_release) {
// A bit of a hack -- but if we are using a dual wield weapon
// and we've triggered the toggle ADS, emulate a RMB tap and
// remove the ADS toggle flag.
if (IsDualWeapon(self.weapon)) {
self.ads_release = false;
self.has_ads_toggle = false;
WeaponCore_AimButtonPressed();
WeaponCore_AimButtonReleased();
return;
}
W_AimIn();
} else if (self.has_ads_toggle) {
W_AimOut();
}
// ADS Button Pressed
if (self.button8) {
WeaponCore_AimButtonPressed();
}
// ADS Button Released
else {
WeaponCore_AimButtonReleased();
}
// Use Button Released
if (!self.button7) {
// Allow use to be triggered again.
self.semi_actions &= ~SEMIACTION_USE;
}
// Switch Button Pressed
if (self.button4) {
W_PutOut();
} else {
// Allow Switch to be pressed again.
self.semi_actions &= ~SEMIACTION_WEAPONSWAP;
}
// Grenade Button Pressed
if (self.button3) {
WeaponCore_GrenadeButtonPressed();
} else {
// Allow Grenade to be pressed again.
self.semi_actions &= ~SEMIACTION_GRENADE;
}
// Reload Button Pressed
if (self.button5) {
W_Reload(S_BOTH);
}
// Melee Button Pressed
if (self.button6) {
WeaponCore_MeleeButtonPressed();
}
// Melee Button Released
else {
// Allow Melee to be pressed again
self.semi_actions &= ~SEMIACTION_MELEE;
}
};