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

2733 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;
int red=20, green=48, blue=176;
qboolean useColor=false;
// 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;
}
// Knightmare- custom client color
if ( self->client && (self->client->pers.color1[3] != 0) )
{
useColor = true;
red = self->client->pers.color1[0];
green = self->client->pers.color1[1];
blue = self->client->pers.color1[2];
}
// 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, useColor, red, green, blue);
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--