mirror of
https://github.com/id-Software/quake2-rerelease-dll.git
synced 2025-02-14 15:51:42 +00:00
1907 lines
48 KiB
C++
1907 lines
48 KiB
C++
// Copyright (c) ZeniMax Media Inc.
|
|
// Licensed under the GNU General Public License 2.0.
|
|
// g_weapon.c
|
|
|
|
#include "g_local.h"
|
|
#include "m_player.h"
|
|
|
|
bool is_quad;
|
|
// RAFAEL
|
|
bool is_quadfire;
|
|
// RAFAEL
|
|
player_muzzle_t is_silenced;
|
|
|
|
// PGM
|
|
byte damage_multiplier;
|
|
// PGM
|
|
|
|
void weapon_grenade_fire(edict_t *ent, bool held);
|
|
// RAFAEL
|
|
void weapon_trap_fire(edict_t *ent, bool held);
|
|
// RAFAEL
|
|
|
|
//========
|
|
// [Kex]
|
|
bool G_CheckInfiniteAmmo(gitem_t *item)
|
|
{
|
|
if (item->flags & IF_NO_INFINITE_AMMO)
|
|
return false;
|
|
|
|
return g_infinite_ammo->integer || g_instagib->integer;
|
|
}
|
|
|
|
//========
|
|
// ROGUE
|
|
byte P_DamageModifier(edict_t *ent)
|
|
{
|
|
is_quad = 0;
|
|
damage_multiplier = 1;
|
|
|
|
if (ent->client->quad_time > level.time)
|
|
{
|
|
damage_multiplier *= 4;
|
|
is_quad = 1;
|
|
|
|
// if we're quad and DF_NO_STACK_DOUBLE is on, return now.
|
|
if (g_dm_no_stack_double->integer)
|
|
return damage_multiplier;
|
|
}
|
|
|
|
if (ent->client->double_time > level.time)
|
|
{
|
|
damage_multiplier *= 2;
|
|
is_quad = 1;
|
|
}
|
|
|
|
return damage_multiplier;
|
|
}
|
|
// ROGUE
|
|
//========
|
|
|
|
// [Paril-KEX] kicks in vanilla take place over 2 10hz server
|
|
// frames; this is to mimic that visual behavior on any tickrate.
|
|
inline float P_CurrentKickFactor(edict_t *ent)
|
|
{
|
|
if (ent->client->kick.time < level.time)
|
|
return 0.f;
|
|
|
|
float f = (ent->client->kick.time - level.time).seconds() / ent->client->kick.total.seconds();
|
|
return f;
|
|
}
|
|
|
|
// [Paril-KEX]
|
|
vec3_t P_CurrentKickAngles(edict_t *ent)
|
|
{
|
|
return ent->client->kick.angles * P_CurrentKickFactor(ent);
|
|
}
|
|
|
|
vec3_t P_CurrentKickOrigin(edict_t *ent)
|
|
{
|
|
return ent->client->kick.origin * P_CurrentKickFactor(ent);
|
|
}
|
|
|
|
void P_AddWeaponKick(edict_t *ent, const vec3_t &origin, const vec3_t &angles)
|
|
{
|
|
ent->client->kick.origin = origin;
|
|
ent->client->kick.angles = angles;
|
|
ent->client->kick.total = 200_ms;
|
|
ent->client->kick.time = level.time + ent->client->kick.total;
|
|
}
|
|
|
|
void P_ProjectSource(edict_t *ent, const vec3_t &angles, vec3_t distance, vec3_t &result_start, vec3_t &result_dir)
|
|
{
|
|
if (ent->client->pers.hand == LEFT_HANDED)
|
|
distance[1] *= -1;
|
|
else if (ent->client->pers.hand == CENTER_HANDED)
|
|
distance[1] = 0;
|
|
|
|
vec3_t forward, right, up;
|
|
vec3_t eye_position = (ent->s.origin + vec3_t{ 0, 0, (float) ent->viewheight });
|
|
|
|
AngleVectors(angles, forward, right, up);
|
|
|
|
result_start = G_ProjectSource2(eye_position, distance, forward, right, up);
|
|
|
|
vec3_t end = eye_position + forward * 8192;
|
|
contents_t mask = MASK_PROJECTILE & ~CONTENTS_DEADMONSTER;
|
|
|
|
// [Paril-KEX]
|
|
if (!G_ShouldPlayersCollide(true))
|
|
mask &= ~CONTENTS_PLAYER;
|
|
|
|
trace_t tr = gi.traceline(eye_position, end, ent, mask);
|
|
|
|
// if the point was a monster & close to us, use raw forward
|
|
// so railgun pierces properly
|
|
if (tr.startsolid || ((tr.contents & (CONTENTS_MONSTER | CONTENTS_PLAYER)) && (tr.fraction * 8192.f) < 128.f))
|
|
result_dir = forward;
|
|
else
|
|
{
|
|
end = tr.endpos;
|
|
result_dir = (end - result_start).normalized();
|
|
|
|
#if 0
|
|
// correction for blocked shots
|
|
trace_t eye_tr = gi.traceline(result_start, result_start + (result_dir * tr.fraction * 8192.f), ent, mask);
|
|
|
|
if ((eye_tr.endpos - tr.endpos).length() > 32.f)
|
|
{
|
|
result_start = eye_position;
|
|
result_dir = (end - result_start).normalized();
|
|
return;
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
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)
|
|
|
|
Monsters that don't directly see the player can move
|
|
to a noise in hopes of seeing the player from there.
|
|
===============
|
|
*/
|
|
void PlayerNoise(edict_t *who, const vec3_t &where, player_noise_t type)
|
|
{
|
|
edict_t *noise;
|
|
|
|
if (type == PNOISE_WEAPON)
|
|
{
|
|
if (who->client->silencer_shots)
|
|
who->client->invisibility_fade_time = level.time + (INVISIBILITY_TIME / 5);
|
|
else
|
|
who->client->invisibility_fade_time = level.time + INVISIBILITY_TIME;
|
|
|
|
if (who->client->silencer_shots)
|
|
{
|
|
who->client->silencer_shots--;
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (deathmatch->integer)
|
|
return;
|
|
|
|
if (who->flags & FL_NOTARGET)
|
|
return;
|
|
|
|
if (type == PNOISE_SELF &&
|
|
(who->client->landmark_free_fall || who->client->landmark_noise_time >= level.time))
|
|
return;
|
|
|
|
// ROGUE
|
|
if (who->flags & FL_DISGUISED)
|
|
{
|
|
if (type == PNOISE_WEAPON)
|
|
{
|
|
level.disguise_violator = who;
|
|
level.disguise_violation_time = level.time + 500_ms;
|
|
}
|
|
else
|
|
return;
|
|
}
|
|
// ROGUE
|
|
|
|
if (!who->mynoise)
|
|
{
|
|
noise = G_Spawn();
|
|
noise->classname = "player_noise";
|
|
noise->mins = { -8, -8, -8 };
|
|
noise->maxs = { 8, 8, 8 };
|
|
noise->owner = who;
|
|
noise->svflags = SVF_NOCLIENT;
|
|
who->mynoise = noise;
|
|
|
|
noise = G_Spawn();
|
|
noise->classname = "player_noise";
|
|
noise->mins = { -8, -8, -8 };
|
|
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;
|
|
who->client->sound_entity = noise;
|
|
who->client->sound_entity_time = level.time;
|
|
}
|
|
else // type == PNOISE_IMPACT
|
|
{
|
|
noise = who->mynoise2;
|
|
who->client->sound2_entity = noise;
|
|
who->client->sound2_entity_time = level.time;
|
|
}
|
|
|
|
noise->s.origin = where;
|
|
noise->absmin = where - noise->maxs;
|
|
noise->absmax = where + noise->maxs;
|
|
noise->teleport_time = level.time;
|
|
gi.linkentity(noise);
|
|
}
|
|
|
|
inline bool G_WeaponShouldStay()
|
|
{
|
|
if (deathmatch->integer)
|
|
return g_dm_weapons_stay->integer;
|
|
else if (coop->integer)
|
|
return !P_UseCoopInstancedItems();
|
|
|
|
return false;
|
|
}
|
|
|
|
void G_CheckAutoSwitch(edict_t *ent, gitem_t *item, bool is_new);
|
|
|
|
bool Pickup_Weapon(edict_t *ent, edict_t *other)
|
|
{
|
|
item_id_t index;
|
|
gitem_t *ammo;
|
|
|
|
index = ent->item->id;
|
|
|
|
if (G_WeaponShouldStay() && other->client->pers.inventory[index])
|
|
{
|
|
if (!(ent->spawnflags & (SPAWNFLAG_ITEM_DROPPED | SPAWNFLAG_ITEM_DROPPED_PLAYER)))
|
|
return false; // leave the weapon for others to pickup
|
|
}
|
|
|
|
bool is_new = !other->client->pers.inventory[index];
|
|
|
|
other->client->pers.inventory[index]++;
|
|
|
|
if (!(ent->spawnflags & SPAWNFLAG_ITEM_DROPPED))
|
|
{
|
|
// give them some ammo with it
|
|
// PGM -- IF APPROPRIATE!
|
|
if (ent->item->ammo) // PGM
|
|
{
|
|
ammo = GetItemByIndex(ent->item->ammo);
|
|
// RAFAEL: Don't get infinite ammo with trap
|
|
if (G_CheckInfiniteAmmo(ammo))
|
|
Add_Ammo(other, ammo, 1000);
|
|
else
|
|
Add_Ammo(other, ammo, ammo->quantity);
|
|
}
|
|
|
|
if (!(ent->spawnflags & SPAWNFLAG_ITEM_DROPPED_PLAYER))
|
|
{
|
|
if (deathmatch->integer)
|
|
{
|
|
if (g_dm_weapons_stay->integer)
|
|
ent->flags |= FL_RESPAWN;
|
|
|
|
SetRespawn( ent, gtime_t::from_sec(g_weapon_respawn_time->integer), !g_dm_weapons_stay->integer);
|
|
}
|
|
if (coop->integer)
|
|
ent->flags |= FL_RESPAWN;
|
|
}
|
|
}
|
|
|
|
G_CheckAutoSwitch(other, ent->item, is_new);
|
|
|
|
return true;
|
|
}
|
|
|
|
static void Weapon_RunThink(edict_t *ent)
|
|
{
|
|
// call active weapon think routine
|
|
if (!ent->client->pers.weapon->weaponthink)
|
|
return;
|
|
|
|
P_DamageModifier(ent);
|
|
// RAFAEL
|
|
is_quadfire = (ent->client->quadfire_time > level.time);
|
|
// RAFAEL
|
|
if (ent->client->silencer_shots)
|
|
is_silenced = MZ_SILENCED;
|
|
else
|
|
is_silenced = MZ_NONE;
|
|
ent->client->pers.weapon->weaponthink(ent);
|
|
}
|
|
|
|
/*
|
|
===============
|
|
ChangeWeapon
|
|
|
|
The old weapon has been dropped all the way, so make the new one
|
|
current
|
|
===============
|
|
*/
|
|
void ChangeWeapon(edict_t *ent)
|
|
{
|
|
// [Paril-KEX]
|
|
if (ent->health > 0 && !g_instant_weapon_switch->integer && ((ent->client->latched_buttons | ent->client->buttons) & BUTTON_HOLSTER))
|
|
return;
|
|
|
|
if (ent->client->grenade_time)
|
|
{
|
|
// force a weapon think to drop the held grenade
|
|
ent->client->weapon_sound = 0;
|
|
Weapon_RunThink(ent);
|
|
ent->client->grenade_time = 0_ms;
|
|
}
|
|
|
|
if (ent->client->pers.weapon)
|
|
{
|
|
ent->client->pers.lastweapon = ent->client->pers.weapon;
|
|
|
|
if (ent->client->newweapon && ent->client->newweapon != ent->client->pers.weapon)
|
|
gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/change.wav"), 1, ATTN_NORM, 0);
|
|
}
|
|
|
|
ent->client->pers.weapon = ent->client->newweapon;
|
|
ent->client->newweapon = nullptr;
|
|
ent->client->machinegun_shots = 0;
|
|
|
|
// set visible model
|
|
if (ent->s.modelindex == MODELINDEX_PLAYER)
|
|
P_AssignClientSkinnum(ent);
|
|
|
|
if (!ent->client->pers.weapon)
|
|
{ // dead
|
|
ent->client->ps.gunindex = 0;
|
|
ent->client->ps.gunskin = 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->ps.gunskin = 0;
|
|
ent->client->weapon_sound = 0;
|
|
|
|
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;
|
|
}
|
|
ent->client->anim_time = 0_ms;
|
|
|
|
// for instantweap, run think immediately
|
|
// to set up correct start frame
|
|
if (g_instant_weapon_switch->integer)
|
|
Weapon_RunThink(ent);
|
|
}
|
|
|
|
/*
|
|
=================
|
|
NoAmmoWeaponChange
|
|
=================
|
|
*/
|
|
void NoAmmoWeaponChange(edict_t *ent, bool sound)
|
|
{
|
|
if (sound)
|
|
{
|
|
if (level.time >= ent->client->empty_click_sound)
|
|
{
|
|
gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0);
|
|
ent->client->empty_click_sound = level.time + 1_sec;
|
|
}
|
|
}
|
|
|
|
constexpr item_id_t no_ammo_order[] = {
|
|
IT_WEAPON_DISRUPTOR,
|
|
IT_WEAPON_RAILGUN,
|
|
IT_WEAPON_PLASMABEAM,
|
|
IT_WEAPON_IONRIPPER,
|
|
IT_WEAPON_HYPERBLASTER,
|
|
IT_WEAPON_ETF_RIFLE,
|
|
IT_WEAPON_CHAINGUN,
|
|
IT_WEAPON_MACHINEGUN,
|
|
IT_WEAPON_SSHOTGUN,
|
|
IT_WEAPON_SHOTGUN,
|
|
IT_WEAPON_PHALANX,
|
|
IT_WEAPON_RLAUNCHER,
|
|
IT_WEAPON_GLAUNCHER,
|
|
IT_WEAPON_PROXLAUNCHER,
|
|
IT_WEAPON_CHAINFIST,
|
|
IT_WEAPON_BLASTER
|
|
};
|
|
|
|
for (size_t i = 0; i < q_countof(no_ammo_order); i++)
|
|
{
|
|
gitem_t *item = GetItemByIndex(no_ammo_order[i]);
|
|
|
|
if (!item)
|
|
gi.Com_ErrorFmt("Invalid no ammo weapon switch weapon {}\n", (int32_t) no_ammo_order[i]);
|
|
|
|
if (!ent->client->pers.inventory[item->id])
|
|
continue;
|
|
|
|
if (item->ammo && ent->client->pers.inventory[item->ammo] < item->quantity)
|
|
continue;
|
|
|
|
ent->client->newweapon = item;
|
|
return;
|
|
}
|
|
}
|
|
|
|
void G_RemoveAmmo(edict_t *ent, int32_t quantity)
|
|
{
|
|
if (G_CheckInfiniteAmmo(ent->client->pers.weapon))
|
|
return;
|
|
|
|
bool pre_warning = ent->client->pers.inventory[ent->client->pers.weapon->ammo] <=
|
|
ent->client->pers.weapon->quantity_warn;
|
|
|
|
ent->client->pers.inventory[ent->client->pers.weapon->ammo] -= quantity;
|
|
|
|
bool post_warning = ent->client->pers.inventory[ent->client->pers.weapon->ammo] <=
|
|
ent->client->pers.weapon->quantity_warn;
|
|
|
|
if (!pre_warning && post_warning)
|
|
gi.local_sound(ent, CHAN_AUTO, gi.soundindex("weapons/lowammo.wav"), 1, ATTN_NORM, 0);
|
|
|
|
if (ent->client->pers.weapon->ammo == IT_AMMO_CELLS)
|
|
G_CheckPowerArmor(ent);
|
|
}
|
|
|
|
void G_RemoveAmmo(edict_t *ent)
|
|
{
|
|
G_RemoveAmmo(ent, ent->client->pers.weapon->quantity);
|
|
}
|
|
|
|
// [Paril-KEX] get time per animation frame
|
|
inline gtime_t Weapon_AnimationTime(edict_t *ent)
|
|
{
|
|
if (g_quick_weapon_switch->integer && (gi.tick_rate == 20 || gi.tick_rate == 40) &&
|
|
(ent->client->weaponstate == WEAPON_ACTIVATING || ent->client->weaponstate == WEAPON_DROPPING))
|
|
ent->client->ps.gunrate = 20;
|
|
else
|
|
ent->client->ps.gunrate = 10;
|
|
|
|
if (ent->client->ps.gunframe != 0 && (!(ent->client->pers.weapon->flags & IF_NO_HASTE) || ent->client->weaponstate != WEAPON_FIRING))
|
|
{
|
|
if (is_quadfire)
|
|
ent->client->ps.gunrate *= 2;
|
|
if (CTFApplyHaste(ent))
|
|
ent->client->ps.gunrate *= 2;
|
|
}
|
|
|
|
// network optimization...
|
|
if (ent->client->ps.gunrate == 10)
|
|
{
|
|
ent->client->ps.gunrate = 0;
|
|
return 100_ms;
|
|
}
|
|
|
|
return gtime_t::from_ms((1.f / ent->client->ps.gunrate) * 1000);
|
|
}
|
|
|
|
/*
|
|
=================
|
|
Think_Weapon
|
|
|
|
Called by ClientBeginServerFrame and ClientThink
|
|
=================
|
|
*/
|
|
void Think_Weapon(edict_t *ent)
|
|
{
|
|
if (ent->client->resp.spectator)
|
|
return;
|
|
|
|
// if just died, put the weapon away
|
|
if (ent->health < 1)
|
|
{
|
|
ent->client->newweapon = nullptr;
|
|
ChangeWeapon(ent);
|
|
}
|
|
|
|
if (!ent->client->pers.weapon)
|
|
{
|
|
if (ent->client->newweapon)
|
|
ChangeWeapon(ent);
|
|
return;
|
|
}
|
|
|
|
// call active weapon think routine
|
|
Weapon_RunThink(ent);
|
|
|
|
// check remainder from haste; on 100ms/50ms server frames we may have
|
|
// 'run next frame in' times that we can't possibly catch up to,
|
|
// so we have to run them now.
|
|
if (33_ms < FRAME_TIME_MS)
|
|
{
|
|
gtime_t relative_time = Weapon_AnimationTime(ent);
|
|
|
|
if (relative_time < FRAME_TIME_MS)
|
|
{
|
|
// check how many we can't run before the next server tick
|
|
gtime_t next_frame = level.time + FRAME_TIME_S;
|
|
int64_t remaining_ms = (next_frame - ent->client->weapon_think_time).milliseconds();
|
|
|
|
while (remaining_ms > 0)
|
|
{
|
|
ent->client->weapon_think_time -= relative_time;
|
|
ent->client->weapon_fire_finished -= relative_time;
|
|
Weapon_RunThink(ent);
|
|
remaining_ms -= relative_time.milliseconds();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
enum weap_switch_t
|
|
{
|
|
WEAP_SWITCH_ALREADY_USING,
|
|
WEAP_SWITCH_NO_WEAPON,
|
|
WEAP_SWITCH_NO_AMMO,
|
|
WEAP_SWITCH_NOT_ENOUGH_AMMO,
|
|
WEAP_SWITCH_VALID
|
|
};
|
|
|
|
weap_switch_t Weapon_AttemptSwitch(edict_t *ent, gitem_t *item, bool silent)
|
|
{
|
|
if (ent->client->pers.weapon == item)
|
|
return WEAP_SWITCH_ALREADY_USING;
|
|
else if (!ent->client->pers.inventory[item->id])
|
|
return WEAP_SWITCH_NO_WEAPON;
|
|
|
|
if (item->ammo && !g_select_empty->integer && !(item->flags & IF_AMMO))
|
|
{
|
|
gitem_t *ammo_item = GetItemByIndex(item->ammo);
|
|
|
|
if (!ent->client->pers.inventory[item->ammo])
|
|
{
|
|
if (!silent)
|
|
gi.LocClient_Print(ent, PRINT_HIGH, "$g_no_ammo", ammo_item->pickup_name, item->pickup_name_definite);
|
|
return WEAP_SWITCH_NO_AMMO;
|
|
}
|
|
else if (ent->client->pers.inventory[item->ammo] < item->quantity)
|
|
{
|
|
if (!silent)
|
|
gi.LocClient_Print(ent, PRINT_HIGH, "$g_not_enough_ammo", ammo_item->pickup_name, item->pickup_name_definite);
|
|
return WEAP_SWITCH_NOT_ENOUGH_AMMO;
|
|
}
|
|
}
|
|
|
|
return WEAP_SWITCH_VALID;
|
|
}
|
|
|
|
inline bool Weapon_IsPartOfChain(gitem_t *item, gitem_t *other)
|
|
{
|
|
return other && other->chain && item->chain && other->chain == item->chain;
|
|
}
|
|
|
|
/*
|
|
================
|
|
Use_Weapon
|
|
|
|
Make the weapon ready if there is ammo
|
|
================
|
|
*/
|
|
void Use_Weapon(edict_t *ent, gitem_t *item)
|
|
{
|
|
gitem_t *wanted, *root;
|
|
weap_switch_t result = WEAP_SWITCH_NO_WEAPON;
|
|
|
|
// if we're switching to a weapon in this chain already,
|
|
// start from the weapon after this one in the chain
|
|
if (!ent->client->no_weapon_chains && Weapon_IsPartOfChain(item, ent->client->newweapon))
|
|
{
|
|
root = ent->client->newweapon;
|
|
wanted = root->chain_next;
|
|
}
|
|
// if we're already holding a weapon in this chain,
|
|
// start from the weapon after that one
|
|
else if (!ent->client->no_weapon_chains && Weapon_IsPartOfChain(item, ent->client->pers.weapon))
|
|
{
|
|
root = ent->client->pers.weapon;
|
|
wanted = root->chain_next;
|
|
}
|
|
// start from beginning of chain (if any)
|
|
else
|
|
wanted = root = item;
|
|
|
|
while (true)
|
|
{
|
|
// try the weapon currently in the chain
|
|
if ((result = Weapon_AttemptSwitch(ent, wanted, false)) == WEAP_SWITCH_VALID)
|
|
break;
|
|
|
|
// no chains
|
|
if (!wanted->chain_next || ent->client->no_weapon_chains)
|
|
break;
|
|
|
|
wanted = wanted->chain_next;
|
|
|
|
// we wrapped back to the root item
|
|
if (wanted == root)
|
|
break;
|
|
}
|
|
|
|
if (result == WEAP_SWITCH_VALID)
|
|
ent->client->newweapon = wanted; // change to this weapon when down
|
|
else if ((result = Weapon_AttemptSwitch(ent, wanted, true)) == WEAP_SWITCH_NO_WEAPON && wanted != ent->client->pers.weapon && wanted != ent->client->newweapon)
|
|
gi.LocClient_Print(ent, PRINT_HIGH, "$g_out_of_item", wanted->pickup_name);
|
|
}
|
|
|
|
/*
|
|
================
|
|
Drop_Weapon
|
|
================
|
|
*/
|
|
void Drop_Weapon(edict_t *ent, gitem_t *item)
|
|
{
|
|
item_id_t index = item->id;
|
|
// see if we're already using it
|
|
if (((item == ent->client->pers.weapon) || (item == ent->client->newweapon)) && (ent->client->pers.inventory[index] == 1))
|
|
{
|
|
gi.LocClient_Print(ent, PRINT_HIGH, "$g_cant_drop_weapon");
|
|
return;
|
|
}
|
|
|
|
edict_t *drop = Drop_Item(ent, item);
|
|
drop->spawnflags |= SPAWNFLAG_ITEM_DROPPED_PLAYER;
|
|
drop->svflags &= ~SVF_INSTANCED;
|
|
ent->client->pers.inventory[index]--;
|
|
}
|
|
|
|
void Weapon_PowerupSound(edict_t *ent)
|
|
{
|
|
if (!CTFApplyStrengthSound(ent))
|
|
{
|
|
if (ent->client->quad_time > level.time && ent->client->double_time > level.time)
|
|
gi.sound(ent, CHAN_ITEM, gi.soundindex("ctf/tech2x.wav"), 1, ATTN_NORM, 0);
|
|
else if (ent->client->quad_time > level.time)
|
|
gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage3.wav"), 1, ATTN_NORM, 0);
|
|
else if (ent->client->double_time > level.time)
|
|
gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/ddamage3.wav"), 1, ATTN_NORM, 0);
|
|
else if (ent->client->quadfire_time > level.time
|
|
&& ent->client->ctf_techsndtime < level.time)
|
|
{
|
|
ent->client->ctf_techsndtime = level.time + 1_sec;
|
|
gi.sound(ent, CHAN_ITEM, gi.soundindex("ctf/tech3.wav"), 1, ATTN_NORM, 0);
|
|
}
|
|
}
|
|
|
|
CTFApplyHasteSound(ent);
|
|
}
|
|
|
|
inline bool Weapon_CanAnimate(edict_t *ent)
|
|
{
|
|
// VWep animations screw up corpses
|
|
return !ent->deadflag && ent->s.modelindex == MODELINDEX_PLAYER;
|
|
}
|
|
|
|
// [Paril-KEX] called when finished to set time until
|
|
// we're allowed to switch to fire again
|
|
inline void Weapon_SetFinished(edict_t *ent)
|
|
{
|
|
ent->client->weapon_fire_finished = level.time + Weapon_AnimationTime(ent);
|
|
}
|
|
|
|
inline bool Weapon_HandleDropping(edict_t *ent, int FRAME_DEACTIVATE_LAST)
|
|
{
|
|
if (ent->client->weaponstate == WEAPON_DROPPING)
|
|
{
|
|
if (ent->client->weapon_think_time <= level.time)
|
|
{
|
|
if (ent->client->ps.gunframe == FRAME_DEACTIVATE_LAST)
|
|
{
|
|
ChangeWeapon(ent);
|
|
return true;
|
|
}
|
|
else if ((FRAME_DEACTIVATE_LAST - ent->client->ps.gunframe) == 4)
|
|
{
|
|
ent->client->anim_priority = ANIM_ATTACK | ANIM_REVERSED;
|
|
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->anim_time = 0_ms;
|
|
}
|
|
|
|
ent->client->ps.gunframe++;
|
|
ent->client->weapon_think_time = level.time + Weapon_AnimationTime(ent);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
inline bool Weapon_HandleActivating(edict_t *ent, int FRAME_ACTIVATE_LAST, int FRAME_IDLE_FIRST)
|
|
{
|
|
if (ent->client->weaponstate == WEAPON_ACTIVATING)
|
|
{
|
|
if (ent->client->weapon_think_time <= level.time || g_instant_weapon_switch->integer)
|
|
{
|
|
ent->client->weapon_think_time = level.time + Weapon_AnimationTime(ent);
|
|
|
|
if (ent->client->ps.gunframe == FRAME_ACTIVATE_LAST || g_instant_weapon_switch->integer)
|
|
{
|
|
ent->client->weaponstate = WEAPON_READY;
|
|
ent->client->ps.gunframe = FRAME_IDLE_FIRST;
|
|
ent->client->weapon_fire_buffered = false;
|
|
if (!g_instant_weapon_switch->integer)
|
|
Weapon_SetFinished(ent);
|
|
else
|
|
ent->client->weapon_fire_finished = 0_ms;
|
|
return true;
|
|
}
|
|
|
|
ent->client->ps.gunframe++;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
inline bool Weapon_HandleNewWeapon(edict_t *ent, int FRAME_DEACTIVATE_FIRST, int FRAME_DEACTIVATE_LAST)
|
|
{
|
|
bool is_holstering = false;
|
|
|
|
if (!g_instant_weapon_switch->integer)
|
|
is_holstering = ((ent->client->latched_buttons | ent->client->buttons) & BUTTON_HOLSTER);
|
|
|
|
if ((ent->client->newweapon || is_holstering) && (ent->client->weaponstate != WEAPON_FIRING))
|
|
{
|
|
if (g_instant_weapon_switch->integer || ent->client->weapon_think_time <= level.time)
|
|
{
|
|
if (!ent->client->newweapon)
|
|
ent->client->newweapon = ent->client->pers.weapon;
|
|
|
|
ent->client->weaponstate = WEAPON_DROPPING;
|
|
|
|
if (g_instant_weapon_switch->integer)
|
|
{
|
|
ChangeWeapon(ent);
|
|
return true;
|
|
}
|
|
|
|
ent->client->ps.gunframe = FRAME_DEACTIVATE_FIRST;
|
|
|
|
if ((FRAME_DEACTIVATE_LAST - FRAME_DEACTIVATE_FIRST) < 4)
|
|
{
|
|
ent->client->anim_priority = ANIM_ATTACK | ANIM_REVERSED;
|
|
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->anim_time = 0_ms;
|
|
}
|
|
|
|
ent->client->weapon_think_time = level.time + Weapon_AnimationTime(ent);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
enum weapon_ready_state_t
|
|
{
|
|
READY_NONE,
|
|
READY_CHANGING,
|
|
READY_FIRING
|
|
};
|
|
|
|
inline weapon_ready_state_t Weapon_HandleReady(edict_t *ent, int FRAME_FIRE_FIRST, int FRAME_IDLE_FIRST, int FRAME_IDLE_LAST, const int *pause_frames)
|
|
{
|
|
if (ent->client->weaponstate == WEAPON_READY)
|
|
{
|
|
bool request_firing = ent->client->weapon_fire_buffered || ((ent->client->latched_buttons | ent->client->buttons) & BUTTON_ATTACK);
|
|
|
|
if (request_firing && ent->client->weapon_fire_finished <= level.time)
|
|
{
|
|
ent->client->latched_buttons &= ~BUTTON_ATTACK;
|
|
ent->client->weapon_think_time = level.time;
|
|
|
|
if ((!ent->client->pers.weapon->ammo) ||
|
|
(ent->client->pers.inventory[ent->client->pers.weapon->ammo] >= ent->client->pers.weapon->quantity))
|
|
{
|
|
ent->client->weaponstate = WEAPON_FIRING;
|
|
return READY_FIRING;
|
|
}
|
|
else
|
|
{
|
|
NoAmmoWeaponChange(ent, true);
|
|
return READY_CHANGING;
|
|
}
|
|
}
|
|
else if (ent->client->weapon_think_time <= level.time)
|
|
{
|
|
ent->client->weapon_think_time = level.time + Weapon_AnimationTime(ent);
|
|
|
|
if (ent->client->ps.gunframe == FRAME_IDLE_LAST)
|
|
{
|
|
ent->client->ps.gunframe = FRAME_IDLE_FIRST;
|
|
return READY_CHANGING;
|
|
}
|
|
|
|
if (pause_frames)
|
|
for (int n = 0; pause_frames[n]; n++)
|
|
if (ent->client->ps.gunframe == pause_frames[n])
|
|
if (irandom(16))
|
|
return READY_CHANGING;
|
|
|
|
ent->client->ps.gunframe++;
|
|
return READY_CHANGING;
|
|
}
|
|
}
|
|
|
|
return READY_NONE;
|
|
}
|
|
|
|
inline void Weapon_HandleFiring(edict_t *ent, int32_t FRAME_IDLE_FIRST, std::function<void()> fire_handler)
|
|
{
|
|
Weapon_SetFinished(ent);
|
|
|
|
if (ent->client->weapon_fire_buffered)
|
|
{
|
|
ent->client->buttons |= BUTTON_ATTACK;
|
|
ent->client->weapon_fire_buffered = false;
|
|
}
|
|
|
|
fire_handler();
|
|
|
|
if (ent->client->ps.gunframe == FRAME_IDLE_FIRST)
|
|
{
|
|
ent->client->weaponstate = WEAPON_READY;
|
|
ent->client->weapon_fire_buffered = false;
|
|
}
|
|
|
|
ent->client->weapon_think_time = level.time + Weapon_AnimationTime(ent);
|
|
}
|
|
|
|
void Weapon_Generic(edict_t *ent, int FRAME_ACTIVATE_LAST, int FRAME_FIRE_LAST, int FRAME_IDLE_LAST, int FRAME_DEACTIVATE_LAST, const int *pause_frames, const int *fire_frames, void (*fire)(edict_t *ent))
|
|
{
|
|
int FRAME_FIRE_FIRST = (FRAME_ACTIVATE_LAST + 1);
|
|
int FRAME_IDLE_FIRST = (FRAME_FIRE_LAST + 1);
|
|
int FRAME_DEACTIVATE_FIRST = (FRAME_IDLE_LAST + 1);
|
|
|
|
if (!Weapon_CanAnimate(ent))
|
|
return;
|
|
|
|
if (Weapon_HandleDropping(ent, FRAME_DEACTIVATE_LAST))
|
|
return;
|
|
else if (Weapon_HandleActivating(ent, FRAME_ACTIVATE_LAST, FRAME_IDLE_FIRST))
|
|
return;
|
|
else if (Weapon_HandleNewWeapon(ent, FRAME_DEACTIVATE_FIRST, FRAME_DEACTIVATE_LAST))
|
|
return;
|
|
else if (auto state = Weapon_HandleReady(ent, FRAME_FIRE_FIRST, FRAME_IDLE_FIRST, FRAME_IDLE_LAST, pause_frames))
|
|
{
|
|
if (state == READY_FIRING)
|
|
{
|
|
ent->client->ps.gunframe = FRAME_FIRE_FIRST;
|
|
ent->client->weapon_fire_buffered = false;
|
|
|
|
if (ent->client->weapon_thunk)
|
|
ent->client->weapon_think_time += FRAME_TIME_S;
|
|
|
|
ent->client->weapon_think_time += Weapon_AnimationTime(ent);
|
|
Weapon_SetFinished(ent);
|
|
|
|
for (int n = 0; fire_frames[n]; n++)
|
|
{
|
|
if (ent->client->ps.gunframe == fire_frames[n])
|
|
{
|
|
Weapon_PowerupSound(ent);
|
|
fire(ent);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
ent->client->anim_time = 0_ms;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (ent->client->weaponstate == WEAPON_FIRING && ent->client->weapon_think_time <= level.time)
|
|
{
|
|
ent->client->ps.gunframe++;
|
|
Weapon_HandleFiring(ent, FRAME_IDLE_FIRST, [&]() {
|
|
for (int n = 0; fire_frames[n]; n++)
|
|
{
|
|
if (ent->client->ps.gunframe == fire_frames[n])
|
|
{
|
|
Weapon_PowerupSound(ent);
|
|
fire(ent);
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
void Weapon_Repeating(edict_t *ent, int FRAME_ACTIVATE_LAST, int FRAME_FIRE_LAST, int FRAME_IDLE_LAST, int FRAME_DEACTIVATE_LAST, const int *pause_frames, void (*fire)(edict_t *ent))
|
|
{
|
|
int FRAME_FIRE_FIRST = (FRAME_ACTIVATE_LAST + 1);
|
|
int FRAME_IDLE_FIRST = (FRAME_FIRE_LAST + 1);
|
|
int FRAME_DEACTIVATE_FIRST = (FRAME_IDLE_LAST + 1);
|
|
|
|
if (!Weapon_CanAnimate(ent))
|
|
return;
|
|
|
|
if (Weapon_HandleDropping(ent, FRAME_DEACTIVATE_LAST))
|
|
return;
|
|
else if (Weapon_HandleActivating(ent, FRAME_ACTIVATE_LAST, FRAME_IDLE_FIRST))
|
|
return;
|
|
else if (Weapon_HandleNewWeapon(ent, FRAME_DEACTIVATE_FIRST, FRAME_DEACTIVATE_LAST))
|
|
return;
|
|
else if (Weapon_HandleReady(ent, FRAME_FIRE_FIRST, FRAME_IDLE_FIRST, FRAME_IDLE_LAST, pause_frames) == READY_CHANGING)
|
|
return;
|
|
|
|
if (ent->client->weaponstate == WEAPON_FIRING && ent->client->weapon_think_time <= level.time)
|
|
{
|
|
Weapon_HandleFiring(ent, FRAME_IDLE_FIRST, [&]() { fire(ent); });
|
|
|
|
if (ent->client->weapon_thunk)
|
|
ent->client->weapon_think_time += FRAME_TIME_S;
|
|
}
|
|
}
|
|
|
|
/*
|
|
======================================================================
|
|
|
|
GRENADE
|
|
|
|
======================================================================
|
|
*/
|
|
|
|
void weapon_grenade_fire(edict_t *ent, bool held)
|
|
{
|
|
int damage = 125;
|
|
int speed;
|
|
float radius;
|
|
|
|
radius = (float) (damage + 40);
|
|
if (is_quad)
|
|
damage *= damage_multiplier;
|
|
|
|
vec3_t start, dir;
|
|
// Paril: kill sideways angle on grenades
|
|
// limit upwards angle so you don't throw behind you
|
|
P_ProjectSource(ent, { max(-62.5f, ent->client->v_angle[0]), ent->client->v_angle[1], ent->client->v_angle[2] }, { 2, 0, -14 }, start, dir);
|
|
|
|
gtime_t timer = ent->client->grenade_time - level.time;
|
|
speed = (int) (ent->health <= 0 ? GRENADE_MINSPEED : min(GRENADE_MINSPEED + (GRENADE_TIMER - timer).seconds() * ((GRENADE_MAXSPEED - GRENADE_MINSPEED) / GRENADE_TIMER.seconds()), GRENADE_MAXSPEED));
|
|
|
|
ent->client->grenade_time = 0_ms;
|
|
|
|
fire_grenade2(ent, start, dir, damage, speed, timer, radius, held);
|
|
|
|
G_RemoveAmmo(ent, 1);
|
|
}
|
|
|
|
void Throw_Generic(edict_t *ent, int FRAME_FIRE_LAST, int FRAME_IDLE_LAST, int FRAME_PRIME_SOUND,
|
|
const char *prime_sound,
|
|
int FRAME_THROW_HOLD, int FRAME_THROW_FIRE, const int *pause_frames, int EXPLODE,
|
|
const char *primed_sound,
|
|
void (*fire)(edict_t *ent, bool held), bool extra_idle_frame)
|
|
{
|
|
// when we die, just toss what we had in our hands.
|
|
if (ent->health <= 0)
|
|
{
|
|
fire(ent, true);
|
|
return;
|
|
}
|
|
|
|
int n;
|
|
int FRAME_IDLE_FIRST = (FRAME_FIRE_LAST + 1);
|
|
|
|
if (ent->client->newweapon && (ent->client->weaponstate == WEAPON_READY))
|
|
{
|
|
if (ent->client->weapon_think_time <= level.time)
|
|
{
|
|
ChangeWeapon(ent);
|
|
ent->client->weapon_think_time = level.time + Weapon_AnimationTime(ent);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (ent->client->weaponstate == WEAPON_ACTIVATING)
|
|
{
|
|
if (ent->client->weapon_think_time <= level.time)
|
|
{
|
|
ent->client->weaponstate = WEAPON_READY;
|
|
if (!extra_idle_frame)
|
|
ent->client->ps.gunframe = FRAME_IDLE_FIRST;
|
|
else
|
|
ent->client->ps.gunframe = FRAME_IDLE_LAST + 1;
|
|
ent->client->weapon_think_time = level.time + Weapon_AnimationTime(ent);
|
|
Weapon_SetFinished(ent);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (ent->client->weaponstate == WEAPON_READY)
|
|
{
|
|
bool request_firing = ent->client->weapon_fire_buffered || ((ent->client->latched_buttons | ent->client->buttons) & BUTTON_ATTACK);
|
|
|
|
if (request_firing && ent->client->weapon_fire_finished <= level.time)
|
|
{
|
|
ent->client->latched_buttons &= ~BUTTON_ATTACK;
|
|
|
|
if (ent->client->pers.inventory[ent->client->pers.weapon->ammo])
|
|
{
|
|
ent->client->ps.gunframe = 1;
|
|
ent->client->weaponstate = WEAPON_FIRING;
|
|
ent->client->grenade_time = 0_ms;
|
|
ent->client->weapon_think_time = level.time + Weapon_AnimationTime(ent);
|
|
}
|
|
else
|
|
NoAmmoWeaponChange(ent, true);
|
|
return;
|
|
}
|
|
else if (ent->client->weapon_think_time <= level.time)
|
|
{
|
|
ent->client->weapon_think_time = level.time + Weapon_AnimationTime(ent);
|
|
|
|
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 (irandom(16))
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
ent->client->ps.gunframe++;
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (ent->client->weaponstate == WEAPON_FIRING)
|
|
{
|
|
if (ent->client->weapon_think_time <= level.time)
|
|
{
|
|
if (prime_sound && ent->client->ps.gunframe == FRAME_PRIME_SOUND)
|
|
gi.sound(ent, CHAN_WEAPON, gi.soundindex(prime_sound), 1, ATTN_NORM, 0);
|
|
|
|
// [Paril-KEX] dualfire/time accel
|
|
gtime_t grenade_wait_time = 1_sec;
|
|
|
|
if (CTFApplyHaste(ent))
|
|
grenade_wait_time *= 0.5f;
|
|
if (is_quadfire)
|
|
grenade_wait_time *= 0.5f;
|
|
|
|
if (ent->client->ps.gunframe == FRAME_THROW_HOLD)
|
|
{
|
|
if (!ent->client->grenade_time && !ent->client->grenade_finished_time)
|
|
{
|
|
ent->client->grenade_time = level.time + GRENADE_TIMER + 200_ms;
|
|
|
|
if (primed_sound)
|
|
ent->client->weapon_sound = gi.soundindex(primed_sound);
|
|
}
|
|
|
|
// they waited too long, detonate it in their hand
|
|
if (EXPLODE && !ent->client->grenade_blew_up && level.time >= ent->client->grenade_time)
|
|
{
|
|
Weapon_PowerupSound(ent);
|
|
ent->client->weapon_sound = 0;
|
|
fire(ent, true);
|
|
ent->client->grenade_blew_up = true;
|
|
|
|
ent->client->grenade_finished_time = level.time + grenade_wait_time;
|
|
}
|
|
|
|
if (ent->client->buttons & BUTTON_ATTACK)
|
|
{
|
|
ent->client->weapon_think_time = level.time + 1_ms;
|
|
return;
|
|
}
|
|
|
|
if (ent->client->grenade_blew_up)
|
|
{
|
|
if (level.time >= ent->client->grenade_finished_time)
|
|
{
|
|
ent->client->ps.gunframe = FRAME_FIRE_LAST;
|
|
ent->client->grenade_blew_up = false;
|
|
ent->client->weapon_think_time = level.time + Weapon_AnimationTime(ent);
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ent->client->ps.gunframe++;
|
|
|
|
Weapon_PowerupSound(ent);
|
|
ent->client->weapon_sound = 0;
|
|
fire(ent, false);
|
|
|
|
if (!EXPLODE || !ent->client->grenade_blew_up)
|
|
ent->client->grenade_finished_time = level.time + grenade_wait_time;
|
|
|
|
if (!ent->deadflag && ent->s.modelindex == MODELINDEX_PLAYER && ent->health > 0) // VWep animations screw up corpses
|
|
{
|
|
if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
|
|
{
|
|
ent->client->anim_priority = ANIM_ATTACK;
|
|
ent->s.frame = FRAME_crattak1 - 1;
|
|
ent->client->anim_end = FRAME_crattak3;
|
|
}
|
|
else
|
|
{
|
|
ent->client->anim_priority = ANIM_ATTACK | ANIM_REVERSED;
|
|
ent->s.frame = FRAME_wave08;
|
|
ent->client->anim_end = FRAME_wave01;
|
|
}
|
|
ent->client->anim_time = 0_ms;
|
|
}
|
|
}
|
|
}
|
|
|
|
ent->client->weapon_think_time = level.time + Weapon_AnimationTime(ent);
|
|
|
|
if ((ent->client->ps.gunframe == FRAME_FIRE_LAST) && (level.time < ent->client->grenade_finished_time))
|
|
return;
|
|
|
|
ent->client->ps.gunframe++;
|
|
|
|
if (ent->client->ps.gunframe == FRAME_IDLE_FIRST)
|
|
{
|
|
ent->client->grenade_finished_time = 0_ms;
|
|
ent->client->weaponstate = WEAPON_READY;
|
|
ent->client->weapon_fire_buffered = false;
|
|
Weapon_SetFinished(ent);
|
|
|
|
if (extra_idle_frame)
|
|
ent->client->ps.gunframe = FRAME_IDLE_LAST + 1;
|
|
|
|
// Paril: if we ran out of the throwable, switch
|
|
// so we don't appear to be holding one that we
|
|
// can't throw
|
|
if (!ent->client->pers.inventory[ent->client->pers.weapon->ammo])
|
|
{
|
|
NoAmmoWeaponChange(ent, false);
|
|
ChangeWeapon(ent);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Weapon_Grenade(edict_t *ent)
|
|
{
|
|
constexpr int pause_frames[] = { 29, 34, 39, 48, 0 };
|
|
|
|
Throw_Generic(ent, 15, 48, 5, "weapons/hgrena1b.wav", 11, 12, pause_frames, true, "weapons/hgrenc1b.wav", weapon_grenade_fire, true);
|
|
|
|
// [Paril-KEX] skip the duped frame
|
|
if (ent->client->ps.gunframe == 1)
|
|
ent->client->ps.gunframe = 2;
|
|
}
|
|
|
|
/*
|
|
======================================================================
|
|
|
|
GRENADE LAUNCHER
|
|
|
|
======================================================================
|
|
*/
|
|
|
|
void weapon_grenadelauncher_fire(edict_t *ent)
|
|
{
|
|
int damage = 120;
|
|
float radius;
|
|
|
|
radius = (float) (damage + 40);
|
|
if (is_quad)
|
|
damage *= damage_multiplier;
|
|
|
|
vec3_t start, dir;
|
|
// Paril: kill sideways angle on grenades
|
|
// limit upwards angle so you don't fire it behind you
|
|
P_ProjectSource(ent, { max(-62.5f, ent->client->v_angle[0]), ent->client->v_angle[1], ent->client->v_angle[2] }, { 8, 0, -8 }, start, dir);
|
|
|
|
P_AddWeaponKick(ent, ent->client->v_forward * -2, { -1.f, 0.f, 0.f });
|
|
|
|
fire_grenade(ent, start, dir, damage, 600, 2.5_sec, radius, (crandom_open() * 10.0f), (200 + crandom_open() * 10.0f), false);
|
|
|
|
gi.WriteByte(svc_muzzleflash);
|
|
gi.WriteEntity(ent);
|
|
gi.WriteByte(MZ_GRENADE | is_silenced);
|
|
gi.multicast(ent->s.origin, MULTICAST_PVS, false);
|
|
|
|
PlayerNoise(ent, start, PNOISE_WEAPON);
|
|
|
|
G_RemoveAmmo(ent);
|
|
}
|
|
|
|
void Weapon_GrenadeLauncher(edict_t *ent)
|
|
{
|
|
constexpr int pause_frames[] = { 34, 51, 59, 0 };
|
|
constexpr int fire_frames[] = { 6, 0 };
|
|
|
|
Weapon_Generic(ent, 5, 16, 59, 64, pause_frames, fire_frames, weapon_grenadelauncher_fire);
|
|
}
|
|
|
|
/*
|
|
======================================================================
|
|
|
|
ROCKET
|
|
|
|
======================================================================
|
|
*/
|
|
|
|
void Weapon_RocketLauncher_Fire(edict_t *ent)
|
|
{
|
|
int damage;
|
|
float damage_radius;
|
|
int radius_damage;
|
|
|
|
damage = irandom(100, 120);
|
|
radius_damage = 120;
|
|
damage_radius = 120;
|
|
if (is_quad)
|
|
{
|
|
damage *= damage_multiplier;
|
|
radius_damage *= damage_multiplier;
|
|
}
|
|
|
|
vec3_t start, dir;
|
|
P_ProjectSource(ent, ent->client->v_angle, { 8, 8, -8 }, start, dir);
|
|
fire_rocket(ent, start, dir, damage, 650, damage_radius, radius_damage);
|
|
|
|
P_AddWeaponKick(ent, ent->client->v_forward * -2, { -1.f, 0.f, 0.f });
|
|
|
|
// send muzzle flash
|
|
gi.WriteByte(svc_muzzleflash);
|
|
gi.WriteEntity(ent);
|
|
gi.WriteByte(MZ_ROCKET | is_silenced);
|
|
gi.multicast(ent->s.origin, MULTICAST_PVS, false);
|
|
|
|
PlayerNoise(ent, start, PNOISE_WEAPON);
|
|
|
|
G_RemoveAmmo(ent);
|
|
}
|
|
|
|
void Weapon_RocketLauncher(edict_t *ent)
|
|
{
|
|
constexpr int pause_frames[] = { 25, 33, 42, 50, 0 };
|
|
constexpr int fire_frames[] = { 5, 0 };
|
|
|
|
Weapon_Generic(ent, 4, 12, 50, 54, pause_frames, fire_frames, Weapon_RocketLauncher_Fire);
|
|
}
|
|
|
|
/*
|
|
======================================================================
|
|
|
|
BLASTER / HYPERBLASTER
|
|
|
|
======================================================================
|
|
*/
|
|
|
|
void Blaster_Fire(edict_t *ent, const vec3_t &g_offset, int damage, bool hyper, effects_t effect)
|
|
{
|
|
if (is_quad)
|
|
damage *= damage_multiplier;
|
|
|
|
vec3_t start, dir;
|
|
P_ProjectSource(ent, ent->client->v_angle, vec3_t{ 24, 8, -8 } + g_offset, start, dir);
|
|
|
|
if (hyper)
|
|
P_AddWeaponKick(ent, ent->client->v_forward * -2, { crandom() * 0.7f, crandom() * 0.7f, crandom() * 0.7f });
|
|
else
|
|
P_AddWeaponKick(ent, ent->client->v_forward * -2, { -1.f, 0.f, 0.f });
|
|
|
|
// let the regular blaster projectiles travel a bit faster because it is a completely useless gun
|
|
int speed = hyper ? 1000 : 1500;
|
|
|
|
fire_blaster(ent, start, dir, damage, speed, effect, hyper ? MOD_HYPERBLASTER : MOD_BLASTER);
|
|
|
|
// send muzzle flash
|
|
gi.WriteByte(svc_muzzleflash);
|
|
gi.WriteEntity(ent);
|
|
if (hyper)
|
|
gi.WriteByte(MZ_HYPERBLASTER | is_silenced);
|
|
else
|
|
gi.WriteByte(MZ_BLASTER | is_silenced);
|
|
gi.multicast(ent->s.origin, MULTICAST_PVS, false);
|
|
|
|
PlayerNoise(ent, start, PNOISE_WEAPON);
|
|
}
|
|
|
|
void Weapon_Blaster_Fire(edict_t *ent)
|
|
{
|
|
// give the blaster 15 across the board instead of just in dm
|
|
int damage = 15;
|
|
Blaster_Fire(ent, vec3_origin, damage, false, EF_BLASTER);
|
|
}
|
|
|
|
void Weapon_Blaster(edict_t *ent)
|
|
{
|
|
constexpr int pause_frames[] = { 19, 32, 0 };
|
|
constexpr int fire_frames[] = { 5, 0 };
|
|
|
|
Weapon_Generic(ent, 4, 8, 52, 55, pause_frames, fire_frames, Weapon_Blaster_Fire);
|
|
}
|
|
|
|
void Weapon_HyperBlaster_Fire(edict_t *ent)
|
|
{
|
|
float rotation;
|
|
vec3_t offset;
|
|
int damage;
|
|
|
|
// start on frame 6
|
|
if (ent->client->ps.gunframe > 20)
|
|
ent->client->ps.gunframe = 6;
|
|
else
|
|
ent->client->ps.gunframe++;
|
|
|
|
// if we reached end of loop, have ammo & holding attack, reset loop
|
|
// otherwise play wind down
|
|
if (ent->client->ps.gunframe == 12)
|
|
{
|
|
if (ent->client->pers.inventory[ent->client->pers.weapon->ammo] && (ent->client->buttons & BUTTON_ATTACK))
|
|
ent->client->ps.gunframe = 6;
|
|
else
|
|
gi.sound(ent, CHAN_AUTO, gi.soundindex("weapons/hyprbd1a.wav"), 1, ATTN_NORM, 0);
|
|
}
|
|
|
|
// play weapon sound for firing loop
|
|
if (ent->client->ps.gunframe >= 6 && ent->client->ps.gunframe <= 11)
|
|
ent->client->weapon_sound = gi.soundindex("weapons/hyprbl1a.wav");
|
|
else
|
|
ent->client->weapon_sound = 0;
|
|
|
|
// fire frames
|
|
bool request_firing = ent->client->weapon_fire_buffered || (ent->client->buttons & BUTTON_ATTACK);
|
|
|
|
if (request_firing)
|
|
{
|
|
if (ent->client->ps.gunframe >= 6 && ent->client->ps.gunframe <= 11)
|
|
{
|
|
ent->client->weapon_fire_buffered = false;
|
|
|
|
if (!ent->client->pers.inventory[ent->client->pers.weapon->ammo])
|
|
{
|
|
NoAmmoWeaponChange(ent, true);
|
|
return;
|
|
}
|
|
|
|
rotation = (ent->client->ps.gunframe - 5) * 2 * PIf / 6;
|
|
offset[0] = -4 * sinf(rotation);
|
|
offset[2] = 0;
|
|
offset[1] = 4 * cosf(rotation);
|
|
|
|
if (deathmatch->integer)
|
|
damage = 15;
|
|
else
|
|
damage = 20;
|
|
Blaster_Fire(ent, offset, damage, true, (ent->client->ps.gunframe % 4) ? EF_NONE : EF_HYPERBLASTER);
|
|
Weapon_PowerupSound(ent);
|
|
|
|
G_RemoveAmmo(ent);
|
|
|
|
ent->client->anim_priority = ANIM_ATTACK;
|
|
if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
|
|
{
|
|
ent->s.frame = FRAME_crattak1 - (int) (frandom() + 0.25f);
|
|
ent->client->anim_end = FRAME_crattak9;
|
|
}
|
|
else
|
|
{
|
|
ent->s.frame = FRAME_attack1 - (int) (frandom() + 0.25f);
|
|
ent->client->anim_end = FRAME_attack8;
|
|
}
|
|
ent->client->anim_time = 0_ms;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Weapon_HyperBlaster(edict_t *ent)
|
|
{
|
|
constexpr int pause_frames[] = { 0 };
|
|
|
|
Weapon_Repeating(ent, 5, 20, 49, 53, pause_frames, Weapon_HyperBlaster_Fire);
|
|
}
|
|
|
|
/*
|
|
======================================================================
|
|
|
|
MACHINEGUN / CHAINGUN
|
|
|
|
======================================================================
|
|
*/
|
|
|
|
void Machinegun_Fire(edict_t *ent)
|
|
{
|
|
int i;
|
|
int damage = 8;
|
|
int kick = 2;
|
|
|
|
if (!(ent->client->buttons & BUTTON_ATTACK))
|
|
{
|
|
ent->client->machinegun_shots = 0;
|
|
ent->client->ps.gunframe = 6;
|
|
return;
|
|
}
|
|
|
|
if (ent->client->ps.gunframe == 4)
|
|
ent->client->ps.gunframe = 5;
|
|
else
|
|
ent->client->ps.gunframe = 4;
|
|
|
|
if (ent->client->pers.inventory[ent->client->pers.weapon->ammo] < 1)
|
|
{
|
|
ent->client->ps.gunframe = 6;
|
|
NoAmmoWeaponChange(ent, true);
|
|
return;
|
|
}
|
|
|
|
if (is_quad)
|
|
{
|
|
damage *= damage_multiplier;
|
|
kick *= damage_multiplier;
|
|
}
|
|
|
|
vec3_t kick_origin {}, kick_angles {};
|
|
for (i = 0; i < 3; i++)
|
|
{
|
|
kick_origin[i] = crandom() * 0.35f;
|
|
kick_angles[i] = crandom() * 0.7f;
|
|
}
|
|
//kick_angles[0] = ent->client->machinegun_shots * -1.5f;
|
|
P_AddWeaponKick(ent, kick_origin, kick_angles);
|
|
|
|
// raise the gun as it is firing
|
|
// [Paril-KEX] disabled as this is a bit hard to do with high
|
|
// tickrate, but it also just sucks in general.
|
|
/*if (!deathmatch->integer)
|
|
{
|
|
ent->client->machinegun_shots++;
|
|
if (ent->client->machinegun_shots > 9)
|
|
ent->client->machinegun_shots = 9;
|
|
}*/
|
|
|
|
// get start / end positions
|
|
vec3_t start, dir;
|
|
// Paril: kill sideways angle on hitscan
|
|
P_ProjectSource(ent, ent->client->v_angle, { 0, 0, -8 }, start, dir);
|
|
G_LagCompensate(ent, start, dir);
|
|
fire_bullet(ent, start, dir, damage, kick, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MOD_MACHINEGUN);
|
|
G_UnLagCompensate();
|
|
Weapon_PowerupSound(ent);
|
|
|
|
gi.WriteByte(svc_muzzleflash);
|
|
gi.WriteEntity(ent);
|
|
gi.WriteByte(MZ_MACHINEGUN | is_silenced);
|
|
gi.multicast(ent->s.origin, MULTICAST_PVS, false);
|
|
|
|
PlayerNoise(ent, start, PNOISE_WEAPON);
|
|
|
|
G_RemoveAmmo(ent);
|
|
|
|
ent->client->anim_priority = ANIM_ATTACK;
|
|
if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
|
|
{
|
|
ent->s.frame = FRAME_crattak1 - (int) (frandom() + 0.25f);
|
|
ent->client->anim_end = FRAME_crattak9;
|
|
}
|
|
else
|
|
{
|
|
ent->s.frame = FRAME_attack1 - (int) (frandom() + 0.25f);
|
|
ent->client->anim_end = FRAME_attack8;
|
|
}
|
|
ent->client->anim_time = 0_ms;
|
|
}
|
|
|
|
void Weapon_Machinegun(edict_t *ent)
|
|
{
|
|
constexpr int pause_frames[] = { 23, 45, 0 };
|
|
|
|
Weapon_Repeating(ent, 3, 5, 45, 49, pause_frames, Machinegun_Fire);
|
|
}
|
|
|
|
void Chaingun_Fire(edict_t *ent)
|
|
{
|
|
int i;
|
|
int shots;
|
|
float r, u;
|
|
int damage;
|
|
int kick = 2;
|
|
|
|
if (deathmatch->integer)
|
|
damage = 6;
|
|
else
|
|
damage = 8;
|
|
|
|
if (ent->client->ps.gunframe > 31)
|
|
{
|
|
ent->client->ps.gunframe = 5;
|
|
gi.sound(ent, CHAN_AUTO, gi.soundindex("weapons/chngnu1a.wav"), 1, ATTN_IDLE, 0);
|
|
}
|
|
else if ((ent->client->ps.gunframe == 14) && !(ent->client->buttons & BUTTON_ATTACK))
|
|
{
|
|
ent->client->ps.gunframe = 32;
|
|
ent->client->weapon_sound = 0;
|
|
return;
|
|
}
|
|
else if ((ent->client->ps.gunframe == 21) && (ent->client->buttons & BUTTON_ATTACK) && ent->client->pers.inventory[ent->client->pers.weapon->ammo])
|
|
{
|
|
ent->client->ps.gunframe = 15;
|
|
}
|
|
else
|
|
{
|
|
ent->client->ps.gunframe++;
|
|
}
|
|
|
|
if (ent->client->ps.gunframe == 22)
|
|
{
|
|
ent->client->weapon_sound = 0;
|
|
gi.sound(ent, CHAN_AUTO, gi.soundindex("weapons/chngnd1a.wav"), 1, ATTN_IDLE, 0);
|
|
}
|
|
|
|
if (ent->client->ps.gunframe < 5 || ent->client->ps.gunframe > 21)
|
|
return;
|
|
|
|
ent->client->weapon_sound = gi.soundindex("weapons/chngnl1a.wav");
|
|
|
|
ent->client->anim_priority = ANIM_ATTACK;
|
|
if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
|
|
{
|
|
ent->s.frame = FRAME_crattak1 - (ent->client->ps.gunframe & 1);
|
|
ent->client->anim_end = FRAME_crattak9;
|
|
}
|
|
else
|
|
{
|
|
ent->s.frame = FRAME_attack1 - (ent->client->ps.gunframe & 1);
|
|
ent->client->anim_end = FRAME_attack8;
|
|
}
|
|
ent->client->anim_time = 0_ms;
|
|
|
|
if (ent->client->ps.gunframe <= 9)
|
|
shots = 1;
|
|
else if (ent->client->ps.gunframe <= 14)
|
|
{
|
|
if (ent->client->buttons & BUTTON_ATTACK)
|
|
shots = 2;
|
|
else
|
|
shots = 1;
|
|
}
|
|
else
|
|
shots = 3;
|
|
|
|
if (ent->client->pers.inventory[ent->client->pers.weapon->ammo] < shots)
|
|
shots = ent->client->pers.inventory[ent->client->pers.weapon->ammo];
|
|
|
|
if (!shots)
|
|
{
|
|
NoAmmoWeaponChange(ent, true);
|
|
return;
|
|
}
|
|
|
|
if (is_quad)
|
|
{
|
|
damage *= damage_multiplier;
|
|
kick *= damage_multiplier;
|
|
}
|
|
|
|
vec3_t kick_origin {}, kick_angles {};
|
|
for (i = 0; i < 3; i++)
|
|
{
|
|
kick_origin[i] = crandom() * 0.35f;
|
|
kick_angles[i] = crandom() * (0.5f + (shots * 0.15f));
|
|
}
|
|
P_AddWeaponKick(ent, kick_origin, kick_angles);
|
|
|
|
vec3_t start, dir;
|
|
P_ProjectSource(ent, ent->client->v_angle, { 0, 0, -8 }, start, dir);
|
|
|
|
G_LagCompensate(ent, start, dir);
|
|
for (i = 0; i < shots; i++)
|
|
{
|
|
// get start / end positions
|
|
// Paril: kill sideways angle on hitscan
|
|
r = crandom() * 4;
|
|
u = crandom() * 4;
|
|
P_ProjectSource(ent, ent->client->v_angle, { 0, r, u + -8 }, start, dir);
|
|
|
|
fire_bullet(ent, start, dir, damage, kick, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MOD_CHAINGUN);
|
|
}
|
|
G_UnLagCompensate();
|
|
|
|
Weapon_PowerupSound(ent);
|
|
|
|
// send muzzle flash
|
|
gi.WriteByte(svc_muzzleflash);
|
|
gi.WriteEntity(ent);
|
|
gi.WriteByte((MZ_CHAINGUN1 + shots - 1) | is_silenced);
|
|
gi.multicast(ent->s.origin, MULTICAST_PVS, false);
|
|
|
|
PlayerNoise(ent, start, PNOISE_WEAPON);
|
|
|
|
G_RemoveAmmo(ent, shots);
|
|
}
|
|
|
|
void Weapon_Chaingun(edict_t *ent)
|
|
{
|
|
constexpr int pause_frames[] = { 38, 43, 51, 61, 0 };
|
|
|
|
Weapon_Repeating(ent, 4, 31, 61, 64, pause_frames, Chaingun_Fire);
|
|
}
|
|
|
|
/*
|
|
======================================================================
|
|
|
|
SHOTGUN / SUPERSHOTGUN
|
|
|
|
======================================================================
|
|
*/
|
|
|
|
void weapon_shotgun_fire(edict_t *ent)
|
|
{
|
|
int damage = 4;
|
|
int kick = 8;
|
|
|
|
vec3_t start, dir;
|
|
// Paril: kill sideways angle on hitscan
|
|
P_ProjectSource(ent, ent->client->v_angle, { 0, 0, -8 }, start, dir);
|
|
|
|
P_AddWeaponKick(ent, ent->client->v_forward * -2, { -2.f, 0.f, 0.f });
|
|
|
|
if (is_quad)
|
|
{
|
|
damage *= damage_multiplier;
|
|
kick *= damage_multiplier;
|
|
}
|
|
|
|
G_LagCompensate(ent, start, dir);
|
|
if (deathmatch->integer)
|
|
fire_shotgun(ent, start, dir, damage, kick, 500, 500, DEFAULT_DEATHMATCH_SHOTGUN_COUNT, MOD_SHOTGUN);
|
|
else
|
|
fire_shotgun(ent, start, dir, damage, kick, 500, 500, DEFAULT_SHOTGUN_COUNT, MOD_SHOTGUN);
|
|
G_UnLagCompensate();
|
|
|
|
// send muzzle flash
|
|
gi.WriteByte(svc_muzzleflash);
|
|
gi.WriteEntity(ent);
|
|
gi.WriteByte(MZ_SHOTGUN | is_silenced);
|
|
gi.multicast(ent->s.origin, MULTICAST_PVS, false);
|
|
|
|
PlayerNoise(ent, start, PNOISE_WEAPON);
|
|
|
|
G_RemoveAmmo(ent);
|
|
}
|
|
|
|
void Weapon_Shotgun(edict_t *ent)
|
|
{
|
|
constexpr int pause_frames[] = { 22, 28, 34, 0 };
|
|
constexpr int fire_frames[] = { 8, 0 };
|
|
|
|
Weapon_Generic(ent, 7, 18, 36, 39, pause_frames, fire_frames, weapon_shotgun_fire);
|
|
}
|
|
|
|
void weapon_supershotgun_fire(edict_t *ent)
|
|
{
|
|
int damage = 6;
|
|
int kick = 12;
|
|
|
|
if (is_quad)
|
|
{
|
|
damage *= damage_multiplier;
|
|
kick *= damage_multiplier;
|
|
}
|
|
|
|
vec3_t start, dir;
|
|
// Paril: kill sideways angle on hitscan
|
|
P_ProjectSource(ent, ent->client->v_angle, { 0, 0, -8 }, start, dir);
|
|
G_LagCompensate(ent, start, dir);
|
|
vec3_t v;
|
|
v[PITCH] = ent->client->v_angle[PITCH];
|
|
v[YAW] = ent->client->v_angle[YAW] - 5;
|
|
v[ROLL] = ent->client->v_angle[ROLL];
|
|
// Paril: kill sideways angle on hitscan
|
|
P_ProjectSource(ent, v, { 0, 0, -8 }, start, dir);
|
|
fire_shotgun(ent, start, dir, damage, kick, DEFAULT_SHOTGUN_HSPREAD, DEFAULT_SHOTGUN_VSPREAD, DEFAULT_SSHOTGUN_COUNT / 2, MOD_SSHOTGUN);
|
|
v[YAW] = ent->client->v_angle[YAW] + 5;
|
|
P_ProjectSource(ent, v, { 0, 0, -8 }, start, dir);
|
|
fire_shotgun(ent, start, dir, damage, kick, DEFAULT_SHOTGUN_HSPREAD, DEFAULT_SHOTGUN_VSPREAD, DEFAULT_SSHOTGUN_COUNT / 2, MOD_SSHOTGUN);
|
|
G_UnLagCompensate();
|
|
|
|
P_AddWeaponKick(ent, ent->client->v_forward * -2, { -2.f, 0.f, 0.f });
|
|
|
|
// send muzzle flash
|
|
gi.WriteByte(svc_muzzleflash);
|
|
gi.WriteEntity(ent);
|
|
gi.WriteByte(MZ_SSHOTGUN | is_silenced);
|
|
gi.multicast(ent->s.origin, MULTICAST_PVS, false);
|
|
|
|
PlayerNoise(ent, start, PNOISE_WEAPON);
|
|
|
|
G_RemoveAmmo(ent);
|
|
}
|
|
|
|
void Weapon_SuperShotgun(edict_t *ent)
|
|
{
|
|
constexpr int pause_frames[] = { 29, 42, 57, 0 };
|
|
constexpr int fire_frames[] = { 7, 0 };
|
|
|
|
Weapon_Generic(ent, 6, 17, 57, 61, pause_frames, fire_frames, weapon_supershotgun_fire);
|
|
}
|
|
|
|
/*
|
|
======================================================================
|
|
|
|
RAILGUN
|
|
|
|
======================================================================
|
|
*/
|
|
|
|
void weapon_railgun_fire(edict_t *ent)
|
|
{
|
|
int damage = 100;
|
|
int kick = 200;
|
|
|
|
if (is_quad)
|
|
{
|
|
damage *= damage_multiplier;
|
|
kick *= damage_multiplier;
|
|
}
|
|
|
|
vec3_t start, dir;
|
|
P_ProjectSource(ent, ent->client->v_angle, { 0, 7, -8 }, start, dir);
|
|
G_LagCompensate(ent, start, dir);
|
|
fire_rail(ent, start, dir, damage, kick);
|
|
G_UnLagCompensate();
|
|
|
|
P_AddWeaponKick(ent, ent->client->v_forward * -3, { -3.f, 0.f, 0.f });
|
|
|
|
// send muzzle flash
|
|
gi.WriteByte(svc_muzzleflash);
|
|
gi.WriteEntity(ent);
|
|
gi.WriteByte(MZ_RAILGUN | is_silenced);
|
|
gi.multicast(ent->s.origin, MULTICAST_PVS, false);
|
|
|
|
PlayerNoise(ent, start, PNOISE_WEAPON);
|
|
|
|
G_RemoveAmmo(ent);
|
|
}
|
|
|
|
void Weapon_Railgun(edict_t *ent)
|
|
{
|
|
constexpr int pause_frames[] = { 56, 0 };
|
|
constexpr int fire_frames[] = { 4, 0 };
|
|
|
|
Weapon_Generic(ent, 3, 18, 56, 61, pause_frames, fire_frames, weapon_railgun_fire);
|
|
}
|
|
|
|
/*
|
|
======================================================================
|
|
|
|
BFG10K
|
|
|
|
======================================================================
|
|
*/
|
|
|
|
void weapon_bfg_fire(edict_t *ent)
|
|
{
|
|
int damage;
|
|
float damage_radius = 1000;
|
|
|
|
if (deathmatch->integer)
|
|
damage = 200;
|
|
else
|
|
damage = 500;
|
|
|
|
if (ent->client->ps.gunframe == 9)
|
|
{
|
|
// send muzzle flash
|
|
gi.WriteByte(svc_muzzleflash);
|
|
gi.WriteEntity(ent);
|
|
gi.WriteByte(MZ_BFG | is_silenced);
|
|
gi.multicast(ent->s.origin, MULTICAST_PVS, false);
|
|
|
|
PlayerNoise(ent, ent->s.origin, PNOISE_WEAPON);
|
|
return;
|
|
}
|
|
|
|
// cells can go down during windup (from power armor hits), so
|
|
// check again and abort firing if we don't have enough now
|
|
if (ent->client->pers.inventory[ent->client->pers.weapon->ammo] < 50)
|
|
return;
|
|
|
|
if (is_quad)
|
|
damage *= damage_multiplier;
|
|
|
|
vec3_t start, dir;
|
|
P_ProjectSource(ent, ent->client->v_angle, { 8, 8, -8 }, start, dir);
|
|
fire_bfg(ent, start, dir, damage, 400, damage_radius);
|
|
|
|
P_AddWeaponKick(ent, ent->client->v_forward * -2, { -20.f, 0, crandom() * 8 });
|
|
ent->client->kick.total = DAMAGE_TIME();
|
|
ent->client->kick.time = level.time + ent->client->kick.total;
|
|
|
|
// send muzzle flash
|
|
gi.WriteByte(svc_muzzleflash);
|
|
gi.WriteEntity(ent);
|
|
gi.WriteByte(MZ_BFG2 | is_silenced);
|
|
gi.multicast(ent->s.origin, MULTICAST_PVS, false);
|
|
|
|
PlayerNoise(ent, start, PNOISE_WEAPON);
|
|
|
|
G_RemoveAmmo(ent);
|
|
}
|
|
|
|
void Weapon_BFG(edict_t *ent)
|
|
{
|
|
constexpr int pause_frames[] = { 39, 45, 50, 55, 0 };
|
|
constexpr int fire_frames[] = { 9, 17, 0 };
|
|
|
|
Weapon_Generic(ent, 8, 32, 54, 58, pause_frames, fire_frames, weapon_bfg_fire);
|
|
}
|
|
|
|
//======================================================================
|
|
|
|
void weapon_disint_fire(edict_t *self)
|
|
{
|
|
vec3_t start, dir;
|
|
P_ProjectSource(self, self->client->v_angle, { 24, 8, -8 }, start, dir);
|
|
|
|
P_AddWeaponKick(self, self->client->v_forward * -2, { -1.f, 0.f, 0.f });
|
|
|
|
fire_disintegrator(self, start, dir, 800);
|
|
|
|
// send muzzle flash
|
|
gi.WriteByte(svc_muzzleflash);
|
|
gi.WriteEntity(self);
|
|
gi.WriteByte(MZ_BLASTER2);
|
|
gi.multicast(self->s.origin, MULTICAST_PVS, false);
|
|
|
|
PlayerNoise(self, start, PNOISE_WEAPON);
|
|
|
|
G_RemoveAmmo(self);
|
|
}
|
|
|
|
void Weapon_Beta_Disintegrator(edict_t *ent)
|
|
{
|
|
constexpr int pause_frames[] = { 30, 37, 45, 0 };
|
|
constexpr int fire_frames[] = { 17, 0 };
|
|
|
|
Weapon_Generic(ent, 16, 23, 46, 50, pause_frames, fire_frames, weapon_disint_fire);
|
|
}
|