thirtyflightsofloving/awaken2/p_weapon.c
Knightmare66 9481c7c513 Rewrote Com_strcpy() and Com_strcat() in game DLLs to not be based on strncpy() and to return size copied.
Changed Zaero and 3ZB2 game DLLs to use WORLD_SIZE for various calculations instead of 8192.
Cleaned up string handling in 3ZB2 game DLL.
Added func_plat2, func_door_secret2, and func_force_wall from Rogue to 3ZB2 game DLL.
Added alternate attack contact explode for grenade launcher in 3ZB2 game DLL.
Added awakening2 game DLL source.
2021-02-01 20:19:52 -05:00

2722 lines
72 KiB
C

// p_weapon.c
#include "g_local.h"
#include "m_player.h"
//CW++
#ifdef LINUX
#define min(a,b) (((a)<(b))?(a):(b))
#endif
//CW--
qboolean is_quad; //CW
static byte is_silenced;
void P_ProjectSource(gclient_t *client, vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t result)
{
vec3_t _distance;
VectorCopy(distance, _distance);
if (client->pers.hand == LEFT_HANDED)
_distance[1] *= -1.0F;
else if (client->pers.hand == CENTER_HANDED)
_distance[1] = 0.0F;
G_ProjectSource(point, _distance, forward, right, result);
}
/*
===============
PlayerNoise
Each player can have two noise objects associated with it:
a personal noise (jumping, pain, weapon firing), and a weapon
target noise (bullet wall impacts)
Bots that don't directly see opponents can move
to a noise in hopes of seeing their enemy from there.
===============
*/
void PlayerNoise(edict_t *who, vec3_t where, int type)
{
edict_t *noise;
//CW++
if (!who)
return;
//CW--
if ((type == PNOISE_WEAPON) && who->client) //CW
{
if (who->client->silencer_shots)
{
who->client->silencer_shots--;
return;
}
}
//CW++
if (NumBotsInGame == 0)
return;
//CW--
if (who->flags & FL_NOTARGET)
return;
if (!who->mynoise)
{
noise = G_Spawn();
noise->classname = "player_noise";
VectorSet(noise->mins, -8, -8, -8);
VectorSet(noise->maxs, 8, 8, 8);
noise->owner = who;
noise->svflags = SVF_NOCLIENT;
who->mynoise = noise;
noise = G_Spawn();
noise->classname = "player_noise";
VectorSet(noise->mins, -8, -8, -8);
VectorSet(noise->maxs, 8, 8, 8);
noise->owner = who;
noise->svflags = SVF_NOCLIENT;
who->mynoise2 = noise;
}
if ((type == PNOISE_SELF) || (type == PNOISE_WEAPON))
noise = who->mynoise;
else //PNOISE_IMPACT
noise = who->mynoise2;
VectorCopy(where, noise->s.origin);
VectorSubtract(where, noise->maxs, noise->absmin);
VectorAdd(where, noise->maxs, noise->absmax);
noise->teleport_time = level.time;
gi.linkentity(noise);
}
qboolean Pickup_Weapon(edict_t *ent, edict_t *other)
{
gitem_t *ammo;
int index;
index = ITEM_INDEX(ent->item);
//Maj++
CheckCampSite(ent, other);
//Maj--
if (((int)dmflags->value & DF_WEAPONS_STAY) && other->client->pers.inventory[index]) //CW
{
if (!(ent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM)))
return false; // leave the weapon for others to pickup
}
other->client->pers.inventory[index]++;
if (!(ent->spawnflags & DROPPED_ITEM))
{
// give them some ammo with it
ammo = FindItem(ent->item->ammo);
if ((int)dmflags->value & DF_INFINITE_AMMO)
Add_Ammo(other, ammo, 1000);
else
Add_Ammo(other, ammo, ammo->quantity);
if (!(ent->spawnflags & DROPPED_PLAYER_ITEM))
{
if ((int)(dmflags->value) & DF_WEAPONS_STAY) //CW
ent->flags |= FL_RESPAWN;
else
{
//CW++
if (ent->delay)
SetRespawn(ent, ent->delay);
else
//CW--
SetRespawn(ent, 30);
}
}
}
return true;
}
/*
===============
ChangeWeapon
The old weapon has been dropped all the way, so make the new one
current
===============
*/
void ChangeWeapon(edict_t *ent)
{
int i;
//CW++
int mode;
if (!ent->client)
{
gi.dprintf("BUG: ChangeWeapon() called with non-client edict.\n");
if (ent->classname)
gi.dprintf(" classname = %s\n", ent->classname);
if (ent->owner && ent->owner->client)
gi.dprintf(" owner->name = %s\n", ent->owner->client->pers.netname);
return;
}
//CW--
ent->client->pers.lastweapon = ent->client->pers.weapon;
ent->client->pers.weapon = ent->client->newweapon;
ent->client->newweapon = NULL;
ent->client->machinegun_shots = 0;
// set visible model
if (ent->s.modelindex == (MAX_MODELS-1))
{
if (ent->client->pers.weapon)
i = ((ent->client->pers.weapon->weapmodel & 0xff) << 8);
else
i = 0;
ent->s.skinnum = (ent - g_edicts - 1) | i;
}
if (ent->client->pers.weapon && ent->client->pers.weapon->ammo)
ent->client->ammo_index = ITEM_INDEX(FindItem(ent->client->pers.weapon->ammo));
else
ent->client->ammo_index = 0;
if (!ent->client->pers.weapon)
{ // dead
ent->client->ps.gunindex = 0;
return;
}
ent->client->weaponstate = WEAPON_ACTIVATING;
ent->client->ps.gunframe = 0;
ent->client->ps.gunindex = gi.modelindex(ent->client->pers.weapon->view_model);
ent->client->anim_priority = ANIM_PAIN;
if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
{
ent->s.frame = FRAME_crpain1;
ent->client->anim_end = FRAME_crpain4;
}
else
{
ent->s.frame = FRAME_pain301;
ent->client->anim_end = FRAME_pain304;
}
//CW++
ent->client->show_gausscharge = false;
ent->client->show_gausstarget = 0;
ent->client->agm_charge = 0;
ent->client->agm_showcharge = false;
ent->client->agm_tripped = false;
ent->client->agm_on = false;
ent->client->agm_push = false;
ent->client->agm_pull = false;
ent->client->agm_target = NULL;
if (ent->isabot)
return;
mode = ent->client->pers.weap_note;
if (ent->client->pers.weapon->weapmodel == WEAP_DESERTEAGLE)
{
if ((mode & WN_VALL) && (ent->client->pers.lastweapon->weapmodel != WEAP_DESERTEAGLE))
unicastSound(ent, gi.soundindex("voice/u_pistol.wav"), 1.0); //r1,CW
}
else if (ent->client->pers.weapon->weapmodel == WEAP_GAUSSPISTOL)
{
if (ent->client->gauss_particle)
{
if (ent->client->pers.inventory[ITEM_INDEX(FindItem("slugs"))] < GP_SLUGS_PER_SHOT)
{
gi_centerprintf(ent, "Not enough Slugs ...\nSwitching to Blaster mode\n");
ent->client->gauss_particle = false;
ent->client->show_gausstarget = 0;
ent->client->show_gausscharge = true;
}
else if (ent->client->pers.inventory[ITEM_INDEX(FindItem("cells"))] < GP_CELLS_PER_SHOT)
{
gi_centerprintf(ent, "Not enough Cells ...\nSwitching to Blaster mode\n");
ent->client->gauss_particle = false;
ent->client->show_gausstarget = 0;
ent->client->show_gausscharge = true;
}
else
{
ent->client->show_gausstarget = 1;
ent->client->show_gausscharge = false;
ent->client->showinventory = 0;
ent->client->showscores = 0;
}
}
if (ent->client->gauss_particle)
{
if (mode & WN_TEXT)
gi_centerprintf(ent, "Particle Beam\n");
if (mode & WN_VSEC)
unicastSound(ent, gi.soundindex("voice/u_partcl.wav"), 1.0); //r1,CW
}
else
{
if (mode & WN_TEXT)
gi_centerprintf(ent, "Blaster\n");
if (mode & WN_VSEC)
unicastSound(ent, gi.soundindex("voice/u_blast.wav"), 1.0); //r1,CW
}
gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/gauss/use.wav"), 1, ATTN_NORM, 0);
}
else if (ent->client->pers.weapon->weapmodel == WEAP_JACKHAMMER)
{
if ((mode & WN_VALL) && (ent->client->pers.lastweapon->weapmodel != WEAP_JACKHAMMER))
unicastSound(ent, gi.soundindex("voice/u_jack.wav"), 1.0); //r1,CW
gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/sshotr1b.wav"), 1, ATTN_NORM, 0);
}
else if (ent->client->pers.weapon->weapmodel == WEAP_MAC10)
{
if ((mode & WN_VALL) && (ent->client->pers.lastweapon->weapmodel != WEAP_MAC10))
unicastSound(ent, gi.soundindex("voice/u_mac10.wav"), 1.0); //r1,CW
}
else if (ent->client->pers.weapon->weapmodel == WEAP_C4)
{
if (mode & WN_VALL)
unicastSound(ent, gi.soundindex("voice/u_c4.wav"), 1.0); //r1,CW
}
else if (ent->client->pers.weapon->weapmodel == WEAP_TRAP)
{
if (mode & WN_VALL)
unicastSound(ent, gi.soundindex("voice/u_traps.wav"), 1.0); //r1,CW
}
else if (ent->client->pers.weapon->weapmodel == WEAP_ESG)
{
if (ent->client->multi_spike)
{
if (mode & WN_TEXT)
gi_centerprintf(ent, "Multi-spikes\n");
if (mode & WN_VSEC)
unicastSound(ent, gi.soundindex("voice/u_multi.wav"), 1.0); //r1,CW
}
else
{
if (mode & WN_TEXT)
gi_centerprintf(ent, "Single spikes\n");
if (mode & WN_VSEC)
unicastSound(ent, gi.soundindex("voice/u_spike.wav"), 1.0); //r1,CW
}
gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/esg/use.wav"), 1, ATTN_NORM, 0);
}
else if (ent->client->pers.weapon->weapmodel == WEAP_FLAMETHROWER)
{
if (ent->client->ft_firebomb)
{
if (ent->client->pers.inventory[ITEM_INDEX(FindItem("cells"))] < FT_CELLS_PER_SHOT)
{
gi_centerprintf(ent, "Not enough Cells ...\nSwitching to Standard mode\n");
ent->client->ft_firebomb = false;
}
}
if (ent->client->ft_firebomb)
{
if (mode & WN_TEXT)
gi_centerprintf(ent, "Firebombs\n");
if (mode & WN_VSEC)
unicastSound(ent, gi.soundindex("voice/u_firebm.wav"), 1.0); //r1,CW
}
else
{
if (mode & WN_TEXT)
gi_centerprintf(ent, "Standard Flamethrower\n");
if (mode & WN_VSEC)
unicastSound(ent, gi.soundindex("voice/u_flames.wav"), 1.0); //r1,CW
}
gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/flamer/use.wav"), 1, ATTN_NORM, 0);
}
else if (ent->client->pers.weapon->weapmodel == WEAP_ROCKETLAUNCHER)
{
if (ent->client->normal_rockets)
{
if (mode & WN_TEXT)
gi_centerprintf(ent, "Normal Rockets\n");
if (mode & WN_VSEC)
unicastSound(ent, gi.soundindex("voice/u_nrockt.wav"), 1.0); //r1,CW
}
else
{
if (mode & WN_TEXT)
gi_centerprintf(ent, "Guided Rockets\n");
if (mode & WN_VSEC)
unicastSound(ent, gi.soundindex("voice/u_grockt.wav"), 1.0); //r1,CW
}
}
else if (ent->client->pers.weapon->weapmodel == WEAP_RAILGUN)
{
if (mode & WN_VALL)
unicastSound(ent, gi.soundindex("voice/u_rail.wav"), 1.0); //r1,CW
}
else if (ent->client->pers.weapon->weapmodel == WEAP_SHOCKRIFLE)
{
if (ent->client->homing_plasma)
{
if (mode & WN_TEXT)
gi_centerprintf(ent, "Homing Plasma\n");
if (mode & WN_VSEC)
unicastSound(ent, gi.soundindex("voice/u_homin.wav"), 1.0); //r1,CW
}
else
{
if (mode & WN_TEXT)
gi_centerprintf(ent, "Disintegrator\n");
if (mode & WN_VSEC)
unicastSound(ent, gi.soundindex("voice/u_shock.wav"), 1.0); //r1,CW
}
}
else if (ent->client->pers.weapon->weapmodel == WEAP_AGM)
{
ent->client->agm_charge = 0;
ent->client->agm_showcharge = false;
ent->client->agm_tripped = false;
if (ent->client->agm_disrupt)
{
if (mode & WN_TEXT)
gi_centerprintf(ent, "Cellular Disruption\n");
if (mode & WN_VSEC)
unicastSound(ent, gi.soundindex("voice/u_cd.wav"), 1.0); //r1,CW
}
else
{
if (mode & WN_TEXT)
gi_centerprintf(ent, "AG Manipulation\n");
if (mode & WN_VSEC)
unicastSound(ent, gi.soundindex("voice/u_agm.wav"), 1.0); //r1,CW
}
}
else if (ent->client->pers.weapon->weapmodel == WEAP_DISCLAUNCHER)
{
if (mode & WN_VALL)
unicastSound(ent, gi.soundindex("voice/u_disc.wav"), 1.0); //r1,CW
}
else if (ent->client->pers.weapon->weapmodel == WEAP_CHAINSAW)
{
if (mode & WN_VALL)
unicastSound(ent, gi.soundindex("voice/u_chnsaw.wav"), 1.0); //r1,CW
gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/chainsaw/use.wav"), 1, ATTN_NORM, 0);
}
else if (ent->client->pers.weapon->weapmodel == WEAP_GRAPPLE)
{
if (mode & WN_VALL)
unicastSound(ent, gi.soundindex("voice/u_grappl.wav"), 1.0); //r1,CW
}
//CW--
}
/*
=================
NoAmmoWeaponChange
=================
*/
void NoAmmoWeaponChange(edict_t *ent)
{
//CW++
if (!ent->client)
{
gi.dprintf("BUG: NoAmmoWeaponChange() called for non-client edict.\nPlease contact musashi@planetquake.com\n");
return;
}
if (ent->health < 1)
{
gi.dprintf("BUG: NoAmmoWeaponChange() called for a dead player.\nPlease contact musashi@planetquake.com\n");
return;
}
if ((ent->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] >= SR_CELLS_PER_SHOT)
&& ent->client->pers.inventory[ITEM_INDEX(FindItem("Shock Rifle"))])
{
ent->client->newweapon = FindItem("Shock Rifle");
return;
}
//CW--
if (ent->client->pers.inventory[ITEM_INDEX(FindItem("Slugs"))]
&& ent->client->pers.inventory[ITEM_INDEX(FindItem("Railgun"))])
{
ent->client->newweapon = FindItem("Railgun");
return;
}
//CW++
if (ent->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))]
&& ent->client->pers.inventory[ITEM_INDEX(FindItem("Gauss Pistol"))])
{
ent->client->newweapon = FindItem("Gauss Pistol");
return;
}
if (ent->client->pers.inventory[ITEM_INDEX(FindItem("Rockets"))])
{
if (ent->client->pers.inventory[ITEM_INDEX(FindItem("Rocket Launcher"))])
{
ent->client->newweapon = FindItem("Rocket Launcher");
return;
}
else if (ent->client->pers.inventory[ITEM_INDEX(FindItem("E.S.G."))])
{
ent->client->newweapon = FindItem("E.S.G.");
return;
}
else if (ent->client->pers.inventory[ITEM_INDEX(FindItem("Disc Launcher"))])
{
ent->client->newweapon = FindItem("Disc Launcher");
return;
}
}
if (ent->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))]
&& ent->client->pers.inventory[ITEM_INDEX(FindItem("Flamethrower"))])
{
ent->client->newweapon = FindItem("Flamethrower");
return;
}
if (ent->client->pers.inventory[ITEM_INDEX(FindItem("Bullets"))]
&& ent->client->pers.inventory[ITEM_INDEX(FindItem("Mac-10"))])
{
ent->client->newweapon = FindItem("Mac-10");
return;
}
if (ent->client->pers.inventory[ITEM_INDEX(FindItem("Shells"))]
&& ent->client->pers.inventory[ITEM_INDEX(FindItem("Jackhammer"))])
{
ent->client->newweapon = FindItem("Jackhammer");
return;
}
if (ent->client->pers.inventory[ITEM_INDEX(FindItem("Bullets"))]
&& ent->client->pers.inventory[ITEM_INDEX(FindItem("Desert Eagle"))])
{
ent->client->newweapon = FindItem("Desert Eagle");
return;
}
if (ent->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))]
&& ent->client->pers.inventory[ITEM_INDEX(FindItem("AG Manipulator"))])
{
ent->client->newweapon = FindItem("AG Manipulator");
return;
}
if (ent->client->pers.inventory[ITEM_INDEX(FindItem("C4"))])
{
ent->client->newweapon = FindItem("C4");
return;
}
if (ent->client->pers.inventory[ITEM_INDEX(FindItem("Traps"))])
{
ent->client->newweapon = FindItem("Traps");
return;
}
ent->client->newweapon = FindItem("Chainsaw");
//CW--
}
/*
=================
Think_Weapon
Called by ClientBeginServerFrame and ClientThink
=================
*/
void Think_Weapon(edict_t *ent)
{
// if just died, put the weapon away
if (ent->health < 1)
{
ent->client->newweapon = NULL;
ChangeWeapon(ent);
}
//DH++
if (ent->flags & FL_TURRET_OWNER)
{
if (((ent->client->latched_buttons | ent->client->buttons) & BUTTON_ATTACK))
{
ent->client->latched_buttons &= ~BUTTON_ATTACK;
turret_breach_fire(ent->turret);
}
return;
}
//DH--
// call active weapon think routine
if (ent->client->pers.weapon && ent->client->pers.weapon->weaponthink)
{
is_quad = (ent->client->quad_framenum > level.framenum);
if (ent->client->silencer_shots)
is_silenced = MZ_SILENCED;
else
is_silenced = 0;
ent->client->pers.weapon->weaponthink(ent);
}
}
/*
================
Use_Weapon
Make the weapon ready if there is ammo
================
*/
void Use_Weapon(edict_t *ent, gitem_t *item)
{
gitem_t *ammo_item;
int ammo_index;
// see if we're already using it
if (item == ent->client->pers.weapon)
return;
//CW++
if (item->weapmodel == WEAP_GRAPPLE)
{
if (!(int)sv_allow_hook->value)
return;
if ((int)sv_hook_offhand->value)
{
gi_centerprintf(ent, "Cannot switch to off-hand grapple!\n");
return;
}
if (level.nohook)
{
gi_centerprintf(ent, "The Grapple is disabled for this level.\n");
return;
}
}
//CW--
if (item->ammo && !((int)g_select_empty->value) && !(item->flags & IT_AMMO))
{
ammo_item = FindItem(item->ammo);
ammo_index = ITEM_INDEX(ammo_item);
if (!ent->client->pers.inventory[ammo_index])
{
gi_cprintf(ent, PRINT_HIGH, "No %s for %s.\n", ammo_item->pickup_name, item->pickup_name);
return;
}
if (ent->client->pers.inventory[ammo_index] < item->quantity)
{
gi_cprintf(ent, PRINT_HIGH, "Not enough %s for %s.\n", ammo_item->pickup_name, item->pickup_name);
return;
}
}
// change to this weapon when down
ent->client->newweapon = item;
}
/*
================
Drop_Weapon
================
*/
void Drop_Weapon(edict_t *ent, gitem_t *item)
{
int index;
if ((int)(dmflags->value) & DF_WEAPONS_STAY)
return;
index = ITEM_INDEX(item);
// see if we're already using it
if (((item == ent->client->pers.weapon) || (item == ent->client->newweapon)) && (ent->client->pers.inventory[index] == 1))
{
gi_cprintf(ent, PRINT_HIGH, "Can't drop current weapon\n");
return;
}
Drop_Item(ent, item);
ent->client->pers.inventory[index]--;
}
/*
================
Weapon_Generic
A generic function to handle the basics of weapon thinking
================
*/
#define FRAME_FIRE_FIRST (FRAME_ACTIVATE_LAST + 1)
#define FRAME_IDLE_FIRST (FRAME_FIRE_LAST + 1)
#define FRAME_DEACTIVATE_FIRST (FRAME_IDLE_LAST + 1)
static void Weapon_Generic2(edict_t *ent, int FRAME_ACTIVATE_LAST, int FRAME_FIRE_LAST, int FRAME_IDLE_LAST, int FRAME_DEACTIVATE_LAST, int *pause_frames, int *fire_frames, void (*fire)(edict_t *ent))
{
int n;
if (ent->deadflag || (ent->s.modelindex != (MAX_MODELS-1))) // VWep animations screw up corpses
return;
if (ent->client->weaponstate == WEAPON_DROPPING)
{
if (ent->client->ps.gunframe == FRAME_DEACTIVATE_LAST)
{
ChangeWeapon(ent);
return;
}
else if ((FRAME_DEACTIVATE_LAST - ent->client->ps.gunframe) == 4)
{
ent->client->anim_priority = ANIM_REVERSE;
if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
{
ent->s.frame = FRAME_crpain4+1;
ent->client->anim_end = FRAME_crpain1;
}
else
{
ent->s.frame = FRAME_pain304+1;
ent->client->anim_end = FRAME_pain301;
}
}
ent->client->ps.gunframe++;
return;
}
if (ent->client->weaponstate == WEAPON_ACTIVATING)
{
//CW++
if (ent->client->ps.gunframe == FRAME_ACTIVATE_LAST || ((int)dmflags->value & DF_FAST_SWITCH))
//CW--
{
ent->client->weaponstate = WEAPON_READY;
ent->client->ps.gunframe = FRAME_IDLE_FIRST;
return;
}
ent->client->ps.gunframe++;
return;
}
if (ent->client->newweapon && (ent->client->weaponstate != WEAPON_FIRING))
{
ent->client->weaponstate = WEAPON_DROPPING;
//CW++
if ((int)dmflags->value & DF_FAST_SWITCH)
//CW--
{
if (ent->client->pers.weapon->weapmodel == WEAP_CHAINSAW)
gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/chainsaw/done.wav"), 1, ATTN_NORM, 0);
ChangeWeapon(ent);
return;
}
else
ent->client->ps.gunframe = FRAME_DEACTIVATE_FIRST;
if ((FRAME_DEACTIVATE_LAST - FRAME_DEACTIVATE_FIRST) < 4)
{
ent->client->anim_priority = ANIM_REVERSE;
if(ent->client->ps.pmove.pm_flags & PMF_DUCKED)
{
ent->s.frame = FRAME_crpain4+1;
ent->client->anim_end = FRAME_crpain1;
}
else
{
ent->s.frame = FRAME_pain304+1;
ent->client->anim_end = FRAME_pain301;
}
}
return;
}
if (ent->client->weaponstate == WEAPON_READY)
{
if (((ent->client->latched_buttons|ent->client->buttons) & BUTTON_ATTACK))
{
ent->client->latched_buttons &= ~BUTTON_ATTACK;
if ((!ent->client->ammo_index) || (ent->client->pers.inventory[ent->client->ammo_index] >= ent->client->pers.weapon->quantity))
{
ent->client->ps.gunframe = FRAME_FIRE_FIRST;
ent->client->weaponstate = WEAPON_FIRING;
// start the animation
ent->client->anim_priority = ANIM_ATTACK;
if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
{
ent->s.frame = FRAME_crattak1-1;
ent->client->anim_end = FRAME_crattak9;
}
else
{
ent->s.frame = FRAME_attack1-1;
ent->client->anim_end = FRAME_attack8;
}
}
else
{
if (level.time >= ent->pain_debounce_time)
{
gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0);
ent->pain_debounce_time = level.time + 1.0;
}
NoAmmoWeaponChange(ent);
}
}
else
{
if (ent->client->ps.gunframe == FRAME_IDLE_LAST)
{
ent->client->ps.gunframe = FRAME_IDLE_FIRST;
return;
}
if (pause_frames)
{
for (n = 0; pause_frames[n]; n++)
{
if (ent->client->ps.gunframe == pause_frames[n])
{
if (rand()&15)
return;
}
}
}
ent->client->ps.gunframe++;
return;
}
}
if (ent->client->weaponstate == WEAPON_FIRING)
{
for (n = 0; fire_frames[n]; n++)
{
if (ent->client->ps.gunframe == fire_frames[n])
{
//ZOID++
if (!CTFApplyStrengthSound(ent))
{
//ZOID--
if (ent->client->quad_framenum > level.framenum)
{
//CW++
if ((ent->client->pers.weapon->weapmodel == WEAP_AGM) && !ent->client->agm_disrupt)
;
else
//CW--
gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage3.wav"), 1, ATTN_NORM, 0);
}
}
//ZOID++
CTFApplyHasteSound(ent);
//ZOID--
fire(ent);
break;
}
}
//CW++
// Reload the Desert Eagle if required (a clip holds 9 bullets).
if (ent->client->pers.weapon->weapmodel == WEAP_DESERTEAGLE)
{
if ((ent->client->machinegun_shots > 8) && (ent->client->ps.gunframe == 8))
{
if (level.time >= ent->pain_debounce_time)
ent->pain_debounce_time = level.time + 1.0;
ent->client->machinegun_shots = 0;
if (ent->client->pers.inventory[ent->client->ammo_index] < 1)
NoAmmoWeaponChange(ent);
else
ent->client->newweapon = FindItem("Desert Eagle");
}
}
// Reload the Jackhammer if required (a clip holds 10 shells).
else if (ent->client->pers.weapon->weapmodel == WEAP_JACKHAMMER)
{
if ((ent->client->machinegun_shots > 9) && (ent->client->ps.gunframe == 5))
{
if (level.time >= ent->pain_debounce_time)
ent->pain_debounce_time = level.time + 1.0;
ent->client->machinegun_shots = 0;
if (ent->client->pers.inventory[ent->client->ammo_index] < 1)
NoAmmoWeaponChange(ent);
else
ent->client->newweapon = FindItem("Jackhammer");
}
}
// Reload the Mac-10 if required (a clip holds 32 bullets).
else if (ent->client->pers.weapon->weapmodel == WEAP_MAC10)
{
if ((ent->client->machinegun_shots > 15) && (ent->client->ps.gunframe == 3))
{
ent->client->ps.gunframe = 5;
if (level.time >= ent->pain_debounce_time)
ent->pain_debounce_time = level.time + 1.0;
ent->client->machinegun_shots = 0;
if (ent->client->pers.inventory[ent->client->ammo_index] < 1)
NoAmmoWeaponChange(ent);
else
ent->client->newweapon = FindItem("Mac-10");
}
}
//CW--
if (!fire_frames[n])
ent->client->ps.gunframe++;
if (ent->client->ps.gunframe == FRAME_IDLE_FIRST+1)
ent->client->weaponstate = WEAPON_READY;
}
}
//ZOID++
void Weapon_Generic(edict_t *ent, int FRAME_ACTIVATE_LAST, int FRAME_FIRE_LAST, int FRAME_IDLE_LAST, int FRAME_DEACTIVATE_LAST, int *pause_frames, int *fire_frames, void (*fire)(edict_t *ent))
{
int oldstate = ent->client->weaponstate;
if (ent->client->frozen_framenum > level.framenum)
return;
Weapon_Generic2(ent, FRAME_ACTIVATE_LAST, FRAME_FIRE_LAST, FRAME_IDLE_LAST, FRAME_DEACTIVATE_LAST, pause_frames, fire_frames, fire);
// Run the weapon frame again if hasted, unless the weapon is the Grapple or AGM.
if ((ent->client->pers.weapon->weapmodel == WEAP_GRAPPLE) && (ent->client->weaponstate == WEAPON_FIRING))
return;
//CW++
if ((ent->client->pers.weapon->weapmodel == WEAP_AGM) && (ent->client->weaponstate == WEAPON_FIRING))
return;
//CW--
// If the player has it/them, apply the Haste Tech effect...
if ((CTFApplyHaste(ent) ||
((ent->client->pers.weapon->weapmodel == WEAP_GRAPPLE) && (ent->client->weaponstate != WEAPON_FIRING)))
&& (oldstate == ent->client->weaponstate))
{
Weapon_Generic2(ent, FRAME_ACTIVATE_LAST, FRAME_FIRE_LAST, FRAME_IDLE_LAST, FRAME_DEACTIVATE_LAST, pause_frames, fire_frames, fire);
}
//CW++
// ... or the Haste powerup effect (not both together).
else if ((ent->client->haste_framenum > level.framenum) && (oldstate == ent->client->weaponstate))
Weapon_Generic2(ent, FRAME_ACTIVATE_LAST, FRAME_FIRE_LAST, FRAME_IDLE_LAST, FRAME_DEACTIVATE_LAST, pause_frames, fire_frames, fire);
//CW--
}
//ZOID--
/*
======================================================================
ROCKET LAUNCHER
======================================================================
*/
void Weapon_RocketLauncher_Fire(edict_t *self)
{
vec3_t offset;
vec3_t start;
vec3_t forward;
vec3_t right;
int damage;
int radius_damage;
//CW++
qboolean guided;
//CW--
// Set damage values.
damage = (int)(sv_rocket_damage->value + (random() * 20.0)); //CW
radius_damage = (int)sv_rocket_radius_damage->value; //CW
if (is_quad)
{
damage *= (int)sv_quad_factor->value; //CW
radius_damage *= (int)sv_quad_factor->value; //CW
}
// Set projectile start position and weapon kick info.
AngleVectors(self->client->v_angle, forward, right, NULL);
VectorScale(forward, -2.0, self->client->kick_origin);
self->client->kick_angles[0] = -1.0;
VectorSet(offset, 8.0, 8.0, self->viewheight-8.0);
P_ProjectSource(self->client, self->s.origin, offset, forward, right, start);
// Fire!
//CW++
guided = (self->client->normal_rockets)?false:true;
//CW--
Fire_Rocket(self, start, forward, damage, sv_rocket_speed->value, sv_rocket_radius->value, radius_damage, guided); //CW
gi.WriteByte(svc_muzzleflash);
gi.WriteShort(self-g_edicts);
gi.WriteByte(MZ_ROCKET | is_silenced);
gi.multicast(self->s.origin, MULTICAST_PVS);
self->client->ps.gunframe++;
PlayerNoise(self, start, PNOISE_WEAPON); //CW
if (!((int)dmflags->value & DF_INFINITE_AMMO))
self->client->pers.inventory[self->client->ammo_index]--;
}
void Weapon_RocketLauncher(edict_t *self)
{
static int pause_frames[] = {25, 33, 42, 50, 0};
static int fire_frames[] = {5, 0};
Weapon_Generic(self, 4, 12, 50, 54, pause_frames, fire_frames, Weapon_RocketLauncher_Fire);
}
/*
======================================================================
RAILGUN
======================================================================
*/
void Weapon_Railgun_Fire(edict_t *self)
{
vec3_t start;
vec3_t forward;
vec3_t right;
vec3_t offset;
int damage;
int kick = 200;
// Set damage and kick values.
damage = (int)sv_railgun_damage->value; //CW
if (is_quad)
{
damage *= (int)sv_quad_factor->value;
kick *= (int)sv_quad_factor->value;
}
// Set projectile start position and weapon kick info.
AngleVectors(self->client->v_angle, forward, right, NULL);
VectorScale(forward, -3.0, self->client->kick_origin);
self->client->kick_angles[0] = -3.0;
VectorSet(offset, 0.0, 7.0, self->viewheight-8.0);
P_ProjectSource(self->client, self->s.origin, offset, forward, right, start);
// Fire!
Fire_Rail(self, start, forward, damage, kick);
gi.WriteByte(svc_muzzleflash);
gi.WriteShort(self-g_edicts);
gi.WriteByte(MZ_RAILGUN | is_silenced);
gi.multicast(self->s.origin, MULTICAST_PVS);
self->client->ps.gunframe++;
PlayerNoise(self, start, PNOISE_WEAPON);
if (!((int)dmflags->value & DF_INFINITE_AMMO))
self->client->pers.inventory[self->client->ammo_index]--;
}
void Weapon_Railgun(edict_t *self)
{
static int pause_frames[] = {56, 0};
static int fire_frames[] = {4, 0};
Weapon_Generic(self, 3, 18, 56, 61, pause_frames, fire_frames, Weapon_Railgun_Fire);
}
//CW++
// Awakening weapons.
/*
======================================================================
DESERT EAGLE
======================================================================
*/
void Weapon_DesertEagle_Fire(edict_t *self)
{
trace_t tr;
vec3_t start;
vec3_t forward;
vec3_t right;
vec3_t offset;
vec3_t t_start;
int damage;
int kick = 2;
// Set damage and kick values. There is a 20% chance of doing extra damage.
damage = (int)sv_deserteagle_damage->value + ((random()<0.20)?15:0);
if (is_quad)
{
damage *= (int)sv_quad_factor->value;
kick *= (int)sv_quad_factor->value;
}
// Set projectile start position and weapon kick info.
AngleVectors(self->client->v_angle, forward, right, NULL);
VectorSet(offset, 0.0, 0.0, self->viewheight);
P_ProjectSource(self->client, self->s.origin, offset, forward, right, start);
VectorScale(forward, -2.0, self->client->kick_origin);
self->client->kick_angles[0] = -1.0;
// Fire!
Fire_Bullet(self, start, forward, damage, kick, (int)sv_deserteagle_hspread->value, (int)sv_deserteagle_vspread->value, MOD_DESERTEAGLE);
gi.WriteByte(svc_muzzleflash);
gi.WriteShort(self-g_edicts);
gi.WriteByte(MZ_BLASTER | is_silenced);
gi.multicast(self->s.origin, MULTICAST_PVS);
self->client->ps.gunframe++;
self->client->machinegun_shots++;
PlayerNoise(self, start, PNOISE_WEAPON);
if (!((int)dmflags->value & DF_INFINITE_AMMO))
self->client->pers.inventory[self->client->ammo_index]--;
// Spawn a tracer sometimes (50% chance, if the path is clear).
VectorMA(start, 200.0, forward, t_start);
tr = gi.trace(start, NULL, NULL, t_start, self, MASK_SHOT);
if ((tr.fraction == 1.0) && (random() < 0.5))
Fire_Tracer(self, t_start, forward, 2000.0, 0.2);
}
void Weapon_DesertEagle(edict_t *self)
{
static int pause_frames[] = {19, 32, 0};
static int fire_frames[] = {6, 0};
Weapon_Generic(self, 5, 8, 52, 55, pause_frames, fire_frames, Weapon_DesertEagle_Fire);
}
/*
======================================================================
JACKHAMMER
======================================================================
*/
void Weapon_Jackhammer_Fire(edict_t *self)
{
vec3_t start;
vec3_t forward;
vec3_t right;
vec3_t offset;
int damage;
int kick = 8;
// Set damage and kick values.
damage = (int)sv_jackhammer_damage->value;
if (is_quad)
{
damage *= (int)sv_quad_factor->value;
kick *= (int)sv_quad_factor->value;
}
// Set projectile start position and weapon kick info.
AngleVectors(self->client->v_angle, forward, right, NULL);
VectorSet(offset, 0.0, 8.0, self->viewheight-8.0);
P_ProjectSource(self->client, self->s.origin, offset, forward, right, start);
VectorScale(forward, -2.0, self->client->kick_origin);
self->client->kick_angles[0] = -2.0;
// Fire!
Fire_Shotgun(self, start, forward, damage, kick, (int)sv_jackhammer_hspread->value, (int)sv_jackhammer_vspread->value, DEFAULT_JACKHAMMER_COUNT, MOD_JACKHAMMER);
gi.WriteByte(svc_muzzleflash);
gi.WriteShort(self-g_edicts);
gi.WriteByte(MZ_SSHOTGUN | is_silenced);
gi.multicast(self->s.origin, MULTICAST_PVS);
self->client->ps.gunframe++;
self->client->machinegun_shots++;
PlayerNoise(self, start, PNOISE_WEAPON);
if (!((int)dmflags->value & DF_INFINITE_AMMO))
self->client->pers.inventory[self->client->ammo_index]--;
}
void Weapon_Jackhammer(edict_t *self)
{
static int pause_frames[] = {45, 0};
static int fire_frames[] = {4, 0};
Weapon_Generic(self, 3, 5, 45, 49, pause_frames, fire_frames, Weapon_Jackhammer_Fire);
}
/*
======================================================================
MAC-10
======================================================================
*/
void Weapon_Mac10_Fire(edict_t *self)
{
vec3_t start;
vec3_t forward;
vec3_t right;
vec3_t offset;
vec3_t t_start;
trace_t tr;
int damage;
int kick = 2;
int i;
// Set damage and kick values.
damage = (int)sv_mac10_damage->value;
if (is_quad)
{
damage *= (int)sv_quad_factor->value;
kick *= (int)sv_quad_factor->value;
}
// Set projectile start position and weapon kick info.
AngleVectors(self->client->v_angle, forward, right, NULL);
VectorSet(offset, 0.0, 0.0, self->viewheight);
P_ProjectSource(self->client, self->s.origin, offset, forward, right, start);
VectorScale(forward, -2.0, self->client->kick_origin);
self->client->kick_angles[0] = -1.0;
for (i = 1; i < 3; ++i)
self->client->kick_angles[i] = (random() < 0.5)?-1.0:1.0;
// Fire! (two bullets per trigger press).
for (i = 0; i < 2; ++i)
{
Fire_Bullet(self, start, forward, damage, kick, (int)sv_mac10_hspread->value, (int)sv_mac10_vspread->value, MOD_MAC10);
if (!((int)dmflags->value & DF_INFINITE_AMMO))
self->client->pers.inventory[self->client->ammo_index]--;
// Check if there's enough ammo; swap weapons if we're dry.
if (self->client->pers.inventory[self->client->ammo_index] < 1)
{
self->client->ps.gunframe = 5;
if (level.time >= self->pain_debounce_time)
{
gi.sound(self, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0);
self->pain_debounce_time = level.time + 1.0;
}
self->client->machinegun_shots = 0;
NoAmmoWeaponChange(self);
return;
}
}
self->client->ps.gunframe++;
self->client->machinegun_shots++;
// Spawn a tracer every 4 shots.
if ((self->client->machinegun_shots % 4) == 0)
{
VectorSet(offset, 24.0, 7.0, self->viewheight-6.0);
P_ProjectSource(self->client, self->s.origin, offset, forward, right, start);
VectorMA(start, 300.0, forward, t_start);
tr = gi.trace(start, NULL, NULL, t_start, self, MASK_SHOT);
if (tr.fraction == 1.0)
Fire_Tracer(self, t_start, forward, 2000.0, 0.3);
}
// Loop the animation sequence if the firing button is still being pressed.
if ((self->client->ps.gunframe == 5) && (self->client->buttons & BUTTON_ATTACK))
self->client->ps.gunframe = 3;
// Do the muzzleflash and sound.
gi.WriteByte(svc_muzzleflash);
gi.WriteShort(self-g_edicts);
gi.WriteByte(MZ_MACHINEGUN | is_silenced);
gi.multicast(self->s.origin, MULTICAST_PVS);
PlayerNoise(self, start, PNOISE_WEAPON);
}
void Weapon_Mac10(edict_t *self)
{
static int pause_frames[] = {29, 45, 0};
static int fire_frames[] = {3, 4, 0};
Weapon_Generic(self, 2, 4, 45, 49, pause_frames, fire_frames, Weapon_Mac10_Fire);
}
/*
======================================================================
C4
======================================================================
*/
void Weapon_C4_Fire(edict_t *self, qboolean held)
{
vec3_t offset;
vec3_t forward;
vec3_t right;
vec3_t start;
float timer;
float radius;
int damage;
int speed;
// Set damage values.
damage = (int)sv_c4_damage->value;
radius = sv_c4_radius->value;
if (is_quad)
damage *= (int)sv_quad_factor->value;
// Set projectile start position.
VectorSet(offset, 8.0, 8.0, self->viewheight);
AngleVectors(self->client->v_angle, forward, right, NULL);
P_ProjectSource(self->client, self->s.origin, offset, forward, right, start);
// Determine throwing speed.
timer = level.time - self->client->grenade_time;
if (CTFApplyHaste(self) || (self->client->haste_framenum > level.framenum))
{
timer *= 2.0;
CTFApplyHasteSound(self);
}
speed = (int)(min(sv_c4_min_speed->value + (timer * sv_c4_hold_accel->value), sv_c4_max_speed->value));
// Fire!
Fire_C4(self, start, forward, damage, speed, radius, held);
gi.sound(self, CHAN_WEAPON, gi.soundindex("weapons/hgrent1a.wav"), 1, ATTN_NORM, 0);
if (!((int)dmflags->value & DF_INFINITE_AMMO))
self->client->pers.inventory[self->client->ammo_index]--;
// Handle player model throwing animation.
if (self->deadflag || (self->s.modelindex != (MAX_MODELS-1))) // VWep animations screw up corpses
return;
if (self->client->ps.pmove.pm_flags & PMF_DUCKED)
{
self->client->anim_priority = ANIM_ATTACK;
self->s.frame = FRAME_crattak1-1;
self->client->anim_end = FRAME_crattak3;
}
else
{
self->client->anim_priority = ANIM_REVERSE;
self->s.frame = FRAME_wave08;
self->client->anim_end = FRAME_wave01;
}
}
void Weapon_C4(edict_t *self)
{
int remaining;
if ((self->client->newweapon) && (self->client->weaponstate == WEAPON_READY))
{
ChangeWeapon(self);
return;
}
if (self->client->weaponstate == WEAPON_ACTIVATING)
{
self->client->weaponstate = WEAPON_READY;
self->client->ps.gunframe = 16;
return;
}
if (self->client->weaponstate == WEAPON_READY)
{
if (((self->client->latched_buttons | self->client->buttons) & BUTTON_ATTACK))
{
self->client->latched_buttons &= ~BUTTON_ATTACK;
if (self->client->pers.inventory[self->client->ammo_index])
{
self->client->ps.gunframe = 1;
self->client->weaponstate = WEAPON_FIRING;
self->client->grenade_time = 0.0;
self->client->c4_boom_time = 0.0;
}
else
{
if (level.time >= self->pain_debounce_time)
{
gi.sound(self, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0);
self->pain_debounce_time = level.time + 1.0;
}
NoAmmoWeaponChange(self);
}
return;
}
if ((self->client->ps.gunframe == 29) || (self->client->ps.gunframe == 34) || (self->client->ps.gunframe == 39) || (self->client->ps.gunframe == 48))
{
if (rand() & 15)
return;
}
if (++self->client->ps.gunframe > 48)
self->client->ps.gunframe = 16;
return;
}
if (self->client->weaponstate == WEAPON_FIRING)
{
if (self->client->ps.gunframe == 5)
gi.sound(self, CHAN_WEAPON, gi.soundindex("weapons/c4/arm.wav"), 1, ATTN_NORM, 0);
if (self->client->ps.gunframe == 11)
{
if (!self->client->grenade_time)
self->client->grenade_time = level.time;
if (sv_c4_timelimit->value > 0.0) // auto-detonation 2-second warning sound
{
if (!self->client->c4_boom_time)
self->client->c4_boom_time = level.time + sv_c4_timelimit->value;
remaining = (int)(self->client->c4_boom_time - level.time);
if ((remaining == 1) || (remaining == 2))
self->client->weapon_sound = gi.soundindex("weapons/hgrenc1b.wav");
}
// If player has waited too long, detonate it in their hand.
if (!self->client->grenade_blew_up && (sv_c4_timelimit->value > 0.0) && (level.time >= self->client->c4_boom_time))
{
self->client->weapon_sound = 0;
Weapon_C4_Fire(self, true);
self->client->grenade_blew_up = true;
}
if (self->client->buttons & BUTTON_ATTACK)
return;
if (self->client->grenade_blew_up)
{
if (level.time >= self->client->c4_boom_time)
{
self->client->ps.gunframe = 15;
self->client->grenade_blew_up = false;
}
else
return;
}
}
if (self->client->ps.gunframe == 12)
{
self->client->weapon_sound = 0;
Weapon_C4_Fire(self, false);
}
self->client->ps.gunframe++;
if (self->client->ps.gunframe == 16)
{
self->client->grenade_time = 0.0;
self->client->weaponstate = WEAPON_READY;
}
}
}
/*
======================================================================
TRAPS
======================================================================
*/
void Weapon_Trap_Fire(edict_t *self, qboolean held)
{
vec3_t offset;
vec3_t forward;
vec3_t right;
vec3_t start;
float timer;
int damage_hook;
int damage_beam;
int speed;
// Set damage values.
damage_hook = (int)sv_trap_hook_damage->value;
damage_beam = (int)sv_trap_beam_damage->value;
if (is_quad)
{
damage_hook *= (int)sv_quad_factor->value;
damage_beam *= (int)sv_quad_factor->value;
}
// Set projectile start position.
VectorSet(offset, 8.0, 8.0, self->viewheight);
AngleVectors(self->client->v_angle, forward, right, NULL);
P_ProjectSource(self->client, self->s.origin, offset, forward, right, start);
// Determine throwing speed.
timer = level.time - self->client->grenade_time;
if (CTFApplyHaste(self) || (self->client->haste_framenum > level.framenum))
{
timer *= 2.0;
CTFApplyHasteSound(self);
}
speed = (int)(min(sv_trap_min_speed->value + (timer * sv_trap_hold_accel->value), sv_trap_max_speed->value));
// Fire!
Fire_Trap(self, start, forward, damage_hook, speed, damage_beam, (int)sv_trap_beam_power->value, held);
gi.sound(self, CHAN_WEAPON, gi.soundindex("weapons/hgrent1a.wav"), 1, ATTN_NORM, 0);
if (!((int)dmflags->value & DF_INFINITE_AMMO))
self->client->pers.inventory[self->client->ammo_index]--;
// Handle player model throwing animation.
if (self->deadflag || (self->s.modelindex != (MAX_MODELS-1))) // VWep animations screw up corpses
return;
if (self->client->ps.pmove.pm_flags & PMF_DUCKED)
{
self->client->anim_priority = ANIM_ATTACK;
self->s.frame = FRAME_crattak1-1;
self->client->anim_end = FRAME_crattak3;
}
else
{
self->client->anim_priority = ANIM_REVERSE;
self->s.frame = FRAME_wave08;
self->client->anim_end = FRAME_wave01;
}
}
void Weapon_Trap(edict_t *self)
{
int remaining;
if ((self->client->newweapon) && (self->client->weaponstate == WEAPON_READY))
{
ChangeWeapon(self);
return;
}
if (self->client->weaponstate == WEAPON_ACTIVATING)
{
self->client->weaponstate = WEAPON_READY;
self->client->ps.gunframe = 16;
return;
}
if (self->client->weaponstate == WEAPON_READY)
{
if (((self->client->latched_buttons | self->client->buttons) & BUTTON_ATTACK))
{
self->client->latched_buttons &= ~BUTTON_ATTACK;
if (self->client->pers.inventory[self->client->ammo_index])
{
self->client->ps.gunframe = 1;
self->client->weaponstate = WEAPON_FIRING;
self->client->grenade_time = 0.0;
self->client->c4_boom_time = 0.0;
}
else
{
if (level.time >= self->pain_debounce_time)
{
gi.sound(self, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0);
self->pain_debounce_time = level.time + 1.0;
}
NoAmmoWeaponChange(self);
}
return;
}
if ((self->client->ps.gunframe == 29) || (self->client->ps.gunframe == 34) || (self->client->ps.gunframe == 39) || (self->client->ps.gunframe == 48))
{
if (rand() & 15)
return;
}
if (++self->client->ps.gunframe > 48)
self->client->ps.gunframe = 16;
return;
}
if (self->client->weaponstate == WEAPON_FIRING)
{
if (self->client->ps.gunframe == 5)
gi.sound(self, CHAN_WEAPON, gi.soundindex("weapons/trap/arm.wav"), 1, ATTN_NORM, 0);
if (self->client->ps.gunframe == 11)
{
if (!self->client->grenade_time)
self->client->grenade_time = level.time;
if (sv_traps_timelimit->value > 0.0) // auto-trapping 2-second warning sound
{
if (!self->client->c4_boom_time)
self->client->c4_boom_time = level.time + sv_traps_timelimit->value;
remaining = (int)(self->client->c4_boom_time - level.time);
if ((remaining == 1) || (remaining == 2))
self->client->weapon_sound = gi.soundindex("weapons/hgrenc1b.wav");
}
// If player has waited too long, set the Trap off in their hand.
if (!self->client->grenade_blew_up && (sv_traps_timelimit->value > 0.0) && (level.time >= self->client->c4_boom_time))
{
self->client->weapon_sound = 0;
Weapon_Trap_Fire(self, true);
self->client->grenade_blew_up = true;
}
if (self->client->buttons & BUTTON_ATTACK)
return;
if (self->client->grenade_blew_up)
{
if (level.time >= self->client->c4_boom_time)
{
self->client->ps.gunframe = 15;
self->client->grenade_blew_up = false;
}
else
return;
}
}
if (self->client->ps.gunframe == 12)
{
self->client->weapon_sound = 0;
Weapon_Trap_Fire(self, false);
}
self->client->ps.gunframe++;
if (self->client->ps.gunframe == 16)
{
self->client->grenade_time = 0.0;
self->client->weaponstate = WEAPON_READY;
}
}
}
/*
======================================================================
EXPLOSIVE SPIKE GUN
======================================================================
*/
void Weapon_ESG_Fire(edict_t *self)
{
vec3_t start;
vec3_t start_p;
vec3_t forward;
vec3_t right;
vec3_t offset;
vec3_t dir;
vec3_t end;
trace_t tr;
int damage;
int radius_damage;
int kick = 2;
// Fire up to 3 spikes quickly if the firing button is pressed, then reload (via viewmodel animation).
if (self->client->ps.gunframe == 14)
{
if ((self->client->buttons & BUTTON_ATTACK) && (self->client->machinegun_shots < 3))
self->client->ps.gunframe = 11;
else
{
gi.sound(self, CHAN_WEAPON, gi.soundindex("weapons/esg/reload.wav"), 1, ATTN_NORM, 0);
self->client->machinegun_shots = 0;
self->client->ps.gunframe++;
return;
}
}
if (self->client->ps.gunframe == 24)
{
if (self->client->buttons & BUTTON_ATTACK)
self->client->ps.gunframe = 11;
else
{
self->client->ps.gunframe++;
return;
}
}
// Set damage and kick values.
damage = (int)sv_spike_damage->value;
radius_damage = (int)sv_spike_bang_damage->value;
if (is_quad)
{
damage *= (int)sv_quad_factor->value;
radius_damage *= (int)sv_quad_factor->value;
kick *= (int)sv_quad_factor->value;
}
// Set projectile start position and weapon kick info.
AngleVectors(self->client->v_angle, forward, right, NULL);
VectorSet(offset, 8.0, 8.0, self->viewheight-8.0);
P_ProjectSource(self->client, self->s.origin, offset, forward, right, start);
VectorScale(forward, -2.0, self->client->kick_origin);
self->client->kick_angles[0] = -2.0;
// Set projectile end position and flight direction.
AngleVectors(self->client->v_angle, forward, right, NULL);
VectorMA(self->s.origin, WORLD_SIZE, forward, end); // was 8192.0
VectorSet(offset, 0.0, 0.0, self->viewheight-2.0);
P_ProjectSource(self->client, self->s.origin, offset, forward, right, start_p);
tr = gi.trace(start_p, NULL, NULL, end, self, MASK_SHOT);
VectorSubtract(tr.endpos, start, dir);
VectorNormalize(dir);
// Fire!
Fire_Spike(self, start, dir, damage, sv_spike_speed->value, kick, sv_spike_bang_radius->value, radius_damage);
gi.sound(self, CHAN_WEAPON, gi.soundindex("weapons/esg/fire.wav"), (is_silenced)?0.2:1, ATTN_NORM, 0);
PlayerNoise(self, start, PNOISE_WEAPON);
self->client->ps.gunframe++;
self->client->machinegun_shots++;
if (!((int)dmflags->value & DF_INFINITE_AMMO))
self->client->pers.inventory[self->client->ammo_index]--;
// Check if there's enough ammo; swap weapons if we're dry.
if (self->client->pers.inventory[self->client->ammo_index] < 1)
{
self->client->ps.gunframe = 29;
if (level.time >= self->pain_debounce_time)
{
gi.sound(self, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0);
self->pain_debounce_time = level.time + 1.0;
}
self->client->machinegun_shots = 0;
NoAmmoWeaponChange(self);
}
}
void Fire_MultiSpikes(edict_t *self, vec3_t start, vec3_t aimdir, int damage, float speed, int kick, float damage_radius, int radius_damage, int count)
{
vec3_t dir;
vec3_t newdir;
vec3_t end;
vec3_t forward;
vec3_t right;
int i;
switch (count)
{
case 0:
case 1:
Fire_Spike(self, start, aimdir, damage, speed, kick, damage_radius, radius_damage);
break;
case 2:
vectoangles(aimdir, dir);
AngleVectors(dir, forward, right, NULL);
for (i = -1; i < 2; i += 2)
{
VectorMA(start, WORLD_SIZE, forward, end); // was 8192.0
VectorMA(end, i * SPIKE_SPREAD_HALF, right, end);
VectorSubtract(end, start, newdir);
VectorNormalize(newdir);
Fire_Spike(self, start, newdir, damage, speed, kick, damage_radius, radius_damage);
}
break;
case 3:
vectoangles(aimdir, dir);
AngleVectors(dir, forward, right, NULL);
for (i = -1; i < 2; i++)
{
VectorMA(start, WORLD_SIZE, forward, end); // was 8192.0
VectorMA(end, i * SPIKE_SPREAD, right, end);
VectorSubtract(end, start, newdir);
VectorNormalize(newdir);
Fire_Spike(self, start, newdir, damage, speed, kick, damage_radius, radius_damage);
}
break;
case 4:
vectoangles(aimdir, dir);
AngleVectors(dir, forward, right, NULL);
for (i = -3; i < 4; i += 2)
{
VectorMA(start, WORLD_SIZE, forward, end); // was 8192.0
VectorMA(end, i * SPIKE_SPREAD_HALF, right, end);
VectorSubtract(end, start, newdir);
VectorNormalize(newdir);
Fire_Spike(self, start, newdir, damage, speed, kick, damage_radius, radius_damage);
}
break;
case 5:
vectoangles(aimdir, dir);
AngleVectors(dir, forward, right, NULL);
for (i = -2; i < 3; i++)
{
VectorMA(start, WORLD_SIZE, forward, end); // was 8192.0
VectorMA(end, i * SPIKE_SPREAD, right, end);
VectorSubtract(end, start, newdir);
VectorNormalize(newdir);
Fire_Spike(self, start, newdir, damage, speed, kick, damage_radius, radius_damage);
}
break;
case 6:
vectoangles(aimdir, dir);
AngleVectors(dir, forward, right, NULL);
for (i = -5; i < 6; i += 2)
{
VectorMA(start, WORLD_SIZE, forward, end); // was 8192.0
VectorMA(end, i * SPIKE_SPREAD_HALF, right, end);
VectorSubtract(end, start, newdir);
VectorNormalize(newdir);
Fire_Spike(self, start, newdir, damage, speed, kick, damage_radius, radius_damage);
}
break;
default:
gi.dprintf("BUG: Fire_MultiSpikes() called for > 6 spikes.\nPlease contact musashi@planetquake.com\n");
return;
}
}
void Weapon_ESG_MultiFire(edict_t *self)
{
vec3_t start;
vec3_t forward;
vec3_t right;
vec3_t offset;
int damage;
int radius_damage;
int kick = 2;
// If we ran out of ammo during the previous function call, fire all the loaded spikes.
if (self->client->pers.inventory[self->client->ammo_index] == 0)
goto fire_now;
// As long as the firing button is held down, keep building up the store of spikes. When there are
// six spikes in the chamber, or the firing button has been released, or we're about to run out
// of ammo, fire the spikes together.
if (self->client->ps.gunframe == 11) // start of weapon kick animation
{
if (self->client->buttons & BUTTON_ATTACK)
{
if (!((int)dmflags->value & DF_INFINITE_AMMO))
self->client->pers.inventory[self->client->ammo_index]--;
if (self->client->machinegun_shots == -1) // reset the "recently fired" flag
self->client->machinegun_shots = 0;
self->client->machinegun_shots++;
if (self->client->machinegun_shots < 6)
{
self->client->ps.gunframe = 15;
gi.sound(self, CHAN_WEAPON, gi.soundindex("weapons/esg/reload.wav"), 1, ATTN_NORM, 0);
if (self->client->pers.inventory[self->client->ammo_index] >= 0)
return;
}
}
else
{
if (self->client->machinegun_shots == -1) // have fired recently, so don't fire another spike yet
return;
}
}
if (self->client->ps.gunframe == 14) // end of weapon kick animation
{
if (self->client->buttons & BUTTON_ATTACK)
self->client->ps.gunframe = 11;
else
self->client->ps.gunframe = 24;
return;
}
if (self->client->ps.gunframe == 22) // near end of reload animation
{
if ((self->client->buttons & BUTTON_ATTACK) && (self->client->machinegun_shots < 6))
{
self->client->ps.gunframe = 11;
return;
}
}
// Set damage and kick values.
fire_now:
damage = (int)sv_spike_damage->value;
radius_damage = (int)sv_spike_bang_damage->value;
if (is_quad)
{
damage *= (int)sv_quad_factor->value;
radius_damage *= (int)sv_quad_factor->value;
kick *= (int)sv_quad_factor->value;
}
// Set projectile start position and weapon kick info.
AngleVectors(self->client->v_angle, forward, right, NULL);
VectorSet(offset, 8.0, 8.0, self->viewheight-8.0);
P_ProjectSource(self->client, self->s.origin, offset, forward, right, start);
VectorScale(forward, -2.0, self->client->kick_origin);
self->client->kick_angles[0] = -2.0;
// Fire!
Fire_MultiSpikes(self, start, forward, damage, sv_spike_speed->value, kick, sv_spike_bang_radius->value, radius_damage, self->client->machinegun_shots);
gi.sound(self, CHAN_WEAPON, gi.soundindex("weapons/esg/fire.wav"), (is_silenced)?0.2:1, ATTN_NORM, 0);
PlayerNoise(self, start, PNOISE_WEAPON);
self->client->ps.gunframe = 12;
if (self->client->machinegun_shots == 0) // was a quick fire-button press, so ammo not depleted yet (see above)
self->client->pers.inventory[self->client->ammo_index]--;
self->client->machinegun_shots = -1; // use as a "recently fired" flag
// Check if there's enough ammo; swap weapons if we're dry.
if (self->client->pers.inventory[self->client->ammo_index] < 1)
{
self->client->ps.gunframe = 29;
if (level.time >= self->pain_debounce_time)
{
gi.sound(self, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0);
self->pain_debounce_time = level.time + 1.0;
}
self->client->machinegun_shots = 0;
NoAmmoWeaponChange(self);
}
}
void Weapon_ESG(edict_t *self)
{
static int pause_frames[] = {30, 40, 0};
static int fire_frames[] = {11, 14, 24, 0};
static int fire_frames_multi[] = {11, 14, 22, 0};
if (self->client->multi_spike)
{
if ((self->client->ps.gunframe == 28) && (self->client->machinegun_shots == -1))
self->client->machinegun_shots = 0; // reset the "recently fired" flag
Weapon_Generic(self, 7, 28, 40, 44, pause_frames, fire_frames_multi, Weapon_ESG_MultiFire);
if (!(self->client->buttons & BUTTON_ATTACK) && (self->client->machinegun_shots > 0))
Weapon_ESG_MultiFire(self);
}
else
Weapon_Generic(self, 7, 28, 40, 44, pause_frames, fire_frames, Weapon_ESG_Fire);
}
/*
======================================================================
FLAMETHROWER
======================================================================
*/
void Weapon_Flamethrower_Fire(edict_t *self)
{
vec3_t forward;
vec3_t right;
vec3_t start;
vec3_t offset;
int damage;
int damage_minor;
// Check if there's enough ammo; swap weapons if we're dry.
if (self->client->pers.inventory[self->client->ammo_index] < 1)
{
self->client->ps.gunframe = 9;
if (level.time >= self->pain_debounce_time)
{
gi.sound(self, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0);
self->pain_debounce_time = level.time + 1.0;
}
NoAmmoWeaponChange(self);
return;
}
// Set projectile/puff start position.
AngleVectors(self->client->v_angle, forward, right, NULL);
VectorSet(offset, 20.0, 8.0, self->viewheight-8.0);
P_ProjectSource(self->client, self->s.origin, offset, forward, right, start);
// Flamethrower doesn't work underwater.
if (self->waterlevel < 2)
{
// Set damage value.
damage = (int)sv_flame_damage->value;
damage_minor = (int)sv_flame_small_damage->value;
if (is_quad)
{
damage *= (int)sv_quad_factor->value;
damage_minor *= (int)sv_quad_factor->value;
}
// Fire!
if (self->client->ps.gunframe == 5)
Fire_Fireball(self, start, forward, damage, damage_minor, sv_flame_speed->value, true);
else
Fire_Fireball(self, start, forward, damage, damage_minor, sv_flame_speed->value, false);
if (self->client->ps.gunframe % 2)
{
gi.WriteByte(svc_muzzleflash);
gi.WriteShort(self-g_edicts);
gi.WriteByte(MZ_HYPERBLASTER | is_silenced);
gi.multicast(self->s.origin, MULTICAST_PVS);
PlayerNoise(self, start, PNOISE_WEAPON);
}
}
else
{
VectorSet(forward, 0.0, 0.0, 1.0);
gi.WriteByte(svc_temp_entity);
gi.WriteByte(TE_HEATBEAM_SPARKS);
gi.WritePosition(start);
gi.WriteDir(forward);
gi.multicast(start, MULTICAST_PVS);
if (self->client->ps.gunframe == 5)
gi.sound(self, CHAN_WEAPON, gi.soundindex("misc/lasfly.wav"), 1, ATTN_NORM, 0);
}
// Advance animation frame, and remove 0.1 second delay between 4-flame bursts when holding down the fire button.
self->client->ps.gunframe++;
if ((self->client->ps.gunframe == 9) && (self->client->buttons & BUTTON_ATTACK))
self->client->ps.gunframe = 5;
// Spend ammo.
if (!((int)dmflags->value & DF_INFINITE_AMMO))
self->client->pers.inventory[self->client->ammo_index]--;
}
void Weapon_Flamethrower_FB_Fire(edict_t *self)
{
vec3_t forward;
vec3_t right;
vec3_t start;
vec3_t offset;
int damage;
int damage_minor;
int kick = 2;
// We want the rate-of-fire to be 1 shot per second.
if (self->last_move_time > level.time)
{
self->client->ps.gunframe = 5;
return;
}
// Check if there's enough ammo; swap weapons if we're dry.
if (self->client->pers.inventory[self->client->ammo_index] < FT_CELLS_PER_SHOT)
{
self->client->ps.gunframe = 9;
if (level.time >= self->pain_debounce_time)
{
gi.sound(self, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0);
self->pain_debounce_time = level.time + 1.0;
}
NoAmmoWeaponChange(self);
return;
}
// Set projectile/puff start position.
AngleVectors(self->client->v_angle, forward, right, NULL);
VectorSet(offset, 20.0, 8.0, self->viewheight-8.0);
P_ProjectSource(self->client, self->s.origin, offset, forward, right, start);
// Flamethrower doesn't work underwater.
if (self->waterlevel < 2)
{
// Set damage value.
damage = (int)sv_firebomb_damage->value;
damage_minor = (int)sv_flame_damage->value;
if (is_quad)
{
damage *= (int)sv_quad_factor->value;
damage_minor *= (int)sv_quad_factor->value;
kick *= (int)sv_quad_factor->value;
}
// Fire!
Fire_Firebomb(self, start, forward, damage, damage_minor, kick, sv_firebomb_radius->value, sv_firebomb_speed->value);
self->last_move_time = level.time + 1.0;
gi.WriteByte(svc_muzzleflash);
gi.WriteShort(self-g_edicts);
gi.WriteByte(MZ_HYPERBLASTER | is_silenced);
gi.multicast(self->s.origin, MULTICAST_PVS);
PlayerNoise(self, start, PNOISE_WEAPON);
}
else
{
VectorSet(forward, 0.0, 0.0, 1.0);
gi.WriteByte(svc_temp_entity);
gi.WriteByte(TE_HEATBEAM_SPARKS);
gi.WritePosition(start);
gi.WriteDir(forward);
gi.multicast(start, MULTICAST_PVS);
if (self->client->ps.gunframe == 5)
gi.sound(self, CHAN_WEAPON, gi.soundindex("misc/lasfly.wav"), 1, ATTN_NORM, 0);
}
self->client->ps.gunframe++;
// Spend ammo.
if (!((int)dmflags->value & DF_INFINITE_AMMO))
self->client->pers.inventory[self->client->ammo_index] -= FT_CELLS_PER_SHOT;
}
void Weapon_Flamethrower(edict_t *self)
{
static int pause_frames[] = {49, 0};
static int fire_frames[] = {5, 6, 7, 8, 0};
static int fire_frames_fb[] = {5, 0};
if (self->client->ft_firebomb)
Weapon_Generic(self, 4, 8, 49, 53, pause_frames, fire_frames_fb, Weapon_Flamethrower_FB_Fire);
else
Weapon_Generic(self, 4, 8, 49, 53, pause_frames, fire_frames, Weapon_Flamethrower_Fire);
}
/*
======================================================================
SHOCK RIFLE
======================================================================
*/
void Weapon_ShockRifle_Fire(edict_t *self)
{
vec3_t start;
vec3_t forward;
vec3_t right;
vec3_t offset;
float speed;
int damage_plasma;
int damage_shockbolt;
int kick = 2;
// Check if there's enough ammo; swap weapons if we're dry.
if (self->client->pers.inventory[self->client->ammo_index] < SR_CELLS_PER_SHOT)
{
self->client->ps.gunframe = 16;
if (level.time >= self->pain_debounce_time)
{
gi.sound(self, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0);
self->pain_debounce_time = level.time + 1.0;
}
NoAmmoWeaponChange(self);
return;
}
// Set damage, kick and speed values.
damage_plasma = (int)sv_shock_homing_damage->value;
damage_shockbolt = (int)sv_shock_radius_damage->value;
if (is_quad)
{
damage_plasma *= (int)sv_quad_factor->value;
damage_shockbolt *= (int)sv_quad_factor->value;
kick *= (int)sv_quad_factor->value;
}
speed = (self->client->homing_plasma)? sv_shock_homing_speed->value : sv_shock_speed->value;
// Set projectile start position and weapon kick info.
AngleVectors(self->client->v_angle, forward, right, NULL);
VectorSet(offset, 8.0, 8.0, self->viewheight-8.0);
P_ProjectSource(self->client, self->s.origin, offset, forward, right, start);
VectorScale(forward, -2.0, self->client->kick_origin);
self->client->kick_angles[0] = -1.0;
// Fire!
Fire_Shock(self, start, forward, damage_plasma, speed, kick, damage_shockbolt, sv_shock_radius->value, self->client->homing_plasma);
gi.WriteByte(svc_muzzleflash);
gi.WriteShort(self-g_edicts);
gi.WriteByte(MZ_BFG | is_silenced);
gi.multicast(self->s.origin, MULTICAST_PVS);
self->client->ps.gunframe++;
PlayerNoise(self, start, PNOISE_WEAPON);
if (!((int)dmflags->value & DF_INFINITE_AMMO))
self->client->pers.inventory[self->client->ammo_index] -= SR_CELLS_PER_SHOT;
}
void Weapon_ShockRifle(edict_t *self)
{
static int pause_frames[] = {18, 29, 39, 58, 0};
static int fire_frames[] = {4, 0};
Weapon_Generic(self, 3, 15, 58, 62, pause_frames, fire_frames, Weapon_ShockRifle_Fire);
}
/*
======================================================================
GAUSS PISTOL
======================================================================
*/
void Weapon_GaussPistol_Fire(edict_t *self)
{
vec3_t start;
vec3_t forward;
vec3_t right;
vec3_t offset;
int damage;
int kick = 2;
qboolean insufficient_ammo = false;
// Check if there's enough ammo; swap weapons if we're dry.
if (self->client->gauss_particle)
{
if ((self->client->pers.inventory[ITEM_INDEX(FindItem ("slugs"))] < GP_SLUGS_PER_SHOT)
|| (self->client->pers.inventory[ITEM_INDEX(FindItem ("cells"))] < GP_CELLS_PER_SHOT))
insufficient_ammo = true;
}
else
{
if (self->client->pers.inventory[self->client->ammo_index] < 1)
insufficient_ammo = true;
}
if (insufficient_ammo)
{
self->client->ps.gunframe = 10;
if (level.time >= self->pain_debounce_time)
{
gi.sound(self, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0);
self->pain_debounce_time = level.time + 1.0;
}
NoAmmoWeaponChange(self);
return;
}
// Set damage and kick values.
damage = (self->client->gauss_particle)? (int)sv_gauss_damage_particle->value : self->client->gauss_dmg;
if (is_quad)
{
damage *= (int)sv_quad_factor->value;
kick *= (int)sv_quad_factor->value;
}
// Set projectile start position and weapon kick info.
AngleVectors(self->client->v_angle, forward, right, NULL);
if (self->client->gauss_particle)
VectorSet(offset, 24.0, 8.0, self->viewheight-5.0); // NB: should be same as offset in ShowGaussTarget()
else
VectorSet(offset, 0.0, 0.0, self->viewheight-2.0);
P_ProjectSource(self->client, self->s.origin, offset, forward, right, start);
VectorScale(forward, -2.0, self->client->kick_origin);
self->client->kick_angles[0] = -1.0;
// Fire!
if (self->client->gauss_particle)
Fire_Particle(self, start, forward, damage, kick);
else
{
Fire_Instabolt(self, start, forward, damage, kick);
self->client->gauss_framenum = level.framenum;
self->client->gauss_dmg = (int)sv_gauss_damage_base->value;
}
gi.WriteByte(svc_muzzleflash);
gi.WriteShort(self-g_edicts);
gi.WriteByte(MZ_SHOTGUN | is_silenced);
gi.multicast(self->s.origin, MULTICAST_PVS);
self->client->ps.gunframe++;
PlayerNoise(self, start, PNOISE_WEAPON);
if (!((int)dmflags->value & DF_INFINITE_AMMO))
{
if (self->client->gauss_particle)
{
self->client->pers.inventory[self->client->ammo_index] -= GP_CELLS_PER_SHOT;
self->client->pers.inventory[ITEM_INDEX(FindItem ("slugs"))] -= GP_SLUGS_PER_SHOT;
}
else
self->client->pers.inventory[self->client->ammo_index]--;
}
}
void Weapon_GaussPistol(edict_t *self)
{
static int pause_frames[] = {18, 50, 0};
static int fire_frames[] = {4, 0};
if (!self->client->show_gausscharge)
{
if (!self->client->gauss_particle && ((self->client->weaponstate == WEAPON_FIRING) || (self->client->weaponstate == WEAPON_READY)))
{
self->client->show_gausscharge = true;
self->client->gauss_framenum = level.framenum;
self->client->gauss_dmg = (int)sv_gauss_damage_base->value;
}
else
self->client->show_gausscharge = false;
}
Weapon_Generic(self, 3, 9, 50, 53, pause_frames, fire_frames, Weapon_GaussPistol_Fire);
}
/*
======================================================================
ANTI-GRAV MANIPULATOR
======================================================================
*/
void AGM_Reset(edict_t *self)
{
self->client->agm_on = false;
self->client->agm_push = false;
self->client->agm_pull = false;
if (self->client->agm_target != NULL)
{
self->client->agm_target->client->held_by_agm = false;
self->client->agm_target->client->flung_by_agm = false;
self->client->agm_target->client->thrown_by_agm = true;
self->client->agm_target = NULL;
}
}
void Weapon_AGM_Fire(edict_t *self)
{
vec3_t start;
vec3_t forward;
vec3_t right;
vec3_t offset;
if (!(self->client->buttons & BUTTON_ATTACK))
{
self->client->weaponstate = WEAPON_READY;
return;
}
// Once the AGM has tripped off, it can't be fired again until it has recharged to 100.
if ((int)sv_agm_mode->value == 0)
{
if (self->client->agm_tripped && (self->client->agm_target == NULL))
{
gi.sound(self, CHAN_WEAPON, gi.soundindex("weapons/agm/agm_trip.wav"), 1, ATTN_STATIC, 0);
return;
}
}
// Wrap round animation to the start of the firing set if the fire button is still being pressed.
self->client->ps.gunframe++;
if ((self->client->ps.gunframe == 21) && self->client->pers.inventory[self->client->ammo_index])
{
if (self->client->buttons & BUTTON_ATTACK)
self->client->ps.gunframe = 6;
else
return;
}
// Check if there's enough ammo; swap weapons if we're dry.
if (self->client->pers.inventory[self->client->ammo_index] < (int)sv_agm_beam_cells->value)
{
self->client->ps.gunframe = 21;
if (level.time >= self->pain_debounce_time)
{
gi.sound(self, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0);
self->pain_debounce_time = level.time + 1.0;
}
AGM_Reset(self);
NoAmmoWeaponChange(self);
return;
}
// Set beam start position.
AngleVectors(self->client->v_angle, forward, right, NULL);
VectorSet(offset, 24.0, 7.0, self->viewheight-6.0);
P_ProjectSource(self->client, self->s.origin, offset, forward, right, start);
// Fire!
if (self->client->agm_target != NULL)
Move_AGM(self, start, forward);
else
{
if (!self->client->agm_on)
{
self->client->agm_on = true;
if (((int)sv_agm_mode->value == 0) && (!((int)dmflags->value & DF_INFINITE_AMMO)))
self->client->pers.inventory[self->client->ammo_index] -= (int)sv_agm_shot_cells->value;
}
Fire_AGM(self, start, forward, self->client->agm_disrupt);
gi.sound(self, CHAN_WEAPON, gi.soundindex("medic/medatck1.wav"), 1, ATTN_NORM, 0);
PlayerNoise(self, start, PNOISE_WEAPON);
}
if (!((int)dmflags->value & DF_INFINITE_AMMO))
{
self->client->pers.inventory[self->client->ammo_index] -= (int)sv_agm_beam_cells->value;
// The Haste powerup/Tech effect needs to be applied separately due to the way that
// the secondary mode is implemented.
if (CTFApplyHaste(self) || (self->client->haste_framenum > level.framenum))
{
self->client->pers.inventory[self->client->ammo_index] -= (int)sv_agm_beam_cells->value;
CTFApplyHasteSound(self);
}
if (self->client->pers.inventory[self->client->ammo_index] < 0)
self->client->pers.inventory[self->client->ammo_index] = 0;
}
if (self->client->pers.inventory[self->client->ammo_index] == 0)
return;
// Spend extra ammo if we're manipulating a player who has the Invulnerability.
if ((self->client->agm_target != NULL) && (self->client->agm_target->client->invincible_framenum > level.framenum))
{
if ((int)sv_agm_invuln_cells->value > 0)
{
self->client->pers.inventory[self->client->ammo_index] -= (int)sv_agm_invuln_cells->value;
if (self->client->pers.inventory[self->client->ammo_index] < 0)
self->client->pers.inventory[self->client->ammo_index] = 0;
}
}
// Decrease AGM charge if there's no target.
if ((self->client->agm_target == NULL) && ((int)sv_agm_mode->value == 0))
{
self->client->agm_charge -= ((is_quad && self->client->agm_disrupt)?2:1) * (int)sv_agm_fire_rate->value;
if (self->client->agm_charge <= 0)
{
self->client->agm_charge = 0;
if (!self->client->agm_tripped)
{
self->client->agm_tripped = true;
AGM_Reset(self);
}
}
}
}
void Weapon_AGM(edict_t *self)
{
static int pause_frames[] = {21, 43, 49, 0};
static int fire_frames[] = {6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 0};
static gitem_t *tech = NULL;
if (!self->client->agm_showcharge && ((int)sv_agm_mode->value == 0))
self->client->agm_showcharge = true;
// Implement the effects of the '+push' and '+pull' commands.
if (self->client->agm_pull)
{
self->client->agm_range -= AGM_RANGE_DELTA;
if (self->client->agm_range < AGM_RANGE_MIN)
self->client->agm_range = AGM_RANGE_MIN;
}
else if (self->client->agm_push)
{
self->client->agm_range += AGM_RANGE_DELTA;
if (self->client->agm_range > AGM_RANGE_MAX)
self->client->agm_range = AGM_RANGE_MAX;
}
// If the player has stopped firing and they had an AGM target, flag that target as being thrown.
if (self->client->agm_on && !(self->client->buttons & BUTTON_ATTACK))
{
AGM_Reset(self);
self->client->ps.gunframe = 21;
// Play Quad or Tech sounds, if appropriate.
if (!tech)
tech = FindItemByClassname("item_tech2");
if (tech && self->client && self->client->pers.inventory[ITEM_INDEX(tech)])
{
if (self->client->ctf_techsndtime < level.time)
{
self->client->ctf_techsndtime = level.time + 1.0;
if (self->client->quad_framenum > level.framenum)
gi.sound(self, CHAN_VOICE, gi.soundindex("ctf/tech2x.wav"), 1, ATTN_NORM, 0);
else
gi.sound(self, CHAN_VOICE, gi.soundindex("ctf/tech2.wav"), 1, ATTN_NORM, 0);
}
}
else if (self->client->quad_framenum > level.framenum)
gi.sound(self, CHAN_ITEM, gi.soundindex("items/damage3.wav"), 1, ATTN_NORM, 0);
}
// Sanity check: if we have an AGM target and they've just died, but for some reason our agm_target
// pointer hasn't been reset, then do it manually here.
if ((self->client->agm_target != NULL) && (self->client->agm_target->health < 1))
{
self->client->agm_on = false;
self->client->agm_target->client->flung_by_agm = false;
self->client->agm_target->client->thrown_by_agm = false;
self->client->agm_target = NULL;
}
// Charge up the AGM if it's not firing.
if ((int)sv_agm_mode->value == 0)
{
if (!self->client->agm_on && (self->client->agm_charge < 100))
{
self->client->agm_charge += (int)sv_agm_charge_rate->value;
if (self->client->agm_charge >= 100)
{
self->client->agm_charge = 100;
if (self->client->agm_tripped)
{
self->client->agm_tripped = false;
gi.sound(self, CHAN_VOICE, gi.soundindex("world/fusein.wav"), 1, ATTN_STATIC, 0);
}
}
}
}
// Weapon actions.
Weapon_Generic(self, 5, 20, 49, 53, pause_frames, fire_frames, Weapon_AGM_Fire);
}
/*
======================================================================
DISC LAUNCHER
======================================================================
*/
void Weapon_DiscLauncher_Fire(edict_t *self)
{
vec3_t offset;
vec3_t start;
vec3_t forward;
vec3_t right;
int damage;
int kick = 20;
// Set damage and kick values.
damage = (int)sv_disc_damage->value;
if (is_quad)
{
damage *= (int)sv_quad_factor->value;
kick *= (int)sv_quad_factor->value;
}
// Set projectile start position and weapon kick info.
AngleVectors(self->client->v_angle, forward, right, NULL);
VectorScale(forward, -2.0, self->client->kick_origin);
self->client->kick_angles[0] = -2.0;
VectorSet(offset, 0.0, 8.0, self->viewheight-8.0);
P_ProjectSource(self->client, self->s.origin, offset, forward, right, start);
// Fire!
Fire_Disc(self, start, forward, damage, sv_disc_speed->value, kick);
gi.WriteByte(svc_muzzleflash);
gi.WriteShort(self-g_edicts);
gi.WriteByte(MZ_IONRIPPER | is_silenced);
gi.multicast(self->s.origin, MULTICAST_PVS);
self->client->ps.gunframe++;
PlayerNoise(self, start, PNOISE_WEAPON);
if (!((int)dmflags->value & DF_INFINITE_AMMO))
self->client->pers.inventory[self->client->ammo_index]--;
}
void Weapon_DiscLauncher(edict_t *self)
{
static int pause_frames[] = {25, 54, 0};
static int fire_frames[] = {5, 0};
Weapon_Generic(self, 4, 14, 54, 58, pause_frames, fire_frames, Weapon_DiscLauncher_Fire);
}
/*
======================================================================
CHAINSAW
======================================================================
*/
void Weapon_Chainsaw_Fire(edict_t *self)
{
vec3_t offset;
vec3_t start;
vec3_t forward;
vec3_t right;
int damage;
int kick = 25;
// Set damage and kick values.
damage = (int)sv_chainsaw_damage->value;
if (is_quad)
{
damage *= (int)sv_quad_factor->value;
kick *= (int)sv_quad_factor->value;
}
if ((self->client->haste_framenum > level.framenum) || CTFApplyHaste(self))
{
damage *= 2;
kick *= 2;
}
// Set damage point start position and weapon kick info.
AngleVectors(self->client->v_angle, forward, right, NULL);
VectorScale(forward, -2.0, self->client->kick_origin);
self->client->kick_angles[0] = -2.0;
VectorSet(offset, 8.0, 8.0, self->viewheight-8.0);
P_ProjectSource(self->client, self->s.origin, offset, forward, right, start);
// Fire!
Fire_Chainsaw(self, start, forward, damage, kick);
gi.sound(self, CHAN_WEAPON, gi.soundindex("weapons/chainsaw/fire.wav"), 1, ATTN_NORM, 0);
PlayerNoise(self, start, PNOISE_WEAPON);
// We just want to repeat a couple of animation frames (19 and 20). Although the chainsaw model
// has two nifty attack sequences, we want to make it more like the Doom II chainsaw.
self->client->ps.gunframe++;
self->client->machinegun_shots = 1;
if ((self->client->buttons & BUTTON_ATTACK) && (self->client->ps.gunframe == 21))
{
self->client->ps.gunframe = 19;
return;
}
if (self->client->ps.gunframe == 8)
self->client->ps.gunframe = 19;
}
void Weapon_Chainsaw(edict_t *self)
{
static int pause_frames[] = {69, 0};
static int fire_frames[] = {7, 19, 20, 21, 0};
// Handle customised view model animation. Note that the machinegun_shots hack is needed
// to prevent screwing up the weaponstate value if instant switching is not enabled.
if ((self->client->ps.gunframe < 22) && self->client->machinegun_shots && !(self->client->buttons & BUTTON_ATTACK))
{
self->client->ps.gunframe = 28;
self->client->machinegun_shots ^= 1;
}
Weapon_Generic(self, 6, 29, 69, 73, pause_frames, fire_frames, Weapon_Chainsaw_Fire);
// Play the "revving down" sound at the start of the putaway sequence. (Note that this frame isn't
// played if DF_FAST_SWITCH is set, so for that case we have to do it in Weapon_Generic2() manually).
if (self->client->ps.gunframe == 70)
gi.sound(self, CHAN_WEAPON, gi.soundindex("weapons/chainsaw/done.wav"), 1, ATTN_NORM, 0);
}
//CW--